From 656e8d30924f1fb9038158e5c2654d2357dc65aa Mon Sep 17 00:00:00 2001 From: awesomeandrey Date: Fri, 13 Dec 2024 18:27:28 +0200 Subject: [PATCH] Minlopro: Lightning Knowledge Setup (P2) --- .github/workflows/create_scratch_org.yml | 2 +- .gitignore | 1 - .prettierrc.json | 6 +- config/data/README.md | 9 +- .../plan-Account-Contact-Opportunity.json | 14 +- ..._kav-Knowledge__DataCategorySelection.json | 14 ++ .../data/{ => samples}/sample-Accounts.json | 2 +- .../data/{ => samples}/sample-Contacts.json | 2 +- .../{ => samples}/sample-CurrencyTypes.json | 2 +- ...mple-Knowledge__DataCategorySelection.json | 148 ++++++++++++++++++ .../data/samples/sample-Knowledge__kav.json | 116 ++++++++++++++ config/data/{ => samples}/sample-Leads.json | 0 .../{ => samples}/sample-Opportunities.json | 2 +- .../export.json | 17 ++ .../Account-Contact-Opportunity.soql | 115 +++++++------- ..._kav-Knowledge__DataCategorySelection.soql | 23 +++ config/data/soql-queries/Lead.soql | 33 ++-- package-lock.json | 140 +++++++++++++++++ package.json | 1 + .../automations/create_crma_scratch_org.sh | 2 +- scripts/automations/create_scratch_org.sh | 6 +- scripts/deploy/build.sh | 4 +- .../deploy/post/apex/1_set_up_qa_user.apex | 7 +- .../apex/delete_all_knowledge_articles.apex | 19 +++ .../apex/publish_all_knowledge_articles.apex | 8 + .../util/data-seeding/export_sample_data.sh | 41 +++++ .../{ => data-seeding}/import_sample_data.sh | 10 +- .../migrate_knowledge_articles.sh | 29 ++++ scripts/util/export_sample_data.sh | 34 ---- .../Car__c/listViews/TEMP.listView-meta.xml | 1 - .../listViews/Detailed.listView-meta.xml | 12 ++ 31 files changed, 679 insertions(+), 141 deletions(-) rename config/data/{ => samples}/plan-Account-Contact-Opportunity.json (60%) create mode 100644 config/data/samples/plan-Knowledge__kav-Knowledge__DataCategorySelection.json rename config/data/{ => samples}/sample-Accounts.json (99%) rename config/data/{ => samples}/sample-Contacts.json (99%) rename config/data/{ => samples}/sample-CurrencyTypes.json (99%) create mode 100644 config/data/samples/sample-Knowledge__DataCategorySelection.json create mode 100644 config/data/samples/sample-Knowledge__kav.json rename config/data/{ => samples}/sample-Leads.json (100%) rename config/data/{ => samples}/sample-Opportunities.json (99%) create mode 100644 config/data/sfdmu/knowledge-articles-export-config/export.json create mode 100644 config/data/soql-queries/Knowledge__kav-Knowledge__DataCategorySelection.soql create mode 100644 scripts/util/apex/delete_all_knowledge_articles.apex create mode 100644 scripts/util/apex/publish_all_knowledge_articles.apex create mode 100644 scripts/util/data-seeding/export_sample_data.sh rename scripts/util/{ => data-seeding}/import_sample_data.sh (61%) create mode 100644 scripts/util/data-seeding/migrate_knowledge_articles.sh delete mode 100644 scripts/util/export_sample_data.sh create mode 100644 src/minlopro/main/objects/Knowledge__kav/listViews/Detailed.listView-meta.xml diff --git a/.github/workflows/create_scratch_org.yml b/.github/workflows/create_scratch_org.yml index b428ea8d..005bd3bf 100644 --- a/.github/workflows/create_scratch_org.yml +++ b/.github/workflows/create_scratch_org.yml @@ -38,7 +38,7 @@ jobs: node-version: '20.x' - name: 'Install Salesforce CLI & Authorize DevHub Org' run: | - bash ./scripts/deploy/build.sh + bash ./scripts/deploy/build.sh -a bash ./scripts/deploy/authorize_org.sh --sfdxUrl "$DEV_HUB_AUTH_URL" --orgAlias "DevHub" sf config set target-dev-hub=DevHub - name: 'Spin Up Brand New Scratch Org' diff --git a/.gitignore b/.gitignore index 9eab5ec4..871471ae 100644 --- a/.gitignore +++ b/.gitignore @@ -25,7 +25,6 @@ manifests/package.xml npm-debug.log* yarn-debug.log* yarn-error.log* -export.json .env .env'' **/.env diff --git a/.prettierrc.json b/.prettierrc.json index 68bb70c2..39271add 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -1,5 +1,5 @@ { - "plugins": ["prettier-plugin-apex", "@prettier/plugin-xml"], + "plugins": ["prettier-plugin-apex", "@prettier/plugin-xml", "prettier-plugin-sql"], "trailingComma": "none", "tabWidth": 4, "semi": true, @@ -17,6 +17,10 @@ { "files": "*.{cls,trigger}", "options": { "parser": "apex" } + }, + { + "files": "*.soql", + "options": { "parser": "sql" } } ] } diff --git a/config/data/README.md b/config/data/README.md index c99cf617..a5944225 100644 --- a/config/data/README.md +++ b/config/data/README.md @@ -1,11 +1,12 @@ -# Sample Data +# Seeding Org With Sample Data Sample data (for `Accounts`, `Contacts`, `Opportunities` and other objects) was generated via [Mockaroo Tool](https://mockaroo.com/). -Refer to bash scripts below in order to export/import sample data: +Use scripts below in order to seed/export/import sample data: -- [`import_sample_data.sh`](../../scripts/util/import_sample_data.sh) -- [`export_sample_data.sh`](../../scripts/util/export_sample_data.sh) +- [`import_sample_data.sh`](../../scripts/util/data-seeding/import_sample_data.sh) +- [`export_sample_data.sh`](../../scripts/util/data-seeding/export_sample_data.sh) +- [`migrate_knowledge_articles.sh`](../../scripts/util/data-seeding/migrate_knowledge_articles.sh) See [Salesforce Docs](https://developer.salesforce.com/docs/atlas.en-us.sfdx_dev.meta/sfdx_dev/sfdx_dev_test_data_example.htm) for more. diff --git a/config/data/plan-Account-Contact-Opportunity.json b/config/data/samples/plan-Account-Contact-Opportunity.json similarity index 60% rename from config/data/plan-Account-Contact-Opportunity.json rename to config/data/samples/plan-Account-Contact-Opportunity.json index 0ee31373..c993b782 100644 --- a/config/data/plan-Account-Contact-Opportunity.json +++ b/config/data/samples/plan-Account-Contact-Opportunity.json @@ -3,24 +3,18 @@ "sobject": "Account", "saveRefs": true, "resolveRefs": false, - "files": [ - "sample-Accounts.json" - ] + "files": ["sample-Accounts.json"] }, { "sobject": "Contact", "saveRefs": false, "resolveRefs": true, - "files": [ - "sample-Contacts.json" - ] + "files": ["sample-Contacts.json"] }, { "sobject": "Opportunity", "saveRefs": false, "resolveRefs": true, - "files": [ - "sample-Opportunities.json" - ] + "files": ["sample-Opportunities.json"] } -] \ No newline at end of file +] diff --git a/config/data/samples/plan-Knowledge__kav-Knowledge__DataCategorySelection.json b/config/data/samples/plan-Knowledge__kav-Knowledge__DataCategorySelection.json new file mode 100644 index 00000000..74f88c5e --- /dev/null +++ b/config/data/samples/plan-Knowledge__kav-Knowledge__DataCategorySelection.json @@ -0,0 +1,14 @@ +[ + { + "sobject": "Knowledge__kav", + "saveRefs": true, + "resolveRefs": false, + "files": ["sample-Knowledge__kav.json"] + }, + { + "sobject": "Knowledge__DataCategorySelection", + "saveRefs": false, + "resolveRefs": true, + "files": ["sample-Knowledge__DataCategorySelection.json"] + } +] diff --git a/config/data/sample-Accounts.json b/config/data/samples/sample-Accounts.json similarity index 99% rename from config/data/sample-Accounts.json rename to config/data/samples/sample-Accounts.json index fe551930..7ab10ce8 100644 --- a/config/data/sample-Accounts.json +++ b/config/data/samples/sample-Accounts.json @@ -1051,4 +1051,4 @@ "Website": "https://our-page.com" } ] -} \ No newline at end of file +} diff --git a/config/data/sample-Contacts.json b/config/data/samples/sample-Contacts.json similarity index 99% rename from config/data/sample-Contacts.json rename to config/data/samples/sample-Contacts.json index beb463c3..c3e37a3b 100644 --- a/config/data/sample-Contacts.json +++ b/config/data/samples/sample-Contacts.json @@ -1201,4 +1201,4 @@ "AccountId": "@AccountRef50" } ] -} \ No newline at end of file +} diff --git a/config/data/sample-CurrencyTypes.json b/config/data/samples/sample-CurrencyTypes.json similarity index 99% rename from config/data/sample-CurrencyTypes.json rename to config/data/samples/sample-CurrencyTypes.json index 1264bb94..f40cae1d 100644 --- a/config/data/sample-CurrencyTypes.json +++ b/config/data/samples/sample-CurrencyTypes.json @@ -34,4 +34,4 @@ "IsCorporate": true } ] -} \ No newline at end of file +} diff --git a/config/data/samples/sample-Knowledge__DataCategorySelection.json b/config/data/samples/sample-Knowledge__DataCategorySelection.json new file mode 100644 index 00000000..9eb3e364 --- /dev/null +++ b/config/data/samples/sample-Knowledge__DataCategorySelection.json @@ -0,0 +1,148 @@ +{ + "records": [ + { + "attributes": { + "type": "Knowledge__DataCategorySelection", + "referenceId": "Knowledge__DataCategorySelectionRef1" + }, + "DataCategoryGroupName": "SalesforceFAQs", + "DataCategoryName": "Development", + "ParentId": "@Knowledge__kavRef1" + }, + { + "attributes": { + "type": "Knowledge__DataCategorySelection", + "referenceId": "Knowledge__DataCategorySelectionRef2" + }, + "DataCategoryGroupName": "SalesforceFAQs", + "DataCategoryName": "Development", + "ParentId": "@Knowledge__kavRef2" + }, + { + "attributes": { + "type": "Knowledge__DataCategorySelection", + "referenceId": "Knowledge__DataCategorySelectionRef3" + }, + "DataCategoryGroupName": "SalesforceFAQs", + "DataCategoryName": "Administration", + "ParentId": "@Knowledge__kavRef3" + }, + { + "attributes": { + "type": "Knowledge__DataCategorySelection", + "referenceId": "Knowledge__DataCategorySelectionRef4" + }, + "DataCategoryGroupName": "SalesforceFAQs", + "DataCategoryName": "Configuration", + "ParentId": "@Knowledge__kavRef3" + }, + { + "attributes": { + "type": "Knowledge__DataCategorySelection", + "referenceId": "Knowledge__DataCategorySelectionRef5" + }, + "DataCategoryGroupName": "SalesforceFAQs", + "DataCategoryName": "Development", + "ParentId": "@Knowledge__kavRef4" + }, + { + "attributes": { + "type": "Knowledge__DataCategorySelection", + "referenceId": "Knowledge__DataCategorySelectionRef6" + }, + "DataCategoryGroupName": "SalesforceFAQs", + "DataCategoryName": "Administration", + "ParentId": "@Knowledge__kavRef4" + }, + { + "attributes": { + "type": "Knowledge__DataCategorySelection", + "referenceId": "Knowledge__DataCategorySelectionRef7" + }, + "DataCategoryGroupName": "SalesforceFAQs", + "DataCategoryName": "Configuration", + "ParentId": "@Knowledge__kavRef4" + }, + { + "attributes": { + "type": "Knowledge__DataCategorySelection", + "referenceId": "Knowledge__DataCategorySelectionRef8" + }, + "DataCategoryGroupName": "SalesforceFAQs", + "DataCategoryName": "Development", + "ParentId": "@Knowledge__kavRef5" + }, + { + "attributes": { + "type": "Knowledge__DataCategorySelection", + "referenceId": "Knowledge__DataCategorySelectionRef9" + }, + "DataCategoryGroupName": "SalesforceFAQs", + "DataCategoryName": "Development", + "ParentId": "@Knowledge__kavRef6" + }, + { + "attributes": { + "type": "Knowledge__DataCategorySelection", + "referenceId": "Knowledge__DataCategorySelectionRef10" + }, + "DataCategoryGroupName": "SalesforceFAQs", + "DataCategoryName": "Administration", + "ParentId": "@Knowledge__kavRef6" + }, + { + "attributes": { + "type": "Knowledge__DataCategorySelection", + "referenceId": "Knowledge__DataCategorySelectionRef11" + }, + "DataCategoryGroupName": "SalesforceFAQs", + "DataCategoryName": "CI_CD", + "ParentId": "@Knowledge__kavRef6" + }, + { + "attributes": { + "type": "Knowledge__DataCategorySelection", + "referenceId": "Knowledge__DataCategorySelectionRef12" + }, + "DataCategoryGroupName": "SalesforceFAQs", + "DataCategoryName": "Development", + "ParentId": "@Knowledge__kavRef7" + }, + { + "attributes": { + "type": "Knowledge__DataCategorySelection", + "referenceId": "Knowledge__DataCategorySelectionRef13" + }, + "DataCategoryGroupName": "SalesforceFAQs", + "DataCategoryName": "CI_CD", + "ParentId": "@Knowledge__kavRef7" + }, + { + "attributes": { + "type": "Knowledge__DataCategorySelection", + "referenceId": "Knowledge__DataCategorySelectionRef14" + }, + "DataCategoryGroupName": "SalesforceFAQs", + "DataCategoryName": "Development", + "ParentId": "@Knowledge__kavRef8" + }, + { + "attributes": { + "type": "Knowledge__DataCategorySelection", + "referenceId": "Knowledge__DataCategorySelectionRef15" + }, + "DataCategoryGroupName": "SalesforceFAQs", + "DataCategoryName": "Administration", + "ParentId": "@Knowledge__kavRef8" + }, + { + "attributes": { + "type": "Knowledge__DataCategorySelection", + "referenceId": "Knowledge__DataCategorySelectionRef16" + }, + "DataCategoryGroupName": "SalesforceFAQs", + "DataCategoryName": "CI_CD", + "ParentId": "@Knowledge__kavRef8" + } + ] +} diff --git a/config/data/samples/sample-Knowledge__kav.json b/config/data/samples/sample-Knowledge__kav.json new file mode 100644 index 00000000..d0214464 --- /dev/null +++ b/config/data/samples/sample-Knowledge__kav.json @@ -0,0 +1,116 @@ +{ + "records": [ + { + "attributes": { + "type": "Knowledge__kav", + "referenceId": "Knowledge__kavRef1" + }, + "Title": "Apex Trigger vs Flow Considerations", + "UrlName": "apex-trigger-vs-flow-considerations", + "Content__c": "

