Skip to content

Commit bf02a90

Browse files
committed
merge: merge branch 'THU/rename_dataset'
2 parents a2d875d + 9b9e32e commit bf02a90

File tree

13 files changed

+277
-6
lines changed

13 files changed

+277
-6
lines changed

cypress/commons/actions/generic/DatasetManager.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,26 @@ export const getDatasetOverviewPlaceholderRetryButton = () => cy.get(SELECTORS.o
9898
export const getDatasetOverviewPlaceholderRollbackButton = () => cy.get(SELECTORS.overview.placeholder.rollbackButton);
9999
export const getDatasetOverviewPlaceholderApiLink = () => cy.get(SELECTORS.overview.placeholder.apiLink);
100100

101+
export const getDatasetNameEditableTextField = () => cy.get(SELECTORS.overview.datasetNameTextField);
102+
export const getRenameDatasetButton = () => cy.get(SELECTORS.overview.renameScenario);
103+
104+
// Parameters
105+
// - newDatasetName (string)
106+
// - options (object) is an optional parameter. If provided, it can have the following properties:
107+
// - id (optional): id of the dataset to update (if not provided, it will be guessed from the request URL)
108+
// - validateRequest (optional): validation function to run on the dataset update request
109+
export const renameDataset = (newDatasetName, options) => {
110+
const renameDatasetAlias = api.interceptUpdateDataset({
111+
id: options?.id,
112+
validateRequest: options?.validateRequest,
113+
});
114+
115+
getRenameDatasetButton().click();
116+
getDatasetNameEditableTextField().type('{selectAll}{backspace}' + newDatasetName + '{enter}');
117+
118+
api.waitAlias(renameDatasetAlias);
119+
};
120+
101121
export const getCategoryAccordionSummary = (categoryId) =>
102122
cy.get(SELECTORS.overview.categories.accordionSummary.replace('$CATEGORY_ID', categoryId));
103123
export const getCategoryAccordionDetails = (categoryId) =>

cypress/commons/constants/generic/IdConstants.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,8 @@ export const GENERIC_SELECTORS = {
241241
},
242242
overview: {
243243
datasetName: '[data-cy=dataset-name]',
244+
datasetNameTextField: '[data-cy=dataset-name-editable-text-field]',
245+
renameScenario: '[data-cy=rename-dataset-button]',
244246
placeholder: {
245247
container: '[data-cy=dataset-overview-placeholder]',
246248
title: '[data-cy=dataset-overview-title]',
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// Copyright (c) Cosmo Tech.
2+
// Licensed under the MIT license.
3+
import { DatasetManager, Login } from '../../commons/actions';
4+
import { stub } from '../../commons/services/stubbing';
5+
import { SOLUTION, WORKSPACE, ORGANIZATION_WITH_DEFAULT_ROLE_USER } from '../../fixtures/stubbing/DatasetManager';
6+
import { EDITABLE_DATASET, NON_EDITABLE_DATASET } from '../../fixtures/stubbing/RenameDataset';
7+
8+
describe('rename datasets in Dataset Manager view', () => {
9+
before(() => {
10+
const linkedWorkspace = { ...WORKSPACE, linkedDatasetIdList: [EDITABLE_DATASET.id, NON_EDITABLE_DATASET.id] };
11+
stub.start();
12+
stub.setOrganizations([ORGANIZATION_WITH_DEFAULT_ROLE_USER]);
13+
stub.setSolutions([SOLUTION]);
14+
stub.setWorkspaces([linkedWorkspace]);
15+
console.log(EDITABLE_DATASET, NON_EDITABLE_DATASET);
16+
stub.setDatasets([EDITABLE_DATASET, NON_EDITABLE_DATASET]);
17+
});
18+
19+
beforeEach(() => Login.login({ url: '/W-stbbdbrwryWithDM/datasetmanager', workspaceId: 'W-stbbdbrwryWithDM' }));
20+
after(stub.stop);
21+
22+
it('cannot rename datasets if role is viewer', () => {
23+
DatasetManager.switchToDatasetManagerView();
24+
DatasetManager.selectDatasetById(NON_EDITABLE_DATASET.id);
25+
DatasetManager.getDatasetNameInOverview().should('have.text', NON_EDITABLE_DATASET.name);
26+
DatasetManager.getDatasetNameEditableTextField().should('not.exist');
27+
DatasetManager.getRenameDatasetButton().should('not.exist');
28+
});
29+
30+
it('can rename datasets if role is editor', () => {
31+
const datasetName = EDITABLE_DATASET.name;
32+
const newName = 'renamed';
33+
34+
DatasetManager.switchToDatasetManagerView();
35+
DatasetManager.selectDatasetById(EDITABLE_DATASET.id);
36+
DatasetManager.getDatasetNameInOverview().should('have.text', datasetName);
37+
DatasetManager.getDatasetNameEditableTextField().should('not.exist');
38+
DatasetManager.getRenameDatasetButton().should('exist').should('be.visible');
39+
40+
// Edit & cancel without confirming the changes
41+
DatasetManager.getRenameDatasetButton().click();
42+
DatasetManager.getDatasetNameInOverview().should('not.exist');
43+
DatasetManager.getDatasetNameEditableTextField().should('exist').should('be.visible');
44+
DatasetManager.getDatasetNameEditableTextField().type('{selectAll}{backspace}' + newName + '{esc}');
45+
DatasetManager.getDatasetNameEditableTextField().should('not.exist'); // Input field disappeared
46+
DatasetManager.getDatasetNameInOverview().should('have.text', datasetName); // Initial name remained unchanged
47+
48+
// Edit & confirm
49+
const validateRequest = (query) => {
50+
console.log(query);
51+
return true;
52+
};
53+
DatasetManager.renameDataset(newName, { validateRequest });
54+
DatasetManager.getDatasetNameInOverview().should('have.text', newName); // Dataset name has been changed
55+
56+
// Switch between dataset and check that the new name has been persisted
57+
DatasetManager.selectDatasetById(NON_EDITABLE_DATASET.id);
58+
DatasetManager.getDatasetNameInOverview().should('have.text', NON_EDITABLE_DATASET.name);
59+
DatasetManager.selectDatasetById(EDITABLE_DATASET.id);
60+
DatasetManager.getDatasetNameInOverview().should('have.text', newName);
61+
});
62+
});
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Copyright (c) Cosmo Tech.
2+
// Licensed under the MIT license.
3+
import { WORKSPACE } from '../DatasetManager/workspaces';
4+
import { DEFAULT_DATASET } from '../default';
5+
6+
export const NON_EDITABLE_DATASET = {
7+
...DEFAULT_DATASET,
8+
main: true,
9+
id: 'd-noneditable',
10+
name: 'non editable dataset',
11+
ingestionStatus: 'SUCCESS',
12+
twincacheStatus: 'FULL',
13+
linkedWorkspaceIdList: [WORKSPACE.id],
14+
security: { default: 'viewer', accessControlList: [] },
15+
};
16+
17+
export const EDITABLE_DATASET = {
18+
...DEFAULT_DATASET,
19+
main: true,
20+
id: 'd-editable',
21+
name: 'editable dataset',
22+
ingestionStatus: 'SUCCESS',
23+
twincacheStatus: 'FULL',
24+
linkedWorkspaceIdList: [WORKSPACE.id],
25+
security: { default: 'editor', accessControlList: [] },
26+
};
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
// Copyright (c) Cosmo Tech.
2+
// Licensed under the MIT license.
3+
4+
export { EDITABLE_DATASET, NON_EDITABLE_DATASET } from './datasets';

public/locales/en/translation.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,8 @@
183183
"deleteButtonTooltip": "Delete",
184184
"shareButtonTooltip": "Share",
185185
"refreshButtonTooltip": "Refresh",
186+
"renameButtonTooltip": "Rename",
187+
"emptyDatasetNameError": "Dataset name cannot be empty",
186188
"createSubdatasetButtonTooltip": "Create sub-dataset",
187189
"editParametersButtonTooltip": "Edit parameters of the dataset"
188190
},

public/locales/fr/translation.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,8 @@
183183
"deleteButtonTooltip": "Supprimer",
184184
"shareButtonTooltip": "Partager",
185185
"refreshButtonTooltip": "Mettre à jour",
186+
"renameButtonTooltip": "Renommer",
187+
"emptyDatasetNameError": "Le nom du dataset ne doit pas être vide",
186188
"createSubdatasetButtonTooltip": "Créer un sous-dataset",
187189
"editParametersButtonTooltip": "Modifier les paramètres du dataset"
188190
},

src/views/DatasetManager/components/DatasetOverview/DatasetOverview.js

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,11 @@ import React, { useMemo } from 'react';
44
import { Card, CardContent, CardHeader, Grid } from '@mui/material';
55
import { INGESTION_STATUS } from '../../../../services/config/ApiConstants';
66
import { useDatasetOverview } from './DatasetOverviewHook';
7-
import { CategoryAccordion, DatasetOverviewPlaceholder, GraphIndicator } from './components';
7+
import { CategoryAccordion, DatasetOverviewPlaceholder, EditableDatasetName, GraphIndicator } from './components';
88
import DatasetActions from './components/DatasetActions/DatasetActions';
99

1010
export const DatasetOverview = () => {
11-
const { categories, graphIndicators, queriesResults, datasetIngestionStatus, dataset, datasetName } =
12-
useDatasetOverview();
11+
const { categories, graphIndicators, queriesResults, datasetIngestionStatus, dataset } = useDatasetOverview();
1312

1413
const showPlaceholder = useMemo(
1514
() =>
@@ -24,15 +23,15 @@ export const DatasetOverview = () => {
2423
});
2524
}, [graphIndicators, queriesResults]);
2625

