Skip to content

Commit

Permalink
feat(ui/secret): support to edit secrets (#9737)
Browse files Browse the repository at this point in the history
  • Loading branch information
gaurav2733 authored Jan 29, 2024
1 parent fdf929b commit f3cc4e0
Show file tree
Hide file tree
Showing 4 changed files with 170 additions and 21 deletions.
73 changes: 55 additions & 18 deletions datahub-web-react/src/app/ingest/secret/SecretBuilderModal.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Button, Form, Input, Modal, Typography } from 'antd';
import React, { useState } from 'react';
import React, { useEffect, useState } from 'react';
import { useEnterKeyListener } from '../../shared/useEnterKeyListener';
import { SecretBuilderState } from './types';

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

type Props = {
initialState?: SecretBuilderState;
editSecret?: SecretBuilderState;
visible: boolean;
onSubmit?: (source: SecretBuilderState, resetState: () => void) => void;
onUpdate?: (source: SecretBuilderState, resetState: () => void) => void;
onCancel?: () => void;
};

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

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

useEffect(() => {
if (editSecret) {
form.setFieldsValue({
name: editSecret.name,
description: editSecret.description,
value: editSecret.value,
});
}
}, [editSecret, form]);

function resetValues() {
setCreateButtonEnabled(false);
form.resetFields();
}

const onCloseModal = () => {
setCreateButtonEnabled(false);
form.resetFields();
onCancel?.();
};

const titleText = editSecret ? 'Edit Secret' : 'Create a new Secret';

return (
<Modal
width={540}
title={<Typography.Text>Create a new Secret</Typography.Text>}
title={<Typography.Text>{titleText}</Typography.Text>}
visible={visible}
onCancel={onCancel}
onCancel={onCloseModal}
zIndex={1051} // one higher than other modals - needed for managed ingestion forms
footer={
<>
<Button onClick={onCancel} type="text">
<Button onClick={onCloseModal} type="text">
Cancel
</Button>
<Button
data-testid="secret-modal-create-button"
id="createSecretButton"
onClick={() =>
onSubmit?.(
{
name: form.getFieldValue(NAME_FIELD_NAME),
description: form.getFieldValue(DESCRIPTION_FIELD_NAME),
value: form.getFieldValue(VALUE_FIELD_NAME),
},
resetValues,
)
}
onClick={() => {
if (!editSecret) {
onSubmit?.(
{
name: form.getFieldValue(NAME_FIELD_NAME),
description: form.getFieldValue(DESCRIPTION_FIELD_NAME),
value: form.getFieldValue(VALUE_FIELD_NAME),
},
resetValues,
);
} else {
onUpdate?.(
{
urn: editSecret?.urn,
name: form.getFieldValue(NAME_FIELD_NAME),
description: form.getFieldValue(DESCRIPTION_FIELD_NAME),
value: form.getFieldValue(VALUE_FIELD_NAME),
},
resetValues,
);
}
}}
disabled={!createButtonEnabled}
>
Create
{!editSecret ? 'Create' : 'Update'}
</Button>
</>
}
Expand All @@ -81,11 +114,15 @@ export const SecretBuilderModal = ({ initialState, visible, onSubmit, onCancel }
},
{ whitespace: false },
{ min: 1, max: 50 },
{ pattern: /^[a-zA-Z_]+[a-zA-Z0-9_]*$/, message: 'Please start the secret name with a letter, followed by letters, digits, or underscores only.' },
{
pattern: /^[a-zA-Z_]+[a-zA-Z0-9_]*$/,
message:
'Please start the secret name with a letter, followed by letters, digits, or underscores only.',
},
]}
hasFeedback
>
<Input placeholder="A name for your secret" />
<Input placeholder="A name for your secret" disabled={editSecret !== undefined} />
</Form.Item>
</Form.Item>
<Form.Item label={<Typography.Text strong>Value</Typography.Text>}>
Expand Down
69 changes: 66 additions & 3 deletions datahub-web-react/src/app/ingest/secret/SecretsList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
useCreateSecretMutation,
useDeleteSecretMutation,
useListSecretsQuery,
useUpdateSecretMutation,
} from '../../../graphql/ingestion.generated';
import { Message } from '../../shared/Message';
import TabToolbar from '../../entity/shared/components/styled/TabToolbar';
Expand All @@ -18,7 +19,11 @@ import { StyledTable } from '../../entity/shared/components/styled/StyledTable';
import { SearchBar } from '../../search/SearchBar';
import { useEntityRegistry } from '../../useEntityRegistry';
import { scrollToTop } from '../../shared/searchUtils';
import { addSecretToListSecretsCache, removeSecretFromListSecretsCache } from './cacheUtils';
import {
addSecretToListSecretsCache,
removeSecretFromListSecretsCache,
updateSecretInListSecretsCache,
} from './cacheUtils';
import { ONE_SECOND_IN_MS } from '../../entity/shared/tabs/Dataset/Queries/utils/constants';

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

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