Apex Trigger vs Flow Considerations

\n

When deciding whether to implement an Apex Trigger or a Flow to handle your business logic, it’s essential to consider factors such as complexity, maintainability, and performance. In many cases, Flows provide a more declarative approach, allowing administrators to build sophisticated logic without writing code, ultimately reducing development time and technical debt. On the other hand, Apex Triggers offer deeper customization options and can be more efficient in handling complex scenarios that require intricate logic or custom error handling.

\n

Before choosing Apex, it’s wise to thoroughly evaluate whether a Flow can achieve the same outcome, potentially saving you time on future maintenance and code refactoring. Flows are also much easier to update and deploy, making them an appealing option if your business processes are expected to evolve frequently. However, if your logic involves DML operations on multiple objects and you need tight control over order of execution, an Apex Trigger might be the more suitable choice. Keep in mind that Flows have improved significantly over the years and can now handle sophisticated branching logic, loops, and even some level of error handling. For a deeper dive into the differences and best practices, consult the Salesforce Flow Documentation or the Apex Developer Guide. Another good resource is Salesforce’s official Triggers and Order of Execution guide that helps you understand when to use Apex code over declarative solutions. Ultimately, striking the right balance between Apex Triggers and Flows can ensure both rapid development and long-term scalability in your Salesforce org.

