@things-factory/integration-ui 7.0.31 → 7.0.35

Sign up to get free protection for your applications and to get access to all the features.
Files changed (34) hide show
  1. package/client/analysis/graph-data.ts +34 -0
  2. package/client/analysis/graph-viewer-old.ts +1097 -0
  3. package/client/analysis/graph-viewer-style.ts +5 -1
  4. package/client/analysis/graph-viewer.ts +245 -942
  5. package/client/analysis/node.ts +73 -0
  6. package/client/analysis/relationship.ts +19 -0
  7. package/client/analysis/utils.ts +41 -0
  8. package/client/pages/integration-analysis.ts +160 -71
  9. package/dist-client/analysis/graph-data.d.ts +36 -0
  10. package/dist-client/analysis/graph-data.js +2 -0
  11. package/dist-client/analysis/graph-data.js.map +1 -0
  12. package/dist-client/analysis/graph-viewer-old.d.ts +110 -0
  13. package/dist-client/analysis/graph-viewer-old.js +808 -0
  14. package/dist-client/analysis/graph-viewer-old.js.map +1 -0
  15. package/dist-client/analysis/graph-viewer-style.js +5 -1
  16. package/dist-client/analysis/graph-viewer-style.js.map +1 -1
  17. package/dist-client/analysis/graph-viewer.d.ts +25 -99
  18. package/dist-client/analysis/graph-viewer.js +189 -703
  19. package/dist-client/analysis/graph-viewer.js.map +1 -1
  20. package/dist-client/analysis/node.d.ts +4 -0
  21. package/dist-client/analysis/node.js +59 -0
  22. package/dist-client/analysis/node.js.map +1 -0
  23. package/dist-client/analysis/relationship.d.ts +4 -0
  24. package/dist-client/analysis/relationship.js +13 -0
  25. package/dist-client/analysis/relationship.js.map +1 -0
  26. package/dist-client/analysis/utils.d.ts +20 -0
  27. package/dist-client/analysis/utils.js +31 -0
  28. package/dist-client/analysis/utils.js.map +1 -0
  29. package/dist-client/pages/integration-analysis.d.ts +8 -3
  30. package/dist-client/pages/integration-analysis.js +153 -70
  31. package/dist-client/pages/integration-analysis.js.map +1 -1
  32. package/dist-client/tsconfig.tsbuildinfo +1 -1
  33. package/dist-server/tsconfig.tsbuildinfo +1 -1
  34. package/package.json +6 -6