const [deleteSecretMutation] = useDeleteSecretMutation();
const [createSecretMutation] = useCreateSecretMutation();
const { loading, error, data, client } = useListSecretsQuery({
const [updateSecretMutation] = useUpdateSecretMutation();
const { loading, error, data, client, refetch } = useListSecretsQuery({
variables: {
input: {
start,
Expand Down Expand Up @@ -125,6 +132,47 @@ export const SecretsList = () => {
});
});
};
const onUpdate = (state: SecretBuilderState, resetBuilderState: () => void) => {
updateSecretMutation({
variables: {
input: {
urn: state.urn as string,
name: state.name as string,
value: state.value as string,
description: state.description as string,
},
},
})
.then(() => {
message.success({
content: `Successfully updated Secret!`,
duration: 3,
});
resetBuilderState();
setIsCreatingSecret(false);
setEditSecret(undefined);
updateSecretInListSecretsCache(
{
urn: state.urn,
name: state.name,
description: state.description,
},
client,
pageSize,
page,
);
setTimeout(() => {
refetch();
}, 2000);
})
.catch((e) => {
message.destroy();
message.error({
content: `Failed to update Secret!: \n ${e.message || ''}`,
duration: 3,
});
});
};

const onDeleteSecret = (urn: string) => {
Modal.confirm({
Expand All @@ -140,6 +188,16 @@ export const SecretsList = () => {
});
};

const onEditSecret = (urnData: any) => {
setIsCreatingSecret(true);
setEditSecret(urnData);
};

const onCancel = () => {
setIsCreatingSecret(false);
setEditSecret(undefined);
};

const tableColumns = [
{
title: 'Name',
Expand All @@ -161,6 +219,9 @@ export const SecretsList = () => {
key: 'x',
render: (_, record: any) => (
<DeleteButtonContainer>
<Button style={{ marginRight: 16 }} onClick={() => onEditSecret(record)}>
EDIT
</Button>
<Button onClick={() => onDeleteSecret(record.urn)} type="text" shape="circle" danger>
<DeleteOutlined />
</Button>
Expand Down Expand Up @@ -234,8 +295,10 @@ export const SecretsList = () => {
</div>
<SecretBuilderModal
visible={isCreatingSecret}
editSecret={editSecret}
onUpdate={onUpdate}
onSubmit={onSubmit}
onCancel={() => setIsCreatingSecret(false)}
onCancel={onCancel}
/>
</>
);
Expand Down
45 changes: 45 additions & 0 deletions datahub-web-react/src/app/ingest/secret/cacheUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,51 @@ export const addSecretToListSecretsCache = (secret, client, pageSize) => {
});
};

export const updateSecretInListSecretsCache = (updatedSecret, client, pageSize, page) => {
const currData: ListSecretsQuery | null = client.readQuery({
query: ListSecretsDocument,
variables: {
input: {
start: (page - 1) * pageSize,
count: pageSize,
},
},
});

const updatedSecretIndex = (currData?.listSecrets?.secrets || [])
.map((secret, index) => {
if (secret.urn === updatedSecret.urn) {
return index;
}
return -1;
})
.find((index) => index !== -1);

if (updatedSecretIndex !== undefined) {
const newSecrets = (currData?.listSecrets?.secrets || []).map((secret, index) => {
return index === updatedSecretIndex ? updatedSecret : secret;
});

client.writeQuery({
query: ListSecretsDocument,
variables: {
input: {
start: (page - 1) * pageSize,
count: pageSize,
},
},
data: {
listSecrets: {
start: currData?.listSecrets?.start || 0,
count: currData?.listSecrets?.count || 1,
total: currData?.listSecrets?.total || 1,
secrets: newSecrets,
},
},
});
}
};

export const clearSecretListCache = (client) => {
// Remove any caching of 'listSecrets'
client.cache.evict({ id: 'ROOT_QUERY', fieldName: 'listSecrets' });
Expand Down
4 changes: 4 additions & 0 deletions datahub-web-react/src/app/ingest/secret/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
* The object represents the state of the Ingestion Source Builder form.
*/
export interface SecretBuilderState {
/**
* The name of the secret.
*/
urn?: string;
/**
* The name of the secret.
*/
Expand Down

0 comments on commit f3cc4e0

Please sign in to comment.