\n

Pros and Cons of Using Flows:

\n\n

Pros and Cons of Using Apex Triggers:

\n\n

Below is another comparison table stats:

\n

\"apex-vs-flow-considerations.png\"

", + "Language": "en_US", + "IsVisibleInApp": true, + "IsVisibleInCsp": false, + "IsVisibleInPrm": false, + "IsVisibleInPkb": true + }, + { + "attributes": { + "type": "Knowledge__kav", + "referenceId": "Knowledge__kavRef2" + }, + "Title": "Transaction Order Of Execution", + "UrlName": "transaction-order-of-execution", + "Content__c": "

Transaction Order of Execution

\n

When a record is saved in Salesforce, the platform follows a specific transaction order of execution to ensure that validation, automation, and integration logic are applied consistently and predictably. Understanding this sequence is critical for developers and administrators who implement workflows, triggers, processes, and flows, as it directly impacts how data and logic interact. The order typically starts with field validations and the assignment of default values, followed by before triggers, and then systematically applies after triggers, workflow rules, processes, and escalation rules. By reviewing the Order of Execution Documentation, teams can gain detailed insights into these steps, avoiding conflicting logic or unintended data changes:

\n

\"SFB-ORDER-OF-EXECUTION-INFOGRAPHIC\"

\n

In many cases, complex business requirements involve multiple triggers and automation tools acting on the same object or related objects. Without proper planning, this can lead to recursion, increased processing time, or even governor limit violations. Developers often rely on static variables or other techniques to prevent unnecessary re-entry into triggers and flows, ensuring each piece of logic fires only as intended. Additionally, understanding where workflow field updates fit into the order—and that they can re-trigger certain automation—is essential for preventing unexpected loops or overwriting data. Administrators and solution architects should carefully design their automation stack, considering where to place logic for optimal performance and reliability.

\n

As Salesforce continues to enhance its platform, newer tools like Record-Triggered Flows must be factored into the order of execution. Monitoring system logs and leveraging debug logs can help diagnose issues and confirm that the sequence of events unfolds as anticipated. By planning automation with the order of execution in mind, teams can maintain cleaner code, more predictable results, and faster deployments. Ultimately, mastering the transaction order of execution not only improves solution robustness but also enhances end-user trust and satisfaction, as the system behaves in a consistent and expected manner.

", + "Language": "en_US", + "IsVisibleInApp": true, + "IsVisibleInCsp": false, + "IsVisibleInPrm": false, + "IsVisibleInPkb": true + }, + { + "attributes": { + "type": "Knowledge__kav", + "referenceId": "Knowledge__kavRef3" + }, + "Title": "Salesforce Environment Types", + "UrlName": "salesforce-environment-types", + "Content__c": "

Salesforce Environment Types

\n

Salesforce offers a variety of environment types, each designed to support different stages of the application lifecycle. At the core, the Production environment hosts live data and processes that directly affect end-users and customers. In addition to Production, Salesforce provides Sandbox environments—isolated copies of your org’s configuration and, optionally, data—allowing teams to develop, test, and train without impacting real-world operations. Sandboxes come in multiple flavors, including Developer, Developer Pro, Partial Copy, and Full sandboxes, each offering different storage capacities and capabilities. For more information on available sandbox types and their features, consult the Salesforce Sandbox Overview.

\n

Beyond traditional sandboxes, Scratch Orgs—introduced as part of Salesforce DX—are ephemeral, source-tracked environments that enable agile development and short-lived experimentation. These scratch orgs are lightweight and can be quickly spun up and discarded, making them ideal for continuous integration and version-controlled development workflows. Another accessible resource is the Trailhead Playgrounds, which serve as free, short-term learning environments that allow users to explore new features and practice configurations without risking production data. These playgrounds are perfect for hands-on training or evaluating new functionality before investing time and effort into a more formal environment strategy.

\n

By understanding the various Salesforce environment types, teams can implement a release management strategy tailored to their organization’s complexity and scale. Selecting the right environment for development, testing, or training activities not only improves code quality but also streamlines deployments and reduces downtime. Ultimately, leveraging the full range of Salesforce environment types ensures more controlled development processes, predictable releases, and the highest possible quality of customer-facing applications and services.

", + "Language": "en_US", + "IsVisibleInApp": true, + "IsVisibleInCsp": false, + "IsVisibleInPrm": false, + "IsVisibleInPkb": true + }, + { + "attributes": { + "type": "Knowledge__kav", + "referenceId": "Knowledge__kavRef4" + }, + "Title": "Understanding What's Hidden in a Debug Log and How to Read It", + "UrlName": "read-and-understand-debug-logs", + "Content__c": "

Understanding What’s Hidden in a Debug Log and How to Read It

\n