@@ -0,0 +1,73 @@
1
+ import { Node } from './graph-data'
2
+ import * as d3 from 'd3'
3
+ import { class2color, class2darkenColor } from './utils'
4
+
5
+ export function appendNodeToGraph(
6
+ svgNodes: d3.Selection<SVGGElement, unknown, HTMLElement, any>, // 타입 지정
7
+ nodes: Node[],
8
+ options: any
9
+ ) {
10
+ const nodeEnter = svgNodes
11
+ .selectAll<SVGGElement, Node>('.node')
12
+ .data(nodes, d => d.id)
13
+ .enter()
14
+ .append('g')
15
+ .attr('class', d => {
16
+ let classes = 'node'
17
+ if (d.icon) classes += ' node-icon'
18
+ if (d.image) classes += ' node-image'
19
+ if (options.highlight) {
20
+ options.highlight.forEach((highlight: any) => {
21
+ if (d.labels[0] === highlight.class && d.properties[highlight.property] === highlight.value) {
22
+ classes += ' node-highlighted'
23
+ }
24
+ })
25
+ }
26
+ return classes
27
+ })
28
+ .call(
29
+ d3
30
+ .drag<SVGGElement, Node>() // 제네릭 타입 지정
31
+ .on('start', (event, d) => dragStarted(event, d, options.simulation))
32
+ .on('drag', (event, d) => dragged(event, d))
33
+ .on('end', (event, d) => dragEnded(event, d, options.simulation))
34
+ )
35
+
36
+ nodeEnter
37
+ .append('circle')
38
+ .attr('r', options.nodeRadius)
39
+ .style('fill', d => class2color(options.classes2colors, d.labels[0], options.colors, options.numClasses))
40
+ .style('stroke', d =>
41
+ class2darkenColor(class2color(options.classes2colors, d.labels[0], options.colors, options.numClasses))
42
+ )
43
+ .style('stroke-width', 2)
44
+
45
+ nodeEnter
46
+ .append('text')
47
+ .attr('dy', 4)
48
+ .attr('text-anchor', 'middle')
49
+ .text(d => d.properties.name || d.id)
50
+
51
+ return nodeEnter
52
+ }
53
+
54
+ function dragStarted(
55
+ event: d3.D3DragEvent<SVGGElement, Node, any>,
56
+ d: Node,
57
+ simulation: d3.Simulation<Node, undefined>
58
+ ) {
59
+ if (!event.active) simulation.alphaTarget(0.3).restart()
60
+ d.fx = d.x
61
+ d.fy = d.y
62
+ }
63
+
64
+ function dragged(event: d3.D3DragEvent<SVGGElement, Node, any>, d: Node) {
65
+ d.fx = event.x
66
+ d.fy = event.y
67
+ }
68
+
69
+ function dragEnded(event: d3.D3DragEvent<SVGGElement, Node, any>, d: Node, simulation: d3.Simulation<Node, undefined>) {
70
+ if (!event.active) simulation.alphaTarget(0)
71
+ d.fx = null
72
+ d.fy = null
73
+ }
@@ -0,0 +1,19 @@
1
+ import { Relationship } from './graph-data'
2
+ import * as d3 from 'd3'
3
+
4
+ export function appendRelationshipToGraph(
5
+ svgRelationships: d3.Selection<SVGGElement, unknown, HTMLElement, any>, // 타입 지정
6
+ relationships: Relationship[],
7
+ options: any
8
+ ) {
9
+ const relationshipEnter = svgRelationships
10
+ .selectAll<SVGLineElement, Relationship>('.relationship')
11
+ .data(relationships, d => d.id)
12
+ .enter()
13
+ .append('line')
14
+ .attr('class', 'relationship')
15
+ .style('stroke', '#aaa')
16
+ .style('stroke-width', 2)
17
+
18
+ return relationshipEnter
19
+ }
@@ -0,0 +1,41 @@
1
+ import * as d3 from 'd3'
2
+
3
+ export function class2color(
4
+ classes2colors: Record<string, string>,
5
+ cls: string,
6
+ colors: string[],
7
+ numClasses: number
8
+ ): string {
9
+ if (!classes2colors[cls]) {
10
+ classes2colors[cls] = colors[numClasses % colors.length]
11
+ }
12
+ return classes2colors[cls]
13
+ }
14
+
15
+ export function class2darkenColor(color: string): string {
16
+ return d3.rgb(color).darker(1).toString()
17
+ }
18
+
19
+ export function unitaryVector(source: any, target: any, newLength?: number) {
20
+ const length =
21
+ Math.sqrt(Math.pow(target.x - source.x, 2) + Math.pow(target.y - source.y, 2)) / Math.sqrt(newLength || 1)
22
+ return {
23
+ x: (target.x - source.x) / length,
24
+ y: (target.y - source.y) / length
25
+ }
26
+ }
27
+
28
+ export function unitaryNormalVector(source: any, target: any, newLength?: number) {
29
+ const vector = unitaryVector(source, target, newLength)
30
+ return rotate({ x: 0, y: 0 }, vector, 90)
31
+ }
32
+
33
+ export function rotate(center: { x: number; y: number }, point: { x: number; y: number }, angle: number) {
34
+ const radians = (Math.PI / 180) * angle
35
+ const cos = Math.cos(radians)
36
+ const sin = Math.sin(radians)
37
+ return {
38
+ x: cos * (point.x - center.x) + sin * (point.y - center.y) + center.x,
39
+ y: cos * (point.y - center.y) - sin * (point.x - center.x) + center.y
40
+ }
41
+ }
@@ -1,13 +1,14 @@
1
1
  import '../analysis/graph-viewer'
