@truedat/dd 8.8.8 → 8.9.0
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/queries.js +11 -2
- package/src/components/StructureLoader.js +24 -2
- package/src/components/__tests__/StructureLoader.spec.js +42 -9
- package/src/reducers/structure.js +2 -0
- package/src/sagas/__tests__/fetchStructure.spec.js +40 -0
- package/src/sagas/fetchStructure.js +13 -1
- package/src/selectors/__tests__/getTabVisibility.spec.js +36 -1
- package/src/selectors/getTabVisibility.js +17 -4
- package/src/components/__tests__/__snapshots__/StructureLoader.spec.js.snap +0 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@truedat/dd",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.9.0",
|
|
4
4
|
"description": "Truedat Web Data Dictionary",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"module": "src/index.js",
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"@testing-library/jest-dom": "^6.6.3",
|
|
52
52
|
"@testing-library/react": "^16.3.0",
|
|
53
53
|
"@testing-library/user-event": "^14.6.1",
|
|
54
|
-
"@truedat/test": "8.
|
|
54
|
+
"@truedat/test": "8.9.0",
|
|
55
55
|
"identity-obj-proxy": "^3.0.0",
|
|
56
56
|
"jest": "^29.7.0",
|
|
57
57
|
"redux-saga-test-plan": "^4.0.6"
|
|
@@ -86,5 +86,5 @@
|
|
|
86
86
|
"svg-pan-zoom": "^3.6.2",
|
|
87
87
|
"swr": "^2.3.3"
|
|
88
88
|
},
|
|
89
|
-
"gitHead": "
|
|
89
|
+
"gitHead": "beff9d7e01556d63d42e54c9f7b641503ee658b2"
|
|
90
90
|
}
|
package/src/api/queries.js
CHANGED
|
@@ -487,7 +487,12 @@ export const LEGACY_DATA_STRUCTURE_VERSION_QUERY = gql`
|
|
|
487
487
|
`;
|
|
488
488
|
|
|
489
489
|
export const buildDSVQuery = (loadStructureMetadata) => gql`
|
|
490
|
-
query DataStructureVersion(
|
|
490
|
+
query DataStructureVersion(
|
|
491
|
+
$dataStructureId: ID!,
|
|
492
|
+
$version: String!,
|
|
493
|
+
$includeProfiledFields: Boolean = false
|
|
494
|
+
$skipProfiledFieldsExternalIds: [String!] = []
|
|
495
|
+
) {
|
|
491
496
|
dataStructureVersion(dataStructureId: $dataStructureId, version: $version) {
|
|
492
497
|
id
|
|
493
498
|
version
|
|
@@ -625,6 +630,9 @@ export const buildDSVQuery = (loadStructureMetadata) => gql`
|
|
|
625
630
|
out
|
|
626
631
|
}
|
|
627
632
|
data_fields_count
|
|
633
|
+
has_profiled_data_fields(
|
|
634
|
+
skip_profiled_fields_external_ids: $skipProfiledFieldsExternalIds
|
|
635
|
+
) @include(if: $includeProfiledFields)
|
|
628
636
|
relations {
|
|
629
637
|
parents {
|
|
630
638
|
id
|
|
@@ -818,6 +826,7 @@ export const DATA_FIELDS_QUERY = gql`
|
|
|
818
826
|
$before: Cursor
|
|
819
827
|
$after: Cursor
|
|
820
828
|
$filters: DataFieldsFilter
|
|
829
|
+
$includeProfile: Boolean = false
|
|
821
830
|
) {
|
|
822
831
|
dataFields(
|
|
823
832
|
dataStructureId: $dataStructureId
|
|
@@ -844,7 +853,7 @@ export const DATA_FIELDS_QUERY = gql`
|
|
|
844
853
|
metadata
|
|
845
854
|
name
|
|
846
855
|
type
|
|
847
|
-
profile {
|
|
856
|
+
profile @include(if: $includeProfile) {
|
|
848
857
|
max
|
|
849
858
|
min
|
|
850
859
|
most_frequent
|
|
@@ -7,13 +7,35 @@ import { clearStructure, fetchStructure } from "../routines";
|
|
|
7
7
|
|
|
8
8
|
export const StructureLoader = ({ fetchStructure, clearStructure }) => {
|
|
9
9
|
const { id, version } = useParams();
|
|
10
|
+
const { profiledFields } = useWebContext();
|
|
10
11
|
|
|
11
12
|
const { loadStructureMetadata } = useWebContext();
|
|
12
13
|
|
|
13
14
|
useEffect(() => {
|
|
14
|
-
fetchStructure({
|
|
15
|
+
fetchStructure({
|
|
16
|
+
id,
|
|
17
|
+
version,
|
|
18
|
+
loadStructureMetadata,
|
|
19
|
+
dataStructureVersionQueryVariables: {
|
|
20
|
+
includeProfiledFields: profiledFields?.enabled !== false,
|
|
21
|
+
skipProfiledFieldsExternalIds: Array.isArray(
|
|
22
|
+
profiledFields?.disabled_external_ids,
|
|
23
|
+
)
|
|
24
|
+
? profiledFields.disabled_external_ids.filter(
|
|
25
|
+
(x) => typeof x === "string",
|
|
26
|
+
)
|
|
27
|
+
: [],
|
|
28
|
+
},
|
|
29
|
+
});
|
|
15
30
|
return () => clearStructure();
|
|
16
|
-
}, [
|
|
31
|
+
}, [
|
|
32
|
+
id,
|
|
33
|
+
version,
|
|
34
|
+
loadStructureMetadata,
|
|
35
|
+
profiledFields,
|
|
36
|
+
fetchStructure,
|
|
37
|
+
clearStructure,
|
|
38
|
+
]);
|
|
17
39
|
|
|
18
40
|
return null;
|
|
19
41
|
};
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { render } from "@truedat/test/render";
|
|
2
|
+
import { useWebContext } from "@truedat/core/webContext";
|
|
2
3
|
import { StructureLoader } from "../StructureLoader";
|
|
3
4
|
|
|
4
|
-
jest.mock("react-router", () => ({
|
|
5
|
-
...jest.requireActual("react-router"),
|
|
6
|
-
useParams: () => ({ id: 1, version: 8 }),
|
|
7
|
-
}));
|
|
8
5
|
jest.mock("@truedat/core/webContext", () => ({
|
|
9
6
|
useWebContext: jest.fn(),
|
|
10
7
|
}));
|
|
11
8
|
|
|
12
|
-
|
|
9
|
+
jest.mock("react-router", () => ({
|
|
10
|
+
...jest.requireActual("react-router"),
|
|
11
|
+
useParams: () => ({ id: 1, version: 8 }),
|
|
12
|
+
}));
|
|
13
13
|
|
|
14
14
|
const props = {
|
|
15
15
|
clearStructure: jest.fn(),
|
|
@@ -17,19 +17,28 @@ const props = {
|
|
|
17
17
|
};
|
|
18
18
|
|
|
19
19
|
describe("<StructureLoader />", () => {
|
|
20
|
-
|
|
20
|
+
beforeEach(() => {
|
|
21
|
+
useWebContext.mockReturnValue({});
|
|
22
|
+
props.clearStructure.mockClear();
|
|
23
|
+
props.fetchStructure.mockClear();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("fetches structure without metadata", () => {
|
|
21
27
|
useWebContext.mockReturnValue({ loadStructureMetadata: false });
|
|
22
|
-
|
|
23
|
-
expect(container).toMatchSnapshot();
|
|
28
|
+
render(<StructureLoader {...props} />);
|
|
24
29
|
|
|
25
30
|
expect(props.fetchStructure).toHaveBeenCalledWith({
|
|
26
31
|
id: 1,
|
|
27
32
|
version: 8,
|
|
28
33
|
loadStructureMetadata: false,
|
|
34
|
+
dataStructureVersionQueryVariables: {
|
|
35
|
+
includeProfiledFields: true,
|
|
36
|
+
skipProfiledFieldsExternalIds: [],
|
|
37
|
+
},
|
|
29
38
|
});
|
|
30
39
|
});
|
|
31
40
|
|
|
32
|
-
it("
|
|
41
|
+
it("fetches structure with metadata and profiled fields", () => {
|
|
33
42
|
useWebContext.mockReturnValue({ loadStructureMetadata: true });
|
|
34
43
|
|
|
35
44
|
render(<StructureLoader {...props} />);
|
|
@@ -38,6 +47,30 @@ describe("<StructureLoader />", () => {
|
|
|
38
47
|
id: 1,
|
|
39
48
|
version: 8,
|
|
40
49
|
loadStructureMetadata: true,
|
|
50
|
+
dataStructureVersionQueryVariables: {
|
|
51
|
+
includeProfiledFields: true,
|
|
52
|
+
skipProfiledFieldsExternalIds: [],
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("passes profiled fields query variables from context", () => {
|
|
58
|
+
useWebContext.mockReturnValue({
|
|
59
|
+
profiledFields: {
|
|
60
|
+
enabled: false,
|
|
61
|
+
disabled_external_ids: ["ext_1", 4, null, "ext_2"],
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
render(<StructureLoader {...props} />);
|
|
66
|
+
|
|
67
|
+
expect(props.fetchStructure).toHaveBeenCalledWith({
|
|
68
|
+
id: 1,
|
|
69
|
+
version: 8,
|
|
70
|
+
dataStructureVersionQueryVariables: {
|
|
71
|
+
includeProfiledFields: false,
|
|
72
|
+
skipProfiledFieldsExternalIds: ["ext_1", "ext_2"],
|
|
73
|
+
},
|
|
41
74
|
});
|
|
42
75
|
});
|
|
43
76
|
});
|
|
@@ -34,12 +34,14 @@ const structureVersionFields = _.pick([
|
|
|
34
34
|
"group",
|
|
35
35
|
"implementation_count",
|
|
36
36
|
"data_fields_count",
|
|
37
|
+
"has_profiled_data_fields",
|
|
37
38
|
"metadata",
|
|
38
39
|
"name",
|
|
39
40
|
"note",
|
|
40
41
|
"parents",
|
|
41
42
|
"children",
|
|
42
43
|
"siblings",
|
|
44
|
+
"profile",
|
|
43
45
|
"source",
|
|
44
46
|
"system",
|
|
45
47
|
"type",
|
|
@@ -119,6 +119,46 @@ describe("sagas: fetchStructureSaga", () => {
|
|
|
119
119
|
}).not.toThrow();
|
|
120
120
|
});
|
|
121
121
|
|
|
122
|
+
it("should forward extra query variables from payload", () => {
|
|
123
|
+
const payload = {
|
|
124
|
+
id,
|
|
125
|
+
dataStructureVersionQueryVariables: {
|
|
126
|
+
includeProfiledFields: false,
|
|
127
|
+
skipProfiledFieldsExternalIds: ["ext_1", 2, null, "ext_2"],
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
const meta = { version };
|
|
131
|
+
const variables = {
|
|
132
|
+
includeProfiledFields: false,
|
|
133
|
+
skipProfiledFieldsExternalIds: ["ext_1", 2, null, "ext_2"],
|
|
134
|
+
dataStructureId: id,
|
|
135
|
+
version,
|
|
136
|
+
note_fields: ["note_field"],
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
expect(() => {
|
|
140
|
+
testSaga(fetchStructureSaga, { payload })
|
|
141
|
+
.next()
|
|
142
|
+
.select(getStructureFieldColumns)
|
|
143
|
+
.next(structureFieldColumns)
|
|
144
|
+
.getContext("client")
|
|
145
|
+
.next(client)
|
|
146
|
+
.put(fetchStructure.request())
|
|
147
|
+
.next()
|
|
148
|
+
.call(client.query, {
|
|
149
|
+
fetchPolicy: "network-only",
|
|
150
|
+
query: buildDSVQuery(false),
|
|
151
|
+
variables,
|
|
152
|
+
})
|
|
153
|
+
.next({ data: { dataStructureVersion } })
|
|
154
|
+
.put({ meta, ...fetchStructure.success(data) })
|
|
155
|
+
.next()
|
|
156
|
+
.put(fetchStructure.fulfill())
|
|
157
|
+
.next()
|
|
158
|
+
.isDone();
|
|
159
|
+
}).not.toThrow();
|
|
160
|
+
});
|
|
161
|
+
|
|
122
162
|
it("should put a failure action when the call returns an error", () => {
|
|
123
163
|
const message = "Request failed";
|
|
124
164
|
const error = { message };
|
|
@@ -16,13 +16,25 @@ export function* fetchStructureSaga({ payload }) {
|
|
|
16
16
|
|
|
17
17
|
yield put(fetchStructure.request());
|
|
18
18
|
|
|
19
|
-
const {
|
|
19
|
+
const {
|
|
20
|
+
id,
|
|
21
|
+
version = "latest",
|
|
22
|
+
dataStructureVersionQueryVariables,
|
|
23
|
+
loadStructureMetadata,
|
|
24
|
+
} = payload;
|
|
25
|
+
const extra =
|
|
26
|
+
dataStructureVersionQueryVariables &&
|
|
27
|
+
typeof dataStructureVersionQueryVariables === "object"
|
|
28
|
+
? dataStructureVersionQueryVariables
|
|
29
|
+
: {};
|
|
30
|
+
|
|
20
31
|
const {
|
|
21
32
|
data: { dataStructureVersion },
|
|
22
33
|
} = yield call(client.query, {
|
|
23
34
|
fetchPolicy: "network-only",
|
|
24
35
|
query: buildDSVQuery(loadStructureMetadata),
|
|
25
36
|
variables: {
|
|
37
|
+
...extra,
|
|
26
38
|
dataStructureId: id,
|
|
27
39
|
version,
|
|
28
40
|
note_fields,
|
|
@@ -77,7 +77,7 @@ describe("selectors: getTabVisibility", () => {
|
|
|
77
77
|
structure: { profile: { foo: "foo" } },
|
|
78
78
|
userPermissions,
|
|
79
79
|
}),
|
|
80
|
-
).toMatchObject({ profile:
|
|
80
|
+
).toMatchObject({ profile: true });
|
|
81
81
|
expect(
|
|
82
82
|
getTabVisibility({
|
|
83
83
|
structureProfile: { patterns: "foo" },
|
|
@@ -92,6 +92,33 @@ describe("selectors: getTabVisibility", () => {
|
|
|
92
92
|
).toMatchObject({ profile: true });
|
|
93
93
|
});
|
|
94
94
|
|
|
95
|
+
it("should include profile when profiled descendants exist or own profile", () => {
|
|
96
|
+
const userPermissions = { view_profiling_permission: true };
|
|
97
|
+
|
|
98
|
+
expect(
|
|
99
|
+
getTabVisibility({
|
|
100
|
+
structure: { has_profiled_data_fields: true },
|
|
101
|
+
userPermissions,
|
|
102
|
+
}),
|
|
103
|
+
).toMatchObject({ profile: true });
|
|
104
|
+
|
|
105
|
+
expect(
|
|
106
|
+
getTabVisibility({
|
|
107
|
+
structure: {
|
|
108
|
+
class: "database",
|
|
109
|
+
has_profiled_data_fields: false,
|
|
110
|
+
},
|
|
111
|
+
userPermissions,
|
|
112
|
+
}),
|
|
113
|
+
).toMatchObject({ profile: false });
|
|
114
|
+
|
|
115
|
+
expect(
|
|
116
|
+
getTabVisibility({
|
|
117
|
+
structure: { has_profiled_data_fields: true },
|
|
118
|
+
}),
|
|
119
|
+
).toMatchObject({ profile: false });
|
|
120
|
+
});
|
|
121
|
+
|
|
95
122
|
it("should include links tab if user has create_link permission", () => {
|
|
96
123
|
expect(
|
|
97
124
|
getTabVisibility({ structureActions: { create_link: {} } }),
|
|
@@ -100,6 +127,14 @@ describe("selectors: getTabVisibility", () => {
|
|
|
100
127
|
});
|
|
101
128
|
});
|
|
102
129
|
|
|
130
|
+
it("should include links if structure has links", () => {
|
|
131
|
+
expect(
|
|
132
|
+
getTabVisibility({ structureActions: { create_link: {} } }),
|
|
133
|
+
).toMatchObject({
|
|
134
|
+
links: true,
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
|
|
103
138
|
it("should include links tab if structure has links_count > 0", () => {
|
|
104
139
|
expect(getTabVisibility({ structure: { links_count: 2 } })).toMatchObject({
|
|
105
140
|
links: true,
|
|
@@ -10,13 +10,26 @@ const fieldsTabVisible = (state) => state?.structure?.data_fields_count > 0;
|
|
|
10
10
|
|
|
11
11
|
const profileTabVisible = ({
|
|
12
12
|
userPermissions,
|
|
13
|
+
structure,
|
|
13
14
|
structureFields,
|
|
14
15
|
structureProfile,
|
|
15
|
-
}) =>
|
|
16
|
-
_.anyPass([_.prop("view_profiling_permission")])(
|
|
17
|
-
|
|
16
|
+
}) => {
|
|
17
|
+
const hasPermission = _.anyPass([_.prop("view_profiling_permission")])(
|
|
18
|
+
userPermissions,
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
if (!hasPermission) return false;
|
|
22
|
+
|
|
23
|
+
const hasProfiledDescendants = structure?.has_profiled_data_fields === true;
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
hasProfiledDescendants ||
|
|
27
|
+
hasProfile(structure) ||
|
|
28
|
+
_.some(hasProfile)(structureFields) ||
|
|
18
29
|
notEmpty(structureProfile?.most_frequent) ||
|
|
19
|
-
notEmpty(structureProfile?.patterns)
|
|
30
|
+
notEmpty(structureProfile?.patterns)
|
|
31
|
+
);
|
|
32
|
+
};
|
|
20
33
|
|
|
21
34
|
const linksTabVisible = (state) => {
|
|
22
35
|
const hasPermission = _.has("create_link", state?.structureActions);
|