Salesforce debug logs provide an invaluable window into the inner workings of your org’s processes. These logs capture events such as Apex code execution, workflow rules, validation rules, and callouts to external systems, making them essential for troubleshooting complex issues. At first glance, a debug log can seem overwhelming, filled with detailed information like execution units, heap sizes, and limits usage, as well as the exact order in which triggers and flows run. However, by learning how to navigate and interpret these logs, administrators, developers, and solution architects can pinpoint logic errors, performance bottlenecks, and potential improvements.

\n

The best starting point is to familiarize yourself with the different log categories and their corresponding events. For reference, consult the Debug Logs Documentation, which breaks down each event type, from the beginning of a transaction to the end. Understanding the meaning of various log levels—such as FINE, FINER, and FINEST—helps filter out unneeded details and narrow your focus. Take advantage of the Developer Console within Salesforce, as it provides features for filtering, searching, and highlighting specific events within a log. This can drastically reduce the time spent hunting through raw text.

\n

When analyzing a log, start by looking for error messages or exception stack traces. These often appear with clear indicators like “EXCEPTION_THROWN” or “FATAL_ERROR”. After identifying the error location, trace backward through the log to see which triggers, workflows, or methods executed previously. By following this chain of events, you can identify conditions that led to the error. Additionally, pay attention to governor limit entries—if the debug log indicates that your code consumed too many SOQL queries, heap space, or CPU time, you’ll know to refactor the code for efficiency or reduce the volume of processed records. You can learn more about governor limits and their implications by exploring the Apex Code Best Practices.

\n

Finally, reading debug logs is an iterative skill. With practice, you’ll begin to recognize common patterns, understand where to look first, and build strategies for managing large logs—such as breaking them into smaller parts or writing test methods that isolate specific code paths. By mastering debug log interpretation, teams can resolve issues faster, design better solutions, and ensure that their Salesforce implementations run smoothly and reliably.

", + "Language": "en_US", + "IsVisibleInApp": true, + "IsVisibleInCsp": false, + "IsVisibleInPrm": false, + "IsVisibleInPkb": false + }, + { + "attributes": { + "type": "Knowledge__kav", + "referenceId": "Knowledge__kavRef5" + }, + "Title": "DataWeave Script Applications", + "UrlName": "data-weave-script-applications", + "Content__c": "

DataWeave Script Applications in Salesforce

\n

When integrating Salesforce with external systems, DataWeave—the powerful data transformation language included in MuleSoft’s Anypoint Platform—provides unparalleled flexibility and efficiency. With DataWeave, you can easily transform incoming data into the precise format required by Salesforce, ensuring seamless synchronization between various applications. By incorporating DataWeave scripts into your Mule applications, you can handle multiple data formats (JSON, XML, CSV) and apply complex logic without resorting to extensive Apex code. For more information on using DataWeave, check out the DataWeave Language Reference.

\n

Additionally, DataWeave works hand-in-hand with Salesforce’s integration capabilities like Salesforce Connect and the Bulk API. These integrations allow you to efficiently move large data sets into and out of your org and ensure data remains consistent as it flows between Salesforce and external systems. By leveraging DataWeave’s built-in functions and transformations, developers can significantly reduce the complexity of their integration logic, making it more maintainable and easier to debug.

\n

As you evolve your data integration strategies, DataWeave helps enrich and cleanse data before it even reaches Salesforce, improving the quality of reports and analytics. DataWeave scripts can be organized into reusable modules, streamlining future integration projects and promoting consistent coding practices. Implementing DataWeave not only shortens development cycles but also increases agility when adapting to new business requirements or integrating with unfamiliar data sources.

\n

To further enhance your DataWeave skills and learn best practices, you can explore the MuleSoft Developer Center and MuleSoft Training Materials. Ultimately, DataWeave’s robust capabilities ensure that your Salesforce integrations remain scalable, future-proof, and aligned with evolving organizational needs.

", + "Language": "en_US", + "IsVisibleInApp": true, + "IsVisibleInCsp": false, + "IsVisibleInPrm": false, + "IsVisibleInPkb": true + }, + { + "attributes": { + "type": "Knowledge__kav", + "referenceId": "Knowledge__kavRef6" + }, + "Title": "How to deploy Salesforce project?", + "UrlName": "how-to-deploy-salesforce-project", + "Content__c": "

How to Deploy a Salesforce Project

\n

Deploying a Salesforce project involves careful planning, the right tools, and a structured approach to ensure a smooth transition from development to production. While traditional methods like Change Sets can still be used, modern best practices strongly recommend leveraging the Salesforce CLI (SFDX) in combination with version control systems such as Git. By embracing the latest Salesforce CLI, development teams gain the ability to streamline deployments, automate testing, and facilitate continuous integration, all while maintaining a clear and auditable history of changes. For more details on the CLI and its capabilities, refer to the Salesforce CLI Documentation.

\n

Before initiating the deployment, it’s essential to have a well-defined release management strategy—one that outlines changes to be deployed, their timing, and the testing phases they’ll undergo. Adopt a sandbox strategy by using multiple environments (development, test, and staging) to catch issues early and ensure that all dependencies, permissions, and metadata are accounted for. Incorporating the Salesforce CLI into your workflow supports seamless integration with CI/CD pipelines, allowing for scripted deployments, advanced validation steps, and automated post-deployment checks. Tools like GitHub Actions, Azure Pipelines, or Jenkins integrate well with the CLI, enabling you to run tests and enforcement rules before pushing changes live.

\n

Additionally, if you need a more declarative interface on top of CLI-driven processes, consider using the DevOps Center. It simplifies branch management, work item tracking, and pipeline promotion—all while maintaining compatibility with the Salesforce CLI under the hood. After deployment, conduct a thorough post-deployment validation to confirm that custom objects, fields, workflows, and automations are functioning correctly in the target environment. By relying on the Salesforce CLI as your central command hub, you gain more control, better traceability, and a more consistent release cadence.

\n

Whether you’re transitioning from older methods or enhancing an existing setup, the Salesforce CLI delivers powerful features and a more productive developer experience. Updating and standardizing your team’s deployment approach with the CLI at the core will yield faster, more reliable releases—and ultimately, a healthier, scalable Salesforce environment.

", + "Language": "en_US", + "IsVisibleInApp": true, + "IsVisibleInCsp": true, + "IsVisibleInPrm": false, + "IsVisibleInPkb": true + }, + { + "attributes": { + "type": "Knowledge__kav", + "referenceId": "Knowledge__kavRef7" + }, + "Title": "Metadata API: Unsupported Objects/Settings", + "UrlName": "metadata-api-unsupported-objects-and-settings", + "Content__c": "

Metadata API: Unsupported Objects/Settings

\n

When working with the Salesforce Metadata API, it’s important to recognize that not all metadata types are currently supported for retrieval and deployment. Although the Metadata API covers a broad array of components—from custom objects and fields to complex settings—there remain certain objects, features, and configurations that are excluded. These unsupported metadata items can range from particular standard objects and certain Chatter settings, to more nuanced aspects of Salesforce Knowledge, depending on the API version and product updates. Before planning a large-scale metadata migration, developers should consult the official Metadata API Developer Guide for the latest details, ensuring that no time is lost attempting to automate unsupported configurations.