2
2
 
3
3
  import gql from 'graphql-tag'
4
- import { css, html } from 'lit'
4
+ import { css, html, nothing } from 'lit'
5
5
  import { customElement, query, state } from 'lit/decorators.js'
6
6
 
7
7
  import { client } from '@operato/graphql'
8
8
  import { i18next, localize } from '@operato/i18n'
9
9
  import { PageView } from '@operato/shell'
10
10
  import { ScrollbarStyles } from '@operato/styles'
11
+ import { GraphData, Node, Relationship } from 'analysis/graph-data'
11
12
  import { GraphViewer } from '../analysis/graph-viewer'
12
13
  import { GraphViewerStyles } from '../analysis/graph-viewer-style'
13
14
 
@@ -18,6 +19,8 @@ export class IntegrationAnalysis extends localize(i18next)(PageView) {
18
19
  ScrollbarStyles,
19
20
  css`
20
21
  :host {
22
+ position: relative;
23
+
21
24
  display: flex;
22
25
  flex-direction: column;
23
26
 
@@ -31,98 +34,107 @@ export class IntegrationAnalysis extends localize(i18next)(PageView) {
31
34
  margin: 0;
32
35
  padding: 0;
33
36
  }
37
+
38
+ .graph-info {
39
+ position: absolute;
40
+ top: 10px;
41
+ left: 10px;
42
+ opacity: 0.8;
43
+ }
34
44
  `
35
45
  ]
36
46
 
37
- @state() analysis: any
38
- @state() graphViewer?: GraphViewer
47
+ @state() private graphViewer?: GraphViewer
48
+ @state() private analysis?: { nodes: Node[]; relationships: Relationship[] }
49
+ @state() private info?: { cls: string; property: string; value: string } | null
39
50
 
40
51
  @query('[graph-container]') container!: HTMLDivElement
52
+ @query('[node-info]') nodeInfo!: HTMLAnchorElement
41
53
 
42
54
  get context() {
43
55
  return {
44
56
  title: i18next.t('text.integration analysis'),
57
+ search: {
58
+ handler: search => this.onSearch(search)
59
+ },
45
60
  help: 'integration/ui/integration-analysis'
46
61
  }
47
62
  }
48
63
 
49
64
  render() {
50
- return html`<div graph-container></div>`
65
+ const info = this.info
66
+
67
+ return html`
68
+ <div
69
+ graph-container
70
+ @node-mouseenter=${(e: CustomEvent) => console.log(e.detail)}
71
+ @node-mouseleave=${(e: CustomEvent) => console.log(e.detail)}
72
+ ></div>
73
+ <div class="graph-info">
74
+ ${info
75
+ ? html`<a href="#" class="${info.cls}" node-info><strong>${info.property}</strong>${info.value}</a>`
76
+ : nothing}
77
+ </div>
78
+ `
51
79
  }
52
80
 
