Skip to content

Commit cbd33d6

Browse files
committed
Integration management "data discovery" tab (#4954)
1 parent 8441329 commit cbd33d6

36 files changed

+1146
-83
lines changed

clients/admin-ui/cypress/e2e/integration-management.cy.ts

+103
Original file line numberDiff line numberDiff line change
@@ -145,5 +145,108 @@ describe("Integration management for data detection & discovery", () => {
145145
cy.getByTestId("save-btn").click();
146146
cy.wait("@patchConnection");
147147
});
148+
149+
it("shows an empty state in 'data discovery' tab when no monitors are configured", () => {
150+
cy.intercept("GET", "/api/v1/plus/discovery-monitor*", {
151+
fixture: "detection-discovery/monitors/empty_monitors.json",
152+
}).as("getEmptyMonitors");
153+
cy.getByTestId("tab-Data discovery").click();
154+
cy.wait("@getEmptyMonitors");
155+
cy.getByTestId("no-results-notice").should("exist");
156+
});
157+
158+
describe("data discovery tab", () => {
159+
beforeEach(() => {
160+
cy.intercept("GET", "/api/v1/plus/discovery-monitor*", {
161+
fixture: "detection-discovery/monitors/monitor_list.json",
162+
}).as("getMonitors");
163+
cy.intercept("GET", "/api/v1/plus/discovery-monitor/*/databases", {
164+
fixture: "detection-discovery/monitors/database_list.json",
165+
}).as("getDatabases");
166+
cy.getByTestId("tab-Data discovery").click();
167+
cy.wait("@getMonitors");
168+
});
169+
170+
it("shows a table of monitors", () => {
171+
cy.getByTestId("row-test monitor 1").should("exist");
172+
});
173+
174+
it("can configure a new monitor", () => {
175+
cy.intercept("PUT", "/api/v1/plus/discovery-monitor*", {
176+
statusCode: 200,
177+
body: {
178+
name: "A new monitor",
179+
key: "a_new_monitor",
180+
connection_config_key: "bq_integration",
181+
classify_params: {
182+
possible_targets: null,
183+
top_n: 5,
184+
remove_stop_words: false,
185+
pii_threshold: 0.4,
186+
num_samples: 25,
187+
num_threads: 1,
188+
},
189+
databases: [],
190+
execution_start_date: "2034-06-03T00:00:00.000Z",
191+
execution_frequency: "Daily",
192+
},
193+
}).as("putMonitor");
194+
cy.getByTestId("add-monitor-btn").click();
195+
cy.getByTestId("input-name").type("A new monitor");
196+
cy.getByTestId("input-execution_start_date").type("2034-06-03");
197+
cy.selectOption("input-execution_frequency", "Daily");
198+
cy.getByTestId("next-btn").click();
199+
cy.wait("@putMonitor").then((interception) => {
200+
expect(interception.request.body).to.eql({
201+
name: "A new monitor",
202+
connection_config_key: "bq_integration",
203+
classify_params: {
204+
num_threads: 1,
205+
num_samples: 25,
206+
},
207+
execution_start_date: "2034-06-03T00:00:00.000Z",
208+
execution_frequency: "Daily",
209+
});
210+
});
211+
cy.getByTestId("select-all").click();
212+
cy.getByTestId("save-btn").click();
213+
cy.wait("@putMonitor").then((interception) => {
214+
expect(interception.request.body.databases).to.length(3);
215+
});
216+
cy.wait("@getMonitors");
217+
});
218+
219+
it("can edit an existing monitor by clicking the edit button", () => {
220+
cy.intercept("PUT", "/api/v1/plus/discovery-monitor*").as("putMonitor");
221+
cy.getByTestId("row-test monitor 1").within(() => {
222+
cy.getByTestId("edit-monitor-btn").click();
223+
});
224+
cy.getByTestId("input-name").should("have.value", "test monitor 1");
225+
cy.getByTestId("next-btn").click();
226+
cy.getByTestId("prj-bigquery-000001-checkbox").should(
227+
"have.attr",
228+
"data-checked"
229+
);
230+
cy.getByTestId("prj-bigquery-000003-checkbox").should(
231+
"not.have.attr",
232+
"data-checked"
233+
);
234+
cy.getByTestId("prj-bigquery-000003-checkbox").click();
235+
cy.getByTestId("save-btn").click();
236+
cy.wait("@putMonitor").then((interception) => {
237+
expect(interception.request.body.databases).to.length(3);
238+
});
239+
});
240+
241+
it("can edit an existing monitor by clicking the table row", () => {
242+
cy.getByTestId("row-test monitor 2").click();
243+
cy.getByTestId("input-name").should("have.value", "test monitor 2");
244+
cy.getByTestId("input-execution_start_date").should("not.exist");
245+
cy.getSelectValueContainer("input-execution_frequency").should(
246+
"contain",
247+
"Weekly"
248+
);
249+
});
250+
});
148251
});
149252
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"items": [
3+
"prj-bigquery-000001",
4+
"prj-bqextract-000002",
5+
"prj-bigquery-000003"
6+
],
7+
"total": 3,
8+
"page": 1,
9+
"size": 50,
10+
"pages": 1
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"items": [],
3+
"total": 0,
4+
"page": 1,
5+
"size": 25,
6+
"pages": 1
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
{
2+
"items": [
3+
{
4+
"name": "test monitor 1",
5+
"key": "test_monitor_1",
6+
"connection_config_key": "bq_test",
7+
"classify_params": {
8+
"possible_targets": null,
9+
"top_n": 5,
10+
"remove_stop_words": false,
11+
"pii_threshold": 0.4,
12+
"num_samples": 25,
13+
"num_threads": 1
14+
},
15+
"databases": ["prj-bigquery-000001", "prj-bqextract-000002"],
16+
"execution_start_date": "2024-06-04T11:11:11+00:00",
17+
"execution_frequency": "Daily"
18+
},
19+
{
20+
"name": "test monitor 2",
21+
"key": "test_monitor_2",
22+
"connection_config_key": "bq_test",
23+
"classify_params": {
24+
"possible_targets": null,
25+
"top_n": 5,
26+
"remove_stop_words": false,
27+
"pii_threshold": 0.4,
28+
"num_samples": 25,
29+
"num_threads": 1
30+
},
31+
"databases": [],
32+
"execution_start_date": "2024-06-04T11:11:11+00:00",
33+
"execution_frequency": "Weekly"
34+
},
35+
{
36+
"name": "test monitor 3",
37+
"key": "test_monitor_3",
38+
"connection_config_key": "bq_test",
39+
"classify_params": {
40+
"possible_targets": null,
41+
"top_n": 5,
42+
"remove_stop_words": false,
43+
"pii_threshold": 0.4,
44+
"num_samples": 25,
45+
"num_threads": 1
46+
},
47+
"databases": [],
48+
"execution_start_date": "2024-06-04T11:11:11+00:00",
49+
"execution_frequency": "Monthly"
50+
}
51+
],
52+
"total": 3,
53+
"page": 1,
54+
"size": 25,
55+
"pages": 1
56+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { createIcon } from "fidesui";
2+
3+
export const MonitorIcon = createIcon({
4+
displayName: "MonitorIcon",
5+
viewBox: "0 0 12 12",
6+
path: (
7+
<path
8+
d="M1.33333 2.21875C1.15 2.21875 1 2.37344 1 2.5625V5.65625H3.66667C3.84167 5.65625 4.00417 5.75078 4.09583 5.90762L4.62708 6.8207L5.88542 4.22324C5.96667 4.05566 6.13125 3.94609 6.3125 3.93965C6.49375 3.9332 6.66667 4.02773 6.76042 4.18887L7.61458 5.65625H8.83333C9.11042 5.65625 9.33333 5.88613 9.33333 6.17188C9.33333 6.45762 9.11042 6.6875 8.83333 6.6875H7.33333C7.15833 6.6875 6.99583 6.59297 6.90417 6.43613L6.37292 5.52305L5.11458 8.12051C5.03333 8.28809 4.86875 8.39766 4.6875 8.4041C4.50625 8.41055 4.33333 8.31602 4.23958 8.15488L3.38542 6.6875H1V9.4375C1 9.62656 1.15 9.78125 1.33333 9.78125H10.6667C10.85 9.78125 11 9.62656 11 9.4375V2.5625C11 2.37344 10.85 2.21875 10.6667 2.21875H1.33333ZM0 2.5625C0 1.8041 0.597917 1.1875 1.33333 1.1875H10.6667C11.4021 1.1875 12 1.8041 12 2.5625V9.4375C12 10.1959 11.4021 10.8125 10.6667 10.8125H1.33333C0.597917 10.8125 0 10.1959 0 9.4375V2.5625Z"
9+
fill="currentColor"
10+
/>
11+
),
12+
});

clients/admin-ui/src/features/common/PickerCard.tsx

+91-71
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ export const usePicker = <T extends { id: string; name: string }>({
4949
};
5050
};
5151

52-
export interface Props<T> {
52+
export interface CheckboxListProps<T> {
5353
title: string;
5454
items: T[];
5555
selected: Array<string>;
@@ -58,9 +58,31 @@ export interface Props<T> {
5858
toggle?: ReactNode;
5959
onViewMore?: () => void;
6060
numSelected: number;
61+
variant?: "default" | "emphasize";
6162
}
6263

63-
const PickerCard = <T extends { id: string; name: string }>({
64+
const PickerCardContainer = ({
65+
title,
66+
children,
67+
}: {
68+
title: string;
69+
children: ReactNode;
70+
}) => (
71+
<Box
72+
p={4}
73+
display="flex"
74+
alignItems="flex-start"
75+
gap="4px"
76+
borderRadius="4px"
77+
boxShadow="0px 1px 2px 0px rgba(0, 0, 0, 0.06), 0px 1px 3px 0px rgba(0, 0, 0, 0.1)"
78+
fontSize="sm"
79+
data-testid={`picker-card-${title}`}
80+
>
81+
{children}
82+
</Box>
83+
);
84+
85+
export const PickerCheckboxList = <T extends { id: string; name: string }>({
6486
title,
6587
items,
6688
selected,
@@ -69,7 +91,8 @@ const PickerCard = <T extends { id: string; name: string }>({
6991
toggle,
7092
onViewMore,
7193
numSelected,
72-
}: Props<T>) => {
94+
variant = "default",
95+
}: CheckboxListProps<T>) => {
7396
const itemsToShow = onViewMore ? items.slice(0, NUM_TO_SHOW) : items;
7497

7598
const { allSelected, someSelected, handleToggleAll, handleToggleSelection } =
@@ -80,77 +103,74 @@ const PickerCard = <T extends { id: string; name: string }>({
80103
});
81104

82105
return (
83-
<Box
84-
p={4}
85-
display="flex"
86-
alignItems="flex-start"
87-
gap="4px"
88-
borderRadius="4px"
89-
boxShadow="0px 1px 2px 0px rgba(0, 0, 0, 0.06), 0px 1px 3px 0px rgba(0, 0, 0, 0.1)"
90-
fontSize="sm"
91-
data-testid={`picker-card-${title}`}
92-
>
93-
<VStack alignItems="flex-start" spacing={3} width="100%" height="100%">
94-
<Flex justifyContent="space-between" width="100%">
95-
<Checkbox
96-
fontSize="md"
97-
textTransform="capitalize"
98-
fontWeight="semibold"
99-
isChecked={allSelected}
100-
size="md"
101-
mr="2"
102-
onChange={handleToggleAll}
103-
colorScheme="complimentary"
104-
data-testid="select-all"
105-
isIndeterminate={!allSelected && someSelected}
106-
>
107-
{title}
108-
</Checkbox>
106+
<VStack alignItems="flex-start" spacing={3} width="100%" height="100%">
107+
<Flex justifyContent="space-between" width="100%">
108+
<Checkbox
109+
fontSize="md"
110+
textTransform={variant === "emphasize" ? "capitalize" : undefined}
111+
fontWeight={variant === "emphasize" ? "semibold" : "auto"}
112+
isChecked={allSelected}
113+
size="md"
114+
mr="2"
115+
onChange={handleToggleAll}
116+
colorScheme="complimentary"
117+
data-testid="select-all"
118+
isIndeterminate={!allSelected && someSelected}
119+
>
120+
{title}
121+
</Checkbox>
109122

110-
{toggle ?? null}
111-
</Flex>
112-
{numSelected > 0 ? (
113-
<Badge
114-
colorScheme="purple"
115-
variant="solid"
116-
width="fit-content"
117-
data-testid="num-selected-badge"
118-
>
119-
{numSelected} selected
120-
</Badge>
121-
) : null}
122-
<VStack paddingLeft="6" fontSize="sm" alignItems="start" spacing="2">
123-
<CheckboxGroup colorScheme="complimentary">
124-
{itemsToShow.map((item) => (
125-
<Flex key={item.id} alignItems="center" gap="8px">
126-
<Checkbox
127-
key={item.id}
128-
isChecked={selected.includes(item.id)}
129-
isIndeterminate={indeterminate.includes(item.id)}
130-
size="md"
131-
fontWeight="500"
132-
onChange={() => handleToggleSelection(item.id)}
133-
data-testid={`${item.name}-checkbox`}
134-
>
135-
{item.name}
136-
</Checkbox>
137-
</Flex>
138-
))}
139-
</CheckboxGroup>
140-
</VStack>
141-
{onViewMore ? (
142-
<Button
143-
size="xs"
144-
variant="ghost"
145-
onClick={onViewMore}
146-
data-testid="view-more-btn"
147-
>
148-
View more
149-
</Button>
150-
) : null}
123+
{toggle ?? null}
124+
</Flex>
125+
{numSelected > 0 ? (
126+
<Badge
127+
colorScheme="purple"
128+
variant="solid"
129+
width="fit-content"
130+
data-testid="num-selected-badge"
131+
>
132+
{numSelected} selected
133+
</Badge>
134+
) : null}
135+
<VStack paddingLeft="6" fontSize="sm" alignItems="start" spacing="2">
136+
<CheckboxGroup colorScheme="complimentary">
137+
{itemsToShow.map((item) => (
138+
<Flex key={item.id} alignItems="center" gap="8px">
139+
<Checkbox
140+
key={item.id}
141+
isChecked={selected.includes(item.id)}
142+
isIndeterminate={indeterminate.includes(item.id)}
143+
size="md"
144+
fontWeight={variant === "emphasize" ? "500" : "auto"}
145+
onChange={() => handleToggleSelection(item.id)}
146+
data-testid={`${item.name}-checkbox`}
147+
>
148+
{item.name}
149+
</Checkbox>
150+
</Flex>
151+
))}
152+
</CheckboxGroup>
151153
</VStack>
152-
</Box>
154+
{onViewMore ? (
155+
<Button
156+
size="xs"
157+
variant="ghost"
158+
onClick={onViewMore}
159+
data-testid="view-more-btn"
160+
>
161+
View more
162+
</Button>
163+
) : null}
164+
</VStack>
153165
);
154166
};
155167

168+
const PickerCard = <T extends { id: string; name: string }>({
169+
...props
170+
}: CheckboxListProps<T>) => (
171+
<PickerCardContainer title={props.title}>
172+
<PickerCheckboxList {...props} variant="emphasize" />
173+
</PickerCardContainer>
174+
);
175+
156176
export default PickerCard;

0 commit comments

Comments
 (0)