\n

In some cases, the items not supported by the Metadata API must be handled through alternative means. This could involve leveraging the Tooling API, employing manual configuration steps directly in the Salesforce org, or working with specialized APIs that address distinct functionality—such as those for Territory Management. By understanding these gaps, teams can adjust their strategy accordingly, focusing their automated deployment efforts on what the Metadata API can handle and finding appropriate workarounds for what it cannot.

\n

Staying current with Salesforce release notes and documentation is crucial, as previously unsupported components may become supported in future releases. A proactive approach not only helps set realistic expectations but also streamlines your deployment pipeline and prevents avoidable bottlenecks. Ultimately, acknowledging the Metadata API’s limitations and evolving your development practices to account for unsupported objects and settings will lead to more efficient, reliable, and maintainable Salesforce deployments.

", + "Language": "en_US", + "IsVisibleInApp": true, + "IsVisibleInCsp": false, + "IsVisibleInPrm": false, + "IsVisibleInPkb": true + }, + { + "attributes": { + "type": "Knowledge__kav", + "referenceId": "Knowledge__kavRef8" + }, + "Title": "Useful Salesforce CLI Plugins", + "UrlName": "useful-salesforce-cli-plugins", + "Content__c": "

Useful Salesforce CLI Plugins

\n

The Salesforce CLI (SFDX) offers a powerful extension framework that allows developers and administrators to augment their workflows with community-driven and third-party plugins. These plugins can automate deployments, enhance data migration, and streamline continuous integration and delivery processes. By incorporating the right plugins, teams can reduce manual steps, improve efficiency, and maintain a cleaner codebase.

\n

SFDMU (Salesforce Data Move Utility):
SFDMU simplifies the process of migrating data between Salesforce environments. It allows you to script and automate complex data migrations, move records while respecting relationships, and keep field values consistent across orgs. SFDMU helps ensure that test environments, such as Scratch Orgs or Sandboxes, are populated with realistic data, improving the reliability of testing and quality assurance.

\n

SGD (SFDX-Git-Delta):
SGD, short for SFDX-Git-Delta, streamlines the process of generating deployment packages by analyzing the differences between two Git commits. By focusing only on changed metadata, SGD reduces deployment times and minimizes the risk of introducing unrelated changes. This plugin helps teams integrate more effectively with Git-based version control, enabling more frequent, granular, and reliable releases.

\n

HARDIS Plugin:
The HARDIS plugin automates and standardizes common tasks involved in Salesforce DevOps. It supports features like metadata retrieval and deployment, data anonymization, and project scaffolding. By leveraging HARDIS, teams can reduce repetitive manual steps, maintain consistent development practices, and accelerate the overall release cycle.

\n

Putting It All Together:
By combining these plugins—SFDMU for data handling, SGD for deployment optimization, and HARDIS for DevOps standardization—teams can create a highly efficient and predictable Salesforce delivery pipeline. Experiment with each plugin in your development environment and integrate them into your CI/CD processes. Over time, you’ll find that these tools can significantly reduce friction, save time, and ensure a more reliable and maintainable Salesforce implementation.

", + "Language": "en_US", + "IsVisibleInApp": true, + "IsVisibleInCsp": false, + "IsVisibleInPrm": false, + "IsVisibleInPkb": true + } + ] +} diff --git a/config/data/sample-Leads.json b/config/data/samples/sample-Leads.json similarity index 100% rename from config/data/sample-Leads.json rename to config/data/samples/sample-Leads.json diff --git a/config/data/sample-Opportunities.json b/config/data/samples/sample-Opportunities.json similarity index 99% rename from config/data/sample-Opportunities.json rename to config/data/samples/sample-Opportunities.json index 688cb028..565c9f71 100644 --- a/config/data/sample-Opportunities.json +++ b/config/data/samples/sample-Opportunities.json @@ -801,4 +801,4 @@ "Type": "Existing Customer - Upgrade" } ] -} \ No newline at end of file +} diff --git a/config/data/sfdmu/knowledge-articles-export-config/export.json b/config/data/sfdmu/knowledge-articles-export-config/export.json new file mode 100644 index 00000000..fa1fdabf --- /dev/null +++ b/config/data/sfdmu/knowledge-articles-export-config/export.json @@ -0,0 +1,17 @@ +{ + "objects": [ + { + "object": "Knowledge__kav", + "operation": "Insert", + "externalId": "UrlName", + "query": "SELECT Content__c, Language, Summary, Title, UrlName, IsVisibleInApp, IsVisibleInCsp, IsVisibleInPrm, IsVisibleInPkb FROM Knowledge__kav WHERE PublishStatus IN ('Online') AND IsLatestVersion = TRUE LIMIT 50" + }, + { + "object": "Knowledge__DataCategorySelection", + "operation": "Insert", + "query": "SELECT ParentId, DataCategoryGroupName, DataCategoryName FROM Knowledge__DataCategorySelection LIMIT 100", + "master": false + } + ], + "bulkApiVersion": "2.0" +} diff --git a/config/data/soql-queries/Account-Contact-Opportunity.soql b/config/data/soql-queries/Account-Contact-Opportunity.soql index 943c1100..fdbb2a01 100644 --- a/config/data/soql-queries/Account-Contact-Opportunity.soql +++ b/config/data/soql-queries/Account-Contact-Opportunity.soql @@ -1,56 +1,61 @@ SELECT - Id, - Name, - RecordTypeId, - Industry, - AnnualRevenue, - Phone, - Description, - Type, - BillingCountryCode, - BillingCountry, - BillingState, - BillingStateCode, - BillingStreet, - BillingCity, - BillingPostalCode, - NumberOfEmployees, - Website, - ( - SELECT - Id, - FirstName, - LastName, - MobilePhone, - Title, - Email, - LeadSource, - Department, - HomePhone, - OtherPhone, - GenderIdentity, - MailingCountry, - MailingCountryCode, - MailingState, - MailingStateCode, - MailingStreet, - MailingCity, - MailingPostalCode - FROM Contacts - ), - ( - SELECT - Id, - AccountId, - Amount, - CloseDate, - CurrencyIsoCode, - Description, - LeadSource, - Name, - Probability, - StageName, - Type - FROM Opportunities - ) -FROM Account \ No newline at end of file + Id, + Name, + RecordTypeId, + Industry, + AnnualRevenue, + Phone, + Description, + Type, + BillingCountryCode, + BillingCountry, + BillingState, + BillingStateCode, + BillingStreet, + BillingCity, + BillingPostalCode, + NumberOfEmployees, + Website, + ( + SELECT + Id, + FirstName, + LastName, + MobilePhone, + Title, + Email, + LeadSource, + Department, + HomePhone, + OtherPhone, + GenderIdentity, + MailingCountry, + MailingCountryCode, + MailingState, + MailingStateCode, + MailingStreet, + MailingCity, + MailingPostalCode + FROM + Contacts + ), + ( + SELECT + Id, + AccountId, + Amount, + CloseDate, + CurrencyIsoCode, + Description, + LeadSource, + Name, + Probability, + StageName, + Type + FROM + Opportunities + ) +FROM + Account +LIMIT + 50 diff --git a/config/data/soql-queries/Knowledge__kav-Knowledge__DataCategorySelection.soql b/config/data/soql-queries/Knowledge__kav-Knowledge__DataCategorySelection.soql new file mode 100644 index 00000000..8847bf75 --- /dev/null +++ b/config/data/soql-queries/Knowledge__kav-Knowledge__DataCategorySelection.soql @@ -0,0 +1,23 @@ +SELECT + Title, + UrlName, + Content__c, + Summary, + Language, + IsVisibleInApp, + IsVisibleInCsp, + IsVisibleInPrm, + IsVisibleInPkb, + ( + SELECT + DataCategoryGroupName, + DataCategoryName + FROM + DataCategorySelections + ) +FROM + Knowledge__kav +WHERE + IsLatestVersion = TRUE +LIMIT + 50 diff --git a/config/data/soql-queries/Lead.soql b/config/data/soql-queries/Lead.soql index d8e5cd02..c73f2c46 100644 --- a/config/data/soql-queries/Lead.soql +++ b/config/data/soql-queries/Lead.soql @@ -1,16 +1,19 @@ SELECT - Id, - Company, - AnnualRevenue, - Email, - GenderIdentity, - CurrencyIsoCode, - LeadSource, - Status, - Phone, - MobilePhone, - FirstName, - LastName, - Title, - Website -FROM Lead + Id, + Company, + AnnualRevenue, + Email, + GenderIdentity, + CurrencyIsoCode, + LeadSource, + Status, + Phone, + MobilePhone, + FirstName, + LastName, + Title, + Website +FROM + Lead +LIMIT + 50 diff --git a/package-lock.json b/package-lock.json index 45ce5619..a5e2bdd4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "pmd-bin": "^1.37.0", "prettier": "^3.1.1", "prettier-plugin-apex": "^2.0.1", + "prettier-plugin-sql": "^0.18.1", "xml2js": "^0.6.2" }, "engines": { @@ -2030,6 +2031,15 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "node_modules/big-integer": { + "version": "1.6.52", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz", + "integrity": "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, "node_modules/binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -2818,6 +2828,12 @@ "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, + "node_modules/discontinuous-range": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/discontinuous-range/-/discontinuous-range-1.0.0.tgz", + "integrity": "sha512-c68LpLbO+7kP/b1Hr1qs8/BJ09F5khZGTxqxZuhzxpmwJKOgRFHJWIb9/KmqnqHhLdO55aOxFH/EGBvUQbL/RQ==", + "dev": true + }, "node_modules/domexception": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz", @@ -5245,6 +5261,15 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, + "node_modules/jsox": { + "version": "1.2.121", + "resolved": "https://registry.npmjs.org/jsox/-/jsox-1.2.121.tgz", + "integrity": "sha512-9Ag50tKhpTwS6r5wh3MJSAvpSof0UBr39Pto8OnzFT32Z/pAbxAsKHzyvsyMEHVslELvHyO/4/jaQELHk8wDcw==", + "dev": true, + "bin": { + "jsox": "lib/cli.js" + } + }, "node_modules/jwa": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", @@ -5557,6 +5582,12 @@ "node": ">=10" } }, + "node_modules/moo": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/moo/-/moo-0.5.2.tgz", + "integrity": "sha512-iSAJLHYKnX41mKcJKjqvnAN9sf0LMDTXDEvFv+ffuRR9a1MIuXLjMNL6EsnDHSkKLTWNqQQ5uo61P4EbU4NU+Q==", + "dev": true + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -5587,6 +5618,34 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, + "node_modules/nearley": { + "version": "2.20.1", + "resolved": "https://registry.npmjs.org/nearley/-/nearley-2.20.1.tgz", + "integrity": "sha512-+Mc8UaAebFzgV+KpI5n7DasuuQCHA89dmwm7JXw3TV43ukfNQ9DnBH3Mdb2g/I4Fdxc26pwimBWvjIw0UAILSQ==", + "dev": true, + "dependencies": { + "commander": "^2.19.0", + "moo": "^0.5.0", + "railroad-diagrams": "^1.0.0", + "randexp": "0.4.6" + }, + "bin": { + "nearley-railroad": "bin/nearley-railroad.js", + "nearley-test": "bin/nearley-test.js", + "nearley-unparse": "bin/nearley-unparse.js", + "nearleyc": "bin/nearleyc.js" + }, + "funding": { + "type": "individual", + "url": "https://nearley.js.org/#give-to-nearley" + } + }, + "node_modules/nearley/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, "node_modules/node-fetch": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", @@ -5657,6 +5716,18 @@ "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", "dev": true }, + "node_modules/node-sql-parser": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/node-sql-parser/-/node-sql-parser-4.18.0.tgz", + "integrity": "sha512-2YEOR5qlI1zUFbGMLKNfsrR5JUvFg9LxIRVE+xJe962pfVLH0rnItqLzv96XVs1Y1UIR8FxsXAuvX/lYAWZ2BQ==", + "dev": true, + "dependencies": { + "big-integer": "^1.6.48" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -5991,6 +6062,27 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/prettier-plugin-sql": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/prettier-plugin-sql/-/prettier-plugin-sql-0.18.1.tgz", + "integrity": "sha512-2+Nob2sg7hzLAKJoE6sfgtkhBZCqOzrWHZPvE4Kee/e80oOyI4qwy9vypeltqNBJwTtq3uiKPrCxlT03bBpOaw==", + "dev": true, + "dependencies": { + "jsox": "^1.2.119", + "node-sql-parser": "^4.12.0", + "sql-formatter": "^15.0.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + }, + "peerDependencies": { + "prettier": "^3.0.3" + } + }, "node_modules/pretty-format": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", @@ -6077,6 +6169,25 @@ } ] }, + "node_modules/railroad-diagrams": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz", + "integrity": "sha512-cz93DjNeLY0idrCNOH6PviZGRN9GJhsdm9hpn1YCS879fj4W+x5IFJhhkRZcwVgMmFF7R82UA/7Oh+R8lLZg6A==", + "dev": true + }, + "node_modules/randexp": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/randexp/-/randexp-0.4.6.tgz", + "integrity": "sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ==", + "dev": true, + "dependencies": { + "discontinuous-range": "1.0.0", + "ret": "~0.1.10" + }, + "engines": { + "node": ">=0.12" + } + }, "node_modules/react-is": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", @@ -6186,6 +6297,15 @@ "node": ">=10" } }, + "node_modules/ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "dev": true, + "engines": { + "node": ">=0.12" + } + }, "node_modules/reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -6459,6 +6579,26 @@ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", "dev": true }, + "node_modules/sql-formatter": { + "version": "15.4.6", + "resolved": "https://registry.npmjs.org/sql-formatter/-/sql-formatter-15.4.6.tgz", + "integrity": "sha512-aH6kwvJpylljHqXe+zpie0Q5snL3uerDLLhjPEBjDCVK1NMRFq4nMJbuPJWYp08LaaaJJgBhShAdAfspcBYY0Q==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1", + "get-stdin": "=8.0.0", + "nearley": "^2.20.1" + }, + "bin": { + "sql-formatter": "bin/sql-formatter-cli.cjs" + } + }, + "node_modules/sql-formatter/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, "node_modules/stack-utils": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", diff --git a/package.json b/package.json index 68c21a21..5a4d6c68 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "pmd-bin": "^1.37.0", "prettier": "^3.1.1", "prettier-plugin-apex": "^2.0.1", + "prettier-plugin-sql": "^0.18.1", "xml2js": "^0.6.2" } } diff --git a/scripts/automations/create_crma_scratch_org.sh b/scripts/automations/create_crma_scratch_org.sh index f7fcf238..ca1f2ed1 100644 --- a/scripts/automations/create_crma_scratch_org.sh +++ b/scripts/automations/create_crma_scratch_org.sh @@ -54,7 +54,7 @@ echo "$SCRATCH_ORG_ALIAS" | bash ./scripts/crm-analytics/util/set_up_admin_user. echo "$SCRATCH_ORG_ALIAS" | bash ./scripts/util/deactivate_all_duplicate_rules.sh # Import sample data -echo "$SCRATCH_ORG_ALIAS" | bash ./scripts/util/import_sample_data.sh +echo "$SCRATCH_ORG_ALIAS" | bash ./scripts/util/data-seeding/import_sample_data.sh # Set up standard user echo "$SCRATCH_ORG_ALIAS" | bash ./scripts/crm-analytics/util/set_up_std_user.sh diff --git a/scripts/automations/create_scratch_org.sh b/scripts/automations/create_scratch_org.sh index 1ea724f2..8ce08554 100644 --- a/scripts/automations/create_scratch_org.sh +++ b/scripts/automations/create_scratch_org.sh @@ -55,7 +55,7 @@ echo "$ADMIN_EMAIL" | bash ./scripts/util/create_qa_user.sh echo "$SCRATCH_ORG_ALIAS" | bash ./scripts/deploy/post/run_post.sh # Import sample data -echo "$SCRATCH_ORG_ALIAS" | bash ./scripts/util/import_sample_data.sh +echo "$SCRATCH_ORG_ALIAS" | bash ./scripts/util/data-seeding/import_sample_data.sh # Publish Digital Experience Site sf community publish --name "DigEx" --target-org "$SCRATCH_ORG_ALIAS" || true @@ -63,5 +63,9 @@ sf community publish --name "DigEx" --target-org "$SCRATCH_ORG_ALIAS" || true # Publish Experience Site for Enhanced Messaging for In-App & Web Experience sf community publish --name "ESW_Minlopro_DigExMessaging" --target-org "$SCRATCH_ORG_ALIAS" || true +# Import & publish Knowledge Articles from DevHub org (leveraging SFDMU plugin) +inputsFile="build/inputs.txt"; touch $inputsFile; echo "$DEV_HUB_ALIAS" > $inputsFile; echo "$SCRATCH_ORG_ALIAS" >> $inputsFile +bash ./scripts/util/data-seeding/migrate_knowledge_articles.sh < $inputsFile + # Reset Metadata Tracking npm run sf:tracking:reset diff --git a/scripts/deploy/build.sh b/scripts/deploy/build.sh index 520c85fd..e024c8bf 100644 --- a/scripts/deploy/build.sh +++ b/scripts/deploy/build.sh @@ -42,9 +42,9 @@ echo 'Installing SF CLI plugins...' echo y | sf plugins install "sfdx-git-delta@latest" if [ "$INSTALL_ALL_MODULES" = true ]; then # https://sfdx-hardis.cloudity.com - echo y | sf plugins install "sfdx-hardis" + echo y | sf plugins install "sfdx-hardis@latest" # https://help.sfdmu.com/get-started - echo y | sf plugins install "sfdmu" + echo y | sf plugins install "sfdmu@latest" # https://forcedotcom.github.io/sfdx-scanner/en/v3.x/scanner-commands/run/ echo y | sf plugins install "@salesforce/sfdx-scanner" fi diff --git a/scripts/deploy/post/apex/1_set_up_qa_user.apex b/scripts/deploy/post/apex/1_set_up_qa_user.apex index da6e1315..04195595 100644 --- a/scripts/deploy/post/apex/1_set_up_qa_user.apex +++ b/scripts/deploy/post/apex/1_set_up_qa_user.apex @@ -20,12 +20,7 @@ Profile minloproProfile = foundProfiles.get(0); // Switch to custom profile + update QA user details; UserRole qaUserRole = [SELECT Id FROM UserRole WHERE DeveloperName = 'DX_User' LIMIT 1][0]; -update new User( - Id = qaUser.Id, - UserRoleId = qaUserRole.Id, - ProfileId = minloproProfile.Id, - UserPermissionsKnowledgeUser = true -); +update new User(Id = qaUser.Id, UserRoleId = qaUserRole.Id, ProfileId = minloproProfile.Id, UserPermissionsKnowledgeUser = true); // Assign PSG intended for internal users; PermissionSetService service = PermissionSetService.getInstance(); diff --git a/scripts/util/apex/delete_all_knowledge_articles.apex b/scripts/util/apex/delete_all_knowledge_articles.apex new file mode 100644 index 00000000..10fab6a6 --- /dev/null +++ b/scripts/util/apex/delete_all_knowledge_articles.apex @@ -0,0 +1,19 @@ +for (Knowledge__kav article : [ + SELECT KnowledgeArticleId, PublishStatus + FROM Knowledge__kav + WHERE IsLatestVersion = TRUE + LIMIT 50 +]) { + switch on article.PublishStatus { + when 'Online' { + KbManagement.PublishingService.archiveOnlineArticle(article.KnowledgeArticleId, null); + KbManagement.PublishingService.deleteArchivedArticle(article.KnowledgeArticleId); + } + when 'Draft' { + KbManagement.PublishingService.deleteDraftArticle(article.KnowledgeArticleId); + } + when else { + KbManagement.PublishingService.deleteArchivedArticle(article.KnowledgeArticleId); + } + } +} diff --git a/scripts/util/apex/publish_all_knowledge_articles.apex b/scripts/util/apex/publish_all_knowledge_articles.apex new file mode 100644 index 00000000..bfd4b2a6 --- /dev/null +++ b/scripts/util/apex/publish_all_knowledge_articles.apex @@ -0,0 +1,8 @@ +for (Knowledge__kav article : [ + SELECT KnowledgeArticleId + FROM Knowledge__kav + WHERE PublishStatus IN ('Draft') AND IsLatestVersion = TRUE + LIMIT 50 +]) { + KbManagement.PublishingService.publishArticle(article.KnowledgeArticleId, true); +} diff --git a/scripts/util/data-seeding/export_sample_data.sh b/scripts/util/data-seeding/export_sample_data.sh new file mode 100644 index 00000000..c485f702 --- /dev/null +++ b/scripts/util/data-seeding/export_sample_data.sh @@ -0,0 +1,41 @@ +#!/bin/bash + +# How to use: +# - bash ./scripts/util/data-seeding/export_sample_data.sh +# - echo $ORG_ALIAS | bash ./scripts/util/data-seeding/export_sample_data.sh + +# Capture target org alias; +read -p "🔶 Enter target org alias: " TARGET_ORG_ALIAS + +mkdir -p "build" +echo "🔵 Exporting sample data from [$TARGET_ORG_ALIAS]..." + +# Export Accounts, Contacts and Opportunities; +sf data export tree \ + --target-org "$TARGET_ORG_ALIAS" \ + --query "config/data/soql-queries/Account-Contact-Opportunity.soql" \ + --plan \ + --prefix "export" \ + --output-dir "build" + +# Export Currency Types; +sf data export tree \ + --target-org "$TARGET_ORG_ALIAS" \ + --query "SELECT Id, IsoCode, ConversionRate, DecimalPlaces, IsActive, IsCorporate FROM CurrencyType" \ + --prefix "export" \ + --output-dir "build" + +# Export Leads; +sf data export tree \ + --target-org "$TARGET_ORG_ALIAS" \ + --query "config/data/soql-queries/Lead.soql" \ + --prefix "export" \ + --output-dir "build" + +# Export Knowledge Articles with Data Category Selections; +sf data export tree \ + --target-org "$TARGET_ORG_ALIAS" \ + --query "config/data/soql-queries/Knowledge__kav-Knowledge__DataCategorySelection.soql" \ + --plan \ + --prefix "export" \ + --output-dir "build" diff --git a/scripts/util/import_sample_data.sh b/scripts/util/data-seeding/import_sample_data.sh similarity index 61% rename from scripts/util/import_sample_data.sh rename to scripts/util/data-seeding/import_sample_data.sh index 8e77eec2..d3932a58 100644 --- a/scripts/util/import_sample_data.sh +++ b/scripts/util/data-seeding/import_sample_data.sh @@ -1,8 +1,8 @@ #!/bin/bash # How to use: -# - bash ./scripts/util/import_sample_data.sh -# - echo $ORG_ALIAS | bash ./scripts/util/import_sample_data.sh +# - bash ./scripts/util/data-seeding/import_sample_data.sh +# - echo $ORG_ALIAS | bash ./scripts/util/data-seeding/import_sample_data.sh # Capture target org alias; read -p "🔶 Enter target org alias: " TARGET_ORG_ALIAS @@ -13,14 +13,14 @@ echo # Import Accounts, Contacts & Opportunities (by plan); sf data import tree \ --target-org "$TARGET_ORG_ALIAS" \ - --plan "config/data/plan-Account-Contact-Opportunity.json" + --plan "config/data/samples/plan-Account-Contact-Opportunity.json" # Import Currency Types (by file); sf data import tree \ --target-org "$TARGET_ORG_ALIAS" \ - --files "config/data/sample-CurrencyTypes.json" + --files "config/data/samples/sample-CurrencyTypes.json" # Import Leads (by file); sf data import tree \ --target-org "$TARGET_ORG_ALIAS" \ - --files "config/data/sample-Leads.json" + --files "config/data/samples/sample-Leads.json" diff --git a/scripts/util/data-seeding/migrate_knowledge_articles.sh b/scripts/util/data-seeding/migrate_knowledge_articles.sh new file mode 100644 index 00000000..39e39cbb --- /dev/null +++ b/scripts/util/data-seeding/migrate_knowledge_articles.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +# How to use: +# - bash ./scripts/util/data-seeding/migrate_knowledge_articles.sh + +read -p "🔶 Enter source org alias to fetch KAVs from: " SOURCE_ORG_ALIAS +read -p "🔶 Enter target org alias to import KAVs to: " TARGET_ORG_ALIAS + +echo "🔵 Exporting knowledge articles from [$SOURCE_ORG_ALIAS] and importing them into [$TARGET_ORG_ALIAS] via SFDMU CLI..." + +# Step 1 - purge all KAVs in the target org +sf apex run --target-org "$TARGET_ORG_ALIAS" --file "scripts/util/apex/delete_all_knowledge_articles.apex" + +# Step 2 - migrate KAVs via SFDMU plugin +mkdir -p "build" +cp -r "config/data/sfdmu/knowledge-articles-export-config" "build" +configFolder="build/knowledge-articles-export-config" +# Use '--simulation' to run a migration process without actually performing any data changes in the Salesforce org +sf sfdmu run \ + --sourceusername "$SOURCE_ORG_ALIAS" \ + --targetusername "$TARGET_ORG_ALIAS" \ + --path "$configFolder" \ + --canmodify \ + --noprompt \ + --verbose \ + --usesf "true" + +# Step 3 - publish all migrated KAVs +sf apex run --target-org "$TARGET_ORG_ALIAS" --file "scripts/util/apex/publish_all_knowledge_articles.apex" diff --git a/scripts/util/export_sample_data.sh b/scripts/util/export_sample_data.sh deleted file mode 100644 index d95e30f8..00000000 --- a/scripts/util/export_sample_data.sh +++ /dev/null @@ -1,34 +0,0 @@ -#!/bin/bash - -# How to use: -# - bash ./scripts/util/export_sample_data.sh -# - echo $ORG_ALIAS | bash ./scripts/util/export_sample_data.sh - -# Capture target org alias; -read -p "🔶 Enter target org alias: " TARGET_ORG_ALIAS - -mkdir -p "build" -echo "🔵 Exporting sample data from [$TARGET_ORG_ALIAS]..." - -# Export Accounts, Contacts and Opportunities; -sf data export tree \ - --target-org "$TARGET_ORG_ALIAS" \ - --query "config/data/soql-queries/Account-Contact-Opportunity.soql" \ - --plan \ - --prefix "export" \ - --output-dir "build" - -# Export Currency Types; -sf data export tree \ - --query "SELECT Id, IsoCode, ConversionRate, DecimalPlaces, IsActive, IsCorporate FROM CurrencyType" \ - --target-org "$TARGET_ORG_ALIAS" \ - --prefix "export" \ - --output-dir "build" - -# Export Leads; -sf data export tree \ - --target-org "$TARGET_ORG_ALIAS" \ - --query "config/data/soql-queries/Lead.soql" \ - --plan \ - --prefix "export" \ - --output-dir "build" diff --git a/src/minlopro-storage-helper/main/objects/Car__c/listViews/TEMP.listView-meta.xml b/src/minlopro-storage-helper/main/objects/Car__c/listViews/TEMP.listView-meta.xml index 47e74b87..a4476555 100644 --- a/src/minlopro-storage-helper/main/objects/Car__c/listViews/TEMP.listView-meta.xml +++ b/src/minlopro-storage-helper/main/objects/Car__c/listViews/TEMP.listView-meta.xml @@ -2,7 +2,6 @@ TEMP NAME - Brand__c Everything diff --git a/src/minlopro/main/objects/Knowledge__kav/listViews/Detailed.listView-meta.xml b/src/minlopro/main/objects/Knowledge__kav/listViews/Detailed.listView-meta.xml new file mode 100644 index 00000000..3f2be87c --- /dev/null +++ b/src/minlopro/main/objects/Knowledge__kav/listViews/Detailed.listView-meta.xml @@ -0,0 +1,12 @@ + + + Detailed + TITLE + ARTICLE_NUMBER + LANGUAGE + URL_NAME + PUBLISH_STATUS + LAST_PUBLISHED_DATE + Everything + +