53
81
  updated(changes) {
54
82
  if (changes.has('analysis')) {
55
- this.graphViewer = new GraphViewer(this.container, {
56
- highlight: [
57
- {
58
- class: 'Connection',
59
- property: 'missing',
60
- value: true
61
- },
62
- {
63
- class: 'Scenario',
64
- property: 'missing',
65
- value: true
66
- },
67
- {
68
- class: 'Tag',
69
- property: 'missing',
70
- value: true
71
- }
72
- ],
73
- icons: {
74
- Connection: 'gear',
75
- Scenario: 'birthday-cake',
76
- Tag: 'paw'
77
- },
78
- // images: {
79
- // Address: 'img/twemoji/1f3e0.svg',
80
- // BirthDate: 'img/twemoji/1f5d3.svg',
81
- // Cookie: 'img/twemoji/1f36a.svg',
82
- // CreditCard: 'img/twemoji/1f4b3.svg',
83
- // Device: 'img/twemoji/1f4bb.svg',
84
- // Email: 'img/twemoji/2709.svg',
85
- // Git: 'img/twemoji/1f5c3.svg',
86
- // Github: 'img/twemoji/1f5c4.svg',
87
- // icons: 'img/twemoji/1f38f.svg',
88
- // Ip: 'img/twemoji/1f4cd.svg',
89
- // Issues: 'img/twemoji/1f4a9.svg',
90
- // Language: 'img/twemoji/1f1f1-1f1f7.svg',
91
- // Options: 'img/twemoji/2699.svg',
92
- // Password: 'img/twemoji/1f511.svg',
93
- // 'Project|name|d3': 'img/twemoji/32-20e3.svg',
94
- // User: 'img/twemoji/1f600.svg'
95
- // },
96
- minCollision: 60,
97
- graphData: {
98
- results: [
83
+ if (!this.graphViewer) {
84
+ this.graphViewer = new GraphViewer(this.container, {
85
+ highlight: [
86
+ {
87
+ class: 'Connection',
88
+ property: 'missing',
89
+ value: true
90
+ },
91
+ {
92
+ class: 'Scenario',
93
+ property: 'missing',
94
+ value: true
95
+ },
99
96
  {
100
- columns: ['Connection', 'Scenario', 'Tag'],
101
- data: [
102
- {
103
- graph: this.analysis
104
- }
105
- ]
97
+ class: 'Tag',
98
+ property: 'missing',
99
+ value: true
106
100
  }
107
101
  ],
102
+ minCollision: 60,
103
+ graphData: {
104
+ results: [
105
+ {
106
+ columns: [],
107
+ data: [
108
+ {
109
+ graph: this.analysis
110
+ }
111
+ ]
112
+ }
113
+ ],
114
+ errors: []
115
+ },
116
+ nodeRadius: 25,
117
+ onNodeDoubleClick: node => {
118
+ switch (node.id) {
119
+ case '25':
120
+ // Google
121
+ window.open(node.properties.url, '_blank')
122
+ break
123
+ default:
124
+ break
125
+ }
126
+ },
127
+ zoomFit: true
128
+ })
129
+ } else {
130
+ this.graphViewer.updateWithGraphData({
131
+ results: [{ columns: [], data: [{ graph: this.analysis! }] }],
108
132
  errors: []
109
- },
110
- nodeRadius: 25,
111
- onNodeDoubleClick: node => {
112
- switch (node.id) {
113
- case '25':
114
- // Google
115
- window.open(node.properties.url, '_blank')
116
- break
117
- default:
118
- var maxNodes = 5,
119
- data = this.graphViewer!.randomD3Data(node, maxNodes)
120
- this.graphViewer!.updateWithD3Data(data)
121
- break
122
- }
123
- },
124
- zoomFit: true
125
- })
133
+ })
134
+ }
135
+ }
136
+
137
+ if (changes.has('info') && this.info) {
126
138
  }
127
139
  }
128
140
 
@@ -148,4 +160,81 @@ export class IntegrationAnalysis extends localize(i18next)(PageView) {
148
160
 
149
161
  this.analysis = response.data.integrationAnalysis
150
162
  }
163
+
164
+ async onSearch(search: string) {
165
+ if (!this.graphViewer) {
166
+ return
167
+ }
168
+
169
+ if (!search) {
170
+ this.graphViewer.updateWithGraphData({
171
+ results: [{ columns: [], data: [{ graph: this.analysis! }] }],
172
+ errors: []
173
+ })
174
+ return
175
+ }
176
+
177
+ const { nodes = [], relationships = [] } = this.analysis || {}
178
+
179
+ // 검색어와 일치하는 노드를 필터링
180
+ const matchingNodes = nodes.filter(node => {
181
+ return node.properties.name && node.properties.name.toLowerCase().includes(search.toLowerCase())
182
+ })
183
+
184
+ if (matchingNodes.length === 0) {
185
+ this.graphViewer.updateWithGraphData({
186
+ results: [{ columns: [], data: [{ graph: { nodes: [], relationships: [] } }] }],
187
+ errors: []
188
+ })
189
+ return
190
+ }
191
+
192
+ // 1차로 연결된 노드와 관계를 찾기 위해 관련된 노드 ID를 추적
193
+ const relatedNodeIds = new Set<string>()
194
+ const filteredNodes = [] as Node[]
195
+ const filteredRelationships = [] as Relationship[]
196
+
197
+ matchingNodes.forEach(node => {
198
+ relatedNodeIds.add(node.id)
199
+ filteredNodes.push(node)
200
+
201
+ relationships.forEach(relationship => {
202
+ const { source, target } = relationship || {}
203
+ if (source!.id === node.id || target!.id === node.id) {
204
+ relatedNodeIds.add(source!.id)
205
+ relatedNodeIds.add(target!.id)
206
+ filteredRelationships.push(relationship!)
207
+ }
208
+ })
209
+ })
210
+
211
+ // 관련된 노드 추가
212
+ relatedNodeIds.forEach(nodeId => {
213
+ const node = nodes.find(n => n.id === nodeId)
214
+ if (node) {
215
+ filteredNodes.push(node)
216
+ }
217
+ })
218
+
219
+ // 새로운 GraphData 구성
220
+ const filteredGraphData: GraphData = {
221
+ results: [
222
+ {
223
+ columns: [],
224
+ data: [
225
+ {
226
+ graph: {
227
+ nodes: filteredNodes,
228
+ relationships: filteredRelationships
229
+ }
230
+ }
231
+ ]
232
+ }
233
+ ],
234
+ errors: []
235
+ }
236
+
237
+ // GraphViewer 업데이트
238
+ this.graphViewer.updateWithGraphData(filteredGraphData)
239
+ }
151
240
  }
