Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Create tab into Digital Twins page preview #1005

Open
wants to merge 14 commits into
base: feature/distributed-demo
Choose a base branch
from
44 changes: 40 additions & 4 deletions client/src/preview/components/asset/AssetBoard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@ import * as React from 'react';
import { Grid } from '@mui/material';
import { useSelector, useDispatch } from 'react-redux';
import { RootState } from 'store/store';
import { deleteAsset } from 'preview/store/assets.slice';
import { AssetCardExecute, AssetCardManage } from './AssetCard';
import { deleteAsset, setAssets } from 'preview/store/assets.slice';
import { setDigitalTwin } from 'preview/store/digitalTwin.slice';
import GitlabInstance from 'preview/util/gitlab';
import DigitalTwin from 'preview/util/gitlabDigitalTwin';
import { getAuthority } from 'util/envUtil';
import { Asset } from './Asset';
import { AssetCardExecute, AssetCardManage } from './AssetCard';

const outerGridContainerProps = {
container: true,
Expand All @@ -19,7 +23,6 @@ const outerGridContainerProps = {

interface AssetBoardProps {
tab: string;
error: string | null;
}

const AssetGridItem: React.FC<{
Expand All @@ -44,10 +47,43 @@ const AssetGridItem: React.FC<{
</Grid>
);

const AssetBoard: React.FC<AssetBoardProps> = ({ tab, error }) => {
const AssetBoard: React.FC<AssetBoardProps> = ({ tab }) => {
const assets = useSelector((state: RootState) => state.assets.items);
const [error, setError] = React.useState<string | null>(null);
const dispatch = useDispatch();

const gitlabInstance = new GitlabInstance(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code has been moved from DigitalTwinsPreview.tsx to here. The non-react part of code belongs to some file in preview/util module.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It has been moved to init.ts in preview/util.

sessionStorage.getItem('username') || '',
getAuthority(),
sessionStorage.getItem('access_token') || '',
);

React.useEffect(() => {
const fetchAssetsAndCreateTwins = async () => {
try {
await gitlabInstance.init();
if (gitlabInstance.projectId) {
const subfolders = await gitlabInstance.getDTSubfolders(
gitlabInstance.projectId,
);
dispatch(setAssets(subfolders));

subfolders.forEach(async (asset) => {
const digitalTwin = new DigitalTwin(asset.name, gitlabInstance);
await digitalTwin.getDescription();
dispatch(setDigitalTwin({ assetName: asset.name, digitalTwin }));
});
} else {
dispatch(setAssets([]));
}
} catch (err) {
setError(`An error occurred while fetching assets: ${err}`);
}
};

fetchAssetsAndCreateTwins();
}, []);

const handleDelete = (deletedAssetPath: string) => {
dispatch(deleteAsset(deletedAssetPath));
};
Expand Down
116 changes: 48 additions & 68 deletions client/src/preview/route/digitaltwins/DigitalTwinsPreview.tsx
Original file line number Diff line number Diff line change
@@ -1,96 +1,76 @@
import * as React from 'react';
import { useState, useEffect } from 'react';
import { useEffect } from 'react';
import { useDispatch } from 'react-redux';
import { Typography } from '@mui/material';
import Layout from 'page/Layout';
import TabComponent from 'components/tab/TabComponent';
import { TabData } from 'components/tab/subcomponents/TabRender';
import AssetBoard from 'preview/components/asset/AssetBoard';
import GitlabInstance from 'preview/util/gitlab';
import { getAuthority } from 'util/envUtil';
import { setAssets } from 'preview/store/assets.slice';
import { Asset } from 'preview/components/asset/Asset';
import DigitalTwin from 'preview/util/gitlabDigitalTwin';
import { setDigitalTwin } from 'preview/store/digitalTwin.slice';
import { addNewFile } from 'preview/store/file.slice';
import tabs from './DigitalTwinTabDataPreview';
import CreatePage from './create/CreatePage';

export const createDTTab = (error: string | null): TabData[] =>
interface DTTabProps {
newDigitalTwinName: string;
setNewDigitalTwinName: React.Dispatch<React.SetStateAction<string>>;
}

export const createDTTab = ({
newDigitalTwinName,
setNewDigitalTwinName,
}: DTTabProps): TabData[] =>
tabs
.filter((tab) => tab.label === 'Manage' || tab.label === 'Execute')
.filter(
(tab) =>
tab.label === 'Manage' ||
tab.label === 'Execute' ||
tab.label === 'Create',
)
.map((tab) => ({
label: tab.label,
body: (
<>
<Typography variant="body1">{tab.body}</Typography>
<AssetBoard tab={tab.label} error={error} />
</>
),
body:
tab.label === 'Create' ? (
<>
<Typography variant="body1">{tab.body}</Typography>
<CreatePage
newDigitalTwinName={newDigitalTwinName}
setNewDigitalTwinName={setNewDigitalTwinName}
/>
</>
) : (
<>
<Typography variant="body1">{tab.body}</Typography>
<AssetBoard tab={tab.label} />
</>
),
}));

export const fetchSubfolders = async (
gitlabInstance: GitlabInstance,
dispatch: ReturnType<typeof useDispatch>,
setError: React.Dispatch<React.SetStateAction<string | null>>,
) => {
try {
await gitlabInstance.init();
if (gitlabInstance.projectId) {
const subfolders = await gitlabInstance.getDTSubfolders(
gitlabInstance.projectId,
);
dispatch(setAssets(subfolders));
return subfolders;
}
dispatch(setAssets([]));
return [];
} catch (error) {
setError('An error occurred');
return [];
}
};

export const createDigitalTwinsForAssets = async (
assets: Asset[],
dispatch: ReturnType<typeof useDispatch>,
) => {
assets.forEach(async (asset) => {
const gitlabInstance = new GitlabInstance(
sessionStorage.getItem('username') || '',
getAuthority(),
sessionStorage.getItem('access_token') || '',
);
await gitlabInstance.init();
const digitalTwin = new DigitalTwin(asset.name, gitlabInstance);
await digitalTwin.getDescription();
dispatch(setDigitalTwin({ assetName: asset.name, digitalTwin }));
});
};

export const DTContent = () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is only creating new DT. Can this intent be made more clear with a restructuring of code?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The purpose of this file has remained unchanged, I have only added the necessary logic for the Create page (the useState newDigitalTwinName) while I have moved the pre-existing logic in the function fetchAssetsAndCreateTwins to the AssetBoard, as it no longer has to be called up only when the main component (DigitalTwinsPreview) is opened, but each time an AssetBoard is rendered, i.e. when switching to the Manage or Execute tabs. This is necessary in order to allow the new DT to be displayed immediately after creation by switching to one of the aforementioned tabs.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The AssetBoard is a react component. It is better to move the actual logic out of it.

const [error, setError] = useState<string | null>(null);
const [newDigitalTwinName, setNewDigitalTwinName] = React.useState('');
const dispatch = useDispatch();
const gitlabInstance = new GitlabInstance(
sessionStorage.getItem('username') || '',
getAuthority(),
sessionStorage.getItem('access_token') || '',
);

useEffect(() => {
fetchSubfolders(gitlabInstance, dispatch, setError).then((assets) => {
if (assets) {
createDigitalTwinsForAssets(assets, dispatch);
}
const defaultFiles = [
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This array and the method to iterate upon this can moved to either to gitlabDigitalTwin.ts

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It has been moved to file.ts in preview/util.

{ name: 'description.md', type: 'description' },
{ name: 'README.md', type: 'description' },
{ name: '.gitlab-ci.yml', type: 'config' },
];
defaultFiles.forEach((file) => {
dispatch(addNewFile(file));
});
}, [dispatch]);

return (
<Layout>
<Typography variant="body1" style={{ marginBottom: 0 }}>
This page demonstrates integration of DTaaS with gitlab CI/CD workflows.
The feature is experimental and requires certain gitlab setup in order
<Typography variant="body1" sx={{ marginBottom: 0 }}>
This page demonstrates integration of DTaaS with GitLab CI/CD workflows.
The feature is experimental and requires certain GitLab setup in order
for it to work.
</Typography>
<TabComponent assetType={createDTTab(error)} scope={[]} />
<TabComponent
assetType={createDTTab({ newDigitalTwinName, setNewDigitalTwinName })}
scope={[]}
/>
</Layout>
);
};
Expand Down
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

File handling relates to operations on gitlab repo from frontend. Perhaps separating all the file handling operations into one module, say util/File.ts helps with modularity of the code?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Functions with file-related operations have been moved to file.ts in preview/util.

Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import * as React from 'react';
import {
Dialog,
DialogActions,
DialogContent,
DialogTitle,
TextField,
Button,
} from '@mui/material';
import { renameFile } from 'preview/store/file.slice';
import { useDispatch } from 'react-redux';
import { Dispatch, SetStateAction, useEffect, useState } from 'react';

interface ChangeFileNameDialogProps {
open: boolean;
onClose: () => void;
fileName: string;
setFileName: Dispatch<SetStateAction<string>>;
setFileType: Dispatch<SetStateAction<string>>;
}

const ChangeFileNameDialog: React.FC<ChangeFileNameDialogProps> = ({
open,
onClose,
fileName,
setFileName,
setFileType,
}) => {
const [modifiedFileName, setModifiedFileName] = useState(fileName);

const dispatch = useDispatch();

useEffect(() => {
setModifiedFileName(fileName);
}, [fileName]);

const handleChangeFileName = () => {
dispatch(renameFile({ oldName: fileName, newName: modifiedFileName }));
setFileName(modifiedFileName);

const extension = modifiedFileName.split('.').pop();
setFileType(extension || '');

onClose();
};

return (
<Dialog open={open} onClose={onClose}>
<DialogTitle>Change the file name</DialogTitle>
<DialogContent>
<TextField
autoFocus
margin="dense"
label="New File Name"
fullWidth
variant="outlined"
value={modifiedFileName}
onChange={(e) => setModifiedFileName(e.target.value)}
/>
</DialogContent>
<DialogActions>
<Button onClick={onClose} color="primary">
Cancel
</Button>
<Button onClick={handleChangeFileName} color="secondary">
Change
</Button>
</DialogActions>
</Dialog>
);
};

export default ChangeFileNameDialog;
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import * as React from 'react';
import { Dispatch, SetStateAction } from 'react';
import { Dialog, DialogActions, DialogContent, Button } from '@mui/material';
import { removeAllCreationFiles, addNewFile } from 'preview/store/file.slice';
import { useDispatch, useSelector } from 'react-redux';
import { RootState } from 'store/store';

interface ConfirmDeleteDialogProps {
open: boolean;
onClose: () => void;
setFileName: Dispatch<SetStateAction<string>>;
setFileContent: Dispatch<SetStateAction<string>>;
setFileType: Dispatch<SetStateAction<string>>;
setNewDigitalTwinName: Dispatch<SetStateAction<string>>;
}

const ConfirmDeleteDialog: React.FC<ConfirmDeleteDialogProps> = ({
open,
onClose,
setFileName,
setFileContent,
setFileType,
setNewDigitalTwinName,
}) => {
const dispatch = useDispatch();

const files = useSelector((state: RootState) => state.files);

const handleConfirmCancel = () => {
setFileName('');
setFileContent('');
setFileType('');
setNewDigitalTwinName('');
dispatch(removeAllCreationFiles());

const defaultFiles = [
VanessaScherma marked this conversation as resolved.
Show resolved Hide resolved
{ name: 'description.md', type: 'description' },
{ name: 'README.md', type: 'description' },
{ name: '.gitlab-ci.yml', type: 'config' },
];

defaultFiles.forEach((file) => {
const fileExists = files.some(
(f) => f.name === file.name && f.isNew === true,
);
if (!fileExists) {
dispatch(addNewFile(file));
}
});

onClose();
};

return (
<Dialog open={open} onClose={onClose}>
<DialogContent>
Are you sure you want to delete the inserted files and their content?
</DialogContent>
<DialogActions>
<Button onClick={onClose}>Cancel</Button>
<Button onClick={handleConfirmCancel}>Yes</Button>
</DialogActions>
</Dialog>
);
};

export default ConfirmDeleteDialog;
Loading
Loading