@truedat/core 8.1.1 → 8.1.4
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.
- package/package.json +3 -3
- package/src/api.js +2 -0
- package/src/components/CatalogMenu.js +2 -2
- package/src/components/GlossaryMenu.js +11 -15
- package/src/components/QualityMenu.js +34 -9
- package/src/components/Submenu.js +48 -7
- package/src/components/UploadJob.js +217 -0
- package/src/components/UploadJobBreadcrumbs.js +38 -0
- package/src/components/UploadJobParser.js +370 -0
- package/src/components/UploadJobs.js +136 -0
- package/src/components/__tests__/Submenu.spec.js +13 -0
- package/src/components/__tests__/UploadJob.spec.js +112 -0
- package/src/components/__tests__/UploadJobBreadcrumbs.spec.js +37 -0
- package/src/components/__tests__/UploadJobParser.spec.js +103 -0
- package/src/components/__tests__/UploadJobs.spec.js +60 -0
- package/src/components/__tests__/__snapshots__/AdminMenu.spec.js.snap +10 -0
- package/src/components/__tests__/__snapshots__/CatalogMenu.spec.js.snap +1 -1
- package/src/components/__tests__/__snapshots__/SideMenu.spec.js.snap +1 -1
- package/src/components/__tests__/__snapshots__/Submenu.spec.js.snap +37 -0
- package/src/components/__tests__/__snapshots__/UploadJobBreadcrumbs.spec.js.snap +42 -0
- package/src/hooks/__tests__/useActiveRoutes.spec.js +83 -0
- package/src/hooks/index.js +1 -0
- package/src/hooks/useActiveRoutes.js +27 -21
- package/src/hooks/useUploadJobs.js +24 -0
- package/src/routes.js +6 -2
- package/src/selectors/__tests__/getRiSubscopes.spec.js +53 -0
- package/src/selectors/getRiSubscopes.js +8 -0
- package/src/selectors/index.js +1 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { render } from "@truedat/test/render";
|
|
2
|
+
import { StatusPill, ResponseCell } from "../UploadJobParser";
|
|
3
|
+
|
|
4
|
+
describe("<StatusPill />", () => {
|
|
5
|
+
it("renders with correct color based on status", async () => {
|
|
6
|
+
const statuses = ["COMPLETED", "FAILED", "ERROR", "INFO", "OTHER"];
|
|
7
|
+
const expectedColors = ["green", "red", "red", "blue", "grey"];
|
|
8
|
+
|
|
9
|
+
statuses.forEach((status, index) => {
|
|
10
|
+
const rendered = render(<StatusPill status={status} />);
|
|
11
|
+
const label = rendered.container.querySelector(".ui.label");
|
|
12
|
+
expect(label).toHaveClass(expectedColors[index]);
|
|
13
|
+
expect(
|
|
14
|
+
rendered.getByText(
|
|
15
|
+
new RegExp(`uploadJob.parser.event.status.${status}`, "i")
|
|
16
|
+
)
|
|
17
|
+
).toBeInTheDocument();
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
describe("<ResponseCell />", () => {
|
|
23
|
+
it("returns null when response is empty", () => {
|
|
24
|
+
const rendered = render(
|
|
25
|
+
<ResponseCell response={null} status="COMPLETED" />
|
|
26
|
+
);
|
|
27
|
+
expect(rendered.container.firstChild).toBeNull();
|
|
28
|
+
|
|
29
|
+
const rendered2 = render(<ResponseCell response={{}} status="COMPLETED" />);
|
|
30
|
+
expect(rendered2.container.firstChild).toBeNull();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("renders FAILED status with message", () => {
|
|
34
|
+
const response = { message: "missing_required_headers" };
|
|
35
|
+
const rendered = render(
|
|
36
|
+
<ResponseCell response={response} status="FAILED" />
|
|
37
|
+
);
|
|
38
|
+
expect(
|
|
39
|
+
rendered.getByText(/missing_required_headers/i)
|
|
40
|
+
).toBeInTheDocument();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("renders ERROR status with details", () => {
|
|
44
|
+
const response = {
|
|
45
|
+
type: "missing_required_headers",
|
|
46
|
+
details: {
|
|
47
|
+
missing_headers: ["header1", "header2"],
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
const rendered = render(
|
|
51
|
+
<ResponseCell response={response} status="ERROR" />
|
|
52
|
+
);
|
|
53
|
+
expect(
|
|
54
|
+
rendered.getByText(
|
|
55
|
+
/uploadJob.parser.error.missing_required_headers/i
|
|
56
|
+
)
|
|
57
|
+
).toBeInTheDocument();
|
|
58
|
+
expect(rendered.getByText(/header1, header2/i)).toBeInTheDocument();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("renders COMPLETED status with summary", () => {
|
|
62
|
+
const response = {
|
|
63
|
+
insert_count: 2,
|
|
64
|
+
update_count: 1,
|
|
65
|
+
error_count: 0,
|
|
66
|
+
};
|
|
67
|
+
const rendered = render(
|
|
68
|
+
<ResponseCell response={response} status="COMPLETED" />
|
|
69
|
+
);
|
|
70
|
+
expect(
|
|
71
|
+
rendered.getByText(/uploadJob.parser.result.summary.created/i)
|
|
72
|
+
).toBeInTheDocument();
|
|
73
|
+
expect(
|
|
74
|
+
rendered.getByText(/uploadJob.parser.result.summary.updated/i)
|
|
75
|
+
).toBeInTheDocument();
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("renders INFO status with implementation details", () => {
|
|
79
|
+
const response = {
|
|
80
|
+
type: "implementation_updated",
|
|
81
|
+
details: {
|
|
82
|
+
id: 123,
|
|
83
|
+
changes: {
|
|
84
|
+
name: "new name",
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
const rendered = render(<ResponseCell response={response} status="INFO" />);
|
|
89
|
+
expect(
|
|
90
|
+
rendered.getByText(
|
|
91
|
+
/uploadJob.parser.info.implementation_updated/i
|
|
92
|
+
)
|
|
93
|
+
).toBeInTheDocument();
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("renders default case for unknown status", () => {
|
|
97
|
+
const response = "some text";
|
|
98
|
+
const rendered = render(
|
|
99
|
+
<ResponseCell response={response} status="UNKNOWN" />
|
|
100
|
+
);
|
|
101
|
+
expect(rendered.getByText(/some text/i)).toBeInTheDocument();
|
|
102
|
+
});
|
|
103
|
+
});
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { render, waitForLoad } from "@truedat/test/render";
|
|
2
|
+
import { useUploadJobs } from "../../hooks/useUploadJobs";
|
|
3
|
+
import UploadJobs from "../UploadJobs";
|
|
4
|
+
|
|
5
|
+
jest.mock("../../hooks/useUploadJobs", () => ({
|
|
6
|
+
useUploadJobs: jest.fn(),
|
|
7
|
+
}));
|
|
8
|
+
|
|
9
|
+
describe("<UploadJobs />", () => {
|
|
10
|
+
it("renders loading state", async () => {
|
|
11
|
+
useUploadJobs.mockReturnValue({
|
|
12
|
+
loading: true,
|
|
13
|
+
data: null,
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
const rendered = render(<UploadJobs />);
|
|
17
|
+
|
|
18
|
+
expect(rendered.container.querySelector(".ui.loader")).toBeInTheDocument();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("renders empty state", async () => {
|
|
22
|
+
useUploadJobs.mockReturnValue({
|
|
23
|
+
loading: false,
|
|
24
|
+
data: {
|
|
25
|
+
data: [],
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const rendered = render(<UploadJobs />);
|
|
30
|
+
await waitForLoad(rendered);
|
|
31
|
+
|
|
32
|
+
expect(rendered.getByText(/No jobs found./i)).toBeInTheDocument();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("renders jobs list", async () => {
|
|
36
|
+
const mockJobs = [
|
|
37
|
+
{
|
|
38
|
+
id: 1,
|
|
39
|
+
filename: "test.csv",
|
|
40
|
+
latest_status: "completed",
|
|
41
|
+
latest_event_response: "Success",
|
|
42
|
+
latest_event_at: "2023-01-01T00:00:00Z",
|
|
43
|
+
},
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
useUploadJobs.mockReturnValue({
|
|
47
|
+
loading: false,
|
|
48
|
+
data: {
|
|
49
|
+
data: mockJobs,
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const rendered = render(<UploadJobs />);
|
|
54
|
+
await waitForLoad(rendered);
|
|
55
|
+
|
|
56
|
+
expect(rendered.getByText(/test.csv/i)).toBeInTheDocument();
|
|
57
|
+
expect(rendered.getByText(/completed/i)).toBeInTheDocument();
|
|
58
|
+
expect(rendered.getByText(/success/i)).toBeInTheDocument();
|
|
59
|
+
});
|
|
60
|
+
});
|
|
@@ -32,6 +32,7 @@ exports[`<AdminMenu /> matches the latest snapshot 1`] = `
|
|
|
32
32
|
class="divider"
|
|
33
33
|
/>
|
|
34
34
|
<a
|
|
35
|
+
aria-checked="false"
|
|
35
36
|
class="item"
|
|
36
37
|
data-discover="true"
|
|
37
38
|
href="/templates"
|
|
@@ -45,6 +46,7 @@ exports[`<AdminMenu /> matches the latest snapshot 1`] = `
|
|
|
45
46
|
</span>
|
|
46
47
|
</a>
|
|
47
48
|
<a
|
|
49
|
+
aria-checked="false"
|
|
48
50
|
class="item"
|
|
49
51
|
data-discover="true"
|
|
50
52
|
href="/hierarchies"
|
|
@@ -58,6 +60,7 @@ exports[`<AdminMenu /> matches the latest snapshot 1`] = `
|
|
|
58
60
|
</span>
|
|
59
61
|
</a>
|
|
60
62
|
<a
|
|
63
|
+
aria-checked="false"
|
|
61
64
|
class="item"
|
|
62
65
|
data-discover="true"
|
|
63
66
|
href="/relationTags"
|
|
@@ -71,6 +74,7 @@ exports[`<AdminMenu /> matches the latest snapshot 1`] = `
|
|
|
71
74
|
</span>
|
|
72
75
|
</a>
|
|
73
76
|
<a
|
|
77
|
+
aria-checked="false"
|
|
74
78
|
class="item"
|
|
75
79
|
data-discover="true"
|
|
76
80
|
href="/subscriptions"
|
|
@@ -84,6 +88,7 @@ exports[`<AdminMenu /> matches the latest snapshot 1`] = `
|
|
|
84
88
|
</span>
|
|
85
89
|
</a>
|
|
86
90
|
<a
|
|
91
|
+
aria-checked="false"
|
|
87
92
|
class="item"
|
|
88
93
|
data-discover="true"
|
|
89
94
|
href="/sources"
|
|
@@ -97,6 +102,7 @@ exports[`<AdminMenu /> matches the latest snapshot 1`] = `
|
|
|
97
102
|
</span>
|
|
98
103
|
</a>
|
|
99
104
|
<a
|
|
105
|
+
aria-checked="false"
|
|
100
106
|
class="item"
|
|
101
107
|
data-discover="true"
|
|
102
108
|
href="/jobs"
|
|
@@ -110,6 +116,7 @@ exports[`<AdminMenu /> matches the latest snapshot 1`] = `
|
|
|
110
116
|
</span>
|
|
111
117
|
</a>
|
|
112
118
|
<a
|
|
119
|
+
aria-checked="false"
|
|
113
120
|
class="item"
|
|
114
121
|
data-discover="true"
|
|
115
122
|
href="/configurations"
|
|
@@ -123,6 +130,7 @@ exports[`<AdminMenu /> matches the latest snapshot 1`] = `
|
|
|
123
130
|
</span>
|
|
124
131
|
</a>
|
|
125
132
|
<a
|
|
133
|
+
aria-checked="false"
|
|
126
134
|
class="item"
|
|
127
135
|
data-discover="true"
|
|
128
136
|
href="/i18n/messages"
|
|
@@ -136,6 +144,7 @@ exports[`<AdminMenu /> matches the latest snapshot 1`] = `
|
|
|
136
144
|
</span>
|
|
137
145
|
</a>
|
|
138
146
|
<a
|
|
147
|
+
aria-checked="false"
|
|
139
148
|
class="item"
|
|
140
149
|
data-discover="true"
|
|
141
150
|
href="/reindex"
|
|
@@ -149,6 +158,7 @@ exports[`<AdminMenu /> matches the latest snapshot 1`] = `
|
|
|
149
158
|
</span>
|
|
150
159
|
</a>
|
|
151
160
|
<a
|
|
161
|
+
aria-checked="false"
|
|
152
162
|
class="item"
|
|
153
163
|
data-discover="true"
|
|
154
164
|
href="/search/elasticIndexes"
|
|
@@ -36,3 +36,40 @@ exports[`<Submenu /> matches the latest snapshot 1`] = `
|
|
|
36
36
|
</div>
|
|
37
37
|
</div>
|
|
38
38
|
`;
|
|
39
|
+
|
|
40
|
+
exports[`<Submenu /> should handle subscope path detection correctly 1`] = `
|
|
41
|
+
<div>
|
|
42
|
+
<div
|
|
43
|
+
class="active item selectable"
|
|
44
|
+
>
|
|
45
|
+
<a
|
|
46
|
+
data-discover="true"
|
|
47
|
+
href="/implementations"
|
|
48
|
+
>
|
|
49
|
+
<i
|
|
50
|
+
aria-hidden="true"
|
|
51
|
+
class="large icon"
|
|
52
|
+
/>
|
|
53
|
+
quality
|
|
54
|
+
</a>
|
|
55
|
+
<div
|
|
56
|
+
class="menu"
|
|
57
|
+
>
|
|
58
|
+
<a
|
|
59
|
+
class="link item"
|
|
60
|
+
data-discover="true"
|
|
61
|
+
href="/implementations"
|
|
62
|
+
>
|
|
63
|
+
implementations
|
|
64
|
+
</a>
|
|
65
|
+
<a
|
|
66
|
+
class="active link item"
|
|
67
|
+
data-discover="true"
|
|
68
|
+
href="/implementations/subscope/subscope1"
|
|
69
|
+
>
|
|
70
|
+
subscope1
|
|
71
|
+
</a>
|
|
72
|
+
</div>
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
`;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
2
|
+
|
|
3
|
+
exports[`ImplementationUploadJobBreadcrumbs renders breadcrumbs with filename 1`] = `
|
|
4
|
+
<div>
|
|
5
|
+
<div
|
|
6
|
+
class="ui breadcrumb"
|
|
7
|
+
>
|
|
8
|
+
<a
|
|
9
|
+
class="section"
|
|
10
|
+
data-discover="true"
|
|
11
|
+
href="/implementations/uploadJobs"
|
|
12
|
+
>
|
|
13
|
+
uploadJobs.implementations.header
|
|
14
|
+
</a>
|
|
15
|
+
<i
|
|
16
|
+
aria-hidden="true"
|
|
17
|
+
class="right angle icon divider"
|
|
18
|
+
/>
|
|
19
|
+
<div
|
|
20
|
+
class="active section"
|
|
21
|
+
>
|
|
22
|
+
test.csv
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
`;
|
|
27
|
+
|
|
28
|
+
exports[`ImplementationUploadJobBreadcrumbs renders breadcrumbs without filename 1`] = `
|
|
29
|
+
<div>
|
|
30
|
+
<div
|
|
31
|
+
class="ui breadcrumb"
|
|
32
|
+
>
|
|
33
|
+
<a
|
|
34
|
+
class="section"
|
|
35
|
+
data-discover="true"
|
|
36
|
+
href="/implementations/uploadJobs"
|
|
37
|
+
>
|
|
38
|
+
uploadJobs.implementations.header
|
|
39
|
+
</a>
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
42
|
+
`;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { useActiveRoutes } from "../useActiveRoutes";
|
|
2
|
+
|
|
3
|
+
// Mock the react-router and lodash functions that are used in the hook
|
|
4
|
+
jest.mock("react-router", () => ({
|
|
5
|
+
useLocation: () => ({ pathname: "/test" }),
|
|
6
|
+
}));
|
|
7
|
+
|
|
8
|
+
jest.mock("lodash/fp", () => ({
|
|
9
|
+
prop: jest.fn((key) => (obj) => obj[key]),
|
|
10
|
+
orderBy: jest.fn((iteratees, orders, collection) => collection.sort()),
|
|
11
|
+
castArray: jest.fn((val) => Array.isArray(val) ? val : [val]),
|
|
12
|
+
size: jest.fn((val) => typeof val === 'string' ? val.length : 0),
|
|
13
|
+
flow: jest.fn((...funcs) => (...args) => funcs.reduceRight((arg, fn) => fn(arg), args)),
|
|
14
|
+
map: jest.fn((fn) => (arr) => arr.map(fn)),
|
|
15
|
+
head: jest.fn((arr) => arr[0]),
|
|
16
|
+
}));
|
|
17
|
+
|
|
18
|
+
jest.mock("@truedat/core/routes", () => ({
|
|
19
|
+
BUCKETS_VIEW: "/buckets/:propertyPath",
|
|
20
|
+
}));
|
|
21
|
+
|
|
22
|
+
jest.mock("react-router", () => ({
|
|
23
|
+
matchPath: jest.fn(() => null),
|
|
24
|
+
}));
|
|
25
|
+
|
|
26
|
+
describe("hooks: useActiveRoutes", () => {
|
|
27
|
+
beforeEach(() => {
|
|
28
|
+
jest.clearAllMocks();
|
|
29
|
+
|
|
30
|
+
// Setup default mocks
|
|
31
|
+
require("lodash/fp").prop.mockImplementation((key) => (obj) => obj[key]);
|
|
32
|
+
require("lodash/fp").orderBy.mockImplementation((iteratees, orders, collection) => collection);
|
|
33
|
+
require("lodash/fp").castArray.mockImplementation((val) => Array.isArray(val) ? val : [val]);
|
|
34
|
+
require("react-router").useLocation = () => ({ pathname: "/test" });
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("should return the most specific route when multiple routes match", () => {
|
|
38
|
+
const mockUseLocation = jest.spyOn(require("react-router"), "useLocation");
|
|
39
|
+
mockUseLocation.mockReturnValue({ pathname: "/implementations/subscope/test" });
|
|
40
|
+
|
|
41
|
+
const mockRoutes = ["/implementations", "/implementations/subscope/test"];
|
|
42
|
+
|
|
43
|
+
const result = useActiveRoutes(mockRoutes, null);
|
|
44
|
+
|
|
45
|
+
// Should return the more specific route
|
|
46
|
+
expect(result).toBe("/implementations/subscope/test");
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("should handle subscope path detection correctly", () => {
|
|
50
|
+
const mockUseLocation = jest.spyOn(require("react-router"), "useLocation");
|
|
51
|
+
mockUseLocation.mockReturnValue({ pathname: "/concepts/subscope/AI_Initiative" });
|
|
52
|
+
|
|
53
|
+
const mockRoutes = ["/concepts", "/concepts/subscope/AI_Initiative"];
|
|
54
|
+
|
|
55
|
+
const result = useActiveRoutes(mockRoutes, null);
|
|
56
|
+
|
|
57
|
+
// Should return the more specific route
|
|
58
|
+
expect(result).toBe("/concepts/subscope/AI_Initiative");
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("should return null when no routes match", () => {
|
|
62
|
+
const mockUseLocation = jest.spyOn(require("react-router"), "useLocation");
|
|
63
|
+
mockUseLocation.mockReturnValue({ pathname: "/other" });
|
|
64
|
+
|
|
65
|
+
const mockRoutes = ["/implementations", "/concepts"];
|
|
66
|
+
|
|
67
|
+
const result = useActiveRoutes(mockRoutes, null);
|
|
68
|
+
|
|
69
|
+
expect(result).toBeNull();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("should handle URL decoding properly", () => {
|
|
73
|
+
const mockUseLocation = jest.spyOn(require("react-router"), "useLocation");
|
|
74
|
+
mockUseLocation.mockReturnValue({ pathname: "/concepts/subscope/AI%20Initiative" });
|
|
75
|
+
|
|
76
|
+
const mockRoutes = ["/concepts", "/concepts/subscope/AI Initiative"];
|
|
77
|
+
|
|
78
|
+
const result = useActiveRoutes(mockRoutes, null);
|
|
79
|
+
|
|
80
|
+
// Should match the decoded route
|
|
81
|
+
expect(result).toBe("/concepts/subscope/AI Initiative");
|
|
82
|
+
});
|
|
83
|
+
});
|
package/src/hooks/index.js
CHANGED
|
@@ -6,27 +6,33 @@ import { matchPath } from "react-router";
|
|
|
6
6
|
export const useActiveRoutes = (route, navFilter) => {
|
|
7
7
|
const location = useLocation();
|
|
8
8
|
const pathname = _.prop("pathname")(location);
|
|
9
|
-
const
|
|
9
|
+
const decodedPathname = decodeURIComponent(pathname);
|
|
10
|
+
const routes = _.orderBy([_.size], ['desc'], _.castArray(route)); // Sort routes by length in descending order to prioritize more specific routes
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
}
|
|
16
|
-
_.reduce(
|
|
17
|
-
(acc, { route, filterMatch }) => {
|
|
18
|
-
const { filterMatchRoutes, pathMatchRoutes } = acc;
|
|
12
|
+
for (const routeObj of _.map((route) => ({
|
|
13
|
+
route,
|
|
14
|
+
filterMatch: matchPath({ path: BUCKETS_VIEW }, route),
|
|
15
|
+
}))(routes)) {
|
|
16
|
+
const { route, filterMatch } = routeObj;
|
|
19
17
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
18
|
+
if (navFilter &&
|
|
19
|
+
filterMatch?.params?.propertyPath &&
|
|
20
|
+
_.includes(filterMatch?.params?.propertyPath, Object.keys(navFilter))) {
|
|
21
|
+
return route;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (route === pathname || route === decodedPathname) {
|
|
25
|
+
return route;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (_.startsWith(`${route}/`)(pathname) || _.startsWith(`${route}/`)(decodedPathname)) {
|
|
29
|
+
return route;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (matchPath({ path: route }, pathname) || matchPath({ path: route }, decodedPathname)) {
|
|
33
|
+
return route;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return null;
|
|
32
38
|
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { compile } from "path-to-regexp";
|
|
2
|
+
import useSWR from "swr";
|
|
3
|
+
import { apiJson } from "@truedat/core/services/api";
|
|
4
|
+
import {
|
|
5
|
+
API_UPLOAD_JOBS,
|
|
6
|
+
API_UPLOAD_JOB,
|
|
7
|
+
} from "../api";
|
|
8
|
+
|
|
9
|
+
export const useUploadJobs = (scope) => {
|
|
10
|
+
const url = scope ? `${API_UPLOAD_JOBS}?scope=${scope}` : API_UPLOAD_JOBS;
|
|
11
|
+
const { data, error, mutate } = useSWR(
|
|
12
|
+
url,
|
|
13
|
+
apiJson
|
|
14
|
+
);
|
|
15
|
+
return { data: data?.data, error, loading: !error && !data, mutate };
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const useUploadJob = (id) => {
|
|
19
|
+
const url = compile(API_UPLOAD_JOB)({
|
|
20
|
+
id: `${id}`,
|
|
21
|
+
});
|
|
22
|
+
const { data, error, mutate } = useSWR(url, apiJson);
|
|
23
|
+
return { data: data?.data, error, loading: !error && !data, mutate };
|
|
24
|
+
};
|
package/src/routes.js
CHANGED
|
@@ -127,6 +127,7 @@ export const IMPLEMENTATION_MOVE = "/implementations/:implementation_id/move";
|
|
|
127
127
|
export const IMPLEMENTATION_NEW = "/implementations/new";
|
|
128
128
|
export const IMPLEMENTATION_NEW_BASIC = "/implementations/basic";
|
|
129
129
|
export const IMPLEMENTATION_NEW_RAW = "/implementations/new_raw";
|
|
130
|
+
export const IMPLEMENTATIONS_BY_SUBSCOPE = "/implementations/subscope/:subscope";
|
|
130
131
|
export const IMPLEMENTATION_RESULTS =
|
|
131
132
|
"/implementations/:implementation_id/results";
|
|
132
133
|
export const IMPLEMENTATION_RESULTS_DETAILS =
|
|
@@ -233,7 +234,6 @@ export const SOURCE_JOBS_NEW = "/sources/:sourceId/jobs/new";
|
|
|
233
234
|
export const STRUCTURE = "/structures/:id";
|
|
234
235
|
export const STRUCTURES = "/structures";
|
|
235
236
|
export const STRUCTURES_BULK_UPDATE = "/structures/bulkUpdate";
|
|
236
|
-
export const STRUCTURES_UPLOAD_EVENTS = "/bulkUpdateTemplateContentEvents";
|
|
237
237
|
export const STRUCTURE_CHILDREN = "/structures/:id/children";
|
|
238
238
|
export const STRUCTURE_FIELDS = "/structures/:id/fields";
|
|
239
239
|
export const STRUCTURE_VERSION_FIELDS =
|
|
@@ -253,6 +253,8 @@ export const STRUCTURE_MEMBERS_NEW = "/structures/:id/members/new";
|
|
|
253
253
|
export const STRUCTURE_METADATA = "/structures/:id/metadata";
|
|
254
254
|
export const STRUCTURE_NOTES = "/structures/:id/notes";
|
|
255
255
|
export const STRUCTURE_NOTES_EDIT = "/structures/:id/notes/edit";
|
|
256
|
+
export const STRUCTURE_NOTES_UPLOAD_JOBS = "/structureNotes/uploadJobs";
|
|
257
|
+
export const STRUCTURE_NOTES_UPLOAD_JOB = "/structureNotes/uploadJobs/:id";
|
|
256
258
|
export const STRUCTURE_PARENTS = "/structures/:id/parents";
|
|
257
259
|
export const STRUCTURE_PROFILE = "/structures/:id/profile";
|
|
258
260
|
export const STRUCTURE_RULES = "/structures/:id/rules";
|
|
@@ -385,6 +387,7 @@ const routes = {
|
|
|
385
387
|
IMPLEMENTATION_NEW,
|
|
386
388
|
IMPLEMENTATION_NEW_BASIC,
|
|
387
389
|
IMPLEMENTATION_NEW_RAW,
|
|
390
|
+
IMPLEMENTATIONS_BY_SUBSCOPE,
|
|
388
391
|
IMPLEMENTATION_RESULTS,
|
|
389
392
|
IMPLEMENTATION_RESULTS_DETAILS,
|
|
390
393
|
IMPLEMENTATION_RESULT_DETAILS,
|
|
@@ -471,7 +474,6 @@ const routes = {
|
|
|
471
474
|
STRUCTURE,
|
|
472
475
|
STRUCTURES,
|
|
473
476
|
STRUCTURES_BULK_UPDATE,
|
|
474
|
-
STRUCTURES_UPLOAD_EVENTS,
|
|
475
477
|
STRUCTURE_CHILDREN,
|
|
476
478
|
STRUCTURE_FIELDS,
|
|
477
479
|
STRUCTURE_VERSION_FIELDS,
|
|
@@ -489,6 +491,8 @@ const routes = {
|
|
|
489
491
|
STRUCTURE_METADATA,
|
|
490
492
|
STRUCTURE_NOTES_EDIT,
|
|
491
493
|
STRUCTURE_NOTES,
|
|
494
|
+
STRUCTURE_NOTES_UPLOAD_JOBS,
|
|
495
|
+
STRUCTURE_NOTES_UPLOAD_JOB,
|
|
492
496
|
STRUCTURE_PARENTS,
|
|
493
497
|
STRUCTURE_PROFILE,
|
|
494
498
|
STRUCTURE_RULES,
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { getRiSubscopes } from "../getRiSubscopes";
|
|
2
|
+
|
|
3
|
+
describe("selectors: getRiSubscopes", () => {
|
|
4
|
+
it("should return subscopes for ri scope", () => {
|
|
5
|
+
const allTemplates = [
|
|
6
|
+
{ scope: "ri", subscope: "subscope1" },
|
|
7
|
+
{ scope: "ri", subscope: "subscope2" },
|
|
8
|
+
{ scope: "ri", subscope: "subscope1" }, // duplicate
|
|
9
|
+
{ scope: "ri", subscope: null }, // should be filtered out
|
|
10
|
+
{ scope: "bg", subscope: "other" }, // different scope
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
const state = { allTemplates };
|
|
14
|
+
|
|
15
|
+
const result = getRiSubscopes(state);
|
|
16
|
+
|
|
17
|
+
expect(result).toEqual(["subscope1", "subscope2"]);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("should return empty array when no templates match", () => {
|
|
21
|
+
const allTemplates = [
|
|
22
|
+
{ scope: "bg", subscope: "subscope1" },
|
|
23
|
+
{ scope: "dq", subscope: "subscope2" },
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
const state = { allTemplates };
|
|
27
|
+
|
|
28
|
+
const result = getRiSubscopes(state);
|
|
29
|
+
|
|
30
|
+
expect(result).toEqual([]);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("should return empty array when no subscopes exist", () => {
|
|
34
|
+
const allTemplates = [
|
|
35
|
+
{ scope: "ri", subscope: null },
|
|
36
|
+
{ scope: "ri", subscope: undefined },
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
const state = { allTemplates };
|
|
40
|
+
|
|
41
|
+
const result = getRiSubscopes(state);
|
|
42
|
+
|
|
43
|
+
expect(result).toEqual([]);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("should return empty array when allTemplates is empty", () => {
|
|
47
|
+
const state = { allTemplates: [] };
|
|
48
|
+
|
|
49
|
+
const result = getRiSubscopes(state);
|
|
50
|
+
|
|
51
|
+
expect(result).toEqual([]);
|
|
52
|
+
});
|
|
53
|
+
});
|
package/src/selectors/index.js
CHANGED
|
@@ -5,6 +5,7 @@ export * from "./makeSearchQuerySelector";
|
|
|
5
5
|
export * from "./getDashboardConfig";
|
|
6
6
|
export * from "./getRecipients";
|
|
7
7
|
export * from "./getConceptSubscope";
|
|
8
|
+
export * from "./getRiSubscopes";
|
|
8
9
|
export * from "./getSidemenuGlossarySubscopes";
|
|
9
10
|
export * from "./subscopedTemplates";
|
|
10
11
|
export * from "./taxonomy";
|