diff --git a/data/bounded-context/Dockerfile b/data/bounded-context/Dockerfile new file mode 100644 index 0000000..701dc67 --- /dev/null +++ b/data/bounded-context/Dockerfile @@ -0,0 +1,15 @@ +FROM neo4j:3.5.14 + +ENV NEO4J_PASSWD neo4j +ENV NEO4J_AUTH neo4j/${NEO4J_PASSWD} + +COPY bounded-context.dump /var/lib/neo4j/import/ + +VOLUME /data + +CMD sed -e 's/^#dbms.read_only=.*$/dbms.read_only=true/' -i /var/lib/neo4j/conf/neo4j.conf && \ + mkdir -p /var/lib/neo4j/data/databases/graph.db && \ + bin/neo4j-admin set-initial-password ${NEO4J_PASSWD} || true && \ + bin/neo4j-admin load --from=/var/lib/neo4j/import/bounded-context.dump --force && \ + bin/neo4j start && sleep 5 && \ + tail -f logs/neo4j.log diff --git a/data/bounded-context/bounded-context.dump b/data/bounded-context/bounded-context.dump new file mode 100644 index 0000000..b9d2ee3 Binary files /dev/null and b/data/bounded-context/bounded-context.dump differ diff --git a/package.json b/package.json index 4bb5305..f8f4158 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "classnames": "^2.2.6", "codecov": "^3.2.0", "core-js": "^2.6.5", - "d3": "^5.9.2", + "d3": "^5.16.0", "dateformat": "^3.0.3", "dns": "^0.2.2", "enzyme": "^3.9.0", @@ -56,6 +56,7 @@ "react-app-polyfill": "^0.2.2", "react-bootstrap-daterangepicker": "^4.1.0", "react-chartjs-2": "^2.7.4", + "react-d3-graph": "^2.5.0", "react-dom": "^16.8.5", "react-load-script": "0.0.6", "react-loadable": "^5.5.0", diff --git a/src/_nav.js b/src/_nav.js index abfc621..b32ae99 100644 --- a/src/_nav.js +++ b/src/_nav.js @@ -1,73 +1,77 @@ export default { items: [ { - name: 'Dashboard', - url: '/dashboard', - icon: 'icon-speedometer' + name: "Dashboard", + url: "/dashboard", + icon: "icon-speedometer" }, { - name: 'Architecture', - url: '/architecture', - icon: 'fa fa-sitemap', + name: "Architecture", + url: "/architecture", + icon: "fa fa-sitemap", children: [ { - name: 'Structure', - url: '/architecture/structure' + name: "Structure", + url: "/architecture/structure" }, { - name: 'File Types', - url: '/architecture/file-types' + name: "File Types", + url: "/architecture/file-types" }, { - name: 'Dependencies', - url: '/architecture/dependencies' + name: "Dependencies", + url: "/architecture/dependencies" + }, + { + name: "Context Map", + url: "/architecture/context-map" } ] }, { - name: 'Resource Management', - url: '/resource-management', - icon: 'icon-people', + name: "Resource Management", + url: "/resource-management", + icon: "icon-people", children: [ { - name: 'Activity', - url: '/resource-management/activity' + name: "Activity", + url: "/resource-management/activity" }, { - name: 'Knowledge Distribution', - url: '/resource-management/knowledge-distribution' + name: "Knowledge Distribution", + url: "/resource-management/knowledge-distribution" } ] }, { - name: 'Risk Management', - url: '/risk-management', - icon: 'fa fa-exclamation-triangle', + name: "Risk Management", + url: "/risk-management", + icon: "fa fa-exclamation-triangle", children: [ { - name: 'Hotspots', - url: '/risk-management/hotspots' + name: "Hotspots", + url: "/risk-management/hotspots" } ] }, { - name: 'Quality Management', - url: '/quality-management', - icon: 'icon-badge', + name: "Quality Management", + url: "/quality-management", + icon: "icon-badge", children: [ { - name: 'Static Code Analysis', - url: '/quality-management/static-code-analysis', + name: "Static Code Analysis", + url: "/quality-management/static-code-analysis", children: [ { - name: 'PMD', - url: '/quality-management/static-code-analysis/pmd' + name: "PMD", + url: "/quality-management/static-code-analysis/pmd" } ] }, { - name: 'Test Coverage', - url: '/quality-management/test-coverage' + name: "Test Coverage", + url: "/quality-management/test-coverage" } ] } diff --git a/src/api/models/BoundedContextes.js b/src/api/models/BoundedContextes.js new file mode 100644 index 0000000..59e2bf2 --- /dev/null +++ b/src/api/models/BoundedContextes.js @@ -0,0 +1,618 @@ +import { neo4jSession } from "../../views/Dashboard/AbstractDashboardComponent"; + +class BoundedContextModel { + constructor(props) { + const boundedContextQuery = + "match (c0:BoundedContext), (c1:DDD:BoundedContext)-[r1:DEPENDS_ON]->(c2:DDD:BoundedContext), (c1)-[r2:CONTAINS]->(e:DDD:DomainEvent),\n" + + "(b:BoundedContext)-[:CONTAINS]->(listener:Class), \n" + + "(listener)-[]-(e) \n" + + "MERGE(b)<-[r3:LISTEN_ON]-(e)\n" + + "return c0,c1,c2,e,r1,r2,r3"; + + const contextMapWithAllElements = + "match (c0:BoundedContext), (c1:DDD:BoundedContext)-[r1:DEPENDS_ON]->(c2:DDD:BoundedContext),\n" + + "(c1)-[r2:CONTAINS]->(e:DDD:DomainEvent),\n" + + "(c0)-[r4:CONTAINS]->(c3:DDD),\n" + + "(b:BoundedContext)-[:CONTAINS]->(listener:Class),\n" + + "(listener)-[]-(e)\n" + + "MERGE(b)<-[r3:HANDLED_BY]-(e)\n" + + "ON MATCH SET c3.boundedContext = c0.name\n" + + "ON MATCH SET c0.boundedContext = c0.name\n" + + "return c0,c1,c2,c3,e,r1,r2,r3,r4"; + + const elementsOfContextQuery = + 'match(c0:DDD {name: "$context$"})-[r]->(c1:DDD),\n' + + "(c1)-[r1]->(c2:DDD)\n" + + 'where c1.sourceFileName <> "package-info.java" and not (c2:BoundedContext) \n' + + "return c0, c1,c2, r, r1"; + + const boundedContextWithoutEvents = + "match (c0:BoundedContext), (c1:DDD:BoundedContext)-[r1:DEPENDS_ON]->(c2:DDD:BoundedContext)" + + " return c0,c1,c2,r1"; + + localStorage.setItem( + "bounded_context_original_query", + boundedContextQuery + ); + + this.state = { + queryString: boundedContextQuery, + elementsOfContextQuery: elementsOfContextQuery, + boundedContextWithoutEvents: boundedContextWithoutEvents, + contextMapWithAllElements: contextMapWithAllElements + }; + + if (!localStorage.getItem("bounded_context_expert_query")) { + localStorage.setItem( + "bounded_context_expert_query", + this.state.queryString + ); + } else { + this.state.queryString = localStorage.getItem( + "bounded_context_expert_query" + ); + } + } + + static transformNodeIds(nodes) { + for (let node of nodes) { + node.id = "node-" + node.identity.low + "-" + node.identity.high; + node.name = node.properties.name; + node.x = Math.floor(Math.random() * 300) + 100; + node.y = Math.floor(Math.random() * 400) + 200; + } + } + + static colorizeNodes(nodes) { + for (let node of nodes) { + if (node.labels.indexOf("BoundedContext")) { + node.color = "#f7dc6f"; + node.dddLabel = "Bounded Context"; + } else { + node.color = "#dedede"; + } + + if (node.labels.indexOf("DomainEvent") > -1) { + node.color = "#c39bd2"; + node.dddLabel = "Domain Event"; + } else if (node.labels.indexOf("AggregateRoot") > -1) { + node.color = "#7dce9f"; + node.dddLabel = "Aggregate Root"; + } else if (node.labels.indexOf("Entity") > -1) { + node.color = "#73c6b5"; + node.dddLabel = "Entity"; + } else if (node.labels.indexOf("Service") > -1) { + node.color = "#f19489"; + node.dddLabel = "Service"; + } else if (node.labels.indexOf("ValueObject") > -1) { + node.color = "#76d7c3"; + node.dddLabel = "Value Object"; + } else if (node.labels.indexOf("Repository") > -1) { + node.color = "#85c1e9"; + node.dddLabel = "Repository"; + } else if (node.labels.indexOf("Factory") > -1) { + node.color = "#e59865"; + node.dddLabel = "Factory"; + } + } + } + + static generateLegend(nodes) { + let legend = []; + let bcs = []; + for (let node of nodes) { + if (bcs.indexOf(node.dddLabel) === -1) { + bcs.push(node.dddLabel); + legend.push({ + name: node.dddLabel, + color: node.color + }); + } + } + + return legend; + } + + static generateLegendCompleteGraph(nodes) { + let legend = []; + let bcs = []; + for (let node of nodes) { + if (bcs.indexOf(node.properties.boundedContext) === -1) { + bcs.push(node.properties.boundedContext); + legend.push({ + name: node.properties.boundedContext, + color: node.color + }); + } + } + + return legend; + } + + static colorizeNodesByContext(nodes) { + let bcs = []; + let colors = [ + "#63b598", + "#ce7d78", + "#ea9e70", + "#a48a9e", + "#c6e1e8", + "#648177", + "#0d5ac1", + "#f205e6", + "#1c0365", + "#14a9ad", + "#4ca2f9", + "#a4e43f", + "#d298e2", + "#6119d0", + "#d2737d", + "#c0a43c", + "#f2510e", + "#651be6", + "#79806e", + "#61da5e", + "#cd2f00", + "#9348af", + "#01ac53", + "#c5a4fb", + "#996635", + "#b11573", + "#4bb473", + "#75d89e", + "#2f3f94", + "#2f7b99", + "#da967d", + "#34891f", + "#b0d87b", + "#ca4751", + "#7e50a8", + "#c4d647", + "#e0eeb8", + "#11dec1", + "#289812", + "#566ca0", + "#ffdbe1", + "#2f1179", + "#935b6d", + "#916988", + "#513d98", + "#aead3a", + "#9e6d71", + "#4b5bdc", + "#0cd36d", + "#250662", + "#cb5bea", + "#228916", + "#ac3e1b", + "#df514a", + "#539397", + "#880977", + "#f697c1", + "#ba96ce", + "#679c9d", + "#c6c42c", + "#5d2c52", + "#48b41b", + "#e1cf3b", + "#5be4f0", + "#57c4d8", + "#a4d17a", + "#225b8", + "#be608b", + "#96b00c", + "#088baf", + "#f158bf", + "#e145ba", + "#ee91e3", + "#05d371", + "#5426e0", + "#4834d0", + "#802234", + "#6749e8", + "#0971f0", + "#8fb413", + "#b2b4f0", + "#c3c89d", + "#c9a941", + "#41d158", + "#fb21a3", + "#51aed9", + "#5bb32d", + "#807fb", + "#21538e", + "#89d534", + "#d36647", + "#7fb411", + "#0023b8", + "#3b8c2a", + "#986b53", + "#f50422", + "#983f7a", + "#ea24a3", + "#79352c", + "#521250", + "#c79ed2", + "#d6dd92", + "#e33e52", + "#b2be57", + "#fa06ec", + "#1bb699", + "#6b2e5f", + "#64820f", + "#1c271", + "#21538e", + "#89d534", + "#d36647", + "#7fb411", + "#0023b8", + "#3b8c2a", + "#986b53", + "#f50422", + "#983f7a", + "#ea24a3", + "#79352c", + "#521250", + "#c79ed2", + "#d6dd92", + "#e33e52", + "#b2be57", + "#fa06ec", + "#1bb699", + "#6b2e5f", + "#64820f", + "#1c271", + "#9cb64a", + "#996c48", + "#9ab9b7", + "#06e052", + "#e3a481", + "#0eb621", + "#fc458e", + "#b2db15", + "#aa226d", + "#792ed8", + "#73872a", + "#520d3a", + "#cefcb8", + "#a5b3d9", + "#7d1d85", + "#c4fd57", + "#f1ae16", + "#8fe22a", + "#ef6e3c", + "#243eeb", + "#1dc18", + "#dd93fd", + "#3f8473", + "#e7dbce", + "#421f79", + "#7a3d93", + "#635f6d", + "#93f2d7", + "#9b5c2a", + "#15b9ee", + "#0f5997", + "#409188", + "#911e20", + "#1350ce", + "#10e5b1", + "#fff4d7", + "#cb2582", + "#ce00be", + "#32d5d6", + "#117232", + "#608572", + "#c79bc2", + "#00f87c", + "#77772a", + "#6995ba", + "#fc6b57", + "#f07815", + "#8fd883", + "#060e27", + "#96e591", + "#21d52e", + "#d00043", + "#b47162", + "#1ec227", + "#4f0f6f", + "#1d1d58", + "#947002", + "#bde052", + "#e08c56", + "#28fcfd", + "#1bb09b", + "#36486a", + "#d02e29", + "#1ae6db", + "#3e464c", + "#a84a8f", + "#911e7e", + "#3f16d9", + "#0f525f", + "#ac7c0a", + "#b4c086", + "#c9d730", + "#30cc49", + "#3d6751", + "#fb4c03", + "#640fc1", + "#62c03e", + "#d3493a", + "#88aa0b", + "#406df9", + "#615af0", + "#14be47", + "#2a3434", + "#4a543f", + "#79bca0", + "#a8b8d4", + "#00efd4", + "#7ad236", + "#7260d8", + "#1deaa7", + "#06f43a", + "#823c59", + "#e3d94c", + "#dc1c06", + "#f53b2a", + "#b46238", + "#2dfff6", + "#a82b89", + "#1a8011", + "#436a9f", + "#1a806a", + "#4cf09d", + "#c188a2", + "#67eb4b", + "#b308d3", + "#fc7e41", + "#af3101", + "#ff065", + "#71b1f4", + "#a2f8a5", + "#e23dd0", + "#d3486d", + "#00f7f9", + "#474893", + "#3cec35", + "#1c65cb", + "#5d1d0c", + "#2d7d2a", + "#ff3420", + "#5cdd87", + "#a259a4", + "#e4ac44", + "#1bede6", + "#8798a4", + "#d7790f", + "#b2c24f", + "#de73c2", + "#d70a9c", + "#125b67", + "#88e9b8", + "#c2b0e2", + "#86e98f", + "#ae90e2", + "#1a806b", + "#436a9e", + "#0ec0ff", + "#f812b3", + "#b17fc9", + "#8d6c2f", + "#d3277a", + "#2ca1ae", + "#9685eb", + "#8a96c6", + "#dba2e6", + "#76fc1b", + "#608fa4", + "#20f6ba", + "#07d7f6", + "#dce77a", + "#77ecca" + ]; + for (let node of nodes) { + if (bcs.indexOf(node.properties.boundedContext) === -1) { + bcs.push(node.properties.boundedContext); + } + + node.color = colors[bcs.indexOf(node.properties.boundedContext)]; + } + } + + static transformLinkIds(links) { + console.log(links); + for (let link of links) { + link.id = "rel" + link.identity.low + "-" + link.identity.high; + link.source = "node-" + link.start.low + "-" + link.start.high; + link.target = "node-" + link.end.low + "-" + link.end.high; + link.name = link.type; + } + } + + static removeDuplicatedElements(array) { + array = array.filter( + (elem, index, self) => + self.findIndex(t => { + return t.id === elem.id; + }) === index + ); + return array; + } + + readBoundedContext(self) { + var nodes = []; + var links = []; + var contextList = []; + neo4jSession + .run(this.state.queryString) + .then(function(result) { + console.log(result); + result.records.forEach(function(record) { + nodes.push(record.get("c0")); + nodes.push(record.get("c1")); + nodes.push(record.get("c2")); + nodes.push(record.get("e")); + links.push(record.get("r1")); + links.push(record.get("r2")); + links.push(record.get("r3")); + }); + + BoundedContextModel.transformNodeIds(nodes); + nodes = BoundedContextModel.removeDuplicatedElements(nodes); + BoundedContextModel.transformLinkIds(links); + links = BoundedContextModel.removeDuplicatedElements(links); + BoundedContextModel.colorizeNodes(nodes); + const generatedLegend = BoundedContextModel.generateLegend( + nodes + ); + + for (let node of nodes) { + if (node.labels.indexOf("BoundedContext") > -1) { + contextList.push(node.name); + } + } + + console.log(links); + self.setState({ + legend: generatedLegend, + graphData: { + nodes: nodes, + links: links + }, + boundedContextList: contextList + }); + }) + .catch(function(error) { + console.log(error); + }); + } + + readBoundedContextWithoutDomainEvents(self) { + var nodes = []; + var links = []; + var contextList = []; + neo4jSession + .run(this.state.queryString) + .then(function(result) { + console.log(result); + result.records.forEach(function(record) { + nodes.push(record.get("c0")); + nodes.push(record.get("c1")); + nodes.push(record.get("c2")); + links.push(record.get("r1")); + }); + + BoundedContextModel.transformNodeIds(nodes); + nodes = BoundedContextModel.removeDuplicatedElements(nodes); + BoundedContextModel.transformLinkIds(links); + links = BoundedContextModel.removeDuplicatedElements(links); + BoundedContextModel.colorizeNodes(nodes); + const generatedLegend = BoundedContextModel.generateLegend( + nodes + ); + + for (let node of nodes) { + if (node.labels.indexOf("BoundedContext") > -1) { + contextList.push(node.name); + } + } + + self.setState({ + legend: generatedLegend, + graphData: { + nodes: nodes, + links: links + }, + boundedContextList: contextList + }); + }) + .catch(function(error) { + console.log(error); + }); + } + + readSpecificContext(self, context) { + var nodes = []; + var links = []; + neo4jSession + .run( + this.state.elementsOfContextQuery.replace("$context$", context) + ) + .then(function(result) { + console.log(result); + result.records.forEach(function(record) { + nodes.push(record.get("c0")); + nodes.push(record.get("c1")); + nodes.push(record.get("c2")); + links.push(record.get("r")); + links.push(record.get("r1")); + }); + + BoundedContextModel.transformNodeIds(nodes); + nodes = BoundedContextModel.removeDuplicatedElements(nodes); + BoundedContextModel.transformLinkIds(links); + links = BoundedContextModel.removeDuplicatedElements(links); + BoundedContextModel.colorizeNodes(nodes); + const generatedLegend = BoundedContextModel.generateLegend( + nodes + ); + + self.setState({ + legend: generatedLegend, + graphData: { + nodes: nodes, + links: links + } + }); + }) + .catch(function(error) { + console.log(error); + }); + } + + contextMapWithAllNodes(self) { + var nodes = []; + var links = []; + neo4jSession + .run(this.state.contextMapWithAllElements) + .then(function(result) { + console.log(result); + result.records.forEach(function(record) { + nodes.push(record.get("c0")); + nodes.push(record.get("c1")); + nodes.push(record.get("c2")); + nodes.push(record.get("c3")); + nodes.push(record.get("e")); + links.push(record.get("r1")); + links.push(record.get("r2")); + links.push(record.get("r3")); + links.push(record.get("r4")); + }); + + BoundedContextModel.transformNodeIds(nodes); + nodes = BoundedContextModel.removeDuplicatedElements(nodes); + BoundedContextModel.transformLinkIds(links); + links = BoundedContextModel.removeDuplicatedElements(links); + BoundedContextModel.colorizeNodesByContext(nodes); + const generatedLegend = BoundedContextModel.generateLegendCompleteGraph( + nodes + ); + + self.setState({ + legend: generatedLegend, + graphData: { + nodes: nodes, + links: links + } + }); + }) + .catch(function(error) { + console.log(error); + }); + } +} + +export default BoundedContextModel; diff --git a/src/routes.js b/src/routes.js index a1e3551..92e80ad 100644 --- a/src/routes.js +++ b/src/routes.js @@ -1,86 +1,162 @@ -import React from 'react'; -import Loadable from 'react-loadable' +import React from "react"; +import Loadable from "react-loadable"; -import DefaultLayout from './containers/DefaultLayout'; +import DefaultLayout from "./containers/DefaultLayout"; function Loading() { - return
Loading...
; + return
Loading...
; } const Dashboard = Loadable({ - loader: () => import('./views/Dashboard'), - loading: Loading, + loader: () => import("./views/Dashboard"), + loading: Loading }); const Structure = Loadable({ - loader: () => import('./views/Dashboard/Architecture/Structure/Structure'), - loading: Loading, + loader: () => import("./views/Dashboard/Architecture/Structure/Structure"), + loading: Loading }); const FileTypes = Loadable({ - loader: () => import('./views/Dashboard/Architecture/FileTypes/FileTypes'), - loading: Loading, + loader: () => import("./views/Dashboard/Architecture/FileTypes/FileTypes"), + loading: Loading }); const Dependencies = Loadable({ - loader: () => import('./views/Dashboard/Architecture/Dependencies/Dependencies'), - loading: Loading, + loader: () => + import("./views/Dashboard/Architecture/Dependencies/Dependencies"), + loading: Loading +}); + +const ContextMap = Loadable({ + loader: () => + import("./views/Dashboard/Architecture/ContextMap/ContextMap"), + loading: Loading }); const Activity = Loadable({ - loader: () => import('./views/Dashboard/ResourceManagement/Activity/Activity'), - loading: Loading, + loader: () => + import("./views/Dashboard/ResourceManagement/Activity/Activity"), + loading: Loading }); const KnowledgeDistribution = Loadable({ - loader: () => import('./views/Dashboard/ResourceManagement/KnowledgeDistribution/KnowledgeDistribution'), - loading: Loading, + loader: () => + import( + "./views/Dashboard/ResourceManagement/KnowledgeDistribution/KnowledgeDistribution" + ), + loading: Loading }); const Hotspots = Loadable({ - loader: () => import('./views/Dashboard/RiskManagement/Hotspots/Hotspots'), - loading: Loading, + loader: () => import("./views/Dashboard/RiskManagement/Hotspots/Hotspots"), + loading: Loading }); const StaticCodeAnalysisPMD = Loadable({ - loader: () => import('./views/Dashboard/QualityManagement/StaticCodeAnalysis/PMD/PMD'), - loading: Loading, + loader: () => + import( + "./views/Dashboard/QualityManagement/StaticCodeAnalysis/PMD/PMD" + ), + loading: Loading }); const TestCoverage = Loadable({ - loader: () => import('./views/Dashboard/QualityManagement/TestCoverage/TestCoverage'), - loading: Loading, + loader: () => + import("./views/Dashboard/QualityManagement/TestCoverage/TestCoverage"), + loading: Loading }); const Settings = Loadable({ - loader: () => import('./views/Dashboard/Header/Settings'), - loading: Loading, + loader: () => import("./views/Dashboard/Header/Settings"), + loading: Loading }); const CustomQuery = Loadable({ - loader: () => import('./views/Dashboard/Header/CustomQuery'), - loading: Loading, + loader: () => import("./views/Dashboard/Header/CustomQuery"), + loading: Loading }); // https://github.com/ReactTraining/react-router/tree/master/packages/react-router-config const routes = [ - { path: '/', exact: true, name: 'Home', component: DefaultLayout }, - { path: '/dashboard', name: 'Dashboard', component: Dashboard }, - { path: '/architecture', exact: true, name: 'Architecture', component: Structure }, - { path: '/architecture/structure', name: 'Structure', component: Structure }, - { path: '/architecture/file-types', name: 'File Types', component: FileTypes }, - { path: '/architecture/dependencies', name: 'Dependencies', component: Dependencies }, - { path: '/resource-management', exact: true, name: 'Resource Management', component: Activity }, - { path: '/resource-management/activity', name: 'Activity', component: Activity }, - { path: '/resource-management/knowledge-distribution', name: 'Knowledge Distribution', component: KnowledgeDistribution }, - { path: '/risk-management', exact: true, name: 'Risk Management', component: Hotspots }, - { path: '/risk-management/hotspots', name: 'Hotspots', component: Hotspots }, - { path: '/quality-management', exact: true, name: 'Quality Management', component: StaticCodeAnalysisPMD }, - { path: '/quality-management/static-code-analysis', name: 'Static Code Analysis', component: StaticCodeAnalysisPMD }, - { path: '/quality-management/static-code-analysis/pmd', name: 'PMD', component: StaticCodeAnalysisPMD }, - { path: '/quality-management/test-coverage', name: 'Test Coverage', component: TestCoverage }, - { path: '/settings', name: 'Settings', component: Settings }, - { path: '/custom-query', name: 'Custom Query', component: CustomQuery }, + { path: "/", exact: true, name: "Home", component: DefaultLayout }, + { path: "/dashboard", name: "Dashboard", component: Dashboard }, + { + path: "/architecture", + exact: true, + name: "Architecture", + component: Structure + }, + { + path: "/architecture/structure", + name: "Structure", + component: Structure + }, + { + path: "/architecture/file-types", + name: "File Types", + component: FileTypes + }, + { + path: "/architecture/dependencies", + name: "Dependencies", + component: Dependencies + }, + { + path: "/architecture/context-map", + name: "Context Map", + component: ContextMap + }, + { + path: "/resource-management", + exact: true, + name: "Resource Management", + component: Activity + }, + { + path: "/resource-management/activity", + name: "Activity", + component: Activity + }, + { + path: "/resource-management/knowledge-distribution", + name: "Knowledge Distribution", + component: KnowledgeDistribution + }, + { + path: "/risk-management", + exact: true, + name: "Risk Management", + component: Hotspots + }, + { + path: "/risk-management/hotspots", + name: "Hotspots", + component: Hotspots + }, + { + path: "/quality-management", + exact: true, + name: "Quality Management", + component: StaticCodeAnalysisPMD + }, + { + path: "/quality-management/static-code-analysis", + name: "Static Code Analysis", + component: StaticCodeAnalysisPMD + }, + { + path: "/quality-management/static-code-analysis/pmd", + name: "PMD", + component: StaticCodeAnalysisPMD + }, + { + path: "/quality-management/test-coverage", + name: "Test Coverage", + component: TestCoverage + }, + { path: "/settings", name: "Settings", component: Settings }, + { path: "/custom-query", name: "Custom Query", component: CustomQuery } ]; export default routes; diff --git a/src/views/Dashboard/Architecture/ContextMap/ContextMap.js b/src/views/Dashboard/Architecture/ContextMap/ContextMap.js new file mode 100644 index 0000000..c55422f --- /dev/null +++ b/src/views/Dashboard/Architecture/ContextMap/ContextMap.js @@ -0,0 +1,257 @@ +import React from "react"; +import DashboardAbstract, { + databaseCredentialsProvided +} from "../../AbstractDashboardComponent"; +import CustomCardHeader from "../../CustomCardHeader/CustomCardHeader"; +import { Row, Col, Card, CardBody } from "reactstrap"; +import ContextMapGraph from "./visualizations/ContextMapGraph"; +import BoundedContextModel from "../../../../api/models/BoundedContextes"; +import "./style.css"; + +var AppDispatcher = require("../../../../AppDispatcher"); + +const contextMapDescription = + "This diagram shows all Domain Driven Design elements and help you to get a overview over the project" + + " structure. "; + +class ContextMap extends DashboardAbstract { + constructor(props) { + super(props); + + this.state = { + query: "", + graphData: [], + boundedContextList: [], + currentContext: "root", + displayDomainEvents: true, + eventSwitchDisabled: false, + legend: [] + }; + + this.focusBoundedContext = this.focusBoundedContext.bind(this); + this.clickOnNode = this.clickOnNode.bind(this); + this.changeDisplayDomainEvents = this.changeDisplayDomainEvents.bind( + this + ); + } + + componentDidMount() { + super.componentDidMount(); + + this.setState({ + query: localStorage.getItem("context_map_expert_query") + }); + + this.updateContextMapData(); + } + + updateContextMapData(context) { + this.clearContextMap(); + //ROOT VIEW + if (typeof context === "undefined" && databaseCredentialsProvided) { + var boundedContextModel = new BoundedContextModel(); + boundedContextModel.readBoundedContext(this); + } + + //DETAIL VIEW + if (typeof context !== "undefined" && databaseCredentialsProvided) { + var boundedContextModel = new BoundedContextModel(); + boundedContextModel.readSpecificContext(this, context); + this.setState({ + eventSwitchDisabled: true + }); + } + } + + clear(event) { + localStorage.setItem( + "context_map_expert_query", + localStorage.getItem("context_map_query") + ); + this.sendQuery(this); + } + + clearContextMap() { + this.setState({ + graphData: [] + }); + } + sendQuery(event) { + this.setState({ + query: localStorage.getItem("context_map_query") + }); + + AppDispatcher.handleAction({ + actionType: "EXPERT_QUERY", + data: { + queryString: localStorage.getItem("context_map_query") + } + }); + } + + focusBoundedContext(context) { + this.setState({ + currentContext: context + }); + this.updateContextMapData(context); + } + + clickOnNode(nodeId) { + if ( + this.state.graphData !== undefined && + this.state.graphData.nodes !== undefined + ) { + for (const node of this.state.graphData.nodes) { + if ( + node.id === nodeId && + node.labels.indexOf("BoundedContext") > -1 + ) { + this.focusBoundedContext(node.name); + break; + } + } + } + } + + isActive(context) { + return this.state.currentContext === context ? "active" : ""; + } + + resetView() { + this.setState({ + currentContext: undefined + }); + this.updateContextMapData(); + } + + showCompleteGraph() { + this.clearContextMap(); + var boundedContextModel = new BoundedContextModel(); + boundedContextModel.contextMapWithAllNodes(this); + } + + showContextMapWithoutEvents() { + this.clearContextMap(); + var boundedContextModel = new BoundedContextModel(); + boundedContextModel.readBoundedContextWithoutDomainEvents(this); + } + + changeDisplayDomainEvents(checked) { + this.setState({ displayDomainEvents: checked }); + this.updateContextMapData(); + } + + render() { + const legend = this.state.legend.map(d => ( +
+ {d.name} +
+ )); + let boundedContextNavigation = ""; + if (this.state.boundedContextList.length !== 0) { + boundedContextNavigation = ( + + ); + } + + var redirect = super.render(); + if (redirect.length > 0) { + return redirect; + } + + return ( +
+ + + + + + + + + + {boundedContextNavigation} +
+
+
+ + + + {legend} + + + + + + + +
+
+
+ +
+
+ ); + } +} + +export default ContextMap; diff --git a/src/views/Dashboard/Architecture/ContextMap/style.css b/src/views/Dashboard/Architecture/ContextMap/style.css new file mode 100644 index 0000000..3c0a041 --- /dev/null +++ b/src/views/Dashboard/Architecture/ContextMap/style.css @@ -0,0 +1,42 @@ +.context-map-navigation > ul { + list-style: none; + margin:0; + padding:0; + font-size: 12px; +} + + +a > li { + cursor: pointer; +} + +a > li { + margin-top: 10px; + color: #3a4248; +} + +a.active > li{ + color: #23282c; + font-weight: bold; +} + +a:hover > li{ + color: #20A8D7; + font-weight: bold; +} + +.node-description { + display: inline-block; + padding: 5px; + padding-left: 10px; + padding-right: 10px; + margin: 0 5px; + border: 1px solid transparent; + border-radius: 15px; + font-size: 10px; + color: white; +} + +.vis-body { + margin-top: -40px; +} diff --git a/src/views/Dashboard/Architecture/ContextMap/visualizations/ContextMapGraph.js b/src/views/Dashboard/Architecture/ContextMap/visualizations/ContextMapGraph.js new file mode 100644 index 0000000..f5365fc --- /dev/null +++ b/src/views/Dashboard/Architecture/ContextMap/visualizations/ContextMapGraph.js @@ -0,0 +1,30 @@ +import React, { Component } from "react"; +import { Graph } from "react-d3-graph"; +import * as myConfig from "./GraphConfigurationContextMap"; + +class ContextMapGraph extends Component { + constructor(props) { + super(props); + } + + render() { + if (this.props.data.length === 0 && !this.props.data.nodes) { + return ""; + } + + return ( +
+
+ +
+
+ ); + } +} + +export default ContextMapGraph; diff --git a/src/views/Dashboard/Architecture/ContextMap/visualizations/GraphConfigurationContextMap.js b/src/views/Dashboard/Architecture/ContextMap/visualizations/GraphConfigurationContextMap.js new file mode 100644 index 0000000..42cdb01 --- /dev/null +++ b/src/views/Dashboard/Architecture/ContextMap/visualizations/GraphConfigurationContextMap.js @@ -0,0 +1,59 @@ +module.exports = { + automaticRearrangeAfterDropNode: false, + collapsible: true, + height: 600, + highlightDegree: 2, + highlightOpacity: 0.2, + linkHighlightBehavior: true, + maxZoom: 8, + minZoom: 0.1, + nodeHighlightBehavior: true, + panAndZoom: false, + staticGraph: false, + width: 1400, + directed: true, + node: { + color: "#FFC454", + fontColor: "black", + fontSize: 12, + fontWeight: "normal", + //highlightColor: "red", + highlightFontSize: 12, + //highlightFontWeight: "bold", + highlightStrokeWidth: 1.5, + labelProperty: "name", + mouseCursor: "pointer", + opacity: 1, + renderLabel: true, + size: 2000, + strokeColor: "none", + strokeWidth: 1.5, + svg: "", + symbolType: "circle" + }, + link: { + color: "#d3d3d3", + fontColor: "#000", + fontSize: 10, + highlightColor: "#788085", + highlightFontSize: 20, + //highlightFontWeight: "bold", + labelProperty: "type", + opacity: 1, + renderLabel: true, + semanticStrokeWidth: true, + strokeWidth: 2 + }, + d3: { + alpha: 0.2, + gravity: -1500, + linkLength: 200, + linkStrength: function(link) { + if (link.type === "CONTAINS") { + return 1; + } else { + return 0.001; + } + } + } +};