12 |
13 |
--------------------------------------------------------------------------------
/js/diagram-viewer/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | module.exports = {
4 | mode: 'development',
5 | entry: './src/index.js',
6 | output: {
7 | libraryTarget: 'umd',
8 | library: "viewer",
9 | filename: 'dev-viewer-app.js',
10 | path: path.resolve(__dirname, './dist')
11 | },
12 | // output: {
13 | // libraryTarget: 'umd',
14 | // library: "viewer",
15 | // filename: 'DiagramViewerApp.resource',
16 | // path: path.resolve(__dirname, '../../graphviz/main/default/staticresources')
17 | // },
18 | module: {
19 | rules: [
20 | {
21 | test: /\.js$/,
22 | exclude: /node_modules/,
23 | loader: 'babel-loader',
24 | query: {
25 | presets: ['es2015']
26 | }
27 | }
28 | ]
29 | }
30 | };
--------------------------------------------------------------------------------
/doc/learning.md:
--------------------------------------------------------------------------------
1 | ## What can you learn from this project?
2 |
3 | * Using SFDX to create a fresh dev env in 5 mins
4 | * composition vs inheritance (DiagramDataService, DeBounce)
5 | * Testing Lightning components using the LTS
6 | * loading fns from a static resource (global fns vs pure fns)
7 | * instant feedback dev using npm tools, then deploying using UMD
8 | * calling the Tooling/Rest APIs from Lightning and future Apex Tooling APIs
9 | * JSON for persistence instead of custom fields, entities etc
10 | * JSON validation for interim values or attribute values
11 | * ADR docs instead of long form architecture docs
12 |
13 | Future
14 |
15 | * TODO debounce
16 | * TODO CI
17 |
18 | ### js static resource/module
19 |
20 | * UMD allows script and node.js use
21 | * must use "production" mode since eval blocked in "development" mode
22 | * exports not available in devtools console since "window" is proxied (i.e. different) for each Lightning component
23 |
--------------------------------------------------------------------------------
/doc/ADR/adr0001-es5-vs-es6.md:
--------------------------------------------------------------------------------
1 | ## Context
2 |
3 | For developers, ES6 provides many nice features but the Lightning Linter defaults to ES5.
4 | It's non-trivial to make the linter use ES6:
5 | https://developer.salesforce.com/forums/?id=9060G000000MVJSQA4
6 | https://salesforce.stackexchange.com/questions/199495/ecma-script-6-eslint-with-lightninglint
7 |
8 | Salesforce supports IE11 for Lightning Experience but IE11 only supports a small set of ES6 features:
9 | http://kangax.github.io/compat-table/es6/#ie11
10 |
11 | ## Decision
12 |
13 | Only use ES5 features in this project, to allow a clean linter run and automated linting in CI.
14 |
15 | ## Status
16 |
17 | Removed all the ES6 errors found by the DX linter and app still works.
18 |
19 | ## Consequences
20 |
21 | Less readable code but higher browser compatibility.
22 |
23 | Caveat: browser compatibility is good but most users of this tool will be admins who generally use Chrome and LTS also uses headless Chrome with full ES6 support.
--------------------------------------------------------------------------------
/graphviz/main/default/aura/Pill/PillController.js:
--------------------------------------------------------------------------------
1 | ({
2 | /**
3 | * Handler when user changes the checkbox selection
4 | */
5 | onSelectionChange : function(component, event, helper) {
6 | var value = component.get('v.value');
7 | if(value.selected){
8 | // Create well-formed entity object and fire mutate event
9 | value.fields = [];
10 | component.getEvent('onDiagramMutate').setParams({entitiesToAdd:[value]}).fire();
11 | component.getEvent('onGoToDetails').setParams({scope:value}).fire();
12 | }
13 | else{
14 | component.getEvent('onDiagramMutate').setParams({entitiesToRemove:[value]}).fire();
15 | component.getEvent('onGoToDetails').setParams({scope:null}).fire();
16 | }
17 | },
18 |
19 | /**
20 | * Handler when user press arrow button to go to detail view
21 | */
22 | onGoToDetails : function(component, event, helper) {
23 | var value = component.get('v.value');
24 | component.getEvent('onGoToDetails').setParams({scope:value}).fire();
25 | },
26 | })
--------------------------------------------------------------------------------
/graphviz/main/default/aura/AutoBuildSelector/AutoBuildSelector.cmp:
--------------------------------------------------------------------------------
1 |
3 |
4 |
8 |
9 |
11 |
12 |
13 |
14 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/graphviz/main/default/classes/LightningUtility.cls:
--------------------------------------------------------------------------------
1 | public class LightningUtility {
2 |
3 |
4 | public class ResultWrapper {
5 | public ServiceStatus serviceStatus;
6 | public Object result;
7 |
8 | public ResultWrapper() {
9 | serviceStatus = new ServiceStatus();
10 | }
11 |
12 | public void setMessage(String message) {
13 | serviceStatus.message = message;
14 | }
15 | }
16 |
17 | public enum ServiceStatusEnum {
18 | success, failure, warning, information
19 | }
20 |
21 | public class ServiceStatus {
22 | public ServiceStatusEnum status;
23 | public String message;
24 | public DateTime timestamp;
25 |
26 | public ServiceStatus() {
27 | this.status = ServiceStatusEnum.Success;
28 | this.timestamp = DateTime.now();
29 | }
30 |
31 | public ServiceStatus(String message, ServiceStatusEnum status) {
32 | this.message = message;
33 | this.status = status;
34 | this.timestamp = DateTime.now();
35 | }
36 | }
37 | }
--------------------------------------------------------------------------------
/graphviz/main/default/aura/SOQLRenderer/SOQLRendererHelper.js:
--------------------------------------------------------------------------------
1 | ({
2 | updateEntities: function (component, entities) {
3 | var opts = [];
4 | var from = component.get("v.from");
5 | entities.forEach(function (entity) {
6 | opts.push({
7 | label: entity,
8 | value: entity,
9 | selected: entity == from // required to help select dom stay correct when options change. see lightning:select docs race condition
10 | });
11 | });
12 | component.set("v.entityOptions", opts);
13 | },
14 | handleError: function(component, error) {
15 | // the SOQL generation pure fn can throw errors intended to be seen by the user for
16 | // some combinations of entities. handle that here
17 | var isMessageForUser = error.name == "UserException";
18 | if (isMessageForUser) {
19 | // error intended for the user from pure fns
20 | window.alert(error.message);
21 | } else {
22 | window.alert(error);
23 | }
24 | return isMessageForUser;
25 | }
26 | })
--------------------------------------------------------------------------------
/graphviz/main/default/pages/AutoBuildBridge.page:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
11 |
12 |
13 |
14 |
26 |
27 |
--------------------------------------------------------------------------------
/graphviz/main/default/aura/DiagramSettingsChangeEvent/DiagramSettingsChangeEvent.evt:
--------------------------------------------------------------------------------
1 |
3 |
4 |
6 |
7 |
9 |
11 |
13 |
15 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | This is free and unencumbered software released into the public domain.
2 |
3 | Anyone is free to copy, modify, publish, use, compile, sell, or
4 | distribute this software, either in source code form or as a compiled
5 | binary, for any purpose, commercial or non-commercial, and by any
6 | means.
7 |
8 | In jurisdictions that recognize copyright laws, the author or authors
9 | of this software dedicate any and all copyright interest in the
10 | software to the public domain. We make this dedication for the benefit
11 | of the public at large and to the detriment of our heirs and
12 | successors. We intend this dedication to be an overt act of
13 | relinquishment in perpetuity of all present and future rights to this
14 | software under copyright law.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
23 |
24 | For more information, please refer to
--------------------------------------------------------------------------------
/graphviz/main/default/aura/Panel/PanelController.js:
--------------------------------------------------------------------------------
1 | ({
2 | /**
3 | * Handler when user toggle collapse state
4 | */
5 | onToggle : function(component, event, helper) {
6 | component.set('v.collapsed', !component.get('v.collapsed'));
7 | },
8 |
9 | /**
10 | * Handler when user delete the panel
11 | */
12 | onRemove : function(component, event, helper) {
13 | component.getEvent("onRemovePanel").setParams({scope:'REMOVE'}).fire();
14 | },
15 |
16 | /**
17 | * Handler when user enter title edit mode
18 | */
19 | onEditMode : function(component, event, helper) {
20 | component.set('v.newTitle', component.get('v.title'));
21 | component.set('v.isEditMode', true);
22 | },
23 |
24 | /**
25 | * Handler when user finish updating title
26 | */
27 | onBlur : function(component, event, helper) {
28 | component.set('v.isEditMode', false);
29 | helper.handleTitleChange(component, event, helper);
30 | },
31 |
32 | /**
33 | * Handler when user editing the title
34 | */
35 | onKeyUp : function(component, event, helper) {
36 | if(event.keyCode === 13){
37 | component.set('v.isEditMode', false);
38 | helper.handleTitleChange(component, event, helper);
39 | }
40 | },
41 | })
--------------------------------------------------------------------------------
/graphviz/main/default/aura/ObjectPanel/ObjectPanelHelper.js:
--------------------------------------------------------------------------------
1 | ({
2 | /**
3 | * Calculates the filtered object list and update UI attribute
4 | */
5 | handleObjectListUpdate : function(component, event, helper){
6 | var allObjects = component.get('v.allObjects');
7 | var selectionMap = component.get('v.selectionMap');
8 | var filteredObjects = [];
9 | var currentState = component.get('v.currentState');
10 | var term = component.get('v.searchTerm');
11 | allObjects.forEach(function(object){
12 | var searchTermExist = term !== undefined && term !== '';
13 | var searchMatch = (searchTermExist && (object.label.toLowerCase().indexOf(term.toLowerCase()) !== -1 || object.apiName.toLowerCase().indexOf(term.toLowerCase()) !== -1));
14 | var isSelected = (selectionMap[object.apiName] != null);
15 | if((currentState == 'ALL' && (!searchTermExist || searchMatch)) || (currentState == 'SEARCH' && searchMatch) || (currentState == 'SELECTED' && isSelected && (!searchTermExist || searchMatch))){
16 | var uiObject = {label:object.label, apiName:object.apiName, selected:isSelected};
17 | filteredObjects.push(uiObject);
18 | }
19 | });
20 | filteredObjects.sort(GraphvizForce.DiagramHelper.compare);
21 | component.set('v.filteredObjects', filteredObjects);
22 | },
23 | })
--------------------------------------------------------------------------------
/graphviz/main/default/aura/AttributePanel/AttributePanel.cmp:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
21 |
22 |
--------------------------------------------------------------------------------
/graphviz/main/default/classes/GraphvizForceControllerTest.cls:
--------------------------------------------------------------------------------
1 | @IsTest
2 | private class GraphvizForceControllerTest {
3 |
4 | @TestSetup
5 | private static void setupTestData(){
6 | List testDiagrams = new List();
7 | for(Integer i=0;i<200;i++){
8 | Graphviz_Diagram__c diagram = new Graphviz_Diagram__c();
9 | testDiagrams.add(diagram);
10 | }
11 | insert testDiagrams;
12 | }
13 |
14 | @IsTest
15 | static void testCoverage() {
16 |
17 | Test.startTest();
18 | List testDiagrams = GraphVizForceController.loadDiagrams();
19 | System.assertEquals(200, testDiagrams.size());
20 |
21 | String testSchema = GraphVizForceController.loadSchema();
22 | System.assertNotEquals(null, testSchema);
23 |
24 | Test.stopTest();
25 | }
26 |
27 | @IsTest
28 | static void testChildRelationships() {
29 | List wrappers = GraphVizForceController.loadSchemaWrappers();
30 | for (GraphVizForceController.ObjectWrapper wrapper : wrappers) {
31 | if (wrapper.apiName == 'Case') {
32 | for (GraphVizForceController.ChildRelationshipWrapper childRel : wrapper.childRelationships) {
33 | System.debug(childRel.relationshipName + ' -> '+childRel.childAPIName);
34 | }
35 | }
36 | }
37 |
38 | }
39 | }
--------------------------------------------------------------------------------
/doc/ADR/adr0003-node-modules.md:
--------------------------------------------------------------------------------
1 | ## Context
2 |
3 | One of the weaknesses of Salesforce development is the slow save/test feedback cycle
4 | e.g. save a Lightning Component, refresh the page, restore the UI state, confirm the change takes > 20 seconds.
5 |
6 | If code can be executed locally on a developers PC and some kind of UI or text output is useful, feedback can be instant.
7 | This is available in the javascript context when building node.js modules and using the Jasmine-CLI watch feature.
8 |
9 | Two parts of the tool logic are complex and have no dependency on the Lightning framework:
10 |
11 | 1. graphviz rendering from the diagram JSON object
12 | 2. SOQL rendering from the diagram JSON object
13 |
14 | ## Decision
15 |
16 | Build all functions for these two rendering processes as node module and use webpack to combine them into a single static resource.
17 |
18 | For graphviz rendering, tests render artifacts to the local file-system so that a local Graphviz client can watch them and instantly update.
19 | This creates instant graphical feedback for the developer.
20 |
21 | ## Status
22 |
23 | Accepted. Confirmed to work in development and in runtime.
24 |
25 | ## Consequences
26 |
27 | No disadvantages uncovered so far.
28 |
29 | Discovered that the node module will only work in Lightning when deployed as a *UMD* module in *production* mode.
30 |
31 | This makes working on the renderers much more enjoyable and has unlocked our ambitions to add useful features.
32 |
33 | TODO blog post
--------------------------------------------------------------------------------
/graphviz/main/default/aura/DiagramConfigurator/DiagramConfigurator.cmp:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
30 |
--------------------------------------------------------------------------------
/graphviz/main/default/aura/DiagramViewer/DiagramViewerController.js:
--------------------------------------------------------------------------------
1 | ({
2 | /**
3 | * Set initialised when static resources are loaded
4 | */
5 | doInit: function (component, event, helper) {
6 | component.set('v.initialised', true);
7 | },
8 |
9 | onContentChange: function (component, event, helper) {
10 | var graphvizContent = component.get('v.graphvizContent');
11 | var initialised = component.get('v.initialised');
12 | if (initialised && !$A.util.isEmpty(graphvizContent)) {
13 |
14 | // store the selection and re-use to maintain zoom state
15 | var graphvizSelection = component.get("v.graphvizSelection");
16 | if ($A.util.isEmpty(graphvizSelection)) {
17 | graphvizSelection = d3.select("#graph").graphviz({useWorker: false});
18 | component.set("v.graphvizSelection", graphvizSelection);
19 | }
20 | graphvizSelection
21 | .attributer(helper.attributer)
22 | .engine("dot")
23 | .transition(d3.transition("diagram-update").duration(helper.transitionTime()).ease(d3.easeLinear))
24 | .dot(graphvizContent)
25 | .render()
26 | .zoom(true)
27 | .on("end", function () {
28 | // fire event to allow parent components to use the SVG content from the diagram
29 | var s = component.find("content").getElement();
30 | component.getEvent('onDiagramRendered').setParams({scope: s.innerHTML}).fire();
31 | });
32 | }
33 | },
34 |
35 | onResetZoom: function (component, event, helper) {
36 | var graphvizSelection = component.get("v.graphvizSelection");
37 | graphvizSelection.resetZoom(d3.transition("reset").duration(helper.transitionTime()).ease(d3.easeLinear));
38 | }
39 | })
--------------------------------------------------------------------------------
/doc/diagram-viewer.md:
--------------------------------------------------------------------------------
1 | ## Diagram Viewer Design
2 |
3 | Currently the diagram is rendered by the DiagramViewer component which invokes the d3.js lib
4 | using d3-graphviz to drive it. This works well enough but it is lucky that it works since
5 | the LockerService tends to break 3rd party libs that manipulate the DOM.
6 |
7 | One aspect of this is that the SVG rendered by d3 is set to width -1.
8 | This causes errors in the console but creates the desired behaviours of:
9 |
10 | 1. auto-scaling the svg diagram to fit the window
11 | 2. making d3 transitions smooth
12 |
13 | Using 100% breaks the transition smoothness so we are sticking with -1 for now.
14 |
15 | The correct way to fix this is to measure the width/height of the div containing
16 | the d3 svg and use that in the *attributer* fn.
17 | Currently this doesn't work because the LockerService blocks measuring the size of elements.
18 |
19 | The fix for this is to run the d3 code in a *lightning:container* but that has other problems....
20 |
21 | ### lightning:container
22 |
23 | The correct way to run d3 is inside a *lightning:container* component.
24 | The [diagram-viewer dir](https://github.com/stevebuik/Graphvizforce-Lightning/tree/master/js/diagram-viewer) contains a prototype of this design and can
25 | be run standalone using a local node.js server. To run this:
26 |
27 | 1. `cd js/diagram-viewer`
28 | 2. `npm install`
29 | 3. in one terminal window `npm run watch`
30 | 4. in another terminal window `./node_modules/.bin/http-server`
31 | 5. open the browser using `http://127.0.0.1:8080`
32 |
33 | Now any change made to index.js or index.html will be reflected instantly on refresh.
34 |
35 | This javascript artifact has the connections for sending a receiving LCC messages but
36 | when run inside a lightning:container it does not work.
37 | Some combination of:
38 |
39 | 1. CSP errors
40 | 2. problems loading the viz.js as a web-worker (disabled)
41 | 3.
42 |
--------------------------------------------------------------------------------
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | jobs:
3 | build:
4 | docker:
5 | - image: steveb8n/circle-sfdx:latest
6 | steps:
7 | - checkout
8 | - setup_remote_docker:
9 | docker_layer_caching: true
10 | - run:
11 | name: Run node.js pure function tests
12 | working_directory: js/pure
13 | command: |
14 | mkdir -p ~/junit
15 | npm install
16 | ./node_modules/jasmine-node/bin/jasmine-node spec --junitreport --output ~/junit
17 | - run:
18 | name: Create auth token
19 | command: echo $SFDC_AUTH > hubauth.txt
20 | - run:
21 | name: Authenticate to Hub Org (using sfdxurl flow)
22 | command: sfdx force:auth:sfdxurl:store --setdefaultdevhubusername --sfdxurlfile hubauth.txt
23 | - run:
24 | name: Remove auth token
25 | command: rm hubauth.txt
26 | - run:
27 | name: Create Scratch Org
28 | command: sfdx force:org:create --definitionfile config/project-scratch-def.json --durationdays 1 --setdefaultusername --setalias circleci
29 | - run:
30 | name: Install Lightning Testing Service package
31 | command: sfdx force:lightning:test:install
32 | - run:
33 | name: Deploy Source
34 | command: sfdx force:source:push
35 | - run:
36 | name: Run Apex Tests
37 | command: sfdx force:apex:test:run --codecoverage --outputdir ~/junit --resultformat junit --wait 2
38 | - run:
39 | name: Run Lightning Testing Service Tests
40 | command: sfdx force:lightning:test:run --configfile config/lts-config.json --appname DiagramViewerTests.app --resultformat junit --outputdir ~/junit
41 | - store_test_results:
42 | path: ~/junit
43 | - run:
44 | name: Delete Scratch Org (ignoring bash return code)
45 | command: sfdx force:org:delete --noprompt --targetusername circleci || true
46 | when: always
47 |
--------------------------------------------------------------------------------
/doc/ADR/adr0002-lightning-out.md:
--------------------------------------------------------------------------------
1 | ## Context
2 |
3 | To create the *Auto-build* feature, access to the Tooling API is required.
4 | There was a [bug that blocked access](https://success.salesforce.com/issues_view?id=a1p3A000000EAUPQA4&title=summer-17-generating-a-session-id-from-lightning-domain-provides-invalid-session-id) to this API from Lightning that was marked as fixed but is not.
5 |
6 | A workaround was found: host the ERD component in a Lightning Out page in a Visualforce page.
7 | The visualforce domains provide a session id that does work with the tooling API.
8 |
9 | This workaround will no longer be needed when the native Apex meta-data api supports Classes and Triggers.
10 |
11 | ## Decision
12 |
13 | Migrated the tab to use a VF page and Lightning Out to init the ERD.
14 |
15 | ## Status
16 |
17 | Accepted. Tested and works except for Toasts.
18 |
19 | Waiting for better meta-data API support in Apex. Currently only Page Layouts supported.
20 |
21 | ## Consequences
22 |
23 | Since the lightning:notificationsLibrary is used to show toasts, this is now broken.
24 | This is because the VF page is run inside an iFrame and cannot send events to the LEX app where toasts are handled.
25 | We will avoid the use of LEX toasts until the above bug is fixed and we can stop using Lightning Out.
26 |
27 | The initial load time of the page is much slower due to Lightning Out bootstrapping itself.
28 | This is not too bad for the user since they can stay on the page and avoid the initialisation delay.
29 | If auto-build is not required, the user can create a *Lightning Component* tab and avoid VF/Lightning Out. This loads much faster.
30 |
31 | Events are not recorded by the Lightning Inspector Chrome plugin. The workaround is to use the TestApp.app to run the app,
32 | which will record events but will stop the auto-build feature from working.
33 | In future, moving the auto-build into a lightning:component will isolate these effects and speed up initial page load.
--------------------------------------------------------------------------------
/graphviz/main/default/aura/Modal/Modal.cmp:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |