Skip to content

Commit f3cc4e0

Browse files
authoredJan 29, 2024··
feat(ui/secret): support to edit secrets (#9737)
1 parent fdf929b commit f3cc4e0

File tree

4 files changed

+170
-21
lines changed

4 files changed

+170
-21
lines changed
 

‎datahub-web-react/src/app/ingest/secret/SecretBuilderModal.tsx

+55-18
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Button, Form, Input, Modal, Typography } from 'antd';
2-
import React, { useState } from 'react';
2+
import React, { useEffect, useState } from 'react';
33
import { useEnterKeyListener } from '../../shared/useEnterKeyListener';
44
import { SecretBuilderState } from './types';
55

@@ -9,12 +9,14 @@ const VALUE_FIELD_NAME = 'value';
99

1010
type Props = {
1111
initialState?: SecretBuilderState;
12+
editSecret?: SecretBuilderState;
1213
visible: boolean;
1314
onSubmit?: (source: SecretBuilderState, resetState: () => void) => void;
15+
onUpdate?: (source: SecretBuilderState, resetState: () => void) => void;
1416
onCancel?: () => void;
1517
};
1618

17-
export const SecretBuilderModal = ({ initialState, visible, onSubmit, onCancel }: Props) => {
19+
export const SecretBuilderModal = ({ initialState, editSecret, visible, onSubmit, onUpdate, onCancel }: Props) => {
1820
const [createButtonEnabled, setCreateButtonEnabled] = useState(false);
1921
const [form] = Form.useForm();
2022

@@ -23,38 +25,69 @@ export const SecretBuilderModal = ({ initialState, visible, onSubmit, onCancel }
2325
querySelectorToExecuteClick: '#createSecretButton',
2426
});
2527

28+
useEffect(() => {
29+
if (editSecret) {
30+
form.setFieldsValue({
31+
name: editSecret.name,
32+
description: editSecret.description,
33+
value: editSecret.value,
34+
});
35+
}
36+
}, [editSecret, form]);
37+
2638
function resetValues() {
39+
setCreateButtonEnabled(false);
2740
form.resetFields();
2841
}
2942

43+
const onCloseModal = () => {
44+
setCreateButtonEnabled(false);
45+
form.resetFields();
46+
onCancel?.();
47+
};
48+
49+
const titleText = editSecret ? 'Edit Secret' : 'Create a new Secret';
50+
3051
return (
3152
<Modal
3253
width={540}
33-
title={<Typography.Text>Create a new Secret</Typography.Text>}
54+
title={<Typography.Text>{titleText}</Typography.Text>}
3455
visible={visible}
35-
onCancel={onCancel}
56+
onCancel={onCloseModal}
3657
zIndex={1051} // one higher than other modals - needed for managed ingestion forms
3758
footer={
3859
<>
39-
<Button onClick={onCancel} type="text">
60+
<Button onClick={onCloseModal} type="text">
4061
Cancel
4162
</Button>
4263
<Button
4364
data-testid="secret-modal-create-button"
4465
id="createSecretButton"
45-
onClick={() =>
46-
onSubmit?.(
47-
{
48-
name: form.getFieldValue(NAME_FIELD_NAME),
49-
description: form.getFieldValue(DESCRIPTION_FIELD_NAME),
50-
value: form.getFieldValue(VALUE_FIELD_NAME),
51-
},
52-
resetValues,
53-
)
54-
}
66+
onClick={() => {
67+
if (!editSecret) {
68+
onSubmit?.(
69+
{
70+
name: form.getFieldValue(NAME_FIELD_NAME),
71+
description: form.getFieldValue(DESCRIPTION_FIELD_NAME),
72+
value: form.getFieldValue(VALUE_FIELD_NAME),
73+
},
74+
resetValues,
75+
);
76+
} else {
77+
onUpdate?.(
78+
{
79+
urn: editSecret?.urn,
80+
name: form.getFieldValue(NAME_FIELD_NAME),
81+
description: form.getFieldValue(DESCRIPTION_FIELD_NAME),
82+
value: form.getFieldValue(VALUE_FIELD_NAME),
83+
},
84+
resetValues,
85+
);
86+
}
87+
}}
5588
disabled={!createButtonEnabled}
5689
>
57-
Create
90+
{!editSecret ? 'Create' : 'Update'}
5891
</Button>
5992
</>
6093
}
@@ -81,11 +114,15 @@ export const SecretBuilderModal = ({ initialState, visible, onSubmit, onCancel }
81114
},
82115
{ whitespace: false },
83116
{ min: 1, max: 50 },
84-
{ pattern: /^[a-zA-Z_]+[a-zA-Z0-9_]*$/, message: 'Please start the secret name with a letter, followed by letters, digits, or underscores only.' },
117+
{
118+
pattern: /^[a-zA-Z_]+[a-zA-Z0-9_]*$/,
119+
message:
120+
'Please start the secret name with a letter, followed by letters, digits, or underscores only.',
121+
},
85122
]}
86123
hasFeedback
87124
>
88-
<Input placeholder="A name for your secret" />
125+
<Input placeholder="A name for your secret" disabled={editSecret !== undefined} />
89126
</Form.Item>
90127
</Form.Item>
91128
<Form.Item label={<Typography.Text strong>Value</Typography.Text>}>

‎datahub-web-react/src/app/ingest/secret/SecretsList.tsx

+66-3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
useCreateSecretMutation,
1010
useDeleteSecretMutation,
1111
useListSecretsQuery,
12+
useUpdateSecretMutation,
1213
} from '../../../graphql/ingestion.generated';
1314
import { Message } from '../../shared/Message';
1415
import TabToolbar from '../../entity/shared/components/styled/TabToolbar';
@@ -18,7 +19,11 @@ import { StyledTable } from '../../entity/shared/components/styled/StyledTable';
1819
import { SearchBar } from '../../search/SearchBar';
1920
import { useEntityRegistry } from '../../useEntityRegistry';
2021
import { scrollToTop } from '../../shared/searchUtils';
21-
import { addSecretToListSecretsCache, removeSecretFromListSecretsCache } from './cacheUtils';
22+
import {
23+
addSecretToListSecretsCache,
24+
removeSecretFromListSecretsCache,
25+
updateSecretInListSecretsCache,
26+
} from './cacheUtils';
2227
import { ONE_SECOND_IN_MS } from '../../entity/shared/tabs/Dataset/Queries/utils/constants';
2328

2429
const DeleteButtonContainer = styled.div`
@@ -48,10 +53,12 @@ export const SecretsList = () => {
4853

4954
// Whether or not there is an urn to show in the modal
5055
const [isCreatingSecret, setIsCreatingSecret] = useState<boolean>(false);
56+
const [editSecret, setEditSecret] = useState<SecretBuilderState | undefined>(undefined);
5157

5258
const [deleteSecretMutation] = useDeleteSecretMutation();
5359
const [createSecretMutation] = useCreateSecretMutation();
54-
const { loading, error, data, client } = useListSecretsQuery({
60+
const [updateSecretMutation] = useUpdateSecretMutation();
61+
const { loading, error, data, client, refetch } = useListSecretsQuery({
5562
variables: {
5663
input: {
5764
start,
@@ -125,6 +132,47 @@ export const SecretsList = () => {
125132
});
126133
});
127134
};
135+
const onUpdate = (state: SecretBuilderState, resetBuilderState: () => void) => {
136+
updateSecretMutation({
137+
variables: {
138+
input: {
139+
urn: state.urn as string,
140+
name: state.name as string,
141+
value: state.value as string,
142+
description: state.description as string,
143+
},
144+
},
145+
})
146+
.then(() => {
147+
message.success({
148+
content: `Successfully updated Secret!`,
149+
duration: 3,
150+
});
151+
resetBuilderState();
152+
setIsCreatingSecret(false);
153+
setEditSecret(undefined);
154+
updateSecretInListSecretsCache(
155+
{
156+
urn: state.urn,
157+
name: state.name,
158+
description: state.description,
159+
},
160+
client,
161+
pageSize,
162+
page,
163+
);
164+
setTimeout(() => {
165+
refetch();
166+
}, 2000);
167+
})
168+
.catch((e) => {
169+
message.destroy();
170+
message.error({
171+
content: `Failed to update Secret!: \n ${e.message || ''}`,
172+
duration: 3,
173+
});
174+
});
175+
};
128176

129177
const onDeleteSecret = (urn: string) => {
130178
Modal.confirm({
@@ -140,6 +188,16 @@ export const SecretsList = () => {
140188
});
141189
};
142190

191+
const onEditSecret = (urnData: any) => {
192+
setIsCreatingSecret(true);
193+
setEditSecret(urnData);
194+
};
195+
196+
const onCancel = () => {
197+
setIsCreatingSecret(false);
198+
setEditSecret(undefined);
199+
};
200+
143201
const tableColumns = [
144202
{
145203
title: 'Name',
@@ -161,6 +219,9 @@ export const SecretsList = () => {
161219
key: 'x',
162220
render: (_, record: any) => (
163221
<DeleteButtonContainer>
222+
<Button style={{ marginRight: 16 }} onClick={() => onEditSecret(record)}>
223+
EDIT
224+
</Button>
164225
<Button onClick={() => onDeleteSecret(record.urn)} type="text" shape="circle" danger>
165226
<DeleteOutlined />
166227
</Button>
@@ -234,8 +295,10 @@ export const SecretsList = () => {
234295
</div>
235296
<SecretBuilderModal
236297
visible={isCreatingSecret}
298+
editSecret={editSecret}
299+
onUpdate={onUpdate}
237300
onSubmit={onSubmit}
238-
onCancel={() => setIsCreatingSecret(false)}
301+
onCancel={onCancel}
239302
/>
240303
</>
241304
);

‎datahub-web-react/src/app/ingest/secret/cacheUtils.ts

+45
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,51 @@ export const addSecretToListSecretsCache = (secret, client, pageSize) => {
6464
});
6565
};
6666

67+
export const updateSecretInListSecretsCache = (updatedSecret, client, pageSize, page) => {
68+
const currData: ListSecretsQuery | null = client.readQuery({
69+
query: ListSecretsDocument,
70+
variables: {
71+
input: {
72+
start: (page - 1) * pageSize,
73+
count: pageSize,
74+
},
75+
},
76+
});
77+
78+
const updatedSecretIndex = (currData?.listSecrets?.secrets || [])
79+
.map((secret, index) => {
80+
if (secret.urn === updatedSecret.urn) {
81+
return index;
82+
}
83+
return -1;
84+
})
85+
.find((index) => index !== -1);
86+
87+
if (updatedSecretIndex !== undefined) {
88+
const newSecrets = (currData?.listSecrets?.secrets || []).map((secret, index) => {
89+
return index === updatedSecretIndex ? updatedSecret : secret;
90+
});
91+
92+
client.writeQuery({
93+
query: ListSecretsDocument,
94+
variables: {
95+
input: {
96+
start: (page - 1) * pageSize,
97+
count: pageSize,
98+
},
99+
},
100+
data: {
101+
listSecrets: {
102+
start: currData?.listSecrets?.start || 0,
103+
count: currData?.listSecrets?.count || 1,
104+
total: currData?.listSecrets?.total || 1,
105+
secrets: newSecrets,
106+
},
107+
},
108+
});
109+
}
110+
};
111+
67112
export const clearSecretListCache = (client) => {
68113
// Remove any caching of 'listSecrets'
69114
client.cache.evict({ id: 'ROOT_QUERY', fieldName: 'listSecrets' });

‎datahub-web-react/src/app/ingest/secret/types.ts

+4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22
* The object represents the state of the Ingestion Source Builder form.
33
*/
44
export interface SecretBuilderState {
5+
/**
6+
* The name of the secret.
7+
*/
8+
urn?: string;
59
/**
610
* The name of the secret.
711
*/

0 commit comments

Comments
 (0)
Please sign in to comment.