@@ -0,0 +1,36 @@
1
+ export interface GraphData {
2
+ results: Array<{
3
+ columns: string[];
4
+ data: Array<{
5
+ graph: {
6
+ nodes: Node[];
7
+ relationships: Relationship[];
8
+ };
9
+ }>;
10
+ }>;
11
+ errors: any[];
12
+ }
13
+ export interface Node {
14
+ id: string;
15
+ labels: string[];
16
+ properties: {
17
+ [key: string]: any;
18
+ };
19
+ x?: number;
20
+ y?: number;
21
+ fx?: number | null;
22
+ fy?: number | null;
23
+ icon?: string;
24
+ }
25
+ export interface Relationship {
26
+ id: string;
27
+ type: string;
28
+ startNode: string;
29
+ endNode: string;
30
+ source?: Node;
31
+ target?: Node;
32
+ properties: {
33
+ [key: string]: any;
34
+ };
35
+ linknum?: number;
36
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=graph-data.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"graph-data.js","sourceRoot":"","sources":["../../client/analysis/graph-data.ts"],"names":[],"mappings":"","sourcesContent":["export interface GraphData {\n results: Array<{\n columns: string[]\n data: Array<{\n graph: {\n nodes: Node[]\n relationships: Relationship[]\n }\n }>\n }>\n errors: any[]\n}\n\nexport interface Node {\n id: string\n labels: string[]\n properties: { [key: string]: any }\n x?: number\n y?: number\n fx?: number | null\n fy?: number | null\n icon?: string\n}\n\nexport interface Relationship {\n id: string\n type: string\n startNode: string\n endNode: string\n source?: Node\n target?: Node\n properties: { [key: string]: any }\n linknum?: number\n}\n"]}
@@ -0,0 +1,110 @@
1
+ export declare class GraphViewer {
2
+ simulation: any;
3
+ private container;
4
+ private info;
5
+ private node;
6
+ private nodes;
7
+ private relationship;
8
+ private relationshipOutline;
9
+ private relationshipOverlay;
10
+ private relationshipText;
11
+ private relationships;
12
+ private selector;
13
+ private svg;
14
+ private svgNodes;
15
+ private svgRelationships;
16
+ private svgScale;
17
+ private svgTranslate;
18
+ private classes2colors;
19
+ private justLoaded;
20
+ private numClasses;
21
+ private options;
22
+ constructor(_selector: any, _options: any);
23
+ appendGraph(container: any): void;
24
+ appendImageToNode(node: any): any;
25
+ appendInfoPanel(container: any): any;
26
+ appendInfoElement(cls: any, isNode: any, property: any, value?: any): void;
27
+ appendInfoElementClass(cls: any, node: any): void;
28
+ appendInfoElementProperty(cls: any, property: any, value: any): void;
29
+ appendInfoElementRelationship(cls: any, relationship: any): void;
30
+ appendNode(): any;
31
+ appendNodeToGraph(): any;
32
+ appendOutlineToNode(node: any): any;
33
+ appendBoxToNode(node: any): any;
34
+ appendRingToNode(node: any): any;
35
+ appendTextToNode(node: any): any;
36
+ appendRelationship(): any;
37
+ appendOutlineToRelationship(r: any): any;
38
+ appendOverlayToRelationship(r: any): any;
39
+ appendTextToRelationship(r: any): any;
40
+ appendRelationshipToGraph(): {
41
+ outline: any;
42
+ overlay: any;
43
+ relationship: any;
44
+ text: any;
45
+ };
46
+ class2color(cls: any): any;
47
+ class2darkenColor(cls: any): any;
48
+ clearInfo(): void;
49
+ color(): any;
50
+ colors(): string[];
51
+ contains(array: any, id: any): boolean;
52
+ defaultColor(): any;
53
+ defaultDarkenColor(): any;
54
+ dragEnded(event: any, d: any): void;
55
+ dragged(event: any, d: any): void;
56
+ dragStarted(event: any, d: any): void;
57
+ extend(obj1: any, obj2: any): {};
58
+ icon(d: any): any;
59
+ image(d: any): any;
60
+ init(_selector: any, _options: any): void;
61
+ initSimulation(): any;
62
+ loadGraphData(): void;
63
+ loadGraphDataFromUrl(dataUrl: any): void;
64
+ merge(target: any, source: any): void;
65
+ graphDataToD3Data(data: any): {
66
+ nodes: any[];
67
+ relationships: any[];
68
+ };
69
+ randomD3Data(d: any, maxNodesToGenerate: any): {
70
+ nodes: any[];
71
+ relationships: any[];
72
+ };
73
+ randomLabel(): string;
74
+ rotate(cx: any, cy: any, x: any, y: any, angle: any): {
75
+ x: any;
76
+ y: any;
77
+ };
78
+ rotatePoint(c: any, p: any, angle: any): {
79
+ x: any;
80
+ y: any;
81
+ };
82
+ rotation(source: any, target: any): number;
83
+ size(): {
84
+ nodes: any;
85
+ relationships: any;
86
+ };
87
+ stickNode(event: any, d: any): void;
88
+ tick(): void;
89
+ tickNodes(): void;
90
+ tickRelationships(): void;
91
+ tickRelationshipsOutlines(): void;
92
+ tickRelationshipsOverlays(): void;
93
+ tickRelationshipsTexts(): void;
94
+ toString(d: any): any;
95
+ unitaryNormalVector(source: any, target: any, newLength?: any): {
96
+ x: any;
97
+ y: any;
98
+ };
99
+ unitaryVector(source: any, target: any, newLength?: any): {
100
+ x: number;
101
+ y: number;
102
+ };
103
+ updateWithD3Data(d3Data: any): void;
104
+ updateWithGraphData(graphData: any): void;
105
+ updateInfo(d: any): void;
106
+ updateNodes(n: any): void;
107
+ updateNodesAndRelationships(n: any, r: any): void;
108
+ updateRelationships(r: any): void;
109
+ zoomFit(transitionDuration: any): void;
110
+ }