26+
const editableDatasetName = <EditableDatasetName />;
2727
return (
2828
<Card
2929
elevation={0}
3030
sx={{ p: 1, width: '100%', height: '100%', overflow: 'auto', backgroundColor: 'transparent' }}
3131
data-cy="dataset-overview-card"
3232
>
3333
<CardHeader
34-
data-cy="dataset-name"
35-
title={datasetName}
34+
title={editableDatasetName}
3635
sx={{ height: '65px' }}
3736
action={<DatasetActions dataset={dataset}></DatasetActions>}
3837
></CardHeader>

src/views/DatasetManager/components/DatasetOverview/DatasetOverviewHook.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,5 @@ export const useDatasetOverview = () => {
4646
queriesResults,
4747
datasetIngestionStatus,
4848
dataset: currentDataset,
49-
datasetName: currentDataset?.name,
5049
};
5150
};
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
// Copyright (c) Cosmo Tech.
2+
// Licensed under the MIT license.
3+
import React, { useEffect, useMemo, useState } from 'react';
4+
import { useTranslation } from 'react-i18next';
5+
import { Edit as EditIcon } from '@mui/icons-material';
6+
import { FormControl, FormHelperText, IconButton, OutlinedInput, Stack, Typography } from '@mui/material';
7+
import { FadingTooltip, PermissionsGate } from '@cosmotech/ui';
8+
import { ACL_PERMISSIONS } from '../../../../../../services/config/accessControl';
9+
import { useEditableDatasetName } from './EditableDatasetNameHook';
10+
11+
const sanitizeValue = (value) => value.trim();
12+
13+
export const EditableDatasetName = () => {
14+
const { t } = useTranslation();
15+
const { dataset, datasetName, renameDataset } = useEditableDatasetName();
16+
17+
const [error, setError] = useState(null);
18+
const [isEditing, setEditing] = useState(false);
19+
const [textFieldValue, setTextFieldValue] = useState(datasetName ?? '');
20+
21+
useEffect(() => {
22+
setTextFieldValue(datasetName);
23+
}, [datasetName]);
24+
25+
const startEdition = (event) => {
26+
event.stopPropagation();
27+
event.preventDefault();
28+
setEditing(true);
29+
};
30+
31+
const editableTextField = useMemo(() => {
32+
const stopEdition = (event) => {
33+
event && event.stopPropagation();
34+
setEditing(false);
35+
setError(null);
36+
};
37+
38+
const cancelEdition = (event) => {
39+
setTextFieldValue(datasetName);
40+
stopEdition(event);
41+
};
42+
43+
const confirmChanges = (event) => {
44+
const sanitizedValue = sanitizeValue(event.target.value);
45+
if (error == null && sanitizedValue.length !== 0 && sanitizedValue !== datasetName) {
46+
renameDataset(sanitizedValue);
47+
setTextFieldValue(sanitizedValue);
48+
} else setTextFieldValue(datasetName);
49+
50+
stopEdition(event);
51+
};
52+
53+
const processNewValue = (event) => {
54+
const newValue = event.target.value;
55+
setTextFieldValue(newValue);
56+
57+
const sanitizedValue = sanitizeValue(newValue);
58+
if (sanitizedValue.length !== 0) setError(null);
59+
else
60+
setError(
61+
t('commoncomponents.datasetmanager.overview.actions.emptyDatasetNameError', 'Dataset name cannot be empty')
62+
);
63+
};
64+
65+
return (
66+
<FormControl data-cy="dataset-name-editable-text-field">
67+
<OutlinedInput
68+
autoFocus
69+
value={textFieldValue}
70+
error={error != null}
71+
onChange={processNewValue}
72+
onBlur={confirmChanges}
73+
onClick={(event) => event.stopPropagation()}
74+
onKeyDown={(event) => {
75+
if (event.key === 'Escape') cancelEdition(event);
76+
else if (event.key === 'Enter') confirmChanges(event);
77+
}}
78+
onFocus={(event) => event.stopPropagation()}
79+
size="small"
80+
sx={{ minWidth: '400px', marginLeft: 0.5, padding: 0.5 }}
81+
/>
82+
<FormHelperText error>{error}</FormHelperText>
83+
</FormControl>
84+
);
85+
}, [datasetName, error, renameDataset, setTextFieldValue, t, textFieldValue]);
86+
87+
const datasetNameElement = useMemo(
88+
() => (
89+
<Typography data-cy="dataset-name" variant="h6">
90+
{datasetName}
91+
</Typography>
92+
),
93+
[datasetName]
94+
);
95+
96+
const datasetNameWithEditButton = useMemo(() => {
97+
return (
98+
<Stack direction="row" spacing={1} alignItems="stretch" justifyContent="flex-start">
99+
{datasetNameElement}
100+
<FadingTooltip
101+
title={t('commoncomponents.datasetmanager.overview.actions.renameButtonTooltip', 'Rename')}
102+
disableInteractive={true}
103+
>
104+
<IconButton onClick={startEdition} data-cy="rename-dataset-button">
105+
<EditIcon color="primary" />
106+
</IconButton>
107+
</FadingTooltip>
108+
</Stack>
109+
);
110+
}, [datasetNameElement, t]);
111+
112+
const userPermissions = dataset?.security?.currentUserPermissions ?? [];
113+
return (
114+
<PermissionsGate
115+
userPermissions={userPermissions}
116+
necessaryPermissions={[ACL_PERMISSIONS.DATASET.WRITE]}
117+
RenderNoPermissionComponent={() => datasetNameElement}
118+
>
119+
{isEditing ? editableTextField : datasetNameWithEditButton}
120+
</PermissionsGate>
121+
);
122+
};

0 commit comments

Comments
 (0)