@things-factory/integration-ui 7.0.33 → 7.0.35

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) 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/package.json +2 -2
@@ -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
+ }