Skip to content

Commit 18c2645

Browse files
alanconwayzhuje
authored andcommitted
refactor: move graph logic to korrel8r/types, separate from UI
1 parent 2a4d23a commit 18c2645

4 files changed

Lines changed: 212 additions & 288 deletions

File tree

web/src/__tests__/types.spec.ts

Lines changed: 55 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,7 @@
1-
import {
2-
Class,
3-
Constraint,
4-
Domain,
5-
Domains,
6-
Node,
7-
Query,
8-
QueryRef,
9-
URIRef,
10-
} from '../korrel8r/types';
11-
121
import * as api from '../korrel8r/client';
132

3+
import { Class, Constraint, Domain, Domains, Graph, Node, Query, URIRef } from '../korrel8r/types';
4+
145
describe('Query', () => {
156
it('converts to/from string', () => {
167
const abc = new Class('a', 'b').query('c=d');
@@ -120,96 +111,75 @@ describe('URIRef', () => {
120111
});
121112

122113
describe('Node', () => {
123-
const domains = new Domains(...['a', 'x'].map((name: string): Domain => new FakeDomain(name)));
124-
125114
it('constructor', () => {
126115
expect(
127-
new Node(
128-
{
129-
class: 'a:b',
130-
count: 10,
131-
queries: [
132-
{ query: 'a:b:c', count: 5 },
133-
{ query: 'a:b:d', count: 5 },
134-
],
135-
},
136-
domains,
137-
),
116+
new Node({
117+
class: 'a:b',
118+
count: 10,
119+
queries: [
120+
{ query: 'a:b:c', count: 5 },
121+
{ query: 'a:b:d', count: 5 },
122+
],
123+
}),
138124
).toEqual({
139-
classStr: 'a:b',
125+
api: {
126+
class: 'a:b',
127+
count: 10,
128+
queries: [
129+
{ query: 'a:b:c', count: 5 },
130+
{ query: 'a:b:d', count: 5 },
131+
],
132+
},
133+
140134
class: { domain: 'a', name: 'b' },
141-
count: 10,
142135
queries: [
143136
{
144-
count: 5,
145-
queryStr: 'a:b:c',
137+
queryCount: { query: 'a:b:c', count: 5 },
146138
query: { class: { domain: 'a', name: 'b' }, selector: 'c' },
147-
link: 'a/b?c',
148139
},
149140
{
150-
count: 5,
151-
queryStr: 'a:b:d',
141+
queryCount: { query: 'a:b:d', count: 5 },
152142
query: { class: { domain: 'a', name: 'b' }, selector: 'd' },
153-
link: 'a/b?d',
154143
},
155144
],
156145
});
157146
});
158147

159-
it('constructor constraint', () => {
160-
expect(
161-
new Node(
162-
{
163-
class: 'a:b',
164-
count: 10,
165-
queries: [{ query: 'a:b:c', count: 5 }],
166-
},
167-
domains,
168-
new Constraint({ start, end }),
169-
),
170-
).toEqual({
171-
class: { domain: 'a', name: 'b' } as Class,
172-
classStr: 'a:b',
173-
count: 10,
174-
queries: [
175-
{
176-
count: 5,
177-
queryStr: 'a:b:c',
178-
query: { class: { domain: 'a', name: 'b' }, selector: 'c' } as Query,
179-
link: `a/b?c&constraint={"start":"${start.toISOString()}","end":"${end.toISOString()}"}`,
180-
} as QueryRef,
181-
],
182-
} as Node);
183-
});
184-
185-
it('constructor class mismatch', () => {
186-
expect(
187-
new Node({ class: 'a:b', queries: [{ query: 'a:x:c', count: 5 }], count: 1 }, domains),
188-
).toEqual({
189-
class: { domain: 'a', name: 'b' },
190-
count: 1,
191-
classStr: 'a:b',
192-
queries: [
193-
{
194-
count: 5,
195-
error: 'query a:x:c: wrong class, expected a:b',
196-
query: {
197-
class: { domain: 'a', name: 'x' },
198-
selector: 'c',
199-
},
200-
queryStr: 'a:x:c',
201-
link: 'a/x?c',
202-
},
203-
],
204-
error: 'No valid links',
205-
} as Node);
206-
});
207-
208148
it('constructor bad class', () => {
209-
expect(new Node({ class: 'foobar', count: 1 }, domains)).toEqual({
210-
classStr: 'foobar',
211-
count: 1,
212-
error: 'node foobar: invalid class: foobar',
149+
expect(new Node({ class: 'foobar', count: 1 })).toEqual({
150+
api: { class: 'foobar', count: 1 },
151+
error: new TypeError('invalid class: foobar'),
152+
queries: [],
213153
});
214154
});
215155
});
156+
157+
describe('Graph', () => {
158+
const a: api.Graph = {
159+
nodes: [
160+
{ class: 'a:x', count: 1, queries: [{ query: 'a:x:one', count: 1 }] },
161+
{ class: 'b:y', count: 2, queries: [{ query: 'b:y:two', count: 2 }] },
162+
{
163+
class: 'c:z',
164+
count: 4,
165+
queries: [
166+
{ query: 'c:z:one', count: 1 },
167+
{ query: 'c:z:three', count: 3 },
168+
],
169+
},
170+
],
171+
edges: [
172+
{ start: 'a:x', goal: 'b:y' },
173+
{ start: 'a:x', goal: 'c:z' },
174+
{ start: 'b:y', goal: 'c:z' },
175+
],
176+
};
177+
const g = new Graph(a);
178+
g.nodes.forEach((n) => expect(g.node(n.id)).toEqual(n)); // Lookup nodes
179+
expect(g.nodes).toEqual(a.nodes.map((n) => new Node(n)));
180+
expect(g.edges).toEqual(
181+
a.edges.map((e: api.Edge) => {
182+
return { api: e, start: g.node(e.start), goal: g.node(e.goal) };
183+
}),
184+
);
185+
});

web/src/components/Korrel8rPanel.tsx

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ import { useDispatch, useSelector } from 'react-redux';
2323
import { useLocationQuery } from '../hooks/useLocationQuery';
2424
import { usePluginAvailable } from '../hooks/usePluginAvailable';
2525
import { getGoalsGraph, getNeighborsGraph } from '../korrel8r-client';
26-
import { ApiError, Graph, Start } from '../korrel8r/client';
27-
import { Constraint } from '../korrel8r/types';
26+
import * as api from '../korrel8r/client';
27+
import * as korrel8r from '../korrel8r/types';
2828
import { defaultSearch, Search, SearchType, setPersistedSearch } from '../redux-actions';
2929
import { State } from '../redux-reducers';
3030
import DateTimeRangePicker from './DateTimeRangePicker';
@@ -33,7 +33,7 @@ import { Korrel8rTopology } from './topology/Korrel8rTopology';
3333
import { LoadingTopology } from './topology/LoadingTopology';
3434

3535
type Result = {
36-
graph?: Graph;
36+
graph?: korrel8r.Graph;
3737
message?: string;
3838
title?: string;
3939
isError?: boolean;
@@ -73,21 +73,21 @@ export default function Korrel8rPanel() {
7373
}
7474
// Make the search request
7575
const queryStr = search?.queryStr?.trim();
76-
const start: Start = { queries: queryStr ? [queryStr] : [] };
76+
const start: api.Start = { queries: queryStr ? [queryStr] : [] };
7777
const cancellableFetch =
7878
search.type === SearchType.Goal
7979
? getGoalsGraph({ start, goals: [search.goal] })
8080
: getNeighborsGraph({ start, depth: search.depth });
8181

8282
cancellableFetch
83-
.then((response: Graph) => {
84-
setResult({ graph: response });
83+
.then((response: api.Graph) => {
84+
setResult({ graph: new korrel8r.Graph(response) });
8585
// Only set the persisted search upon a successful query. It would be a
8686
// poor feeling to create a query that fails, and then be forced to rerun it
8787
// when opening the panel later
8888
dispatch(setPersistedSearch(search));
8989
})
90-
.catch((e: ApiError) => {
90+
.catch((e: api.ApiError) => {
9191
setResult({
9292
message: e.body?.error || e.message || 'Unknown Error',
9393
title: e?.body?.error ? t('Korrel8r Error') : t('Request Failed'),
@@ -118,7 +118,7 @@ export default function Korrel8rPanel() {
118118
// Update the constraint based on 'start' or 'end' type
119119
const updatedSearch = {
120120
...search,
121-
constraint: new Constraint({
121+
constraint: new korrel8r.Constraint({
122122
...search.constraint,
123123
[type]: updatedDate.toISOString(), // Update the corresponding date in search
124124
}),

0 commit comments

Comments
 (0)