From 19f0c539fe4bf87336db8b72d11600db15bf6774 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Wed, 7 Oct 2020 21:31:52 +0530 Subject: [PATCH 001/239] responsive refactor ingestion client --- .../src/components/controls/LoadingButton.tsx | 12 +++- client/src/components/index.ts | 4 +- .../components/shared/Sidebar/SidebarMenu.tsx | 66 ------------------- .../shared/Sidebar/SidebarMenuOption.tsx | 50 -------------- client/src/components/shared/Sidebar/index.ts | 5 -- .../{Sidebar => }/SidebarBottomNavigator.tsx | 22 ++++--- .../pages/Home/components/SidePanelOption.tsx | 13 +++- .../IngestionSidebarMenuOption.tsx | 4 +- .../Metadata/Photogrammetry/AssetContents.tsx | 22 ++++--- .../Metadata/Photogrammetry/Description.tsx | 4 +- .../Metadata/Photogrammetry/IdInputField.tsx | 13 ++-- .../Photogrammetry/IdentifierList.tsx | 18 +++-- .../Metadata/Photogrammetry/SelectField.tsx | 13 ++-- .../Metadata/Photogrammetry/index.tsx | 15 +++-- .../Ingestion/components/Metadata/index.tsx | 13 +++- .../components/SubjectItem/ItemList.tsx | 39 ++++++----- .../components/SubjectItem/ProjectList.tsx | 3 +- .../components/SubjectItem/SearchList.tsx | 10 +-- .../components/SubjectItem/SubjectList.tsx | 10 ++- .../SubjectItem/SubjectListItem.tsx | 19 ++++-- .../components/SubjectItem/index.tsx | 26 ++++---- .../components/Uploads/FileListItem.tsx | 49 ++++++++------ .../components/Uploads/UploadCompleteList.tsx | 42 +----------- .../components/Uploads/UploadFilesPicker.tsx | 3 +- .../components/Uploads/UploadList.tsx | 19 ++++-- .../components/Uploads/UploadListHeader.tsx | 11 ++-- .../Ingestion/components/Uploads/index.tsx | 52 ++++++++------- client/src/pages/Login/index.tsx | 2 - .../components/RepositoryTreeView/index.tsx | 4 +- client/src/store/upload.ts | 9 ++- client/src/theme/index.ts | 2 +- client/src/theme/typography.ts | 19 ++++-- 32 files changed, 271 insertions(+), 322 deletions(-) delete mode 100644 client/src/components/shared/Sidebar/SidebarMenu.tsx delete mode 100644 client/src/components/shared/Sidebar/SidebarMenuOption.tsx delete mode 100644 client/src/components/shared/Sidebar/index.ts rename client/src/components/shared/{Sidebar => }/SidebarBottomNavigator.tsx (83%) diff --git a/client/src/components/controls/LoadingButton.tsx b/client/src/components/controls/LoadingButton.tsx index 38fa5ec61..d1d63413f 100644 --- a/client/src/components/controls/LoadingButton.tsx +++ b/client/src/components/controls/LoadingButton.tsx @@ -1,5 +1,6 @@ import React from 'react'; import { Button, ButtonProps } from '@material-ui/core'; +import { makeStyles } from '@material-ui/core/styles'; import Progress from '../shared/Progress'; type LoadingButtonProps = ButtonProps & { @@ -7,11 +8,18 @@ type LoadingButtonProps = ButtonProps & { loaderSize?: number; }; +const useStyles = makeStyles(({ typography }) => ({ + button: { + fontSize: typography.caption.fontSize + } +})); + function LoadingButton(props: LoadingButtonProps): React.ReactElement { - const { loading, loaderSize, ...rest } = props; + const { loading, loaderSize, className, ...rest } = props; + const classes = useStyles(); return ( - diff --git a/client/src/components/index.ts b/client/src/components/index.ts index d1a4a3188..ebfc74743 100644 --- a/client/src/components/index.ts +++ b/client/src/components/index.ts @@ -8,8 +8,8 @@ import PublicRoute from './shared/PublicRoute'; import FieldType from './shared/FieldType'; import Loader from './shared/Loader'; import Progress from './shared/Progress'; +import SidebarBottomNavigator from './shared/SidebarBottomNavigator'; import LoadingButton from './controls/LoadingButton'; import RepositoryIcon from './controls/RepositoryIcon'; -export * from './shared/Sidebar'; -export { Header, PrivateRoute, PublicRoute, FieldType, LoadingButton, Loader, Progress, RepositoryIcon }; +export { Header, PrivateRoute, PublicRoute, FieldType, LoadingButton, Loader, Progress, SidebarBottomNavigator, RepositoryIcon }; diff --git a/client/src/components/shared/Sidebar/SidebarMenu.tsx b/client/src/components/shared/Sidebar/SidebarMenu.tsx deleted file mode 100644 index 6e297979f..000000000 --- a/client/src/components/shared/Sidebar/SidebarMenu.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import { Box, Grid, Typography } from '@material-ui/core'; -import { makeStyles } from '@material-ui/core/styles'; -import React, { useState, useEffect } from 'react'; -import { useParams } from 'react-router'; -import SidebarMenuOption, { SidebarRouteTypes } from './SidebarMenuOption'; - -const useStyles = makeStyles(({ palette, typography, spacing }) => ({ - container: { - display: 'flex', - flexDirection: 'column', - padding: '2em 1em', - borderRight: `1px solid ${palette.primary.light}` - }, - menuLabel: { - color: palette.primary.contrastText, - fontWeight: typography.fontWeightMedium, - marginLeft: spacing(2), - marginBottom: spacing(5) - }, - menuOptions: { - display: 'flex', - flex: 1 - } -})); - -export type SidebarOption = { - label: string; - type: SidebarRouteTypes -}; - -interface SidebarMenuProps { - title: string; - paramIdentifier: string; - initialRoute?: SidebarRouteTypes; - options: SidebarOption[]; - children: React.ReactNode; -} - -export function SidebarMenu(props: SidebarMenuProps): React.ReactElement { - const { title, children, initialRoute, options, paramIdentifier } = props; - const param = useParams()[paramIdentifier]; - const [selectedOption, setSelectedOption] = useState(initialRoute || param); - - const classes = useStyles(); - - useEffect(() => { - setSelectedOption(param); - }, [param]); - - const sidebarOptions = options.map(option => ({ - ...option, - isSelected: selectedOption === option.type - })); - - return ( - - - {title} - - {sidebarOptions.map((props, index) => )} - - - {children} - - ); -} diff --git a/client/src/components/shared/Sidebar/SidebarMenuOption.tsx b/client/src/components/shared/Sidebar/SidebarMenuOption.tsx deleted file mode 100644 index 0c62d91be..000000000 --- a/client/src/components/shared/Sidebar/SidebarMenuOption.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { Typography } from '@material-ui/core'; -import { makeStyles } from '@material-ui/core/styles'; -import React, { memo } from 'react'; -import { INGESTION_ROUTES_TYPE } from '../../../constants'; -import { Colors } from '../../../theme'; -import { Link } from 'react-router-dom'; - -const useStyles = makeStyles(({ palette }) => ({ - container: { - display: 'flex', - alignItems: 'center', - justifyContent: 'flex-start', - padding: '0.8rem 1.25rem', - width: 150, - transition: 'all 250ms ease-in', - textDecoration: 'none', - overflow: 'hidden', - borderRadius: 10, - marginTop: 2, - color: ({ isSelected }: SidebarMenuOptionProps) => isSelected ? palette.primary.main : palette.primary.dark, - backgroundColor: ({ isSelected }: SidebarMenuOptionProps) => isSelected ? palette.primary.light : Colors.defaults.white, - '&:hover': { - cursor: 'pointer', - color: palette.primary.main, - backgroundColor: palette.primary.light - }, - }, -})); - -export type SidebarRouteTypes = INGESTION_ROUTES_TYPE; - -export interface SidebarMenuOptionProps { - label: string; - type: SidebarRouteTypes; - isSelected: boolean; -} - -function SidebarMenuOption(props: SidebarMenuOptionProps): React.ReactElement { - const { label, type } = props; - - const classes = useStyles(props); - - return ( - - {label} - - ); -} - -export default memo(SidebarMenuOption); \ No newline at end of file diff --git a/client/src/components/shared/Sidebar/index.ts b/client/src/components/shared/Sidebar/index.ts deleted file mode 100644 index 786982c74..000000000 --- a/client/src/components/shared/Sidebar/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -import SidebarBottomNavigator from './SidebarBottomNavigator'; - -export * from './SidebarMenu'; -export * from './SidebarMenuOption'; -export { SidebarBottomNavigator }; diff --git a/client/src/components/shared/Sidebar/SidebarBottomNavigator.tsx b/client/src/components/shared/SidebarBottomNavigator.tsx similarity index 83% rename from client/src/components/shared/Sidebar/SidebarBottomNavigator.tsx rename to client/src/components/shared/SidebarBottomNavigator.tsx index 7fa27347f..d445b38a5 100644 --- a/client/src/components/shared/Sidebar/SidebarBottomNavigator.tsx +++ b/client/src/components/shared/SidebarBottomNavigator.tsx @@ -2,10 +2,10 @@ import { Box } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; import React from 'react'; import { Link } from 'react-router-dom'; -import { Colors } from '../../../theme'; -import LoadingButton from '../../controls/LoadingButton'; +import { Colors } from '../../theme'; +import LoadingButton from '../controls/LoadingButton'; -const useStyles = makeStyles(({ palette }) => ({ +const useStyles = makeStyles(({ palette, breakpoints }) => ({ container: { display: 'flex', bottom: 0, @@ -14,12 +14,18 @@ const useStyles = makeStyles(({ palette }) => ({ width: '51vw', padding: '20px 0px', marginLeft: 40, - background: palette.background.paper + background: palette.background.paper, + [breakpoints.down('lg')]: { + marginLeft: 20 + } }, navButton: { - minHeight: 36, + minHeight: 35, minWidth: 100, - color: Colors.defaults.white + color: Colors.defaults.white, + [breakpoints.down('lg')]: { + height: 30, + } }, link: { textDecoration: 'none' @@ -44,8 +50,6 @@ function SidebarBottomNavigator(props: SidebarBottomNavigatorProps): React.React let leftButton = ( ({ +const useStyles = makeStyles(({ palette, spacing, typography, breakpoints }) => ({ container: { display: 'flex', alignItems: 'center', @@ -22,16 +22,23 @@ const useStyles = makeStyles(({ palette, spacing, typography }) => ({ cursor: 'pointer', backgroundColor: fade(palette.primary.light, 0.2) }, + [breakpoints.down('lg')]: { + width: ({ isExpanded }: SidePanelOptionProps) => isExpanded ? 180 : 50, + } }, iconContainer: { display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 8, - borderRadius: 10, + borderRadius: 5, backgroundColor: ({ color }: SidePanelOptionProps) => fade(color, 0.2), minHeight: 32, - minWidth: 32 + minWidth: 32, + [breakpoints.down('lg')]: { + minHeight: 25, + minWidth: 25, + } }, detailsContainer: { display: 'flex', diff --git a/client/src/pages/Ingestion/components/IngestionSidebar/IngestionSidebarMenuOption.tsx b/client/src/pages/Ingestion/components/IngestionSidebar/IngestionSidebarMenuOption.tsx index 100d4c827..65a35aa23 100644 --- a/client/src/pages/Ingestion/components/IngestionSidebar/IngestionSidebarMenuOption.tsx +++ b/client/src/pages/Ingestion/components/IngestionSidebar/IngestionSidebarMenuOption.tsx @@ -11,12 +11,12 @@ const useStyles = makeStyles(({ palette }) => ({ alignItems: 'flex-start', justifyContent: 'center', flexDirection: 'column', - padding: '0.8rem 1.25rem', + padding: '0.8rem 1rem', width: 150, transition: 'all 250ms ease-in', textDecoration: 'none', overflow: 'hidden', - borderRadius: 10, + borderRadius: 5, marginTop: 2, color: ({ isSelected }: IngestionSidebarMenuOptionProps) => isSelected ? palette.primary.main : palette.primary.dark, backgroundColor: ({ isSelected }: IngestionSidebarMenuOptionProps) => isSelected ? palette.primary.light : Colors.defaults.white, diff --git a/client/src/pages/Ingestion/components/Metadata/Photogrammetry/AssetContents.tsx b/client/src/pages/Ingestion/components/Metadata/Photogrammetry/AssetContents.tsx index fb37cb2c1..f54534a74 100644 --- a/client/src/pages/Ingestion/components/Metadata/Photogrammetry/AssetContents.tsx +++ b/client/src/pages/Ingestion/components/Metadata/Photogrammetry/AssetContents.tsx @@ -1,10 +1,10 @@ -import { Box, Typography, Select, MenuItem } from '@material-ui/core'; -import { makeStyles } from '@material-ui/core/styles'; +import { Box, MenuItem, Select, Typography } from '@material-ui/core'; +import { fade, makeStyles } from '@material-ui/core/styles'; import React from 'react'; import { FieldType } from '../../../../../components'; import { StateFolder, VocabularyOption } from '../../../../../store'; -const useStyles = makeStyles(({ palette, typography }) => ({ +const useStyles = makeStyles(({ palette, typography, breakpoints }) => ({ header: { display: 'flex', flex: 1, @@ -16,7 +16,7 @@ const useStyles = makeStyles(({ palette, typography }) => ({ flex: 1, alignItems: 'center', justifyContent: 'center', - color: palette.primary.contrastText + color: palette.primary.dark }, emptyFolders: { marginTop: 10, @@ -28,12 +28,18 @@ const useStyles = makeStyles(({ palette, typography }) => ({ }, select: { height: 30, - width: '100%', + minWidth: 200, + maxWidth: 200, padding: '0px 10px', background: palette.background.paper, - border: `1px solid ${palette.primary.contrastText}`, + border: `1px solid ${fade(palette.primary.contrastText, 0.4)}`, borderRadius: 5, - fontFamily: typography.fontFamily + fontFamily: typography.fontFamily, + [breakpoints.down('lg')]: { + fontSize: '0.8em', + minWidth: 180, + maxWidth: 180, + } }, })); @@ -65,7 +71,7 @@ function AssetContents(props: AssetContentsProps): React.ReactElement { return ( - + {name} diff --git a/client/src/pages/Ingestion/components/Metadata/Photogrammetry/Description.tsx b/client/src/pages/Ingestion/components/Metadata/Photogrammetry/Description.tsx index 7aab06756..5fae9d408 100644 --- a/client/src/pages/Ingestion/components/Metadata/Photogrammetry/Description.tsx +++ b/client/src/pages/Ingestion/components/Metadata/Photogrammetry/Description.tsx @@ -1,4 +1,4 @@ -import { makeStyles } from '@material-ui/core/styles'; +import { fade, makeStyles } from '@material-ui/core/styles'; import React from 'react'; import { DebounceInput } from 'react-debounce-input'; import { FieldType } from '../../../../../components'; @@ -10,7 +10,7 @@ const useStyles = makeStyles(({ palette, typography }) => ({ padding: 10, resize: 'none', overflow: 'scroll', - border: `1px solid ${palette.primary.contrastText}`, + border: `1px solid ${fade(palette.primary.contrastText, 0.4)}`, borderRadius: 5, fontFamily: typography.fontFamily } diff --git a/client/src/pages/Ingestion/components/Metadata/Photogrammetry/IdInputField.tsx b/client/src/pages/Ingestion/components/Metadata/Photogrammetry/IdInputField.tsx index 39581f06e..bcbe993ee 100644 --- a/client/src/pages/Ingestion/components/Metadata/Photogrammetry/IdInputField.tsx +++ b/client/src/pages/Ingestion/components/Metadata/Photogrammetry/IdInputField.tsx @@ -1,17 +1,22 @@ /* eslint-disable @typescript-eslint/ban-ts-comment */ -import { makeStyles } from '@material-ui/core/styles'; +import { fade, makeStyles } from '@material-ui/core/styles'; import React from 'react'; import { DebounceInput } from 'react-debounce-input'; import { FieldType } from '../../../../../components'; -const useStyles = makeStyles(({ palette, typography }) => ({ +const useStyles = makeStyles(({ palette, typography, breakpoints }) => ({ input: { width: '50%', outline: 'none', - border: `1px solid ${palette.primary.contrastText}`, + border: `1px solid ${fade(palette.primary.contrastText, 0.4)}`, padding: 8, borderRadius: 5, - fontFamily: typography.fontFamily + fontFamily: typography.fontFamily, + [breakpoints.down('lg')]: { + fontSize: '0.8em', + minWidth: 160, + maxWidth: 160, + } } })); diff --git a/client/src/pages/Ingestion/components/Metadata/Photogrammetry/IdentifierList.tsx b/client/src/pages/Ingestion/components/Metadata/Photogrammetry/IdentifierList.tsx index 2685b482b..093d6e086 100644 --- a/client/src/pages/Ingestion/components/Metadata/Photogrammetry/IdentifierList.tsx +++ b/client/src/pages/Ingestion/components/Metadata/Photogrammetry/IdentifierList.tsx @@ -1,12 +1,12 @@ import { Box, Button, Checkbox, MenuItem, Select } from '@material-ui/core'; -import { makeStyles } from '@material-ui/core/styles'; +import { fade, makeStyles } from '@material-ui/core/styles'; import React from 'react'; import { DebounceInput } from 'react-debounce-input'; import { MdRemoveCircleOutline } from 'react-icons/md'; import { FieldType } from '../../../../../components'; import { StateIdentifier, VocabularyOption } from '../../../../../store'; -const useStyles = makeStyles(({ palette, typography, spacing }) => ({ +const useStyles = makeStyles(({ palette, typography, spacing, breakpoints }) => ({ container: { marginTop: 20 }, @@ -27,7 +27,7 @@ const useStyles = makeStyles(({ palette, typography, spacing }) => ({ padding: '0px 2px', paddingBottom: 5, backgroundColor: 'transparent', - fontSize: typography.body1.fontSize, + fontSize: '0.9em', fontFamily: typography.fontFamily, borderBottom: `1px solid ${palette.grey[300]}`, '&:focus': { @@ -45,9 +45,12 @@ const useStyles = makeStyles(({ palette, typography, spacing }) => ({ padding: '0px 10px', marginLeft: 20, background: palette.background.paper, - border: `1px solid ${palette.primary.contrastText}`, + border: `1px solid ${fade(palette.primary.contrastText, 0.4)}`, borderRadius: 5, - fontFamily: typography.fontFamily + fontFamily: typography.fontFamily, + [breakpoints.down('lg')]: { + fontSize: '0.8em', + } }, identifierOption: { marginLeft: 20, @@ -55,7 +58,10 @@ const useStyles = makeStyles(({ palette, typography, spacing }) => ({ }, addIdentifier: { color: palette.background.paper, - width: 80 + width: 80, + [breakpoints.down('lg')]: { + fontSize: '0.8em', + } } })); diff --git a/client/src/pages/Ingestion/components/Metadata/Photogrammetry/SelectField.tsx b/client/src/pages/Ingestion/components/Metadata/Photogrammetry/SelectField.tsx index 9327cef48..8ce4cd63e 100644 --- a/client/src/pages/Ingestion/components/Metadata/Photogrammetry/SelectField.tsx +++ b/client/src/pages/Ingestion/components/Metadata/Photogrammetry/SelectField.tsx @@ -1,17 +1,22 @@ import { MenuItem, Select } from '@material-ui/core'; -import { makeStyles } from '@material-ui/core/styles'; +import { fade, makeStyles } from '@material-ui/core/styles'; import React from 'react'; import { FieldType } from '../../../../../components'; import { VocabularyOption } from '../../../../../store'; -const useStyles = makeStyles(({ palette, typography }) => ({ +const useStyles = makeStyles(({ palette, typography, breakpoints }) => ({ select: { width: '54%', padding: '0px 10px', background: palette.background.paper, - border: `1px solid ${palette.primary.contrastText}`, + border: `1px solid ${fade(palette.primary.contrastText, 0.4)}`, borderRadius: 5, - fontFamily: typography.fontFamily + fontFamily: typography.fontFamily, + [breakpoints.down('lg')]: { + fontSize: '0.8em', + minWidth: 180, + maxWidth: 180, + } }, })); diff --git a/client/src/pages/Ingestion/components/Metadata/Photogrammetry/index.tsx b/client/src/pages/Ingestion/components/Metadata/Photogrammetry/index.tsx index 8c2b59332..7dc03d150 100644 --- a/client/src/pages/Ingestion/components/Metadata/Photogrammetry/index.tsx +++ b/client/src/pages/Ingestion/components/Metadata/Photogrammetry/index.tsx @@ -1,6 +1,6 @@ /* eslint-disable react-hooks/exhaustive-deps */ import { Box, Checkbox, Typography } from '@material-ui/core'; -import { makeStyles, withStyles } from '@material-ui/core/styles'; +import { fade, makeStyles, withStyles } from '@material-ui/core/styles'; import React from 'react'; import { FieldType } from '../../../../../components'; import DateFnsUtils from '@date-io/date-fns'; @@ -15,7 +15,7 @@ import IdInputField from './IdInputField'; import lodash from 'lodash'; import AssetContents from './AssetContents'; -const useStyles = makeStyles(({ palette, typography, spacing }) => ({ +const useStyles = makeStyles(({ palette, typography, spacing, breakpoints }) => ({ container: { marginTop: 20 }, @@ -41,11 +41,18 @@ const useStyles = makeStyles(({ palette, typography, spacing }) => ({ date: { width: '50%', background: palette.background.paper, - border: `1px solid ${palette.primary.contrastText}`, + border: `1px solid ${fade(palette.primary.contrastText, 0.4)}`, padding: '1px 8px', color: Colors.defaults.white, borderRadius: 5, - fontFamily: typography.fontFamily + fontFamily: typography.fontFamily, + [breakpoints.down('lg')]: { + minWidth: 160, + maxWidth: 160, + '& > div > input': { + fontSize: '0.8em', + } + } } })); diff --git a/client/src/pages/Ingestion/components/Metadata/index.tsx b/client/src/pages/Ingestion/components/Metadata/index.tsx index 434a1e970..394e0806e 100644 --- a/client/src/pages/Ingestion/components/Metadata/index.tsx +++ b/client/src/pages/Ingestion/components/Metadata/index.tsx @@ -11,18 +11,25 @@ import { useItem, useMetadata, useProject, useVocabulary, FileId, StateItem, Sta import useIngest from '../../hooks/useIngest'; import Photogrammetry from './Photogrammetry'; -const useStyles = makeStyles(({ palette }) => ({ +const useStyles = makeStyles(({ palette, breakpoints }) => ({ container: { display: 'flex', flex: 1, - flexDirection: 'column' + flexDirection: 'column', + overflow: 'auto', + maxHeight: 'calc(100vh - 60px)' }, content: { display: 'flex', flex: 1, flexDirection: 'column', width: '50vw', - padding: '40px 0px 0px 40px' + padding: 40, + paddingBottom: 0, + [breakpoints.down('lg')]: { + padding: 20, + paddingBottom: 0, + } }, breadcrumbs: { marginBottom: 10, diff --git a/client/src/pages/Ingestion/components/SubjectItem/ItemList.tsx b/client/src/pages/Ingestion/components/SubjectItem/ItemList.tsx index 48f46ebe2..a6d61f049 100644 --- a/client/src/pages/Ingestion/components/SubjectItem/ItemList.tsx +++ b/client/src/pages/Ingestion/components/SubjectItem/ItemList.tsx @@ -1,12 +1,14 @@ -import React from 'react'; -import { TableContainer, Table, TableCell, TableHead, TableRow, TableBody, Checkbox } from '@material-ui/core'; +import { Table, TableBody, TableCell, TableContainer, TableHead, TableRow } from '@material-ui/core'; +import { grey } from '@material-ui/core/colors'; import { makeStyles } from '@material-ui/core/styles'; -import { useItem, StateItem, defaultItem } from '../../../../store'; -import { FaRegCircle, FaDotCircle } from 'react-icons/fa'; -import { grey, blue } from '@material-ui/core/colors'; +import React from 'react'; import { DebounceInput } from 'react-debounce-input'; +import { RiRecordCircleFill, RiCheckboxBlankCircleLine } from 'react-icons/ri'; +import { MdCheckBox, MdCheckBoxOutlineBlank } from 'react-icons/md'; +import { defaultItem, StateItem, useItem } from '../../../../store'; +import { palette } from '../../../../theme'; -const useStyles = makeStyles(({ palette, spacing, typography }) => ({ +const useStyles = makeStyles(({ palette, spacing, typography, breakpoints }) => ({ container: { maxHeight: '18vh', backgroundColor: palette.background.paper @@ -14,9 +16,13 @@ const useStyles = makeStyles(({ palette, spacing, typography }) => ({ headerText: { position: 'sticky', top: 0, + fontSize: '0.8em', backgroundColor: palette.background.paper, color: palette.primary.contrastText, - zIndex: 10 + zIndex: 10, + [breakpoints.down('lg')]: { + padding: '5px 16px', + } }, body: { overflow: 'auto' @@ -28,14 +34,15 @@ const useStyles = makeStyles(({ palette, spacing, typography }) => ({ marginTop: spacing(4) }, selected: { - cursor: 'pointer' + cursor: 'pointer', + marginTop: 4 }, nameInput: { width: '100%', border: 'none', outline: 'none', padding: '0px 2px', - fontSize: typography.body1.fontSize, + fontSize: '1em', fontFamily: typography.fontFamily, '&:focus': { outline: 'none', @@ -143,23 +150,21 @@ function ItemListItem(props: ItemListItemProps) { const cellStyle = { width: 100, - padding: '8px 16px' }; return ( - {!selected && onUpdateSelected(true)} size={24} color={grey[500]} />} - {selected && onUpdateSelected(false)} size={24} color={blue[500]} />} + {!selected && onUpdateSelected(true)} size={20} color={grey[400]} />} + {selected && onUpdateSelected(false)} size={20} color={palette.primary.main} />} {children} {isDefaultItem ? ( - onUpdateEntireSubject(target.checked)} - color='primary' - /> + <> + {!entireSubject && onUpdateEntireSubject(true)} size={20} color={grey[500]} />} + {entireSubject && onUpdateEntireSubject(false)} size={20} color={palette.primary.main} />} + ) : entireSubject ? 'Yes' : 'No'} diff --git a/client/src/pages/Ingestion/components/SubjectItem/ProjectList.tsx b/client/src/pages/Ingestion/components/SubjectItem/ProjectList.tsx index 928f19b91..530b7573d 100644 --- a/client/src/pages/Ingestion/components/SubjectItem/ProjectList.tsx +++ b/client/src/pages/Ingestion/components/SubjectItem/ProjectList.tsx @@ -8,7 +8,8 @@ const useStyles = makeStyles(({ palette }) => ({ projectSelect: { width: '100%', padding: '0px 10px', - backgroundColor: palette.background.paper + backgroundColor: palette.background.paper, + fontSize: '0.8em' } })); diff --git a/client/src/pages/Ingestion/components/SubjectItem/SearchList.tsx b/client/src/pages/Ingestion/components/SubjectItem/SearchList.tsx index 586a4ac5b..dc55d5437 100644 --- a/client/src/pages/Ingestion/components/SubjectItem/SearchList.tsx +++ b/client/src/pages/Ingestion/components/SubjectItem/SearchList.tsx @@ -11,7 +11,7 @@ import SubjectList from './SubjectList'; import { toast } from 'react-toastify'; import { actionOnKeyPress } from '../../../../utils/shared'; -const useStyles = makeStyles(({ palette }) => ({ +const useStyles = makeStyles(({ palette, breakpoints }) => ({ container: { display: 'flex', alignItems: 'center', @@ -25,7 +25,11 @@ const useStyles = makeStyles(({ palette }) => ({ searchButton: { height: 35, width: 60, - color: palette.background.paper + color: palette.background.paper, + [breakpoints.down('lg')]: { + height: 30 + } + } })); @@ -77,8 +81,6 @@ function SearchList(): React.ReactElement { /> ({ +const useStyles = makeStyles(({ palette, spacing, breakpoints }) => ({ container: { maxHeight: '20vh', backgroundColor: palette.background.paper @@ -12,8 +12,12 @@ const useStyles = makeStyles(({ palette, spacing }) => ({ headerText: { position: 'sticky', top: 0, + fontSize: '0.8em', backgroundColor: palette.background.paper, - color: palette.primary.contrastText + color: palette.primary.contrastText, + [breakpoints.down('lg')]: { + padding: '5px 16px', + } }, body: { overflow: 'auto' @@ -56,7 +60,7 @@ function SubjectList(props: SubjectListProps): React.ReactElement { - + {header.map((label, index) => {label})} diff --git a/client/src/pages/Ingestion/components/SubjectItem/SubjectListItem.tsx b/client/src/pages/Ingestion/components/SubjectItem/SubjectListItem.tsx index c1215daf2..f1dff97c4 100644 --- a/client/src/pages/Ingestion/components/SubjectItem/SubjectListItem.tsx +++ b/client/src/pages/Ingestion/components/SubjectItem/SubjectListItem.tsx @@ -5,14 +5,15 @@ import { MdAddCircleOutline, MdRemoveCircleOutline } from 'react-icons/md'; import { StateSubject } from '../../../../store'; const useStyles = makeStyles(() => ({ - name: { + label: { width: '100%', }, options: { marginLeft: 20 }, option: { - cursor: 'pointer' + cursor: 'pointer', + marginTop: 4 } })); @@ -49,13 +50,17 @@ function SubjectListItem(props: SubjectListItemProps): React.ReactElement { return ( - {arkId} - {unit} + + {arkId} + + + {unit} + - - {name} + + {name} - {selected ? : } + {selected ? : } diff --git a/client/src/pages/Ingestion/components/SubjectItem/index.tsx b/client/src/pages/Ingestion/components/SubjectItem/index.tsx index e81faf4f9..c5bae8410 100644 --- a/client/src/pages/Ingestion/components/SubjectItem/index.tsx +++ b/client/src/pages/Ingestion/components/SubjectItem/index.tsx @@ -6,24 +6,30 @@ import { toast } from 'react-toastify'; import { FieldType, SidebarBottomNavigator } from '../../../../components'; import { HOME_ROUTES, INGESTION_ROUTE, resolveSubRoute } from '../../../../constants'; import { useItem, useMetadata, useProject, useSubject, useVocabulary } from '../../../../store'; -import useIngest from '../../hooks/useIngest'; import ItemList from './ItemList'; import ProjectList from './ProjectList'; import SearchList from './SearchList'; import SubjectList from './SubjectList'; -const useStyles = makeStyles(({ palette }) => ({ +const useStyles = makeStyles(({ palette, breakpoints }) => ({ container: { display: 'flex', flex: 1, - flexDirection: 'column' + flexDirection: 'column', + overflow: 'auto', + maxHeight: 'calc(100vh - 60px)' }, content: { display: 'flex', flex: 1, width: '50vw', flexDirection: 'column', - padding: '40px 0px 0px 40px' + padding: 40, + paddingBottom: 0, + [breakpoints.down('lg')]: { + padding: 20, + paddingBottom: 0, + } }, filesLabel: { color: palette.primary.dark, @@ -51,7 +57,6 @@ function SubjectItem(): React.ReactElement { const [metadatas, updateMetadataFolders] = useMetadata(state => [state.metadatas, state.updateMetadataFolders]); const selectedItem = getSelectedItem(); - const { ingestionReset } = useIngest(); useEffect(() => { if (subjects.length > 0) { @@ -73,15 +78,6 @@ function SubjectItem(): React.ReactElement { } }, [selectedItem]); - const onPrevious = async () => { - const isConfirmed = global.confirm('Are you sure you want to go to navigate away? changes might be lost'); - if (isConfirmed) { - ingestionReset(); - const nextRoute = resolveSubRoute(HOME_ROUTES.INGESTION, INGESTION_ROUTE.ROUTES.UPLOADS); - history.push(nextRoute); - } - }; - const onNext = async (): Promise => { let error: boolean = false; @@ -173,8 +169,8 @@ function SubjectItem(): React.ReactElement { rightLoading={metadataStepLoading} leftLabel='Previous' rightLabel='Next' + leftRoute={resolveSubRoute(HOME_ROUTES.INGESTION, INGESTION_ROUTE.ROUTES.UPLOADS)} onClickRight={onNext} - onClickLeft={onPrevious} /> ); diff --git a/client/src/pages/Ingestion/components/Uploads/FileListItem.tsx b/client/src/pages/Ingestion/components/Uploads/FileListItem.tsx index b21f068b1..42349a5ac 100644 --- a/client/src/pages/Ingestion/components/Uploads/FileListItem.tsx +++ b/client/src/pages/Ingestion/components/Uploads/FileListItem.tsx @@ -1,17 +1,18 @@ +import { Box, MenuItem, Select, Typography } from '@material-ui/core'; +import { green, grey, red, yellow } from '@material-ui/core/colors'; +import { fade, makeStyles } from '@material-ui/core/styles'; +import { motion } from 'framer-motion'; import React from 'react'; -import { Box, Typography, Select, MenuItem } from '@material-ui/core'; -import { green, red, yellow, grey, blue } from '@material-ui/core/colors'; -import { makeStyles, fade } from '@material-ui/core/styles'; +import { FaCheckCircle, FaRedo, FaRegCircle } from 'react-icons/fa'; import { IoIosCloseCircle } from 'react-icons/io'; -import { FaRedo, FaRegCircle, FaCheckCircle } from 'react-icons/fa'; import { MdFileUpload } from 'react-icons/md'; +import { Progress } from '../../../../components'; +import { FileId, VocabularyOption } from '../../../../store'; +import { palette } from '../../../../theme'; import Colors from '../../../../theme/colors'; import { formatBytes } from '../../../../utils/upload'; -import { FileId, VocabularyOption } from '../../../../store'; -import { motion } from 'framer-motion'; -import { Progress } from '../../../../components'; -const useStyles = makeStyles(({ palette, typography }) => ({ +const useStyles = makeStyles(({ palette, typography, breakpoints }) => ({ container: { position: 'relative', display: 'flex', @@ -33,10 +34,10 @@ const useStyles = makeStyles(({ palette, typography }) => ({ details: { display: 'flex', flexDirection: 'column', - flex: 2, zIndex: 'inherit', - padding: '10px 0px', - marginLeft: 20 + paddingRight: 10, + marginLeft: 15, + flex: 2 }, name: { fontWeight: typography.fontWeightMedium, @@ -65,23 +66,33 @@ const useStyles = makeStyles(({ palette, typography }) => ({ }, type: { display: 'flex', - flex: 2, + padding: '0px 10px', + flex: 1, alignItems: 'center', justifyContent: 'center' }, typeSelect: { - width: '80%', + maxWidth: 250, + minWidth: 250, padding: '0px 10px', borderRadius: 5, fontSize: '0.8rem', - border: `1px solid ${fade(palette.primary.main, 0.3)}` + border: `1px solid ${fade(palette.primary.main, 0.3)}`, + [breakpoints.down('lg')]: { + maxWidth: 180, + minWidth: 180, + fontSize: '0.6rem', + } }, options: { display: 'flex', - width: '8vw', + flex: 1, alignItems: 'center', justifyContent: 'flex-end', zIndex: 'inherit', + [breakpoints.down('lg')]: { + flex: 1, + } }, option: { cursor: 'pointer', @@ -136,10 +147,10 @@ function FileListItem(props: FileListItemProps): React.ReactElement { if (!complete) { options = ( - {!uploading && !failed && } + {!uploading && !failed && } {uploading && !failed && } {failed && } - + ); } @@ -147,8 +158,8 @@ function FileListItem(props: FileListItemProps): React.ReactElement { if (complete) { options = ( - {!selected && } - {selected && } + {!selected && } + {selected && } ); } diff --git a/client/src/pages/Ingestion/components/Uploads/UploadCompleteList.tsx b/client/src/pages/Ingestion/components/Uploads/UploadCompleteList.tsx index 9242521de..192e8f54b 100644 --- a/client/src/pages/Ingestion/components/Uploads/UploadCompleteList.tsx +++ b/client/src/pages/Ingestion/components/Uploads/UploadCompleteList.tsx @@ -1,54 +1,16 @@ /* eslint-disable react-hooks/exhaustive-deps */ import { useQuery } from '@apollo/client'; import { Box, Typography } from '@material-ui/core'; -import { makeStyles } from '@material-ui/core/styles'; import React, { useEffect } from 'react'; import { FieldType } from '../../../../components'; import { parseAssetVersionToState, useUpload } from '../../../../store'; import { GetUploadedAssetVersionDocument } from '../../../../types/graphql'; import FileList from './FileList'; +import { useUploadListStyles } from './UploadList'; import UploadListHeader from './UploadListHeader'; -const useStyles = makeStyles(({ palette, spacing }) => ({ - container: { - display: 'flex', - flex: 1, - flexDirection: 'column', - marginTop: 20, - maxHeight: 'auto', - width: '50vw', - }, - list: { - display: 'flex', - flexDirection: 'column', - alignItems: 'center', - minHeight: 80, - maxHeight: '20vh', - 'overflow-y': 'auto', - 'overflow-x': 'hidden', - width: '100%', - '&::-webkit-scrollbar': { - '-webkit-appearance': 'none' - }, - '&::-webkit-scrollbar:vertical': { - width: 12 - }, - '&::-webkit-scrollbar-thumb': { - borderRadius: 8, - border: '2px solid white', - backgroundColor: palette.text.disabled - } - }, - listDetail: { - textAlign: 'center', - color: palette.grey[500], - fontStyle: 'italic', - marginTop: spacing(4) - }, -})); - function UploadListComplete(): React.ReactElement { - const classes = useStyles(); + const classes = useUploadListStyles(); const { completed, loadCompleted } = useUpload(); const { data, loading, error } = useQuery(GetUploadedAssetVersionDocument); diff --git a/client/src/pages/Ingestion/components/Uploads/UploadFilesPicker.tsx b/client/src/pages/Ingestion/components/Uploads/UploadFilesPicker.tsx index 9e397ff91..cf1e89ac1 100644 --- a/client/src/pages/Ingestion/components/Uploads/UploadFilesPicker.tsx +++ b/client/src/pages/Ingestion/components/Uploads/UploadFilesPicker.tsx @@ -13,9 +13,10 @@ const useStyles = makeStyles(({ palette, typography, spacing }) => ({ alignItems: 'center', justifyContent: 'center', height: '20vh', - width: '51vw', + width: '50vw', border: `1px dashed ${palette.primary.main}`, borderRadius: 10, + padding: 10, backgroundColor: palette.primary.light }, icon: { diff --git a/client/src/pages/Ingestion/components/Uploads/UploadList.tsx b/client/src/pages/Ingestion/components/Uploads/UploadList.tsx index 0c575c164..d15afb0b5 100644 --- a/client/src/pages/Ingestion/components/Uploads/UploadList.tsx +++ b/client/src/pages/Ingestion/components/Uploads/UploadList.tsx @@ -6,7 +6,7 @@ import { useUpload } from '../../../../store'; import FileList from './FileList'; import UploadListHeader from './UploadListHeader'; -const useStyles = makeStyles(({ palette, spacing }) => ({ +export const useUploadListStyles = makeStyles(({ palette, breakpoints }) => ({ container: { display: 'flex', flex: 1, @@ -19,8 +19,8 @@ const useStyles = makeStyles(({ palette, spacing }) => ({ display: 'flex', flexDirection: 'column', alignItems: 'center', - minHeight: 80, - maxHeight: '20vh', + minHeight: '16vh', + maxHeight: '16vh', 'overflow-y': 'auto', 'overflow-x': 'hidden', width: '100%', @@ -34,23 +34,30 @@ const useStyles = makeStyles(({ palette, spacing }) => ({ borderRadius: 8, border: '2px solid white', backgroundColor: palette.text.disabled + }, + [breakpoints.down('lg')]: { + minHeight: '20vh', + maxHeight: '20vh', } }, listDetail: { textAlign: 'center', color: palette.grey[500], fontStyle: 'italic', - marginTop: spacing(4) + marginTop: '8%', + [breakpoints.down('lg')]: { + marginTop: '10%', + } }, })); function UploadList(): React.ReactElement { - const classes = useStyles(); + const classes = useUploadListStyles(); const { pending } = useUpload(); return ( - + {!pending.length && Add files to upload} diff --git a/client/src/pages/Ingestion/components/Uploads/UploadListHeader.tsx b/client/src/pages/Ingestion/components/Uploads/UploadListHeader.tsx index a355b202b..26bafc78c 100644 --- a/client/src/pages/Ingestion/components/Uploads/UploadListHeader.tsx +++ b/client/src/pages/Ingestion/components/Uploads/UploadListHeader.tsx @@ -2,7 +2,7 @@ import { Box, Typography } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; import React from 'react'; -const useStyles = makeStyles(({ palette, typography }) => ({ +const useStyles = makeStyles(({ palette, typography, breakpoints }) => ({ header: { display: 'flex', alignItems: 'center', @@ -10,6 +10,9 @@ const useStyles = makeStyles(({ palette, typography }) => ({ width: '100%', borderRadius: 5, background: palette.background.paper, + [breakpoints.down('lg')]: { + height: 35, + } }, fileDetails: { display: 'flex', @@ -43,13 +46,13 @@ function UploadListHeader(): React.ReactElement { return ( - Filename + Filename - Size + Size - Asset Type + Asset Type ); diff --git a/client/src/pages/Ingestion/components/Uploads/index.tsx b/client/src/pages/Ingestion/components/Uploads/index.tsx index 74c040bdb..55c6d4e13 100644 --- a/client/src/pages/Ingestion/components/Uploads/index.tsx +++ b/client/src/pages/Ingestion/components/Uploads/index.tsx @@ -13,16 +13,24 @@ import { useHistory } from 'react-router'; import { toast } from 'react-toastify'; import { useVocabulary, useUpload, useMetadata } from '../../../../store'; -const useStyles = makeStyles(({ palette, typography, spacing }) => ({ +const useStyles = makeStyles(({ palette, typography, spacing, breakpoints }) => ({ container: { display: 'flex', - flexDirection: 'column' + flex: 1, + flexDirection: 'column', + overflow: 'auto', + maxHeight: 'calc(100vh - 60px)' }, content: { display: 'flex', flex: 1, flexDirection: 'column', - padding: '40px 0px 0px 40px' + padding: 40, + paddingBottom: 0, + [breakpoints.down('lg')]: { + padding: 20, + paddingBottom: 0, + } }, fileDrop: { display: 'flex', @@ -111,30 +119,30 @@ function Uploads(): React.ReactElement { if (!loadingVocabulary) { content = ( - - - - - + + + + + + + ); } return ( - - - - {content} - - + + + {content} - + + ); } diff --git a/client/src/pages/Login/index.tsx b/client/src/pages/Login/index.tsx index 8a85233f2..898a16aa5 100644 --- a/client/src/pages/Login/index.tsx +++ b/client/src/pages/Login/index.tsx @@ -168,8 +168,6 @@ function Login(): React.ReactElement { type='submit' className={classes.loginButton} disableElevation - variant='contained' - color='primary' onKeyDown={({ key }) => actionOnKeyPress(key, 'Enter', submitForm)} loading={isSubmitting} > diff --git a/client/src/pages/Repository/components/RepositoryTreeView/index.tsx b/client/src/pages/Repository/components/RepositoryTreeView/index.tsx index 1afbc30b3..7a04b1994 100644 --- a/client/src/pages/Repository/components/RepositoryTreeView/index.tsx +++ b/client/src/pages/Repository/components/RepositoryTreeView/index.tsx @@ -20,8 +20,8 @@ const useStyles = makeStyles(({ palette, breakpoints }) => ({ flexDirection: 'column', overflow: 'auto', [breakpoints.down('lg')]: { - maxHeight: '71vh', - maxWidth: '80.5vw' + maxHeight: '70vh', + maxWidth: '81.5vw' } }, tree: { diff --git a/client/src/store/upload.ts b/client/src/store/upload.ts index a34f187fd..7f44c6a17 100644 --- a/client/src/store/upload.ts +++ b/client/src/store/upload.ts @@ -298,7 +298,14 @@ export const useUpload = create((set: SetState, get: G set({ completed: updatedCompleted }); }, reset: (): void => { - set({ completed: [], pending: [], loading: true }); + const { completed } = get(); + const unselectFiles = (file: IngestionFile): IngestionFile => ({ + ...file, + selected: false + }); + + const updatedCompleted: IngestionFile[] = completed.map(unselectFiles); + set({ completed: updatedCompleted, loading: false }); } })); diff --git a/client/src/theme/index.ts b/client/src/theme/index.ts index d6142e1e5..85c1a5eac 100644 --- a/client/src/theme/index.ts +++ b/client/src/theme/index.ts @@ -9,7 +9,7 @@ export const palette = { light: '#ECF5FD', main: '#0079C4', dark: '#2C405A', - contrastText: '#8DABC4' + contrastText: '#637889' }, secondary: { light: '#FFFCD1', diff --git a/client/src/theme/typography.ts b/client/src/theme/typography.ts index 86dfb3710..01f6a7e96 100644 --- a/client/src/theme/typography.ts +++ b/client/src/theme/typography.ts @@ -1,3 +1,4 @@ +import { grey } from '@material-ui/core/colors'; import { Breakpoints } from '@material-ui/core/styles/createBreakpoints'; import { Overrides } from '@material-ui/core/styles/overrides'; @@ -8,40 +9,46 @@ function pxToRem(value: number): string { // https://material-ui.com/customization/breakpoints/ function createTypographyOverrides(breakpoints: Breakpoints): Overrides { return { + MuiTableCell: { + root: { + padding: '6px 10px', + borderBottom: `0.5px solid ${grey[100]}` + } + }, MuiTypography: { h4: { fontSize: pxToRem(36), - [breakpoints.down('xs')]: { + [breakpoints.down('lg')]: { fontSize: pxToRem(28) } }, subtitle1: { fontSize: pxToRem(22), - [breakpoints.down('xs')]: { + [breakpoints.down('lg')]: { fontSize: pxToRem(18) } }, subtitle2: { fontSize: pxToRem(22), - [breakpoints.down('xs')]: { + [breakpoints.down('lg')]: { fontSize: pxToRem(20) } }, body1: { fontSize: pxToRem(16), - [breakpoints.down('xs')]: { + [breakpoints.down('lg')]: { fontSize: pxToRem(14) } }, body2: { fontSize: pxToRem(18), - [breakpoints.down('xs')]: { + [breakpoints.down('lg')]: { fontSize: pxToRem(16) } }, caption: { fontSize: pxToRem(14), - [breakpoints.down('xs')]: { + [breakpoints.down('lg')]: { fontSize: pxToRem(12) } } From 19e5f3b985f84c6b97243649c72c3b706b6bd9b6 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Thu, 8 Oct 2020 16:02:12 +0530 Subject: [PATCH 002/239] added header searchbox --- client/src/components/shared/Header.tsx | 86 +++++++++++++++++++++---- 1 file changed, 75 insertions(+), 11 deletions(-) diff --git a/client/src/components/shared/Header.tsx b/client/src/components/shared/Header.tsx index 174ce22cf..4728ac976 100644 --- a/client/src/components/shared/Header.tsx +++ b/client/src/components/shared/Header.tsx @@ -1,15 +1,16 @@ import { Box, Typography } from '@material-ui/core'; -import { makeStyles } from '@material-ui/core/styles'; +import { fade, makeStyles } from '@material-ui/core/styles'; import React from 'react'; +import { DebounceInput } from 'react-debounce-input'; import { IoIosLogOut, IoIosNotifications, IoIosSearch } from 'react-icons/io'; -import { Link, useHistory } from 'react-router-dom'; +import { Link, useHistory, useLocation } from 'react-router-dom'; import { toast } from 'react-toastify'; import Logo from '../../assets/images/logo-packrat.square.png'; import { HOME_ROUTES, resolveRoute, ROUTES } from '../../constants'; -import { useUser } from '../../store'; +import { useRepositoryFilter, useUser } from '../../store'; import { Colors } from '../../theme'; -const useStyles = makeStyles(({ palette, spacing }) => ({ +const useStyles = makeStyles(({ palette, spacing, typography, breakpoints }) => ({ container: { display: 'flex', height: 60, @@ -23,6 +24,41 @@ const useStyles = makeStyles(({ palette, spacing }) => ({ cursor: 'pointer', paddingRight: spacing(2), }, + searchBox: { + display: 'flex', + alignItems: 'center', + marginLeft: 50, + padding: 5, + width: '40vw', + minWidth: '30vw', + borderRadius: 5, + border: `0.25px solid ${fade(Colors.defaults.white, 0.65)}`, + [breakpoints.down('lg')]: { + marginLeft: 30, + }, + }, + search: { + height: 25, + width: '100%', + fontSize: 18, + marginLeft: 5, + outline: 'none', + border: 'none', + color: fade(Colors.defaults.white, 0.65), + background: 'transparent', + fontFamily: typography.fontFamily, + [breakpoints.down('lg')]: { + height: 20, + fontSize: 14, + }, + '&::placeholder': { + color: fade(Colors.defaults.white, 0.65), + fontStyle: 'italic' + }, + '&::-moz-placeholder': { + fontStyle: 'italic' + } + }, navOptionsContainer: { display: 'flex', flex: 1, @@ -41,7 +77,14 @@ const useStyles = makeStyles(({ palette, spacing }) => ({ function Header(): React.ReactElement { const classes = useStyles(); const history = useHistory(); + const { pathname } = useLocation(); const { user, logout } = useUser(); + const [search, updateSearch] = useRepositoryFilter(state => [state.search, state.updateSearch]); + + const onSearch = (): void => { + const route: string = resolveRoute(HOME_ROUTES.REPOSITORY); + history.push(route); + }; const onLogout = async (): Promise => { const isConfirmed = global.confirm('Are you sure you want to logout?'); @@ -58,16 +101,37 @@ function Header(): React.ReactElement { } }; + const isRepository = pathname === resolveRoute(HOME_ROUTES.REPOSITORY); + return ( - - packrat - - {user?.Name} + + + packrat + + {user?.Name} + + {isRepository && ( + + + updateSearch(target.value)} + forceNotifyByEnter + debounceTimeout={400} + placeholder='Search...' + /> + + )} - - - + {!isRepository && ( + + + + )} From f72d5d7feb98e3ea78bb7ca1e91c2d4479faa40f Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Thu, 8 Oct 2020 16:03:19 +0530 Subject: [PATCH 003/239] added repository store --- client/src/store/index.ts | 1 + client/src/store/repository.ts | 13 +++++++++++++ client/src/theme/index.ts | 2 +- 3 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 client/src/store/repository.ts diff --git a/client/src/store/index.ts b/client/src/store/index.ts index 31ff43e9b..9800b379c 100644 --- a/client/src/store/index.ts +++ b/client/src/store/index.ts @@ -5,4 +5,5 @@ export * from './subject'; export * from './project'; export * from './item'; export * from './metadata'; +export * from './repository'; export * from './utils'; diff --git a/client/src/store/repository.ts b/client/src/store/repository.ts new file mode 100644 index 000000000..3492de0b5 --- /dev/null +++ b/client/src/store/repository.ts @@ -0,0 +1,13 @@ +import create, { SetState } from 'zustand'; + +type RepositoryStore = { + search: string; + updateSearch: (value: string) => void; +}; + +export const useRepositoryFilter = create((set: SetState) => ({ + search: '', + updateSearch: (value: string): void => { + set({ search: value }); + } +})); diff --git a/client/src/theme/index.ts b/client/src/theme/index.ts index 85c1a5eac..d6142e1e5 100644 --- a/client/src/theme/index.ts +++ b/client/src/theme/index.ts @@ -9,7 +9,7 @@ export const palette = { light: '#ECF5FD', main: '#0079C4', dark: '#2C405A', - contrastText: '#637889' + contrastText: '#8DABC4' }, secondary: { light: '#FFFCD1', From 77c3991b10c64509783a831fed8f95941037925e Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Thu, 8 Oct 2020 16:04:25 +0530 Subject: [PATCH 004/239] added collapsable filter view --- .../components/RepositoryFilterView/index.tsx | 176 ++++++++---------- .../RepositoryTreeHeader.tsx | 4 + .../components/RepositoryTreeView/index.tsx | 13 +- client/src/pages/Repository/index.tsx | 10 +- 4 files changed, 97 insertions(+), 106 deletions(-) diff --git a/client/src/pages/Repository/components/RepositoryFilterView/index.tsx b/client/src/pages/Repository/components/RepositoryFilterView/index.tsx index d5ae6c197..c32902977 100644 --- a/client/src/pages/Repository/components/RepositoryFilterView/index.tsx +++ b/client/src/pages/Repository/components/RepositoryFilterView/index.tsx @@ -1,136 +1,112 @@ -import React from 'react'; import { Box, Typography } from '@material-ui/core'; -import { makeStyles } from '@material-ui/core/styles'; +import { fade, makeStyles } from '@material-ui/core/styles'; +import React from 'react'; import { RepositoryFilter } from '../../index'; -import { DebounceInput } from 'react-debounce-input'; -import { motion } from 'framer-motion'; -import { eSystemObjectType } from '../../../../types/server'; -import { RepositoryIcon } from '../../../../components'; -import { Colors, palette } from '../../../../theme'; +import { FaChevronUp, FaChevronDown } from 'react-icons/fa'; +import { FiLink2 } from 'react-icons/fi'; +import { palette } from '../../../../theme'; +import { toast } from 'react-toastify'; const useStyles = makeStyles(({ palette, typography, breakpoints }) => ({ container: { display: 'flex', - flexDirection: 'column', + height: ({ isExpanded }: RepositoryFilterViewProps) => isExpanded ? 150 : 30, background: palette.primary.light, - borderRadius: 10, + borderRadius: 5, padding: 20, marginBottom: 10, + transition: '250ms height ease', [breakpoints.down('lg')]: { padding: 10, - borderRadius: 5 } }, - search: { - height: 30, - width: '100%', - padding: '10px 0px', - fontSize: 18, - outline: 'none', - border: 'none', - background: 'transparent', - borderBottom: `1px solid ${palette.primary.main}`, - fontFamily: typography.fontFamily, - [breakpoints.down('lg')]: { - height: 20, - fontSize: 14, - padding: '5px 0px' - }, - '&::placeholder': { - fontStyle: 'italic' - }, - '&::-moz-placeholder': { - fontStyle: 'italic' - } + content: { + display: 'flex', + flex: 1, + alignItems: 'center', + }, + defaultFilter: { + color: palette.primary.dark, + fontWeight: typography.fontWeightRegular }, - filter: { + anchor: { display: 'flex', alignItems: 'center', - minWidth: 125, - width: 125, - padding: '8px 10px', - borderRadius: 5, - cursor: 'pointer', - color: palette.primary.contrastText, - background: palette.background.paper, + justifyContent: 'flex-end', + height: 30, + padding: '0px 10px', + borderRadius: 10, + marginLeft: 5, + transition: 'all 250ms linear', [breakpoints.down('lg')]: { - minWidth: 100, - width: 100 + padding: '0px 5px', + }, + '&:hover': { + cursor: 'pointer', + backgroundColor: fade(palette.primary.light, 0.2) }, - '&:not(:first-child)': { - marginLeft: 10 - } - }, - filterSelected: { - color: palette.background.paper, - background: palette.primary.main }, - filterText: { - marginLeft: 10, - [breakpoints.down('lg')]: { - fontSize: 10 - } + options: { + display: 'flex', + alignItems: 'flex-end', + justifyContent: 'center' } })); interface RepositoryFilterViewProps { filter: RepositoryFilter; onChange: (field: string, value: string | boolean) => void; + isExpanded: boolean; + onToggle: () => void; } function RepositoryFilterView(props: RepositoryFilterViewProps): React.ReactElement { - const { filter, onChange } = props; - const classes = useStyles(); + const { isExpanded, onToggle } = props; + const classes = useStyles(props); - const CheckboxFilters = [ - { - value: filter.units, - name: 'units', - type: eSystemObjectType.eUnit - }, - { - value: filter.projects, - name: 'projects', - type: eSystemObjectType.eProject + const onCopyLink = (): void => { + if ('clipboard' in navigator) { + navigator.clipboard.writeText(''); + toast.success('Link has been copied to your clipboard'); } - ]; + }; - return ( - - null} - forceNotifyByEnter - debounceTimeout={400} - placeholder='Search...' - /> - - {CheckboxFilters.map(({ value, name, type }, index: number) => { - const selected = value; - const textColor = selected ? palette.primary.main : Colors.defaults.white; - const backgroundColor = selected ? Colors.defaults.white : palette.primary.contrastText; + let content: React.ReactNode = ( + + Filter + + ); - const iconProps = { objectType: type, textColor, backgroundColor }; + if (isExpanded) { + content = ( + + + + ); + } - return ( - onChange(name, !value)} - whileTap={{ scale: 0.95 }} - > - - - {name.toUpperCase()} - - - ); - })} + return ( + + + {content} + + + + + {isExpanded ? + : + } + ); diff --git a/client/src/pages/Repository/components/RepositoryTreeView/RepositoryTreeHeader.tsx b/client/src/pages/Repository/components/RepositoryTreeView/RepositoryTreeHeader.tsx index d008b9be3..b08ae64e9 100644 --- a/client/src/pages/Repository/components/RepositoryTreeView/RepositoryTreeHeader.tsx +++ b/client/src/pages/Repository/components/RepositoryTreeView/RepositoryTreeHeader.tsx @@ -37,6 +37,10 @@ const useStyles = makeStyles(({ palette, typography, breakpoints }) => ({ left: 20, width: '60%', backgroundColor: palette.primary.light, + [breakpoints.down('lg')]: { + paddingLeft: 10, + left: 10, + } } })); interface RepositoryTreeHeaderProps { diff --git a/client/src/pages/Repository/components/RepositoryTreeView/index.tsx b/client/src/pages/Repository/components/RepositoryTreeView/index.tsx index 7a04b1994..5d036a0d3 100644 --- a/client/src/pages/Repository/components/RepositoryTreeView/index.tsx +++ b/client/src/pages/Repository/components/RepositoryTreeView/index.tsx @@ -15,12 +15,13 @@ const useStyles = makeStyles(({ palette, breakpoints }) => ({ container: { display: 'flex', flex: 5, - maxHeight: '72vh', + maxHeight: ({ isExpanded }: RepositoryTreeViewProps) => isExpanded ? '70vh' : '80vh', maxWidth: '83.5vw', flexDirection: 'column', overflow: 'auto', + transition: '250ms height ease', [breakpoints.down('lg')]: { - maxHeight: '70vh', + maxHeight: ({ isExpanded }: RepositoryTreeViewProps) => isExpanded ? '60vh' : '77vh', maxWidth: '81.5vw' } }, @@ -35,17 +36,21 @@ const useStyles = makeStyles(({ palette, breakpoints }) => ({ maxWidth: '83.5vw', alignItems: 'center', justifyContent: 'center', - color: palette.primary.dark + color: palette.primary.dark, + [breakpoints.down('lg')]: { + maxHeight: ({ isExpanded }: RepositoryTreeViewProps) => isExpanded ? '60vh' : '77vh', + } } })); interface RepositoryTreeViewProps { filter: RepositoryFilter; + isExpanded: boolean; } function RepositoryTreeView(props: RepositoryTreeViewProps): React.ReactElement { const { filter } = props; - const classes = useStyles(); + const classes = useStyles(props); const [expandedNodes, setExpandedNodes] = useState(new Map() as ExpandedNodeMap); const objectTypes = getSystemObjectTypesForFilter(filter); diff --git a/client/src/pages/Repository/index.tsx b/client/src/pages/Repository/index.tsx index b5cff4d9f..6eb696171 100644 --- a/client/src/pages/Repository/index.tsx +++ b/client/src/pages/Repository/index.tsx @@ -13,6 +13,7 @@ const useStyles = makeStyles(({ breakpoints }) => ({ maxWidth: '100vw', flexDirection: 'column', padding: 40, + paddingBottom: 0, [breakpoints.down('lg')]: { padding: 20, } @@ -38,6 +39,7 @@ function Repository(): React.ReactElement { const defaultFilterState = Object.keys(queries).length ? queries : initialFilterState; + const [isExpanded, setIsExpanded] = useState(true); const [filter, setFilter] = useState(defaultFilterState); useEffect(() => { @@ -45,6 +47,10 @@ function Repository(): React.ReactElement { history.push(route); }, [filter, history]); + const onToggle = (): void => { + setIsExpanded(isExpanded => !isExpanded); + }; + const onChange = (name: string, value: string | boolean) => { setFilter(filter => ({ ...filter, @@ -56,8 +62,8 @@ function Repository(): React.ReactElement { return ( - - + + ); } From ed755de6ffcaf8130d7f195c96f529f27e2a9e32 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Thu, 8 Oct 2020 16:33:24 +0530 Subject: [PATCH 005/239] minor fixes --- .../RepositoryTreeView/RepositoryTreeHeader.tsx | 12 +++++++----- .../RepositoryTreeView/RepositoryTreeNode.tsx | 4 ++-- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/client/src/pages/Repository/components/RepositoryTreeView/RepositoryTreeHeader.tsx b/client/src/pages/Repository/components/RepositoryTreeView/RepositoryTreeHeader.tsx index b08ae64e9..6ef19365b 100644 --- a/client/src/pages/Repository/components/RepositoryTreeView/RepositoryTreeHeader.tsx +++ b/client/src/pages/Repository/components/RepositoryTreeView/RepositoryTreeHeader.tsx @@ -1,9 +1,9 @@ -import React from 'react'; import { Box } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; -import { MetadataView } from './StyledTreeItem'; -import { getTreeViewColumns, getTreeWidth } from '../../../../utils/repository'; +import React from 'react'; import { eMetadata } from '../../../../types/server'; +import { getTreeViewColumns, getTreeWidth } from '../../../../utils/repository'; +import { MetadataView } from './StyledTreeItem'; const useStyles = makeStyles(({ palette, typography, breakpoints }) => ({ container: { @@ -33,8 +33,9 @@ const useStyles = makeStyles(({ palette, typography, breakpoints }) => ({ } }, treeViewText: { - paddingLeft: 20, left: 20, + height: 20, + paddingLeft: 20, width: '60%', backgroundColor: palette.primary.light, [breakpoints.down('lg')]: { @@ -43,6 +44,7 @@ const useStyles = makeStyles(({ palette, typography, breakpoints }) => ({ } } })); + interface RepositoryTreeHeaderProps { metadataColumns: eMetadata[]; } @@ -57,7 +59,7 @@ function RepositoryTreeHeader(props: RepositoryTreeHeaderProps): React.ReactElem return ( - Tree view + diff --git a/client/src/pages/Repository/components/RepositoryTreeView/RepositoryTreeNode.tsx b/client/src/pages/Repository/components/RepositoryTreeView/RepositoryTreeNode.tsx index fed653e9a..b07ae7ac4 100644 --- a/client/src/pages/Repository/components/RepositoryTreeView/RepositoryTreeNode.tsx +++ b/client/src/pages/Repository/components/RepositoryTreeView/RepositoryTreeNode.tsx @@ -45,10 +45,10 @@ function RepositoryTreeNode(props: RepositoryTreeNodeProps): React.ReactElement const loadData = expandedNodes.has(nodeId); React.useEffect(() => { - if (loadData) { + if (loadData && !getObjectChildrenLoading) { getObjectChildren(); } - }, [loadData, getObjectChildren]); + }, [loadData, getObjectChildrenLoading, getObjectChildren]); return ( Date: Fri, 9 Oct 2020 14:21:48 +0530 Subject: [PATCH 006/239] move expanded state to store --- .../components/RepositoryTreeView/index.tsx | 11 ++++++----- client/src/pages/Repository/index.tsx | 15 +++++---------- client/src/store/repository.ts | 11 +++++++++-- 3 files changed, 20 insertions(+), 17 deletions(-) diff --git a/client/src/pages/Repository/components/RepositoryTreeView/index.tsx b/client/src/pages/Repository/components/RepositoryTreeView/index.tsx index 5d036a0d3..1673ae3fc 100644 --- a/client/src/pages/Repository/components/RepositoryTreeView/index.tsx +++ b/client/src/pages/Repository/components/RepositoryTreeView/index.tsx @@ -5,6 +5,7 @@ import lodash from 'lodash'; import React, { useState } from 'react'; import { BsChevronDown, BsChevronRight } from 'react-icons/bs'; import { Loader } from '../../../../components'; +import { useRepositoryFilter } from '../../../../store'; import { getSortedTreeEntries, getSystemObjectTypesForFilter, getTreeWidth } from '../../../../utils/repository'; import { useGetRootObjects } from '../../hooks/useRepository'; import { RepositoryFilter } from '../../index'; @@ -15,13 +16,13 @@ const useStyles = makeStyles(({ palette, breakpoints }) => ({ container: { display: 'flex', flex: 5, - maxHeight: ({ isExpanded }: RepositoryTreeViewProps) => isExpanded ? '70vh' : '80vh', + maxHeight: (isExpanded: boolean) => isExpanded ? '70vh' : '80vh', maxWidth: '83.5vw', flexDirection: 'column', overflow: 'auto', transition: '250ms height ease', [breakpoints.down('lg')]: { - maxHeight: ({ isExpanded }: RepositoryTreeViewProps) => isExpanded ? '60vh' : '77vh', + maxHeight: (isExpanded: boolean) => isExpanded ? '61vh' : '79vh', maxWidth: '81.5vw' } }, @@ -38,19 +39,19 @@ const useStyles = makeStyles(({ palette, breakpoints }) => ({ justifyContent: 'center', color: palette.primary.dark, [breakpoints.down('lg')]: { - maxHeight: ({ isExpanded }: RepositoryTreeViewProps) => isExpanded ? '60vh' : '77vh', + maxHeight: (isExpanded: boolean) => isExpanded ? '60vh' : '77vh', } } })); interface RepositoryTreeViewProps { filter: RepositoryFilter; - isExpanded: boolean; } function RepositoryTreeView(props: RepositoryTreeViewProps): React.ReactElement { const { filter } = props; - const classes = useStyles(props); + const { isExpanded } = useRepositoryFilter(); + const classes = useStyles(isExpanded); const [expandedNodes, setExpandedNodes] = useState(new Map() as ExpandedNodeMap); const objectTypes = getSystemObjectTypesForFilter(filter); diff --git a/client/src/pages/Repository/index.tsx b/client/src/pages/Repository/index.tsx index 6eb696171..b03a9f196 100644 --- a/client/src/pages/Repository/index.tsx +++ b/client/src/pages/Repository/index.tsx @@ -1,10 +1,10 @@ -import React, { useEffect, useState } from 'react'; import { Box } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; -import RepositoryFilterView from './components/RepositoryFilterView'; -import RepositoryTreeView from './components/RepositoryTreeView'; +import React, { useEffect, useState } from 'react'; import { useHistory, useLocation } from 'react-router'; import { generateRepositoryUrl, parseRepositoryUrl } from '../../utils/repository'; +import RepositoryFilterView from './components/RepositoryFilterView'; +import RepositoryTreeView from './components/RepositoryTreeView'; const useStyles = makeStyles(({ breakpoints }) => ({ container: { @@ -39,7 +39,6 @@ function Repository(): React.ReactElement { const defaultFilterState = Object.keys(queries).length ? queries : initialFilterState; - const [isExpanded, setIsExpanded] = useState(true); const [filter, setFilter] = useState(defaultFilterState); useEffect(() => { @@ -47,10 +46,6 @@ function Repository(): React.ReactElement { history.push(route); }, [filter, history]); - const onToggle = (): void => { - setIsExpanded(isExpanded => !isExpanded); - }; - const onChange = (name: string, value: string | boolean) => { setFilter(filter => ({ ...filter, @@ -62,8 +57,8 @@ function Repository(): React.ReactElement { return ( - - + + ); } diff --git a/client/src/store/repository.ts b/client/src/store/repository.ts index 3492de0b5..665a22702 100644 --- a/client/src/store/repository.ts +++ b/client/src/store/repository.ts @@ -1,13 +1,20 @@ -import create, { SetState } from 'zustand'; +import create, { GetState, SetState } from 'zustand'; type RepositoryStore = { + isExpanded: boolean; search: string; updateSearch: (value: string) => void; + toggleFilter: () => void; }; -export const useRepositoryFilter = create((set: SetState) => ({ +export const useRepositoryFilter = create((set: SetState, get: GetState) => ({ + isExpanded: true, search: '', updateSearch: (value: string): void => { set({ search: value }); + }, + toggleFilter: (): void => { + const { isExpanded } = get(); + set({ isExpanded: !isExpanded }); } })); From 77316d8db039c4d0407d99a6d69fe75963c3ae8e Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Fri, 9 Oct 2020 20:09:03 +0530 Subject: [PATCH 007/239] scope out repository filter view components --- .../RepositoryFilterView/FilterDate.tsx | 99 ++++++++++++ .../RepositoryFilterView/FilterSelect.tsx | 65 ++++++++ .../components/RepositoryFilterView/index.tsx | 141 ++++++++++++++---- .../components/RepositoryTreeView/index.tsx | 4 +- client/src/pages/Repository/index.tsx | 14 +- 5 files changed, 282 insertions(+), 41 deletions(-) create mode 100644 client/src/pages/Repository/components/RepositoryFilterView/FilterDate.tsx create mode 100644 client/src/pages/Repository/components/RepositoryFilterView/FilterSelect.tsx diff --git a/client/src/pages/Repository/components/RepositoryFilterView/FilterDate.tsx b/client/src/pages/Repository/components/RepositoryFilterView/FilterDate.tsx new file mode 100644 index 000000000..d3acb5303 --- /dev/null +++ b/client/src/pages/Repository/components/RepositoryFilterView/FilterDate.tsx @@ -0,0 +1,99 @@ +import DateFnsUtils from '@date-io/date-fns'; +import { Box, Typography } from '@material-ui/core'; +import { makeStyles } from '@material-ui/core/styles'; +import { KeyboardDatePicker, MuiPickersUtilsProvider } from '@material-ui/pickers'; +import React from 'react'; +import { AiOutlineCalendar } from 'react-icons/ai'; +import { palette } from '../../../../theme'; + +const useStyles = makeStyles(({ palette, breakpoints }) => ({ + label: { + fontSize: '0.8em', + color: palette.primary.dark, + }, + toText: { + fontSize: '0.8em', + color: palette.primary.dark, + margin: '0px 10px' + }, + date: { + width: 150, + paddingLeft: 5, + marginTop: 0, + marginBottom: 0, + borderRadius: 5, + border: `0.5px solid ${palette.primary.contrastText}`, + [breakpoints.down('lg')]: { + height: 26, + } + }, +})); + +interface FilterDateProps { + label: string; + name: string; +} + +function FilterDate(props: FilterDateProps): React.ReactElement { + const { label, name } = props; + + const classes = useStyles(); + + const onFromDate = (fromDate: string | null | undefined) => { + console.log(fromDate); + }; + + const onToDate = (toDate: string | null | undefined) => { + console.log(toDate); + }; + + const InputProps = { + disableUnderline: true, + style: { + fontSize: '0.8em', + color: palette.primary.dark + } + }; + + const fromDateStyle = { + marginLeft: 45 + }; + + const keyboardIcon: React.ReactNode = ; + + const datePickerProps = { + disableToolbar: true, + format: 'MM/dd/yyyy', + name, + className: classes.date, + style: fromDateStyle, + keyboardIcon, + InputProps + }; + + return ( + + {label} + + onFromDate(value)} + variant='inline' + margin='normal' + + /> + to + onToDate(value)} + variant='inline' + margin='normal' + /> + + + ); +} + +export default FilterDate; \ No newline at end of file diff --git a/client/src/pages/Repository/components/RepositoryFilterView/FilterSelect.tsx b/client/src/pages/Repository/components/RepositoryFilterView/FilterSelect.tsx new file mode 100644 index 000000000..530356d39 --- /dev/null +++ b/client/src/pages/Repository/components/RepositoryFilterView/FilterSelect.tsx @@ -0,0 +1,65 @@ +import { Box, MenuItem, Select, Typography } from '@material-ui/core'; +import { makeStyles } from '@material-ui/core/styles'; +import React from 'react'; + +const useStyles = makeStyles(({ palette, breakpoints }) => ({ + label: { + fontSize: '0.8em', + color: palette.primary.dark, + }, + select: { + width: 160, + height: 30, + marginLeft: 10, + padding: '0px 5px', + fontSize: '0.8em', + color: palette.primary.dark, + borderRadius: 5, + border: `0.5px solid ${palette.primary.contrastText}`, + [breakpoints.down('lg')]: { + height: 26, + } + }, + icon: { + color: palette.primary.contrastText, + } +})); + +interface FilterSelectProps { + label: string; + name: string; +} + +function FilterSelect(props: FilterSelectProps): React.ReactElement { + const { label, name } = props; + + const classes = useStyles(); + + const onChange = (v) => { + console.log(v); + }; + + const inputProps = { + classes: { + icon: classes.icon + } + }; + + return ( + + {label} + + + ); +} + +export default FilterSelect; \ No newline at end of file diff --git a/client/src/pages/Repository/components/RepositoryFilterView/index.tsx b/client/src/pages/Repository/components/RepositoryFilterView/index.tsx index c32902977..4b017ea35 100644 --- a/client/src/pages/Repository/components/RepositoryFilterView/index.tsx +++ b/client/src/pages/Repository/components/RepositoryFilterView/index.tsx @@ -1,29 +1,32 @@ -import { Box, Typography } from '@material-ui/core'; -import { fade, makeStyles } from '@material-ui/core/styles'; -import React from 'react'; -import { RepositoryFilter } from '../../index'; -import { FaChevronUp, FaChevronDown } from 'react-icons/fa'; +import { Box, Chip, Typography } from '@material-ui/core'; +import { fade, makeStyles, withStyles } from '@material-ui/core/styles'; +import React, { useState } from 'react'; +import { FaChevronDown, FaChevronUp } from 'react-icons/fa'; import { FiLink2 } from 'react-icons/fi'; -import { palette } from '../../../../theme'; +import { IoIosRemoveCircle } from 'react-icons/io'; import { toast } from 'react-toastify'; +import { useRepositoryFilter } from '../../../../store'; +import { Colors, palette } from '../../../../theme'; +import FilterDate from './FilterDate'; +import FilterSelect from './FilterSelect'; const useStyles = makeStyles(({ palette, typography, breakpoints }) => ({ container: { display: 'flex', - height: ({ isExpanded }: RepositoryFilterViewProps) => isExpanded ? 150 : 30, + height: (isExpanded: boolean) => isExpanded ? 250 : 35, background: palette.primary.light, borderRadius: 5, - padding: 20, + padding: 10, marginBottom: 10, transition: '250ms height ease', [breakpoints.down('lg')]: { - padding: 10, + height: (isExpanded: boolean) => isExpanded ? 230 : 20, } }, content: { display: 'flex', flex: 1, - alignItems: 'center', + flexDirection: 'column' }, defaultFilter: { color: palette.primary.dark, @@ -33,12 +36,13 @@ const useStyles = makeStyles(({ palette, typography, breakpoints }) => ({ display: 'flex', alignItems: 'center', justifyContent: 'flex-end', - height: 30, + height: 35, padding: '0px 10px', borderRadius: 10, marginLeft: 5, transition: 'all 250ms linear', [breakpoints.down('lg')]: { + height: 20, padding: '0px 5px', }, '&:hover': { @@ -46,6 +50,30 @@ const useStyles = makeStyles(({ palette, typography, breakpoints }) => ({ backgroundColor: fade(palette.primary.light, 0.2) }, }, + caption: { + marginTop: 4, + marginLeft: 4, + fontSize: '0.75em', + color: palette.primary.dark, + fontStyle: 'italic', + fontWeight: typography.fontWeightLight + }, + textArea: { + width: 280, + padding: '5px 8px', + borderRadius: 5, + backgroundColor: Colors.defaults.white, + fontSize: '0.8em' + }, + chip: { + marginLeft: 10, + color: palette.primary.dark + }, + selectContainer: { + display: 'flex', + flexDirection: 'column', + marginRight: 20 + }, options: { display: 'flex', alignItems: 'flex-end', @@ -53,16 +81,27 @@ const useStyles = makeStyles(({ palette, typography, breakpoints }) => ({ } })); -interface RepositoryFilterViewProps { - filter: RepositoryFilter; - onChange: (field: string, value: string | boolean) => void; - isExpanded: boolean; - onToggle: () => void; -} +const StyledChip = withStyles(({ palette }) => ({ + outlined: { + height: 30, + fontSize: '0.8em', + border: `0.5px solid ${palette.primary.contrastText}` + } +}))(Chip); -function RepositoryFilterView(props: RepositoryFilterViewProps): React.ReactElement { - const { isExpanded, onToggle } = props; - const classes = useStyles(props); +function RepositoryFilterView(): React.ReactElement { + const [isExpanded, toggleFilter] = useRepositoryFilter(state => [state.isExpanded, state.toggleFilter]); + const classes = useStyles(isExpanded); + const [chips] = useState([ + { + type: 'Unit', + name: 'NMNH' + }, + { + type: 'Project', + name: 'Seashell' + } + ]); const onCopyLink = (): void => { if ('clipboard' in navigator) { @@ -71,16 +110,62 @@ function RepositoryFilterView(props: RepositoryFilterViewProps): React.ReactElem } }; - let content: React.ReactNode = ( - - Filter - - ); + let content: React.ReactNode = null; if (isExpanded) { content = ( + + + + Unit: All + + click to select + + + {chips.map((chip, index: number) => { + const { type, name } = chip; + const handleDelete = () => null; + const label = `${type}: ${name}`; + return ( + } + className={classes.chip} + onDelete={handleDelete} + variant='outlined' + /> + ); + })} + + + + + + + + + + + + + + + + + + + + + + + + + + ); } @@ -92,19 +177,19 @@ function RepositoryFilterView(props: RepositoryFilterViewProps): React.ReactElem - + {isExpanded ? : } diff --git a/client/src/pages/Repository/components/RepositoryTreeView/index.tsx b/client/src/pages/Repository/components/RepositoryTreeView/index.tsx index 1673ae3fc..332f3d2ae 100644 --- a/client/src/pages/Repository/components/RepositoryTreeView/index.tsx +++ b/client/src/pages/Repository/components/RepositoryTreeView/index.tsx @@ -16,13 +16,13 @@ const useStyles = makeStyles(({ palette, breakpoints }) => ({ container: { display: 'flex', flex: 5, - maxHeight: (isExpanded: boolean) => isExpanded ? '70vh' : '80vh', + maxHeight: (isExpanded: boolean) => isExpanded ? '64vh' : '82vh', maxWidth: '83.5vw', flexDirection: 'column', overflow: 'auto', transition: '250ms height ease', [breakpoints.down('lg')]: { - maxHeight: (isExpanded: boolean) => isExpanded ? '61vh' : '79vh', + maxHeight: (isExpanded: boolean) => isExpanded ? '50vh' : '80vh', maxWidth: '81.5vw' } }, diff --git a/client/src/pages/Repository/index.tsx b/client/src/pages/Repository/index.tsx index b03a9f196..6424eb2ad 100644 --- a/client/src/pages/Repository/index.tsx +++ b/client/src/pages/Repository/index.tsx @@ -16,6 +16,7 @@ const useStyles = makeStyles(({ breakpoints }) => ({ paddingBottom: 0, [breakpoints.down('lg')]: { padding: 20, + paddingBottom: 0, } } })); @@ -39,25 +40,16 @@ function Repository(): React.ReactElement { const defaultFilterState = Object.keys(queries).length ? queries : initialFilterState; - const [filter, setFilter] = useState(defaultFilterState); + const [filter,] = useState(defaultFilterState); useEffect(() => { const route = generateRepositoryUrl(filter); history.push(route); }, [filter, history]); - const onChange = (name: string, value: string | boolean) => { - setFilter(filter => ({ - ...filter, - [name]: value, - ...(name === 'units' && { projects: false }), - ...(name === 'projects' && { units: false }), - })); - }; - return ( - + ); From f232c566459182ff071ad3dc4c6684dc79868f10 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Mon, 12 Oct 2020 14:51:04 +0530 Subject: [PATCH 008/239] fix filter date view padding issue --- .../Repository/components/RepositoryFilterView/FilterDate.tsx | 2 +- .../pages/Repository/components/RepositoryFilterView/index.tsx | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/client/src/pages/Repository/components/RepositoryFilterView/FilterDate.tsx b/client/src/pages/Repository/components/RepositoryFilterView/FilterDate.tsx index d3acb5303..7c3201d3b 100644 --- a/client/src/pages/Repository/components/RepositoryFilterView/FilterDate.tsx +++ b/client/src/pages/Repository/components/RepositoryFilterView/FilterDate.tsx @@ -66,7 +66,6 @@ function FilterDate(props: FilterDateProps): React.ReactElement { format: 'MM/dd/yyyy', name, className: classes.date, - style: fromDateStyle, keyboardIcon, InputProps }; @@ -77,6 +76,7 @@ function FilterDate(props: FilterDateProps): React.ReactElement { onFromDate(value)} variant='inline' diff --git a/client/src/pages/Repository/components/RepositoryFilterView/index.tsx b/client/src/pages/Repository/components/RepositoryFilterView/index.tsx index 4b017ea35..ebfad0bcb 100644 --- a/client/src/pages/Repository/components/RepositoryFilterView/index.tsx +++ b/client/src/pages/Repository/components/RepositoryFilterView/index.tsx @@ -63,7 +63,8 @@ const useStyles = makeStyles(({ palette, typography, breakpoints }) => ({ padding: '5px 8px', borderRadius: 5, backgroundColor: Colors.defaults.white, - fontSize: '0.8em' + fontSize: '0.8em', + cursor: 'pointer' }, chip: { marginLeft: 10, From 87c7413cb0e43e8cdd5ee1667c634c92aae5181c Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Mon, 12 Oct 2020 14:52:58 +0530 Subject: [PATCH 009/239] - Fix confirm prompt after ingestion --- client/src/pages/Ingestion/components/Metadata/index.tsx | 4 ++-- .../src/pages/Ingestion/components/SubjectItem/index.tsx | 5 +++-- client/src/pages/Ingestion/index.tsx | 7 ++++--- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/client/src/pages/Ingestion/components/Metadata/index.tsx b/client/src/pages/Ingestion/components/Metadata/index.tsx index 394e0806e..e6c8c9971 100644 --- a/client/src/pages/Ingestion/components/Metadata/index.tsx +++ b/client/src/pages/Ingestion/components/Metadata/index.tsx @@ -128,8 +128,8 @@ function Metadata(): React.ReactElement { } else { const nextMetadata = metadatas[metadataIndex + 1]; const { file: { id, type } } = nextMetadata; - const nextRoute = resolveSubRoute(HOME_ROUTES.INGESTION, `${INGESTION_ROUTE.ROUTES.METADATA}?fileId=${id}&type=${type}`); - + const { isLast } = getMetadataInfo(id); + const nextRoute = resolveSubRoute(HOME_ROUTES.INGESTION, `${INGESTION_ROUTE.ROUTES.METADATA}?fileId=${id}&type=${type}&last=${isLast}`); history.push(nextRoute); } }; diff --git a/client/src/pages/Ingestion/components/SubjectItem/index.tsx b/client/src/pages/Ingestion/components/SubjectItem/index.tsx index c5bae8410..a02208bf4 100644 --- a/client/src/pages/Ingestion/components/SubjectItem/index.tsx +++ b/client/src/pages/Ingestion/components/SubjectItem/index.tsx @@ -54,7 +54,7 @@ function SubjectItem(): React.ReactElement { const subjects = useSubject(state => state.subjects); const [projects, projectsLoading, getSelectedProject] = useProject(state => [state.projects, state.loading, state.getSelectedProject]); const [itemsLoading, getSelectedItem] = useItem(state => [state.loading, state.getSelectedItem]); - const [metadatas, updateMetadataFolders] = useMetadata(state => [state.metadatas, state.updateMetadataFolders]); + const [metadatas, updateMetadataFolders, getMetadataInfo] = useMetadata(state => [state.metadatas, state.updateMetadataFolders, state.getMetadataInfo]); const selectedItem = getSelectedItem(); @@ -121,7 +121,8 @@ function SubjectItem(): React.ReactElement { } const { file: { id, type } } = metadatas[0]; - const nextRoute = resolveSubRoute(HOME_ROUTES.INGESTION, `${INGESTION_ROUTE.ROUTES.METADATA}?fileId=${id}&type=${type}`); + const { isLast } = getMetadataInfo(id); + const nextRoute = resolveSubRoute(HOME_ROUTES.INGESTION, `${INGESTION_ROUTE.ROUTES.METADATA}?fileId=${id}&type=${type}&last=${isLast}`); history.push(nextRoute); }; diff --git a/client/src/pages/Ingestion/index.tsx b/client/src/pages/Ingestion/index.tsx index 4ad9a8946..b59404504 100644 --- a/client/src/pages/Ingestion/index.tsx +++ b/client/src/pages/Ingestion/index.tsx @@ -37,11 +37,11 @@ function Ingestion(): React.ReactElement { enabled: false }); - metadatas.forEach(({ file: { id, name, type } }) => { + metadatas.forEach(({ file: { name } }) => { updatedOptions.push({ title: 'Metadata', subtitle: name, - route: `${INGESTION_ROUTE.ROUTES.METADATA}?fileId=${id}&type=${type}`, + route: INGESTION_ROUTE.ROUTES.METADATA, enabled: false }); }); @@ -59,7 +59,8 @@ function Ingestion(): React.ReactElement { } if (url.includes(INGESTION_ROUTES_TYPE.METADATA)) { - allowChange = pathname.includes(INGESTION_ROUTES_TYPE.METADATA) || pathname.includes(INGESTION_ROUTES_TYPE.SUBJECT_ITEM) || pathname.includes(INGESTION_ROUTES_TYPE.METADATA); + if (url.includes('last=true')) return true; + allowChange = pathname.includes(INGESTION_ROUTES_TYPE.METADATA) || pathname.includes(INGESTION_ROUTES_TYPE.SUBJECT_ITEM); } if (allowChange) return true; From 8ad6f3a3f21828c60be7947d85209e4ae0f1ce26 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Mon, 12 Oct 2020 14:57:01 +0530 Subject: [PATCH 010/239] adjust upload list view item --- .../Ingestion/components/Uploads/FileListItem.tsx | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/client/src/pages/Ingestion/components/Uploads/FileListItem.tsx b/client/src/pages/Ingestion/components/Uploads/FileListItem.tsx index 42349a5ac..a7eb1065e 100644 --- a/client/src/pages/Ingestion/components/Uploads/FileListItem.tsx +++ b/client/src/pages/Ingestion/components/Uploads/FileListItem.tsx @@ -23,7 +23,11 @@ const useStyles = makeStyles(({ palette, typography, breakpoints }) => ({ borderRadius: 5, width: '100%', zIndex: 10, - overflow: 'hidden' + overflow: 'hidden', + [breakpoints.down('lg')]: { + minHeight: 50, + marginTop: 5, + } }, item: { display: 'flex', @@ -147,10 +151,10 @@ function FileListItem(props: FileListItemProps): React.ReactElement { if (!complete) { options = ( - {!uploading && !failed && } + {!uploading && !failed && } {uploading && !failed && } {failed && } - + ); } @@ -158,8 +162,8 @@ function FileListItem(props: FileListItemProps): React.ReactElement { if (complete) { options = ( - {!selected && } - {selected && } + {!selected && } + {selected && } ); } From 259d97b519c90e47e854b69b1255a93e833a2ae9 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Mon, 12 Oct 2020 14:58:46 +0530 Subject: [PATCH 011/239] added react fast refresh webpack plugin --- client/config-overrides.js | 3 ++- client/package.json | 1 + yarn.lock | 52 +++++++++++++++++++++++++++++++++++--- 3 files changed, 51 insertions(+), 5 deletions(-) diff --git a/client/config-overrides.js b/client/config-overrides.js index 62661fadc..0b5224a62 100644 --- a/client/config-overrides.js +++ b/client/config-overrides.js @@ -1,3 +1,4 @@ const { override, addExternalBabelPlugin } = require('customize-cra'); +const { addReactRefresh } = require('customize-cra-react-refresh'); -module.exports = override(addExternalBabelPlugin('react-activation/babel')); +module.exports = override(addExternalBabelPlugin('react-activation/babel'), addReactRefresh()); diff --git a/client/package.json b/client/package.json index ef9091855..3e4d34f1c 100644 --- a/client/package.json +++ b/client/package.json @@ -42,6 +42,7 @@ "@types/yup": "0.29.7", "apollo-upload-client": "14.1.2", "customize-cra": "1.0.0", + "customize-cra-react-refresh": "1.1.0", "formik": "2.1.5", "formik-material-ui": "3.0.0", "framer-motion": "2.6.13", diff --git a/yarn.lock b/yarn.lock index 9a0c13141..204a2d9cf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2901,6 +2901,18 @@ dependencies: "@types/node" ">= 8" +"@pmmmwh/react-refresh-webpack-plugin@^0.3.2": + version "0.3.3" + resolved "https://registry.yarnpkg.com/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.3.3.tgz#40a3d674f42a011b7f30a9609aa8fb68ec3c39c9" + integrity sha512-uc6FmPEegAZawSHjUMFQwU7EjaDn7zy1iD/KD/wBROL9F4378OES8MKMYHoRAKT61Fk7LxVKZSDR5VespMQiqw== + dependencies: + ansi-html "^0.0.7" + error-stack-parser "^2.0.6" + html-entities "^1.2.1" + lodash.debounce "^4.0.8" + native-url "^0.2.6" + schema-utils "^2.6.5" + "@prisma/cli@2.4.1": version "2.4.1" resolved "https://registry.yarnpkg.com/@prisma/cli/-/cli-2.4.1.tgz#95f6cae48ff19c6177bb9f85816b27e1ffe5af53" @@ -4076,7 +4088,7 @@ ansi-escapes@^4.2.1, ansi-escapes@^4.3.1: dependencies: type-fest "^0.11.0" -ansi-html@0.0.7: +ansi-html@0.0.7, ansi-html@^0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/ansi-html/-/ansi-html-0.0.7.tgz#813584021962a9e9e6fd039f940d12f56ca7859e" integrity sha1-gTWEAhliqenm/QOflA0S9WynhZ4= @@ -6447,6 +6459,14 @@ currently-unhandled@^0.4.1: dependencies: array-find-index "^1.0.1" +customize-cra-react-refresh@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/customize-cra-react-refresh/-/customize-cra-react-refresh-1.1.0.tgz#0bb3723028b94d8f6f97af26b1f4aec0636271df" + integrity sha512-v7moZc3mv0ZnM9hUnC9YpUhjpWkCjYSL/oLx/y8o5CKFOnd3yOPxlmfMjrmpWTVPbpOIFTCPGjDIAiEYu63TGg== + dependencies: + "@pmmmwh/react-refresh-webpack-plugin" "^0.3.2" + react-refresh "^0.8.1" + customize-cra@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/customize-cra/-/customize-cra-1.0.0.tgz#73286563631aa08127ad4d30a2e3c89cf4e93c8d" @@ -7111,6 +7131,13 @@ error-ex@^1.2.0, error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" +error-stack-parser@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/error-stack-parser/-/error-stack-parser-2.0.6.tgz#5a99a707bd7a4c58a797902d48d82803ede6aad8" + integrity sha512-d51brTeqC+BHlwF0BhPtcYgF5nlzf9ZZ0ZIUQNZpc9ZB9qw5IJ2diTrBY9jlCJkTLITYPjmiX6OWCwH+fuyNgQ== + dependencies: + stackframe "^1.1.1" + es-abstract@^1.17.0, es-abstract@^1.17.0-next.1, es-abstract@^1.17.2, es-abstract@^1.17.5: version "1.17.6" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.6.tgz#9142071707857b2cacc7b89ecb670316c3e2d52a" @@ -10915,7 +10942,7 @@ lodash.clonedeep@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8= -lodash.debounce@^4: +lodash.debounce@^4, lodash.debounce@^4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168= @@ -11664,6 +11691,13 @@ nanomatch@^1.2.9: snapdragon "^0.8.1" to-regex "^3.0.1" +native-url@^0.2.6: + version "0.2.6" + resolved "https://registry.yarnpkg.com/native-url/-/native-url-0.2.6.tgz#ca1258f5ace169c716ff44eccbddb674e10399ae" + integrity sha512-k4bDC87WtgrdD362gZz6zoiXQrl40kYlBmpfmSjwRO1VU0V5ccwJTlxuE72F6m3V0vc1xOf6n3UCP9QyerRqmA== + dependencies: + querystring "^0.2.0" + natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" @@ -13726,7 +13760,7 @@ querystring-es3@^0.2.0: resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73" integrity sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM= -querystring@0.2.0: +querystring@0.2.0, querystring@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA= @@ -13925,6 +13959,11 @@ react-node-key@^0.1.7: jsx-ast-utils "^2.2.1" szfe-tools "^0.0.0-beta.7" +react-refresh@^0.8.1: + version "0.8.3" + resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.8.3.tgz#721d4657672d400c5e3c75d063c4a85fb2d5d68f" + integrity sha512-X8jZHc7nCMjaCqoU+V2I0cOhNW+QMBwSUkeXnTi8IPe6zaRWfn60ZzvFDZqWPfmSJfjub7dDW1SP0jaHWLu/hg== + react-router-dom@5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.2.0.tgz#9e65a4d0c45e13289e66c7b17c7e175d0ea15662" @@ -14795,7 +14834,7 @@ schema-utils@^1.0.0: ajv-errors "^1.0.0" ajv-keywords "^3.1.0" -schema-utils@^2.5.0, schema-utils@^2.6.0, schema-utils@^2.6.1, schema-utils@^2.6.4: +schema-utils@^2.5.0, schema-utils@^2.6.0, schema-utils@^2.6.1, schema-utils@^2.6.4, schema-utils@^2.6.5: version "2.7.1" resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.7.1.tgz#1ca4f32d1b24c590c203b8e7a50bf0ea4cd394d7" integrity sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg== @@ -15337,6 +15376,11 @@ stack-utils@^1.0.1: resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-1.0.2.tgz#33eba3897788558bebfc2db059dc158ec36cebb8" integrity sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA== +stackframe@^1.1.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/stackframe/-/stackframe-1.2.0.tgz#52429492d63c62eb989804c11552e3d22e779303" + integrity sha512-GrdeshiRmS1YLMYgzF16olf2jJ/IzxXY9lhKOskuVziubpTYcYqyOwYeJKzQkwy7uN0fYSsbsC4RQaXf9LCrYA== + static-extend@^0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" From 5e874bcf0375f0877f78f9a921801cb46141a654 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Tue, 13 Oct 2020 15:41:24 +0530 Subject: [PATCH 012/239] refactor store hooks --- client/src/components/shared/Header.tsx | 6 ++-- client/src/components/shared/PrivateRoute.tsx | 6 ++-- client/src/components/shared/PublicRoute.tsx | 6 ++-- client/src/index.tsx | 8 ++--- .../Metadata/Photogrammetry/index.tsx | 18 +++++----- .../Ingestion/components/Metadata/index.tsx | 10 +++--- .../components/SubjectItem/ItemList.tsx | 6 ++-- .../components/SubjectItem/ProjectList.tsx | 4 +-- .../components/SubjectItem/SubjectList.tsx | 8 ++--- .../components/SubjectItem/index.tsx | 12 +++---- .../Ingestion/components/Uploads/FileList.tsx | 10 +++--- .../components/Uploads/UploadCompleteList.tsx | 4 +-- .../components/Uploads/UploadFilesPicker.tsx | 4 +-- .../components/Uploads/UploadList.tsx | 4 +-- .../Ingestion/components/Uploads/index.tsx | 18 +++++----- client/src/pages/Ingestion/hooks/useIngest.ts | 35 ++++++++++++------- client/src/pages/Ingestion/index.tsx | 10 +++--- client/src/pages/Login/index.tsx | 4 +-- .../components/RepositoryFilterView/index.tsx | 4 +-- .../components/RepositoryTreeView/index.tsx | 4 +-- client/src/store/item.ts | 2 +- client/src/store/metadata.ts | 28 +++++++-------- client/src/store/project.ts | 4 +-- client/src/store/repository.ts | 2 +- client/src/store/subject.ts | 10 +++--- client/src/store/upload.ts | 6 ++-- client/src/store/user.ts | 2 +- client/src/store/vocabulary.ts | 2 +- 28 files changed, 124 insertions(+), 113 deletions(-) diff --git a/client/src/components/shared/Header.tsx b/client/src/components/shared/Header.tsx index 4728ac976..7142e0ed4 100644 --- a/client/src/components/shared/Header.tsx +++ b/client/src/components/shared/Header.tsx @@ -7,7 +7,7 @@ import { Link, useHistory, useLocation } from 'react-router-dom'; import { toast } from 'react-toastify'; import Logo from '../../assets/images/logo-packrat.square.png'; import { HOME_ROUTES, resolveRoute, ROUTES } from '../../constants'; -import { useRepositoryFilter, useUser } from '../../store'; +import { useRepositoryFilterStore, useUserStore } from '../../store'; import { Colors } from '../../theme'; const useStyles = makeStyles(({ palette, spacing, typography, breakpoints }) => ({ @@ -78,8 +78,8 @@ function Header(): React.ReactElement { const classes = useStyles(); const history = useHistory(); const { pathname } = useLocation(); - const { user, logout } = useUser(); - const [search, updateSearch] = useRepositoryFilter(state => [state.search, state.updateSearch]); + const { user, logout } = useUserStore(); + const [search, updateSearch] = useRepositoryFilterStore(state => [state.search, state.updateSearch]); const onSearch = (): void => { const route: string = resolveRoute(HOME_ROUTES.REPOSITORY); diff --git a/client/src/components/shared/PrivateRoute.tsx b/client/src/components/shared/PrivateRoute.tsx index 31d898922..bd5242eea 100644 --- a/client/src/components/shared/PrivateRoute.tsx +++ b/client/src/components/shared/PrivateRoute.tsx @@ -3,9 +3,9 @@ * Renders a route only if the user is authenticated else redirects to login */ import React from 'react'; -import { Route, Redirect, RouteProps } from 'react-router-dom'; +import { Redirect, Route, RouteProps } from 'react-router-dom'; import { ROUTES } from '../../constants'; -import { useUser } from '../../store'; +import { useUserStore } from '../../store'; interface PrivateRouteProps { component?: React.ComponentType; @@ -13,7 +13,7 @@ interface PrivateRouteProps { } function PrivateRoute({ component: Component, children, ...rest }: PrivateRouteProps & RouteProps): React.ReactElement { - const user = useUser(state => state.user); + const user = useUserStore(state => state.user); const render = props => { diff --git a/client/src/components/shared/PublicRoute.tsx b/client/src/components/shared/PublicRoute.tsx index 8e207ca54..c18b1fba0 100644 --- a/client/src/components/shared/PublicRoute.tsx +++ b/client/src/components/shared/PublicRoute.tsx @@ -3,9 +3,9 @@ * Renders a route based on authentication and restriction specified */ import React from 'react'; -import { Route, Redirect, RouteProps } from 'react-router-dom'; +import { Redirect, Route, RouteProps } from 'react-router-dom'; import { ROUTES } from '../../constants'; -import { useUser } from '../../store'; +import { useUserStore } from '../../store'; interface PublicRouteProps { restricted?: boolean; @@ -13,7 +13,7 @@ interface PublicRouteProps { } function PublicRoute({ component: Component, restricted = false, ...rest }: PublicRouteProps & RouteProps): React.ReactElement { - const user = useUser(state => state.user); + const user = useUserStore(state => state.user); const render = props => ( !!user && restricted ? : diff --git a/client/src/index.tsx b/client/src/index.tsx index 48aa9c825..6ce1a9bbb 100644 --- a/client/src/index.tsx +++ b/client/src/index.tsx @@ -1,6 +1,7 @@ import { ApolloProvider } from '@apollo/client'; import { ThemeProvider } from '@material-ui/core'; import React, { useCallback, useEffect, useState } from 'react'; +import { AliveScope } from 'react-activation'; import ReactDOM from 'react-dom'; import { BrowserRouter as Router, Switch } from 'react-router-dom'; import { Slide, ToastContainer } from 'react-toastify'; @@ -10,14 +11,13 @@ import { ROUTES } from './constants'; import './global/root.css'; import { apolloClient } from './graphql'; import { About, Home, Login } from './pages'; -import theme from './theme'; -import { AliveScope } from 'react-activation'; import * as serviceWorker from './serviceWorker'; -import { useUser } from './store'; +import { useUserStore } from './store'; +import theme from './theme'; function AppRouter(): React.ReactElement { const [loading, setLoading] = useState(true); - const initialize = useUser(state => state.initialize); + const initialize = useUserStore(state => state.initialize); const initializeUser = useCallback(async () => { await initialize(); diff --git a/client/src/pages/Ingestion/components/Metadata/Photogrammetry/index.tsx b/client/src/pages/Ingestion/components/Metadata/Photogrammetry/index.tsx index 7dc03d150..80d237261 100644 --- a/client/src/pages/Ingestion/components/Metadata/Photogrammetry/index.tsx +++ b/client/src/pages/Ingestion/components/Metadata/Photogrammetry/index.tsx @@ -1,19 +1,19 @@ /* eslint-disable react-hooks/exhaustive-deps */ +import DateFnsUtils from '@date-io/date-fns'; import { Box, Checkbox, Typography } from '@material-ui/core'; import { fade, makeStyles, withStyles } from '@material-ui/core/styles'; +import { KeyboardDatePicker, MuiPickersUtilsProvider } from '@material-ui/pickers'; +import lodash from 'lodash'; import React from 'react'; import { FieldType } from '../../../../../components'; -import DateFnsUtils from '@date-io/date-fns'; -import { MuiPickersUtilsProvider, KeyboardDatePicker } from '@material-ui/pickers'; +import { StateIdentifier, StateMetadata, useMetadataStore, useVocabularyStore } from '../../../../../store'; import { Colors } from '../../../../../theme'; -import { StateMetadata, useVocabulary, useMetadata, StateIdentifier } from '../../../../../store'; import { eVocabularySetID } from '../../../../../types/server'; +import AssetContents from './AssetContents'; import Description from './Description'; import IdentifierList from './IdentifierList'; -import SelectField from './SelectField'; import IdInputField from './IdInputField'; -import lodash from 'lodash'; -import AssetContents from './AssetContents'; +import SelectField from './SelectField'; const useStyles = makeStyles(({ palette, typography, spacing, breakpoints }) => ({ container: { @@ -80,12 +80,12 @@ function Photogrammetry(props: PhotogrammetryProps): React.ReactElement { const { metadataIndex } = props; const classes = useStyles(); - const { getFieldErrors, updatePhotogrammetryField } = useMetadata(); - const metadata: StateMetadata = useMetadata(state => state.metadatas[metadataIndex]); + const { getFieldErrors, updatePhotogrammetryField } = useMetadataStore(); + const metadata: StateMetadata = useMetadataStore(state => state.metadatas[metadataIndex]); const errors = getFieldErrors(metadata); const { photogrammetry } = metadata; - const { getEntries, getInitialEntry } = useVocabulary(); + const { getEntries, getInitialEntry } = useVocabularyStore(); const setField = ({ target }): void => { const { name, value } = target; diff --git a/client/src/pages/Ingestion/components/Metadata/index.tsx b/client/src/pages/Ingestion/components/Metadata/index.tsx index e6c8c9971..d84924224 100644 --- a/client/src/pages/Ingestion/components/Metadata/index.tsx +++ b/client/src/pages/Ingestion/components/Metadata/index.tsx @@ -7,7 +7,7 @@ import { Redirect, useHistory, useLocation } from 'react-router'; import { toast } from 'react-toastify'; import { SidebarBottomNavigator } from '../../../../components'; import { HOME_ROUTES, INGESTION_ROUTE, resolveSubRoute } from '../../../../constants'; -import { useItem, useMetadata, useProject, useVocabulary, FileId, StateItem, StateMetadata, StateProject } from '../../../../store'; +import { FileId, StateItem, StateMetadata, StateProject, useItemStore, useMetadataStore, useProjectStore, useVocabularyStore } from '../../../../store'; import useIngest from '../../hooks/useIngest'; import Photogrammetry from './Photogrammetry'; @@ -49,11 +49,11 @@ function Metadata(): React.ReactElement { const [ingestionLoading, setIngestionLoading] = useState(false); - const getSelectedProject = useProject(state => state.getSelectedProject); - const getSelectedItem = useItem(state => state.getSelectedItem); - const [metadatas, getFieldErrors, getMetadataInfo] = useMetadata(state => [state.metadatas, state.getFieldErrors, state.getMetadataInfo]); + const getSelectedProject = useProjectStore(state => state.getSelectedProject); + const getSelectedItem = useItemStore(state => state.getSelectedItem); + const [metadatas, getFieldErrors, getMetadataInfo] = useMetadataStore(state => [state.metadatas, state.getFieldErrors, state.getMetadataInfo]); const { ingestPhotogrammetryData, ingestionComplete } = useIngest(); - const getAssetType = useVocabulary(state => state.getAssetType); + const getAssetType = useVocabularyStore(state => state.getAssetType); const metadataLength = metadatas.length; const query = qs.parse(search) as QueryParams; diff --git a/client/src/pages/Ingestion/components/SubjectItem/ItemList.tsx b/client/src/pages/Ingestion/components/SubjectItem/ItemList.tsx index a6d61f049..48295eb98 100644 --- a/client/src/pages/Ingestion/components/SubjectItem/ItemList.tsx +++ b/client/src/pages/Ingestion/components/SubjectItem/ItemList.tsx @@ -3,9 +3,9 @@ import { grey } from '@material-ui/core/colors'; import { makeStyles } from '@material-ui/core/styles'; import React from 'react'; import { DebounceInput } from 'react-debounce-input'; -import { RiRecordCircleFill, RiCheckboxBlankCircleLine } from 'react-icons/ri'; import { MdCheckBox, MdCheckBoxOutlineBlank } from 'react-icons/md'; -import { defaultItem, StateItem, useItem } from '../../../../store'; +import { RiCheckboxBlankCircleLine, RiRecordCircleFill } from 'react-icons/ri'; +import { defaultItem, StateItem, useItemStore } from '../../../../store'; import { palette } from '../../../../theme'; const useStyles = makeStyles(({ palette, spacing, typography, breakpoints }) => ({ @@ -58,7 +58,7 @@ const useStyles = makeStyles(({ palette, spacing, typography, breakpoints }) => function ItemList(): React.ReactElement { const classes = useStyles(); - const [items, updateItem] = useItem(state => [state.items, state.updateItem]); + const [items, updateItem] = useItemStore(state => [state.items, state.updateItem]); const selectableHeaderStyle = { width: 100 diff --git a/client/src/pages/Ingestion/components/SubjectItem/ProjectList.tsx b/client/src/pages/Ingestion/components/SubjectItem/ProjectList.tsx index 530b7573d..d30d68829 100644 --- a/client/src/pages/Ingestion/components/SubjectItem/ProjectList.tsx +++ b/client/src/pages/Ingestion/components/SubjectItem/ProjectList.tsx @@ -2,7 +2,7 @@ import { MenuItem, Select } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; import lodash from 'lodash'; import React from 'react'; -import { useProject } from '../../../../store'; +import { useProjectStore } from '../../../../store'; const useStyles = makeStyles(({ palette }) => ({ projectSelect: { @@ -15,7 +15,7 @@ const useStyles = makeStyles(({ palette }) => ({ function ProjectList(): React.ReactElement { const classes = useStyles(); - const [projects, getSelectedProject, updateSelectedProject] = useProject(state => [state.projects, state.getSelectedProject, state.updateSelectedProject]); + const [projects, getSelectedProject, updateSelectedProject] = useProjectStore(state => [state.projects, state.getSelectedProject, state.updateSelectedProject]); const noProjects = !projects.length; const selectedProject = getSelectedProject(); diff --git a/client/src/pages/Ingestion/components/SubjectItem/SubjectList.tsx b/client/src/pages/Ingestion/components/SubjectItem/SubjectList.tsx index f08ce865f..d4a87f2f7 100644 --- a/client/src/pages/Ingestion/components/SubjectItem/SubjectList.tsx +++ b/client/src/pages/Ingestion/components/SubjectItem/SubjectList.tsx @@ -1,8 +1,8 @@ -import React from 'react'; -import { Typography, TableContainer, Table, TableCell, TableHead, TableRow, TableBody } from '@material-ui/core'; +import { Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Typography } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; +import React from 'react'; +import { StateSubject, useSubjectStore } from '../../../../store'; import SubjectListItem from './SubjectListItem'; -import { useSubject, StateSubject } from '../../../../store'; const useStyles = makeStyles(({ palette, spacing, breakpoints }) => ({ container: { @@ -38,7 +38,7 @@ interface SubjectListProps { function SubjectList(props: SubjectListProps): React.ReactElement { const { subjects, emptyLabel, selected } = props; - const [addSubject, removeSubject] = useSubject(state => [state.addSubject, state.removeSubject]); + const [addSubject, removeSubject] = useSubjectStore(state => [state.addSubject, state.removeSubject]); const classes = useStyles(); const header: string[] = ['ARK / ID', 'UNIT', 'NAME']; diff --git a/client/src/pages/Ingestion/components/SubjectItem/index.tsx b/client/src/pages/Ingestion/components/SubjectItem/index.tsx index a02208bf4..fe42359dd 100644 --- a/client/src/pages/Ingestion/components/SubjectItem/index.tsx +++ b/client/src/pages/Ingestion/components/SubjectItem/index.tsx @@ -5,7 +5,7 @@ import { Redirect, useHistory } from 'react-router'; import { toast } from 'react-toastify'; import { FieldType, SidebarBottomNavigator } from '../../../../components'; import { HOME_ROUTES, INGESTION_ROUTE, resolveSubRoute } from '../../../../constants'; -import { useItem, useMetadata, useProject, useSubject, useVocabulary } from '../../../../store'; +import { useItemStore, useMetadataStore, useProjectStore, useSubjectStore, useVocabularyStore } from '../../../../store'; import ItemList from './ItemList'; import ProjectList from './ProjectList'; import SearchList from './SearchList'; @@ -50,11 +50,11 @@ function SubjectItem(): React.ReactElement { const [itemError, setItemError] = useState(false); const [metadataStepLoading, setMetadataStepLoading] = useState(false); - const updateVocabularyEntries = useVocabulary(state => state.updateVocabularyEntries); - const subjects = useSubject(state => state.subjects); - const [projects, projectsLoading, getSelectedProject] = useProject(state => [state.projects, state.loading, state.getSelectedProject]); - const [itemsLoading, getSelectedItem] = useItem(state => [state.loading, state.getSelectedItem]); - const [metadatas, updateMetadataFolders, getMetadataInfo] = useMetadata(state => [state.metadatas, state.updateMetadataFolders, state.getMetadataInfo]); + const updateVocabularyEntries = useVocabularyStore(state => state.updateVocabularyEntries); + const subjects = useSubjectStore(state => state.subjects); + const [projects, projectsLoading, getSelectedProject] = useProjectStore(state => [state.projects, state.loading, state.getSelectedProject]); + const [itemsLoading, getSelectedItem] = useItemStore(state => [state.loading, state.getSelectedItem]); + const [metadatas, updateMetadataFolders, getMetadataInfo] = useMetadataStore(state => [state.metadatas, state.updateMetadataFolders, state.getMetadataInfo]); const selectedItem = getSelectedItem(); diff --git a/client/src/pages/Ingestion/components/Uploads/FileList.tsx b/client/src/pages/Ingestion/components/Uploads/FileList.tsx index 351753480..211f4c569 100644 --- a/client/src/pages/Ingestion/components/Uploads/FileList.tsx +++ b/client/src/pages/Ingestion/components/Uploads/FileList.tsx @@ -1,19 +1,19 @@ import { AnimatePresence } from 'framer-motion'; import React from 'react'; -import { useUpload, useVocabulary, FileId, IngestionFile, FileUploadStatus, VocabularyOption } from '../../../../store'; -import FileListItem from './FileListItem'; +import { FileId, FileUploadStatus, IngestionFile, useUploadStore, useVocabularyStore, VocabularyOption } from '../../../../store'; import { eVocabularySetID } from '../../../../types/server'; +import FileListItem from './FileListItem'; interface FileListProps { files: IngestionFile[]; } function FileList(props: FileListProps): React.ReactElement { - const { selectFile } = useUpload(); - const { getEntries } = useVocabulary(); + const { selectFile } = useUploadStore(); + const { getEntries } = useVocabularyStore(); const { files } = props; - const { startUpload, retryUpload, cancelUpload, removeUpload, changeAssetType } = useUpload(); + const { startUpload, retryUpload, cancelUpload, removeUpload, changeAssetType } = useUploadStore(); const onChangeType = (id: FileId, assetType: number): void => changeAssetType(id, assetType); diff --git a/client/src/pages/Ingestion/components/Uploads/UploadCompleteList.tsx b/client/src/pages/Ingestion/components/Uploads/UploadCompleteList.tsx index 192e8f54b..cd4267a00 100644 --- a/client/src/pages/Ingestion/components/Uploads/UploadCompleteList.tsx +++ b/client/src/pages/Ingestion/components/Uploads/UploadCompleteList.tsx @@ -3,7 +3,7 @@ import { useQuery } from '@apollo/client'; import { Box, Typography } from '@material-ui/core'; import React, { useEffect } from 'react'; import { FieldType } from '../../../../components'; -import { parseAssetVersionToState, useUpload } from '../../../../store'; +import { parseAssetVersionToState, useUploadStore } from '../../../../store'; import { GetUploadedAssetVersionDocument } from '../../../../types/graphql'; import FileList from './FileList'; import { useUploadListStyles } from './UploadList'; @@ -12,7 +12,7 @@ import UploadListHeader from './UploadListHeader'; function UploadListComplete(): React.ReactElement { const classes = useUploadListStyles(); - const { completed, loadCompleted } = useUpload(); + const { completed, loadCompleted } = useUploadStore(); const { data, loading, error } = useQuery(GetUploadedAssetVersionDocument); useEffect(() => { diff --git a/client/src/pages/Ingestion/components/Uploads/UploadFilesPicker.tsx b/client/src/pages/Ingestion/components/Uploads/UploadFilesPicker.tsx index cf1e89ac1..bcf4505a4 100644 --- a/client/src/pages/Ingestion/components/Uploads/UploadFilesPicker.tsx +++ b/client/src/pages/Ingestion/components/Uploads/UploadFilesPicker.tsx @@ -3,8 +3,8 @@ import { makeStyles } from '@material-ui/core/styles'; import React from 'react'; import Dropzone from 'react-dropzone'; import { BsCloudUpload } from 'react-icons/bs'; +import { useUploadStore } from '../../../../store'; import { Colors } from '../../../../theme'; -import { useUpload } from '../../../../store'; const useStyles = makeStyles(({ palette, typography, spacing }) => ({ container: { @@ -37,7 +37,7 @@ const useStyles = makeStyles(({ palette, typography, spacing }) => ({ function UploadFilesPicker(): React.ReactElement { const classes = useStyles(); - const { loading, loadPending } = useUpload(); + const { loading, loadPending } = useUploadStore(); const onDrop = (acceptedFiles: File[]) => { loadPending(acceptedFiles); diff --git a/client/src/pages/Ingestion/components/Uploads/UploadList.tsx b/client/src/pages/Ingestion/components/Uploads/UploadList.tsx index d15afb0b5..d6ac46721 100644 --- a/client/src/pages/Ingestion/components/Uploads/UploadList.tsx +++ b/client/src/pages/Ingestion/components/Uploads/UploadList.tsx @@ -2,7 +2,7 @@ import { Box, Typography } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; import React from 'react'; import { FieldType } from '../../../../components'; -import { useUpload } from '../../../../store'; +import { useUploadStore } from '../../../../store'; import FileList from './FileList'; import UploadListHeader from './UploadListHeader'; @@ -53,7 +53,7 @@ export const useUploadListStyles = makeStyles(({ palette, breakpoints }) => ({ function UploadList(): React.ReactElement { const classes = useUploadListStyles(); - const { pending } = useUpload(); + const { pending } = useUploadStore(); return ( diff --git a/client/src/pages/Ingestion/components/Uploads/index.tsx b/client/src/pages/Ingestion/components/Uploads/index.tsx index 55c6d4e13..c5f46948a 100644 --- a/client/src/pages/Ingestion/components/Uploads/index.tsx +++ b/client/src/pages/Ingestion/components/Uploads/index.tsx @@ -1,17 +1,17 @@ /* eslint-disable react-hooks/exhaustive-deps */ import { Box } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; -import React, { useState, useEffect } from 'react'; +import React, { useEffect, useState } from 'react'; import KeepAlive from 'react-activation'; -import { SidebarBottomNavigator, Loader } from '../../../../components'; +import { useHistory } from 'react-router'; +import { toast } from 'react-toastify'; +import { Loader, SidebarBottomNavigator } from '../../../../components'; import { HOME_ROUTES, INGESTION_ROUTE, resolveSubRoute } from '../../../../constants'; +import { useMetadataStore, useUploadStore, useVocabularyStore } from '../../../../store'; import { Colors } from '../../../../theme'; +import UploadCompleteList from './UploadCompleteList'; import UploadFilesPicker from './UploadFilesPicker'; import UploadList from './UploadList'; -import UploadCompleteList from './UploadCompleteList'; -import { useHistory } from 'react-router'; -import { toast } from 'react-toastify'; -import { useVocabulary, useUpload, useMetadata } from '../../../../store'; const useStyles = makeStyles(({ palette, typography, spacing, breakpoints }) => ({ container: { @@ -65,9 +65,9 @@ function Uploads(): React.ReactElement { const [loadingVocabulary, setLoadingVocabulary] = useState(true); const [gettingAssetDetails, setGettingAssetDetails] = useState(false); const [discardingFiles, setDiscardingFiles] = useState(false); - const { completed, discardFiles } = useUpload(); - const { updateMetadataSteps } = useMetadata(); - const { updateVocabularyEntries } = useVocabulary(); + const { completed, discardFiles } = useUploadStore(); + const { updateMetadataSteps } = useMetadataStore(); + const { updateVocabularyEntries } = useVocabularyStore(); const fetchVocabularyEntries = async () => { setLoadingVocabulary(true); diff --git a/client/src/pages/Ingestion/hooks/useIngest.ts b/client/src/pages/Ingestion/hooks/useIngest.ts index 78a6ca881..b4ce351e8 100644 --- a/client/src/pages/Ingestion/hooks/useIngest.ts +++ b/client/src/pages/Ingestion/hooks/useIngest.ts @@ -1,12 +1,23 @@ -import { useItem, useProject, useMetadata, useVocabulary, useSubject, useUpload, defaultItem, StateIdentifier, StateItem, StateProject } from '../../../store'; -import { IngestDataMutation, IngestIdentifierInput, IngestFolderInput, IngestPhotogrammetryInput, IngestDataDocument, IngestSubjectInput } from '../../../types/graphql'; -import { apolloClient } from '../../../graphql'; -import lodash from 'lodash'; import { FetchResult } from '@apollo/client'; -import { toast } from 'react-toastify'; +import lodash from 'lodash'; import { useHistory } from 'react-router'; -import { HOME_ROUTES, resolveSubRoute, INGESTION_ROUTES_TYPE } from '../../../constants/routes'; +import { toast } from 'react-toastify'; +import { HOME_ROUTES, INGESTION_ROUTES_TYPE, resolveSubRoute } from '../../../constants/routes'; +import { apolloClient } from '../../../graphql'; +import { + defaultItem, + StateIdentifier, + StateItem, + StateProject, + useItemStore, + useMetadataStore, + useProjectStore, + useSubjectStore, + useUploadStore, + useVocabularyStore +} from '../../../store'; import { isNewItem, parseFileId } from '../../../store/utils'; +import { IngestDataDocument, IngestDataMutation, IngestFolderInput, IngestIdentifierInput, IngestPhotogrammetryInput, IngestSubjectInput } from '../../../types/graphql'; interface UseIngest { ingestPhotogrammetryData: () => Promise; @@ -15,12 +26,12 @@ interface UseIngest { } function useIngest(): UseIngest { - const [{ removeSelectedUploads }, resetUploads] = useUpload(state => [state, state.reset]); - const [{ subjects }, resetSubjects] = useSubject(state => [state, state.reset]); - const [{ getSelectedProject }, resetProjects] = useProject(state => [state, state.reset]); - const [{ getSelectedItem }, resetItems] = useItem(state => [state, state.reset]); - const [{ metadatas, getSelectedIdentifiers }, resetMetadatas] = useMetadata(state => [state, state.reset]); - const { getAssetType } = useVocabulary(); + const [{ removeSelectedUploads }, resetUploads] = useUploadStore(state => [state, state.reset]); + const [{ subjects }, resetSubjects] = useSubjectStore(state => [state, state.reset]); + const [{ getSelectedProject }, resetProjects] = useProjectStore(state => [state, state.reset]); + const [{ getSelectedItem }, resetItems] = useItemStore(state => [state, state.reset]); + const [{ metadatas, getSelectedIdentifiers }, resetMetadatas] = useMetadataStore(state => [state, state.reset]); + const { getAssetType } = useVocabularyStore(); const history = useHistory(); diff --git a/client/src/pages/Ingestion/index.tsx b/client/src/pages/Ingestion/index.tsx index b59404504..815358b76 100644 --- a/client/src/pages/Ingestion/index.tsx +++ b/client/src/pages/Ingestion/index.tsx @@ -2,15 +2,15 @@ import { Box } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; import React, { useEffect, useState } from 'react'; import { Redirect, useRouteMatch } from 'react-router'; +import { Prompt } from 'react-router-dom'; import { PrivateRoute } from '../../components'; +import { HOME_ROUTES, INGESTION_PARAMS_TYPE, INGESTION_ROUTE, INGESTION_ROUTES_TYPE, resolveRoute, resolveSubRoute } from '../../constants'; +import { useMetadataStore } from '../../store'; import { IngestionSidebarMenu, IngestionSidebarOption } from './components/IngestionSidebar'; -import { HOME_ROUTES, INGESTION_ROUTE, INGESTION_ROUTES_TYPE, INGESTION_PARAMS_TYPE, resolveRoute, resolveSubRoute } from '../../constants'; -import Uploads from './components/Uploads'; import Metadata from './components/Metadata'; import SubjectItem from './components/SubjectItem'; -import { Prompt } from 'react-router-dom'; +import Uploads from './components/Uploads'; import useIngest from './hooks/useIngest'; -import { useMetadata } from '../../store'; const useStyles = makeStyles(() => ({ container: { @@ -22,7 +22,7 @@ const useStyles = makeStyles(() => ({ function Ingestion(): React.ReactElement { const classes = useStyles(); const { path } = useRouteMatch(); - const { metadatas } = useMetadata(); + const { metadatas } = useMetadataStore(); const { ingestionReset } = useIngest(); const [options, setOptions] = useState([]); diff --git a/client/src/pages/Login/index.tsx b/client/src/pages/Login/index.tsx index 898a16aa5..ea99980d4 100644 --- a/client/src/pages/Login/index.tsx +++ b/client/src/pages/Login/index.tsx @@ -9,7 +9,7 @@ import LoginBackground from '../../assets/images/login-background.png'; import { LoadingButton } from '../../components'; import Config from '../../config'; import { ROUTES } from '../../constants'; -import { useUser } from '../../store'; +import { useUserStore } from '../../store'; import { actionOnKeyPress } from '../../utils/shared'; import useLoginForm, { ILoginForm } from './hooks/useLoginForm'; @@ -92,7 +92,7 @@ const useStyles = makeStyles(({ palette, typography, spacing, breakpoints }) => function Login(): React.ReactElement { const classes = useStyles(); const history = useHistory(); - const { login } = useUser(); + const { login } = useUserStore(); const { initialValues, loginValidationSchema } = useLoginForm(); diff --git a/client/src/pages/Repository/components/RepositoryFilterView/index.tsx b/client/src/pages/Repository/components/RepositoryFilterView/index.tsx index ebfad0bcb..a71309cfb 100644 --- a/client/src/pages/Repository/components/RepositoryFilterView/index.tsx +++ b/client/src/pages/Repository/components/RepositoryFilterView/index.tsx @@ -5,7 +5,7 @@ import { FaChevronDown, FaChevronUp } from 'react-icons/fa'; import { FiLink2 } from 'react-icons/fi'; import { IoIosRemoveCircle } from 'react-icons/io'; import { toast } from 'react-toastify'; -import { useRepositoryFilter } from '../../../../store'; +import { useRepositoryFilterStore } from '../../../../store'; import { Colors, palette } from '../../../../theme'; import FilterDate from './FilterDate'; import FilterSelect from './FilterSelect'; @@ -91,7 +91,7 @@ const StyledChip = withStyles(({ palette }) => ({ }))(Chip); function RepositoryFilterView(): React.ReactElement { - const [isExpanded, toggleFilter] = useRepositoryFilter(state => [state.isExpanded, state.toggleFilter]); + const [isExpanded, toggleFilter] = useRepositoryFilterStore(state => [state.isExpanded, state.toggleFilter]); const classes = useStyles(isExpanded); const [chips] = useState([ { diff --git a/client/src/pages/Repository/components/RepositoryTreeView/index.tsx b/client/src/pages/Repository/components/RepositoryTreeView/index.tsx index 332f3d2ae..3447178e8 100644 --- a/client/src/pages/Repository/components/RepositoryTreeView/index.tsx +++ b/client/src/pages/Repository/components/RepositoryTreeView/index.tsx @@ -5,7 +5,7 @@ import lodash from 'lodash'; import React, { useState } from 'react'; import { BsChevronDown, BsChevronRight } from 'react-icons/bs'; import { Loader } from '../../../../components'; -import { useRepositoryFilter } from '../../../../store'; +import { useRepositoryFilterStore } from '../../../../store'; import { getSortedTreeEntries, getSystemObjectTypesForFilter, getTreeWidth } from '../../../../utils/repository'; import { useGetRootObjects } from '../../hooks/useRepository'; import { RepositoryFilter } from '../../index'; @@ -50,7 +50,7 @@ interface RepositoryTreeViewProps { function RepositoryTreeView(props: RepositoryTreeViewProps): React.ReactElement { const { filter } = props; - const { isExpanded } = useRepositoryFilter(); + const { isExpanded } = useRepositoryFilterStore(); const classes = useStyles(isExpanded); const [expandedNodes, setExpandedNodes] = useState(new Map() as ExpandedNodeMap); diff --git a/client/src/store/item.ts b/client/src/store/item.ts index 70673744f..27bd0adb7 100644 --- a/client/src/store/item.ts +++ b/client/src/store/item.ts @@ -25,7 +25,7 @@ type ItemStore = { reset: () => void; }; -export const useItem = create((set: SetState, get: GetState) => ({ +export const useItemStore = create((set: SetState, get: GetState) => ({ items: [defaultItem], loading: false, getSelectedItem: (): StateItem | undefined => { diff --git a/client/src/store/metadata.ts b/client/src/store/metadata.ts index cc67b2f08..3a53c2a78 100644 --- a/client/src/store/metadata.ts +++ b/client/src/store/metadata.ts @@ -13,12 +13,12 @@ import { Project } from '../types/graphql'; import { eVocabularySetID } from '../types/server'; -import { useItem, StateItem } from './item'; -import { useProject, StateProject } from './project'; -import { useSubject, StateSubject } from './subject'; -import { useUpload, FileId, IngestionFile } from './upload'; +import { useItemStore, StateItem } from './item'; +import { useProjectStore, StateProject } from './project'; +import { useSubjectStore, StateSubject } from './subject'; +import { useUploadStore, FileId, IngestionFile } from './upload'; import { parseFileId, parseItemToState, parseProjectToState, parseSubjectUnitIdentifierToState } from './utils'; -import { useVocabulary } from './vocabulary'; +import { useVocabularyStore } from './vocabulary'; type MetadataInfo = { metadata: StateMetadata; @@ -113,11 +113,11 @@ type MetadataStore = { reset: () => void; }; -export const useMetadata = create((set: SetState, get: GetState) => ({ +export const useMetadataStore = create((set: SetState, get: GetState) => ({ metadatas: [], getSelectedIdentifiers: (metadata: StateMetadata): StateIdentifier[] | undefined => lodash.filter(metadata.photogrammetry.identifiers, { selected: true }), getFieldErrors: (metadata: StateMetadata): FieldErrors => { - const { getAssetType } = useVocabulary.getState(); + const { getAssetType } = useVocabularyStore.getState(); const errors: FieldErrors = { photogrammetry: { dateCaptured: false, @@ -156,11 +156,11 @@ export const useMetadata = create((set: SetState, }, updateMetadataSteps: async (): Promise => { const { getStateFolders } = get(); - const { completed, getSelectedFiles } = useUpload.getState(); - const { getInitialEntry } = useVocabulary.getState(); - const { addSubjects } = useSubject.getState(); - const { addProjects } = useProject.getState(); - const { addItems } = useItem.getState(); + const { completed, getSelectedFiles } = useUploadStore.getState(); + const { getInitialEntry } = useVocabularyStore.getState(); + const { addSubjects } = useSubjectStore.getState(); + const { addProjects } = useProjectStore.getState(); + const { addItems } = useItemStore.getState(); const selectedFiles = getSelectedFiles(completed, true); @@ -320,7 +320,7 @@ export const useMetadata = create((set: SetState, return stateFolders; }, getInitialStateFolders: (folders: string[]): StateFolder[] => { - const { getInitialEntry } = useVocabulary.getState(); + const { getInitialEntry } = useVocabularyStore.getState(); const stateFolders: StateFolder[] = folders.map((folder, index: number) => ({ id: index, name: folder, @@ -378,7 +378,7 @@ export const useMetadata = create((set: SetState, set({ metadatas: updatedMetadatas }); }, updateCameraSettings: async (metadatas: StateMetadata[]): Promise => { - const { getAssetType } = useVocabulary.getState(); + const { getAssetType } = useVocabularyStore.getState(); const updatedMetadatas = metadatas.slice(); diff --git a/client/src/store/project.ts b/client/src/store/project.ts index 927ae84fe..6ab64fb6a 100644 --- a/client/src/store/project.ts +++ b/client/src/store/project.ts @@ -18,10 +18,10 @@ type ProjectStore = { reset: () => void; }; -export const useProject = create((set: SetState, get: GetState) => ({ +export const useProjectStore = create((set: SetState, get: GetState) => ({ projects: [], loading: false, - getSelectedProject: () => { + getSelectedProject: (): StateProject | undefined => { const { projects } = get(); return lodash.find(projects, { selected: true }); }, diff --git a/client/src/store/repository.ts b/client/src/store/repository.ts index 665a22702..1779e09f3 100644 --- a/client/src/store/repository.ts +++ b/client/src/store/repository.ts @@ -7,7 +7,7 @@ type RepositoryStore = { toggleFilter: () => void; }; -export const useRepositoryFilter = create((set: SetState, get: GetState) => ({ +export const useRepositoryFilterStore = create((set: SetState, get: GetState) => ({ isExpanded: true, search: '', updateSearch: (value: string): void => { diff --git a/client/src/store/subject.ts b/client/src/store/subject.ts index caab4ca91..61fcaf7a8 100644 --- a/client/src/store/subject.ts +++ b/client/src/store/subject.ts @@ -12,8 +12,8 @@ import { GetIngestionItemsForSubjectsDocument, Item } from '../types/graphql'; -import { useItem, StateItem } from './item'; -import { useProject, StateProject } from './project'; +import { useItemStore, StateItem } from './item'; +import { useProjectStore, StateProject } from './project'; export type StateSubject = { id: number; @@ -31,7 +31,7 @@ type SubjectStore = { reset: () => void; }; -export const useSubject = create((set: SetState, get: GetState) => ({ +export const useSubjectStore = create((set: SetState, get: GetState) => ({ subjects: [], addSubjects: async (fetchedSubjects: StateSubject[]): Promise => { const { subjects } = get(); @@ -61,8 +61,8 @@ export const useSubject = create((set: SetState, get updateProjectsAndItemsForSubjects(selectedSubjects); }, updateProjectsAndItemsForSubjects: async (selectedSubjects: StateSubject[]): Promise => { - const { addProjects, loadingProjects } = useProject.getState(); - const { addItems, loadingItems } = useItem.getState(); + const { addProjects, loadingProjects } = useProjectStore.getState(); + const { addItems, loadingItems } = useItemStore.getState(); if (!selectedSubjects.length) { addItems([]); diff --git a/client/src/store/upload.ts b/client/src/store/upload.ts index 7f44c6a17..b20768e0b 100644 --- a/client/src/store/upload.ts +++ b/client/src/store/upload.ts @@ -3,7 +3,7 @@ import lodash from 'lodash'; import { toast } from 'react-toastify'; import { eVocabularySetID } from '../types/server'; import { generateFileId } from '../utils/upload'; -import { useVocabulary } from './vocabulary'; +import { useVocabularyStore } from './vocabulary'; import { apolloClient, apolloUploader } from '../graphql'; import { DiscardUploadedAssetVersionsDocument, DiscardUploadedAssetVersionsMutation, UploadAssetDocument, UploadAssetMutation, UploadStatus } from '../types/graphql'; import { FetchResult } from '@apollo/client'; @@ -50,7 +50,7 @@ type UploadStore = { reset: () => void; }; -export const useUpload = create((set: SetState, get: GetState) => ({ +export const useUploadStore = create((set: SetState, get: GetState) => ({ completed: [], pending: [], loading: true, @@ -65,7 +65,7 @@ export const useUpload = create((set: SetState, get: G const alreadyContains = !!lodash.find(pending, { id }); const { name, size } = file; - const { getInitialEntry } = useVocabulary.getState(); + const { getInitialEntry } = useVocabularyStore.getState(); const type = getInitialEntry(eVocabularySetID.eAssetAssetType); if (!type) { diff --git a/client/src/store/user.ts b/client/src/store/user.ts index bd6a29534..ea9e3179d 100644 --- a/client/src/store/user.ts +++ b/client/src/store/user.ts @@ -11,7 +11,7 @@ type UserStore = { logout: () => Promise; }; -export const useUser = create((set: SetState, get: GetState) => ({ +export const useUserStore = create((set: SetState, get: GetState) => ({ user: null, initialize: async () => { const { user } = get(); diff --git a/client/src/store/vocabulary.ts b/client/src/store/vocabulary.ts index 58d4c1c66..0f1d33add 100644 --- a/client/src/store/vocabulary.ts +++ b/client/src/store/vocabulary.ts @@ -20,7 +20,7 @@ type VocabularyStore = { getAssetType: (idVocabulary: number) => AssetType; }; -export const useVocabulary = create((set: SetState, get: GetState) => ({ +export const useVocabularyStore = create((set: SetState, get: GetState) => ({ vocabularies: new Map(), updateVocabularyEntries: async (): Promise => { const variables = { From b73f8c536cb8e759ec653134ad15d62eb4aa589a Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Tue, 13 Oct 2020 15:57:12 +0530 Subject: [PATCH 013/239] sort upload list by date --- .../components/Uploads/UploadCompleteList.tsx | 11 +- client/src/types/graphql.tsx | 2237 +++++++++-------- .../queries/asset/getUploadedAssetVersion.ts | 1 + 3 files changed, 1129 insertions(+), 1120 deletions(-) diff --git a/client/src/pages/Ingestion/components/Uploads/UploadCompleteList.tsx b/client/src/pages/Ingestion/components/Uploads/UploadCompleteList.tsx index cd4267a00..b402c4511 100644 --- a/client/src/pages/Ingestion/components/Uploads/UploadCompleteList.tsx +++ b/client/src/pages/Ingestion/components/Uploads/UploadCompleteList.tsx @@ -8,6 +8,7 @@ import { GetUploadedAssetVersionDocument } from '../../../../types/graphql'; import FileList from './FileList'; import { useUploadListStyles } from './UploadList'; import UploadListHeader from './UploadListHeader'; +import lodash from 'lodash'; function UploadListComplete(): React.ReactElement { const classes = useUploadListStyles(); @@ -21,13 +22,19 @@ function UploadListComplete(): React.ReactElement { const { AssetVersion } = getUploadedAssetVersion; const fileIds: string[] = completed.map(({ id }) => id); - const completedFiles = AssetVersion.map(assetVersion => { + const sortedAssetVersion = lodash.orderBy(AssetVersion, ['DateCreated'], ['desc']); + + if (!sortedAssetVersion) { + return; + } + + const completedFiles = sortedAssetVersion.map(assetVersion => { const { idAssetVersion } = assetVersion; const id = String(idAssetVersion); if (fileIds.includes(id)) { - return completed.find(file => file.id === id); + return completed.find(file => file.id === id) || assetVersion; } return parseAssetVersionToState(assetVersion, assetVersion.Asset.VAssetType); }); diff --git a/client/src/types/graphql.tsx b/client/src/types/graphql.tsx index 630dd210e..ad9c96251 100644 --- a/client/src/types/graphql.tsx +++ b/client/src/types/graphql.tsx @@ -1829,7 +1829,7 @@ export type GetUploadedAssetVersionQuery = ( & { AssetVersion: Array<( { __typename?: 'AssetVersion' } - & Pick + & Pick & { Asset?: Maybe<( { __typename?: 'Asset' } @@ -2342,31 +2342,31 @@ export type GetWorkflowQuery = ( export const DiscardUploadedAssetVersionsDocument = gql` - mutation discardUploadedAssetVersions($input: DiscardUploadedAssetVersionsInput!) { - discardUploadedAssetVersions(input: $input) { - success - } - } - `; + mutation discardUploadedAssetVersions($input: DiscardUploadedAssetVersionsInput!) { + discardUploadedAssetVersions(input: $input) { + success + } +} + `; export type DiscardUploadedAssetVersionsMutationFn = Apollo.MutationFunction; /** -* __useDiscardUploadedAssetVersionsMutation__ -* -* To run a mutation, you first call `useDiscardUploadedAssetVersionsMutation` within a React component and pass it any options that fit your needs. -* When your component renders, `useDiscardUploadedAssetVersionsMutation` returns a tuple that includes: -* - A mutate function that you can call at any time to execute the mutation -* - An object with fields that represent the current status of the mutation's execution -* -* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; -* -* @example -* const [discardUploadedAssetVersionsMutation, { data, loading, error }] = useDiscardUploadedAssetVersionsMutation({ - * variables: { - * input: // value for 'input' - * }, - * }); - */ + * __useDiscardUploadedAssetVersionsMutation__ + * + * To run a mutation, you first call `useDiscardUploadedAssetVersionsMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useDiscardUploadedAssetVersionsMutation` returns a tuple that includes: + * - A mutate function that you can call at any time to execute the mutation + * - An object with fields that represent the current status of the mutation's execution + * + * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; + * + * @example + * const [discardUploadedAssetVersionsMutation, { data, loading, error }] = useDiscardUploadedAssetVersionsMutation({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ export function useDiscardUploadedAssetVersionsMutation(baseOptions?: Apollo.MutationHookOptions) { return Apollo.useMutation(DiscardUploadedAssetVersionsDocument, baseOptions); } @@ -2374,34 +2374,34 @@ export type DiscardUploadedAssetVersionsMutationHookResult = ReturnType; export type DiscardUploadedAssetVersionsMutationOptions = Apollo.BaseMutationOptions; export const UploadAssetDocument = gql` - mutation uploadAsset($file: Upload!, $type: Int!) { - uploadAsset(file: $file, type: $type) { - status - idAssetVersions - error - } - } - `; + mutation uploadAsset($file: Upload!, $type: Int!) { + uploadAsset(file: $file, type: $type) { + status + idAssetVersions + error + } +} + `; export type UploadAssetMutationFn = Apollo.MutationFunction; /** -* __useUploadAssetMutation__ -* -* To run a mutation, you first call `useUploadAssetMutation` within a React component and pass it any options that fit your needs. -* When your component renders, `useUploadAssetMutation` returns a tuple that includes: -* - A mutate function that you can call at any time to execute the mutation -* - An object with fields that represent the current status of the mutation's execution -* -* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; -* -* @example -* const [uploadAssetMutation, { data, loading, error }] = useUploadAssetMutation({ - * variables: { - * file: // value for 'file' - * type: // value for 'type' - * }, - * }); - */ + * __useUploadAssetMutation__ + * + * To run a mutation, you first call `useUploadAssetMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useUploadAssetMutation` returns a tuple that includes: + * - A mutate function that you can call at any time to execute the mutation + * - An object with fields that represent the current status of the mutation's execution + * + * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; + * + * @example + * const [uploadAssetMutation, { data, loading, error }] = useUploadAssetMutation({ + * variables: { + * file: // value for 'file' + * type: // value for 'type' + * }, + * }); + */ export function useUploadAssetMutation(baseOptions?: Apollo.MutationHookOptions) { return Apollo.useMutation(UploadAssetDocument, baseOptions); } @@ -2409,33 +2409,33 @@ export type UploadAssetMutationHookResult = ReturnType; export type UploadAssetMutationOptions = Apollo.BaseMutationOptions; export const CreateCaptureDataDocument = gql` - mutation createCaptureData($input: CreateCaptureDataInput!) { - createCaptureData(input: $input) { - CaptureData { - idCaptureData - } - } - } - `; + mutation createCaptureData($input: CreateCaptureDataInput!) { + createCaptureData(input: $input) { + CaptureData { + idCaptureData + } + } +} + `; export type CreateCaptureDataMutationFn = Apollo.MutationFunction; /** -* __useCreateCaptureDataMutation__ -* -* To run a mutation, you first call `useCreateCaptureDataMutation` within a React component and pass it any options that fit your needs. -* When your component renders, `useCreateCaptureDataMutation` returns a tuple that includes: -* - A mutate function that you can call at any time to execute the mutation -* - An object with fields that represent the current status of the mutation's execution -* -* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; -* -* @example -* const [createCaptureDataMutation, { data, loading, error }] = useCreateCaptureDataMutation({ - * variables: { - * input: // value for 'input' - * }, - * }); - */ + * __useCreateCaptureDataMutation__ + * + * To run a mutation, you first call `useCreateCaptureDataMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useCreateCaptureDataMutation` returns a tuple that includes: + * - A mutate function that you can call at any time to execute the mutation + * - An object with fields that represent the current status of the mutation's execution + * + * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; + * + * @example + * const [createCaptureDataMutation, { data, loading, error }] = useCreateCaptureDataMutation({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ export function useCreateCaptureDataMutation(baseOptions?: Apollo.MutationHookOptions) { return Apollo.useMutation(CreateCaptureDataDocument, baseOptions); } @@ -2443,33 +2443,33 @@ export type CreateCaptureDataMutationHookResult = ReturnType; export type CreateCaptureDataMutationOptions = Apollo.BaseMutationOptions; export const CreateCaptureDataPhotoDocument = gql` - mutation createCaptureDataPhoto($input: CreateCaptureDataPhotoInput!) { - createCaptureDataPhoto(input: $input) { - CaptureDataPhoto { - idCaptureDataPhoto - } - } - } - `; + mutation createCaptureDataPhoto($input: CreateCaptureDataPhotoInput!) { + createCaptureDataPhoto(input: $input) { + CaptureDataPhoto { + idCaptureDataPhoto + } + } +} + `; export type CreateCaptureDataPhotoMutationFn = Apollo.MutationFunction; /** -* __useCreateCaptureDataPhotoMutation__ -* -* To run a mutation, you first call `useCreateCaptureDataPhotoMutation` within a React component and pass it any options that fit your needs. -* When your component renders, `useCreateCaptureDataPhotoMutation` returns a tuple that includes: -* - A mutate function that you can call at any time to execute the mutation -* - An object with fields that represent the current status of the mutation's execution -* -* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; -* -* @example -* const [createCaptureDataPhotoMutation, { data, loading, error }] = useCreateCaptureDataPhotoMutation({ - * variables: { - * input: // value for 'input' - * }, - * }); - */ + * __useCreateCaptureDataPhotoMutation__ + * + * To run a mutation, you first call `useCreateCaptureDataPhotoMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useCreateCaptureDataPhotoMutation` returns a tuple that includes: + * - A mutate function that you can call at any time to execute the mutation + * - An object with fields that represent the current status of the mutation's execution + * + * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; + * + * @example + * const [createCaptureDataPhotoMutation, { data, loading, error }] = useCreateCaptureDataPhotoMutation({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ export function useCreateCaptureDataPhotoMutation(baseOptions?: Apollo.MutationHookOptions) { return Apollo.useMutation(CreateCaptureDataPhotoDocument, baseOptions); } @@ -2477,31 +2477,31 @@ export type CreateCaptureDataPhotoMutationHookResult = ReturnType; export type CreateCaptureDataPhotoMutationOptions = Apollo.BaseMutationOptions; export const IngestDataDocument = gql` - mutation ingestData($input: IngestDataInput!) { - ingestData(input: $input) { - success - } - } - `; + mutation ingestData($input: IngestDataInput!) { + ingestData(input: $input) { + success + } +} + `; export type IngestDataMutationFn = Apollo.MutationFunction; /** -* __useIngestDataMutation__ -* -* To run a mutation, you first call `useIngestDataMutation` within a React component and pass it any options that fit your needs. -* When your component renders, `useIngestDataMutation` returns a tuple that includes: -* - A mutate function that you can call at any time to execute the mutation -* - An object with fields that represent the current status of the mutation's execution -* -* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; -* -* @example -* const [ingestDataMutation, { data, loading, error }] = useIngestDataMutation({ - * variables: { - * input: // value for 'input' - * }, - * }); - */ + * __useIngestDataMutation__ + * + * To run a mutation, you first call `useIngestDataMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useIngestDataMutation` returns a tuple that includes: + * - A mutate function that you can call at any time to execute the mutation + * - An object with fields that represent the current status of the mutation's execution + * + * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; + * + * @example + * const [ingestDataMutation, { data, loading, error }] = useIngestDataMutation({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ export function useIngestDataMutation(baseOptions?: Apollo.MutationHookOptions) { return Apollo.useMutation(IngestDataDocument, baseOptions); } @@ -2509,33 +2509,33 @@ export type IngestDataMutationHookResult = ReturnType; export type IngestDataMutationOptions = Apollo.BaseMutationOptions; export const CreateModelDocument = gql` - mutation createModel($input: CreateModelInput!) { - createModel(input: $input) { - Model { - idModel - } - } - } - `; + mutation createModel($input: CreateModelInput!) { + createModel(input: $input) { + Model { + idModel + } + } +} + `; export type CreateModelMutationFn = Apollo.MutationFunction; /** -* __useCreateModelMutation__ -* -* To run a mutation, you first call `useCreateModelMutation` within a React component and pass it any options that fit your needs. -* When your component renders, `useCreateModelMutation` returns a tuple that includes: -* - A mutate function that you can call at any time to execute the mutation -* - An object with fields that represent the current status of the mutation's execution -* -* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; -* -* @example -* const [createModelMutation, { data, loading, error }] = useCreateModelMutation({ - * variables: { - * input: // value for 'input' - * }, - * }); - */ + * __useCreateModelMutation__ + * + * To run a mutation, you first call `useCreateModelMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useCreateModelMutation` returns a tuple that includes: + * - A mutate function that you can call at any time to execute the mutation + * - An object with fields that represent the current status of the mutation's execution + * + * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; + * + * @example + * const [createModelMutation, { data, loading, error }] = useCreateModelMutation({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ export function useCreateModelMutation(baseOptions?: Apollo.MutationHookOptions) { return Apollo.useMutation(CreateModelDocument, baseOptions); } @@ -2543,33 +2543,33 @@ export type CreateModelMutationHookResult = ReturnType; export type CreateModelMutationOptions = Apollo.BaseMutationOptions; export const CreateSceneDocument = gql` - mutation createScene($input: CreateSceneInput!) { - createScene(input: $input) { - Scene { - idScene - } - } - } - `; + mutation createScene($input: CreateSceneInput!) { + createScene(input: $input) { + Scene { + idScene + } + } +} + `; export type CreateSceneMutationFn = Apollo.MutationFunction; /** -* __useCreateSceneMutation__ -* -* To run a mutation, you first call `useCreateSceneMutation` within a React component and pass it any options that fit your needs. -* When your component renders, `useCreateSceneMutation` returns a tuple that includes: -* - A mutate function that you can call at any time to execute the mutation -* - An object with fields that represent the current status of the mutation's execution -* -* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; -* -* @example -* const [createSceneMutation, { data, loading, error }] = useCreateSceneMutation({ - * variables: { - * input: // value for 'input' - * }, - * }); - */ + * __useCreateSceneMutation__ + * + * To run a mutation, you first call `useCreateSceneMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useCreateSceneMutation` returns a tuple that includes: + * - A mutate function that you can call at any time to execute the mutation + * - An object with fields that represent the current status of the mutation's execution + * + * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; + * + * @example + * const [createSceneMutation, { data, loading, error }] = useCreateSceneMutation({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ export function useCreateSceneMutation(baseOptions?: Apollo.MutationHookOptions) { return Apollo.useMutation(CreateSceneDocument, baseOptions); } @@ -2577,33 +2577,33 @@ export type CreateSceneMutationHookResult = ReturnType; export type CreateSceneMutationOptions = Apollo.BaseMutationOptions; export const CreateItemDocument = gql` - mutation createItem($input: CreateItemInput!) { - createItem(input: $input) { - Item { - idItem - } - } - } - `; + mutation createItem($input: CreateItemInput!) { + createItem(input: $input) { + Item { + idItem + } + } +} + `; export type CreateItemMutationFn = Apollo.MutationFunction; /** -* __useCreateItemMutation__ -* -* To run a mutation, you first call `useCreateItemMutation` within a React component and pass it any options that fit your needs. -* When your component renders, `useCreateItemMutation` returns a tuple that includes: -* - A mutate function that you can call at any time to execute the mutation -* - An object with fields that represent the current status of the mutation's execution -* -* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; -* -* @example -* const [createItemMutation, { data, loading, error }] = useCreateItemMutation({ - * variables: { - * input: // value for 'input' - * }, - * }); - */ + * __useCreateItemMutation__ + * + * To run a mutation, you first call `useCreateItemMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useCreateItemMutation` returns a tuple that includes: + * - A mutate function that you can call at any time to execute the mutation + * - An object with fields that represent the current status of the mutation's execution + * + * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; + * + * @example + * const [createItemMutation, { data, loading, error }] = useCreateItemMutation({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ export function useCreateItemMutation(baseOptions?: Apollo.MutationHookOptions) { return Apollo.useMutation(CreateItemDocument, baseOptions); } @@ -2611,33 +2611,33 @@ export type CreateItemMutationHookResult = ReturnType; export type CreateItemMutationOptions = Apollo.BaseMutationOptions; export const CreateProjectDocument = gql` - mutation createProject($input: CreateProjectInput!) { - createProject(input: $input) { - Project { - idProject - } - } - } - `; + mutation createProject($input: CreateProjectInput!) { + createProject(input: $input) { + Project { + idProject + } + } +} + `; export type CreateProjectMutationFn = Apollo.MutationFunction; /** -* __useCreateProjectMutation__ -* -* To run a mutation, you first call `useCreateProjectMutation` within a React component and pass it any options that fit your needs. -* When your component renders, `useCreateProjectMutation` returns a tuple that includes: -* - A mutate function that you can call at any time to execute the mutation -* - An object with fields that represent the current status of the mutation's execution -* -* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; -* -* @example -* const [createProjectMutation, { data, loading, error }] = useCreateProjectMutation({ - * variables: { - * input: // value for 'input' - * }, - * }); - */ + * __useCreateProjectMutation__ + * + * To run a mutation, you first call `useCreateProjectMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useCreateProjectMutation` returns a tuple that includes: + * - A mutate function that you can call at any time to execute the mutation + * - An object with fields that represent the current status of the mutation's execution + * + * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; + * + * @example + * const [createProjectMutation, { data, loading, error }] = useCreateProjectMutation({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ export function useCreateProjectMutation(baseOptions?: Apollo.MutationHookOptions) { return Apollo.useMutation(CreateProjectDocument, baseOptions); } @@ -2645,33 +2645,33 @@ export type CreateProjectMutationHookResult = ReturnType; export type CreateProjectMutationOptions = Apollo.BaseMutationOptions; export const CreateSubjectDocument = gql` - mutation createSubject($input: CreateSubjectInput!) { - createSubject(input: $input) { - Subject { - idSubject - } - } - } - `; + mutation createSubject($input: CreateSubjectInput!) { + createSubject(input: $input) { + Subject { + idSubject + } + } +} + `; export type CreateSubjectMutationFn = Apollo.MutationFunction; /** -* __useCreateSubjectMutation__ -* -* To run a mutation, you first call `useCreateSubjectMutation` within a React component and pass it any options that fit your needs. -* When your component renders, `useCreateSubjectMutation` returns a tuple that includes: -* - A mutate function that you can call at any time to execute the mutation -* - An object with fields that represent the current status of the mutation's execution -* -* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; -* -* @example -* const [createSubjectMutation, { data, loading, error }] = useCreateSubjectMutation({ - * variables: { - * input: // value for 'input' - * }, - * }); - */ + * __useCreateSubjectMutation__ + * + * To run a mutation, you first call `useCreateSubjectMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useCreateSubjectMutation` returns a tuple that includes: + * - A mutate function that you can call at any time to execute the mutation + * - An object with fields that represent the current status of the mutation's execution + * + * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; + * + * @example + * const [createSubjectMutation, { data, loading, error }] = useCreateSubjectMutation({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ export function useCreateSubjectMutation(baseOptions?: Apollo.MutationHookOptions) { return Apollo.useMutation(CreateSubjectDocument, baseOptions); } @@ -2679,33 +2679,33 @@ export type CreateSubjectMutationHookResult = ReturnType; export type CreateSubjectMutationOptions = Apollo.BaseMutationOptions; export const CreateUnitDocument = gql` - mutation createUnit($input: CreateUnitInput!) { - createUnit(input: $input) { - Unit { - idUnit - } - } - } - `; + mutation createUnit($input: CreateUnitInput!) { + createUnit(input: $input) { + Unit { + idUnit + } + } +} + `; export type CreateUnitMutationFn = Apollo.MutationFunction; /** -* __useCreateUnitMutation__ -* -* To run a mutation, you first call `useCreateUnitMutation` within a React component and pass it any options that fit your needs. -* When your component renders, `useCreateUnitMutation` returns a tuple that includes: -* - A mutate function that you can call at any time to execute the mutation -* - An object with fields that represent the current status of the mutation's execution -* -* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; -* -* @example -* const [createUnitMutation, { data, loading, error }] = useCreateUnitMutation({ - * variables: { - * input: // value for 'input' - * }, - * }); - */ + * __useCreateUnitMutation__ + * + * To run a mutation, you first call `useCreateUnitMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useCreateUnitMutation` returns a tuple that includes: + * - A mutate function that you can call at any time to execute the mutation + * - An object with fields that represent the current status of the mutation's execution + * + * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; + * + * @example + * const [createUnitMutation, { data, loading, error }] = useCreateUnitMutation({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ export function useCreateUnitMutation(baseOptions?: Apollo.MutationHookOptions) { return Apollo.useMutation(CreateUnitDocument, baseOptions); } @@ -2713,36 +2713,36 @@ export type CreateUnitMutationHookResult = ReturnType; export type CreateUnitMutationOptions = Apollo.BaseMutationOptions; export const CreateUserDocument = gql` - mutation createUser($input: CreateUserInput!) { - createUser(input: $input) { - User { - idUser - Name - Active - DateActivated - } - } - } - `; + mutation createUser($input: CreateUserInput!) { + createUser(input: $input) { + User { + idUser + Name + Active + DateActivated + } + } +} + `; export type CreateUserMutationFn = Apollo.MutationFunction; /** -* __useCreateUserMutation__ -* -* To run a mutation, you first call `useCreateUserMutation` within a React component and pass it any options that fit your needs. -* When your component renders, `useCreateUserMutation` returns a tuple that includes: -* - A mutate function that you can call at any time to execute the mutation -* - An object with fields that represent the current status of the mutation's execution -* -* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; -* -* @example -* const [createUserMutation, { data, loading, error }] = useCreateUserMutation({ - * variables: { - * input: // value for 'input' - * }, - * }); - */ + * __useCreateUserMutation__ + * + * To run a mutation, you first call `useCreateUserMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useCreateUserMutation` returns a tuple that includes: + * - A mutate function that you can call at any time to execute the mutation + * - An object with fields that represent the current status of the mutation's execution + * + * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; + * + * @example + * const [createUserMutation, { data, loading, error }] = useCreateUserMutation({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ export function useCreateUserMutation(baseOptions?: Apollo.MutationHookOptions) { return Apollo.useMutation(CreateUserDocument, baseOptions); } @@ -2750,33 +2750,33 @@ export type CreateUserMutationHookResult = ReturnType; export type CreateUserMutationOptions = Apollo.BaseMutationOptions; export const CreateVocabularyDocument = gql` - mutation createVocabulary($input: CreateVocabularyInput!) { - createVocabulary(input: $input) { - Vocabulary { - idVocabulary - } - } - } - `; + mutation createVocabulary($input: CreateVocabularyInput!) { + createVocabulary(input: $input) { + Vocabulary { + idVocabulary + } + } +} + `; export type CreateVocabularyMutationFn = Apollo.MutationFunction; /** -* __useCreateVocabularyMutation__ -* -* To run a mutation, you first call `useCreateVocabularyMutation` within a React component and pass it any options that fit your needs. -* When your component renders, `useCreateVocabularyMutation` returns a tuple that includes: -* - A mutate function that you can call at any time to execute the mutation -* - An object with fields that represent the current status of the mutation's execution -* -* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; -* -* @example -* const [createVocabularyMutation, { data, loading, error }] = useCreateVocabularyMutation({ - * variables: { - * input: // value for 'input' - * }, - * }); - */ + * __useCreateVocabularyMutation__ + * + * To run a mutation, you first call `useCreateVocabularyMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useCreateVocabularyMutation` returns a tuple that includes: + * - A mutate function that you can call at any time to execute the mutation + * - An object with fields that represent the current status of the mutation's execution + * + * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; + * + * @example + * const [createVocabularyMutation, { data, loading, error }] = useCreateVocabularyMutation({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ export function useCreateVocabularyMutation(baseOptions?: Apollo.MutationHookOptions) { return Apollo.useMutation(CreateVocabularyDocument, baseOptions); } @@ -2784,33 +2784,33 @@ export type CreateVocabularyMutationHookResult = ReturnType; export type CreateVocabularyMutationOptions = Apollo.BaseMutationOptions; export const CreateVocabularySetDocument = gql` - mutation createVocabularySet($input: CreateVocabularySetInput!) { - createVocabularySet(input: $input) { - VocabularySet { - idVocabularySet - } - } - } - `; + mutation createVocabularySet($input: CreateVocabularySetInput!) { + createVocabularySet(input: $input) { + VocabularySet { + idVocabularySet + } + } +} + `; export type CreateVocabularySetMutationFn = Apollo.MutationFunction; /** -* __useCreateVocabularySetMutation__ -* -* To run a mutation, you first call `useCreateVocabularySetMutation` within a React component and pass it any options that fit your needs. -* When your component renders, `useCreateVocabularySetMutation` returns a tuple that includes: -* - A mutate function that you can call at any time to execute the mutation -* - An object with fields that represent the current status of the mutation's execution -* -* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; -* -* @example -* const [createVocabularySetMutation, { data, loading, error }] = useCreateVocabularySetMutation({ - * variables: { - * input: // value for 'input' - * }, - * }); - */ + * __useCreateVocabularySetMutation__ + * + * To run a mutation, you first call `useCreateVocabularySetMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useCreateVocabularySetMutation` returns a tuple that includes: + * - A mutate function that you can call at any time to execute the mutation + * - An object with fields that represent the current status of the mutation's execution + * + * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; + * + * @example + * const [createVocabularySetMutation, { data, loading, error }] = useCreateVocabularySetMutation({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ export function useCreateVocabularySetMutation(baseOptions?: Apollo.MutationHookOptions) { return Apollo.useMutation(CreateVocabularySetDocument, baseOptions); } @@ -2818,31 +2818,31 @@ export type CreateVocabularySetMutationHookResult = ReturnType; export type CreateVocabularySetMutationOptions = Apollo.BaseMutationOptions; export const GetAccessPolicyDocument = gql` - query getAccessPolicy($input: GetAccessPolicyInput!) { - getAccessPolicy(input: $input) { - AccessPolicy { - idAccessPolicy - } - } - } - `; + query getAccessPolicy($input: GetAccessPolicyInput!) { + getAccessPolicy(input: $input) { + AccessPolicy { + idAccessPolicy + } + } +} + `; /** -* __useGetAccessPolicyQuery__ -* -* To run a query within a React component, call `useGetAccessPolicyQuery` and pass it any options that fit your needs. -* When your component renders, `useGetAccessPolicyQuery` returns an object from Apollo Client that contains loading, error, and data properties -* you can use to render your UI. -* -* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; -* -* @example -* const { data, loading, error } = useGetAccessPolicyQuery({ - * variables: { - * input: // value for 'input' - * }, - * }); - */ + * __useGetAccessPolicyQuery__ + * + * To run a query within a React component, call `useGetAccessPolicyQuery` and pass it any options that fit your needs. + * When your component renders, `useGetAccessPolicyQuery` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. + * + * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useGetAccessPolicyQuery({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ export function useGetAccessPolicyQuery(baseOptions?: Apollo.QueryHookOptions) { return Apollo.useQuery(GetAccessPolicyDocument, baseOptions); } @@ -2853,31 +2853,31 @@ export type GetAccessPolicyQueryHookResult = ReturnType; export type GetAccessPolicyQueryResult = Apollo.QueryResult; export const GetAssetDocument = gql` - query getAsset($input: GetAssetInput!) { - getAsset(input: $input) { - Asset { - idAsset - } - } - } - `; + query getAsset($input: GetAssetInput!) { + getAsset(input: $input) { + Asset { + idAsset + } + } +} + `; /** -* __useGetAssetQuery__ -* -* To run a query within a React component, call `useGetAssetQuery` and pass it any options that fit your needs. -* When your component renders, `useGetAssetQuery` returns an object from Apollo Client that contains loading, error, and data properties -* you can use to render your UI. -* -* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; -* -* @example -* const { data, loading, error } = useGetAssetQuery({ - * variables: { - * input: // value for 'input' - * }, - * }); - */ + * __useGetAssetQuery__ + * + * To run a query within a React component, call `useGetAssetQuery` and pass it any options that fit your needs. + * When your component renders, `useGetAssetQuery` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. + * + * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useGetAssetQuery({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ export function useGetAssetQuery(baseOptions?: Apollo.QueryHookOptions) { return Apollo.useQuery(GetAssetDocument, baseOptions); } @@ -2888,85 +2888,85 @@ export type GetAssetQueryHookResult = ReturnType; export type GetAssetLazyQueryHookResult = ReturnType; export type GetAssetQueryResult = Apollo.QueryResult; export const GetAssetVersionsDetailsDocument = gql` - query getAssetVersionsDetails($input: GetAssetVersionsDetailsInput!) { - getAssetVersionsDetails(input: $input) { - valid - Details { - idAssetVersion - SubjectUnitIdentifier { - idSubject - SubjectName - UnitAbbreviation - IdentifierPublic - IdentifierCollection - } - Project { - idProject - Name - } - Item { - idItem - Name - EntireSubject - } - CaptureDataPhoto { - idAssetVersion - dateCaptured - datasetType - systemCreated - description - cameraSettingUniform - datasetFieldId - itemPositionType - itemPositionFieldId - itemArrangementFieldId - focusType - lightsourceType - backgroundRemovalMethod - clusterType - clusterGeometryFieldId - directory - folders { - name - variantType - } - identifiers { - identifier - identifierType - } - } - Model { - idAssetVersion - authoritative - dateCreated - creationMethod - modality - purpose - units - master - directory - } - } - } - } - `; + query getAssetVersionsDetails($input: GetAssetVersionsDetailsInput!) { + getAssetVersionsDetails(input: $input) { + valid + Details { + idAssetVersion + SubjectUnitIdentifier { + idSubject + SubjectName + UnitAbbreviation + IdentifierPublic + IdentifierCollection + } + Project { + idProject + Name + } + Item { + idItem + Name + EntireSubject + } + CaptureDataPhoto { + idAssetVersion + dateCaptured + datasetType + systemCreated + description + cameraSettingUniform + datasetFieldId + itemPositionType + itemPositionFieldId + itemArrangementFieldId + focusType + lightsourceType + backgroundRemovalMethod + clusterType + clusterGeometryFieldId + directory + folders { + name + variantType + } + identifiers { + identifier + identifierType + } + } + Model { + idAssetVersion + authoritative + dateCreated + creationMethod + modality + purpose + units + master + directory + } + } + } +} + `; /** -* __useGetAssetVersionsDetailsQuery__ -* -* To run a query within a React component, call `useGetAssetVersionsDetailsQuery` and pass it any options that fit your needs. -* When your component renders, `useGetAssetVersionsDetailsQuery` returns an object from Apollo Client that contains loading, error, and data properties -* you can use to render your UI. -* -* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; -* -* @example -* const { data, loading, error } = useGetAssetVersionsDetailsQuery({ - * variables: { - * input: // value for 'input' - * }, - * }); - */ + * __useGetAssetVersionsDetailsQuery__ + * + * To run a query within a React component, call `useGetAssetVersionsDetailsQuery` and pass it any options that fit your needs. + * When your component renders, `useGetAssetVersionsDetailsQuery` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. + * + * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useGetAssetVersionsDetailsQuery({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ export function useGetAssetVersionsDetailsQuery(baseOptions?: Apollo.QueryHookOptions) { return Apollo.useQuery(GetAssetVersionsDetailsDocument, baseOptions); } @@ -2977,33 +2977,33 @@ export type GetAssetVersionsDetailsQueryHookResult = ReturnType; export type GetAssetVersionsDetailsQueryResult = Apollo.QueryResult; export const GetContentsForAssetVersionsDocument = gql` - query getContentsForAssetVersions($input: GetContentsForAssetVersionsInput!) { - getContentsForAssetVersions(input: $input) { - AssetVersionContent { - idAssetVersion - folders - all - } - } - } - `; + query getContentsForAssetVersions($input: GetContentsForAssetVersionsInput!) { + getContentsForAssetVersions(input: $input) { + AssetVersionContent { + idAssetVersion + folders + all + } + } +} + `; /** -* __useGetContentsForAssetVersionsQuery__ -* -* To run a query within a React component, call `useGetContentsForAssetVersionsQuery` and pass it any options that fit your needs. -* When your component renders, `useGetContentsForAssetVersionsQuery` returns an object from Apollo Client that contains loading, error, and data properties -* you can use to render your UI. -* -* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; -* -* @example -* const { data, loading, error } = useGetContentsForAssetVersionsQuery({ - * variables: { - * input: // value for 'input' - * }, - * }); - */ + * __useGetContentsForAssetVersionsQuery__ + * + * To run a query within a React component, call `useGetContentsForAssetVersionsQuery` and pass it any options that fit your needs. + * When your component renders, `useGetContentsForAssetVersionsQuery` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. + * + * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useGetContentsForAssetVersionsQuery({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ export function useGetContentsForAssetVersionsQuery(baseOptions?: Apollo.QueryHookOptions) { return Apollo.useQuery(GetContentsForAssetVersionsDocument, baseOptions); } @@ -3014,39 +3014,40 @@ export type GetContentsForAssetVersionsQueryHookResult = ReturnType; export type GetContentsForAssetVersionsQueryResult = Apollo.QueryResult; export const GetUploadedAssetVersionDocument = gql` - query getUploadedAssetVersion { - getUploadedAssetVersion { - AssetVersion { - idAssetVersion - StorageSize - FileName - Asset { - idAsset - VAssetType { - idVocabulary - Term - } - } - } - } - } - `; + query getUploadedAssetVersion { + getUploadedAssetVersion { + AssetVersion { + idAssetVersion + StorageSize + FileName + DateCreated + Asset { + idAsset + VAssetType { + idVocabulary + Term + } + } + } + } +} + `; /** -* __useGetUploadedAssetVersionQuery__ -* -* To run a query within a React component, call `useGetUploadedAssetVersionQuery` and pass it any options that fit your needs. -* When your component renders, `useGetUploadedAssetVersionQuery` returns an object from Apollo Client that contains loading, error, and data properties -* you can use to render your UI. -* -* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; -* -* @example -* const { data, loading, error } = useGetUploadedAssetVersionQuery({ - * variables: { - * }, - * }); - */ + * __useGetUploadedAssetVersionQuery__ + * + * To run a query within a React component, call `useGetUploadedAssetVersionQuery` and pass it any options that fit your needs. + * When your component renders, `useGetUploadedAssetVersionQuery` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. + * + * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useGetUploadedAssetVersionQuery({ + * variables: { + * }, + * }); + */ export function useGetUploadedAssetVersionQuery(baseOptions?: Apollo.QueryHookOptions) { return Apollo.useQuery(GetUploadedAssetVersionDocument, baseOptions); } @@ -3057,31 +3058,31 @@ export type GetUploadedAssetVersionQueryHookResult = ReturnType; export type GetUploadedAssetVersionQueryResult = Apollo.QueryResult; export const GetCaptureDataDocument = gql` - query getCaptureData($input: GetCaptureDataInput!) { - getCaptureData(input: $input) { - CaptureData { - idCaptureData - } - } - } - `; + query getCaptureData($input: GetCaptureDataInput!) { + getCaptureData(input: $input) { + CaptureData { + idCaptureData + } + } +} + `; /** -* __useGetCaptureDataQuery__ -* -* To run a query within a React component, call `useGetCaptureDataQuery` and pass it any options that fit your needs. -* When your component renders, `useGetCaptureDataQuery` returns an object from Apollo Client that contains loading, error, and data properties -* you can use to render your UI. -* -* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; -* -* @example -* const { data, loading, error } = useGetCaptureDataQuery({ - * variables: { - * input: // value for 'input' - * }, - * }); - */ + * __useGetCaptureDataQuery__ + * + * To run a query within a React component, call `useGetCaptureDataQuery` and pass it any options that fit your needs. + * When your component renders, `useGetCaptureDataQuery` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. + * + * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useGetCaptureDataQuery({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ export function useGetCaptureDataQuery(baseOptions?: Apollo.QueryHookOptions) { return Apollo.useQuery(GetCaptureDataDocument, baseOptions); } @@ -3092,31 +3093,31 @@ export type GetCaptureDataQueryHookResult = ReturnType; export type GetCaptureDataQueryResult = Apollo.QueryResult; export const GetCaptureDataPhotoDocument = gql` - query getCaptureDataPhoto($input: GetCaptureDataPhotoInput!) { - getCaptureDataPhoto(input: $input) { - CaptureDataPhoto { - idCaptureDataPhoto - } - } - } - `; + query getCaptureDataPhoto($input: GetCaptureDataPhotoInput!) { + getCaptureDataPhoto(input: $input) { + CaptureDataPhoto { + idCaptureDataPhoto + } + } +} + `; /** -* __useGetCaptureDataPhotoQuery__ -* -* To run a query within a React component, call `useGetCaptureDataPhotoQuery` and pass it any options that fit your needs. -* When your component renders, `useGetCaptureDataPhotoQuery` returns an object from Apollo Client that contains loading, error, and data properties -* you can use to render your UI. -* -* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; -* -* @example -* const { data, loading, error } = useGetCaptureDataPhotoQuery({ - * variables: { - * input: // value for 'input' - * }, - * }); - */ + * __useGetCaptureDataPhotoQuery__ + * + * To run a query within a React component, call `useGetCaptureDataPhotoQuery` and pass it any options that fit your needs. + * When your component renders, `useGetCaptureDataPhotoQuery` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. + * + * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useGetCaptureDataPhotoQuery({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ export function useGetCaptureDataPhotoQuery(baseOptions?: Apollo.QueryHookOptions) { return Apollo.useQuery(GetCaptureDataPhotoDocument, baseOptions); } @@ -3127,29 +3128,29 @@ export type GetCaptureDataPhotoQueryHookResult = ReturnType; export type GetCaptureDataPhotoQueryResult = Apollo.QueryResult; export const AreCameraSettingsUniformDocument = gql` - query areCameraSettingsUniform($input: AreCameraSettingsUniformInput!) { - areCameraSettingsUniform(input: $input) { - isUniform - } - } - `; + query areCameraSettingsUniform($input: AreCameraSettingsUniformInput!) { + areCameraSettingsUniform(input: $input) { + isUniform + } +} + `; /** -* __useAreCameraSettingsUniformQuery__ -* -* To run a query within a React component, call `useAreCameraSettingsUniformQuery` and pass it any options that fit your needs. -* When your component renders, `useAreCameraSettingsUniformQuery` returns an object from Apollo Client that contains loading, error, and data properties -* you can use to render your UI. -* -* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; -* -* @example -* const { data, loading, error } = useAreCameraSettingsUniformQuery({ - * variables: { - * input: // value for 'input' - * }, - * }); - */ + * __useAreCameraSettingsUniformQuery__ + * + * To run a query within a React component, call `useAreCameraSettingsUniformQuery` and pass it any options that fit your needs. + * When your component renders, `useAreCameraSettingsUniformQuery` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. + * + * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useAreCameraSettingsUniformQuery({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ export function useAreCameraSettingsUniformQuery(baseOptions?: Apollo.QueryHookOptions) { return Apollo.useQuery(AreCameraSettingsUniformDocument, baseOptions); } @@ -3160,31 +3161,31 @@ export type AreCameraSettingsUniformQueryHookResult = ReturnType; export type AreCameraSettingsUniformQueryResult = Apollo.QueryResult; export const GetLicenseDocument = gql` - query getLicense($input: GetLicenseInput!) { - getLicense(input: $input) { - License { - idLicense - } - } - } - `; + query getLicense($input: GetLicenseInput!) { + getLicense(input: $input) { + License { + idLicense + } + } +} + `; /** -* __useGetLicenseQuery__ -* -* To run a query within a React component, call `useGetLicenseQuery` and pass it any options that fit your needs. -* When your component renders, `useGetLicenseQuery` returns an object from Apollo Client that contains loading, error, and data properties -* you can use to render your UI. -* -* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; -* -* @example -* const { data, loading, error } = useGetLicenseQuery({ - * variables: { - * input: // value for 'input' - * }, - * }); - */ + * __useGetLicenseQuery__ + * + * To run a query within a React component, call `useGetLicenseQuery` and pass it any options that fit your needs. + * When your component renders, `useGetLicenseQuery` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. + * + * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useGetLicenseQuery({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ export function useGetLicenseQuery(baseOptions?: Apollo.QueryHookOptions) { return Apollo.useQuery(GetLicenseDocument, baseOptions); } @@ -3195,31 +3196,31 @@ export type GetLicenseQueryHookResult = ReturnType; export type GetLicenseLazyQueryHookResult = ReturnType; export type GetLicenseQueryResult = Apollo.QueryResult; export const GetModelDocument = gql` - query getModel($input: GetModelInput!) { - getModel(input: $input) { - Model { - idModel - } - } - } - `; + query getModel($input: GetModelInput!) { + getModel(input: $input) { + Model { + idModel + } + } +} + `; /** -* __useGetModelQuery__ -* -* To run a query within a React component, call `useGetModelQuery` and pass it any options that fit your needs. -* When your component renders, `useGetModelQuery` returns an object from Apollo Client that contains loading, error, and data properties -* you can use to render your UI. -* -* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; -* -* @example -* const { data, loading, error } = useGetModelQuery({ - * variables: { - * input: // value for 'input' - * }, - * }); - */ + * __useGetModelQuery__ + * + * To run a query within a React component, call `useGetModelQuery` and pass it any options that fit your needs. + * When your component renders, `useGetModelQuery` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. + * + * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useGetModelQuery({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ export function useGetModelQuery(baseOptions?: Apollo.QueryHookOptions) { return Apollo.useQuery(GetModelDocument, baseOptions); } @@ -3230,38 +3231,38 @@ export type GetModelQueryHookResult = ReturnType; export type GetModelLazyQueryHookResult = ReturnType; export type GetModelQueryResult = Apollo.QueryResult; export const GetObjectChildrenDocument = gql` - query getObjectChildren($input: GetObjectChildrenInput!) { - getObjectChildren(input: $input) { - success - error - entries { - idSystemObject - name - objectType - idObject - metadata - } - metadataColumns - } - } - `; + query getObjectChildren($input: GetObjectChildrenInput!) { + getObjectChildren(input: $input) { + success + error + entries { + idSystemObject + name + objectType + idObject + metadata + } + metadataColumns + } +} + `; /** -* __useGetObjectChildrenQuery__ -* -* To run a query within a React component, call `useGetObjectChildrenQuery` and pass it any options that fit your needs. -* When your component renders, `useGetObjectChildrenQuery` returns an object from Apollo Client that contains loading, error, and data properties -* you can use to render your UI. -* -* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; -* -* @example -* const { data, loading, error } = useGetObjectChildrenQuery({ - * variables: { - * input: // value for 'input' - * }, - * }); - */ + * __useGetObjectChildrenQuery__ + * + * To run a query within a React component, call `useGetObjectChildrenQuery` and pass it any options that fit your needs. + * When your component renders, `useGetObjectChildrenQuery` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. + * + * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useGetObjectChildrenQuery({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ export function useGetObjectChildrenQuery(baseOptions?: Apollo.QueryHookOptions) { return Apollo.useQuery(GetObjectChildrenDocument, baseOptions); } @@ -3272,31 +3273,31 @@ export type GetObjectChildrenQueryHookResult = ReturnType; export type GetObjectChildrenQueryResult = Apollo.QueryResult; export const GetIntermediaryFileDocument = gql` - query getIntermediaryFile($input: GetIntermediaryFileInput!) { - getIntermediaryFile(input: $input) { - IntermediaryFile { - idIntermediaryFile - } - } - } - `; + query getIntermediaryFile($input: GetIntermediaryFileInput!) { + getIntermediaryFile(input: $input) { + IntermediaryFile { + idIntermediaryFile + } + } +} + `; /** -* __useGetIntermediaryFileQuery__ -* -* To run a query within a React component, call `useGetIntermediaryFileQuery` and pass it any options that fit your needs. -* When your component renders, `useGetIntermediaryFileQuery` returns an object from Apollo Client that contains loading, error, and data properties -* you can use to render your UI. -* -* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; -* -* @example -* const { data, loading, error } = useGetIntermediaryFileQuery({ - * variables: { - * input: // value for 'input' - * }, - * }); - */ + * __useGetIntermediaryFileQuery__ + * + * To run a query within a React component, call `useGetIntermediaryFileQuery` and pass it any options that fit your needs. + * When your component renders, `useGetIntermediaryFileQuery` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. + * + * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useGetIntermediaryFileQuery({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ export function useGetIntermediaryFileQuery(baseOptions?: Apollo.QueryHookOptions) { return Apollo.useQuery(GetIntermediaryFileDocument, baseOptions); } @@ -3307,31 +3308,31 @@ export type GetIntermediaryFileQueryHookResult = ReturnType; export type GetIntermediaryFileQueryResult = Apollo.QueryResult; export const GetSceneDocument = gql` - query getScene($input: GetSceneInput!) { - getScene(input: $input) { - Scene { - idScene - } - } - } - `; + query getScene($input: GetSceneInput!) { + getScene(input: $input) { + Scene { + idScene + } + } +} + `; /** -* __useGetSceneQuery__ -* -* To run a query within a React component, call `useGetSceneQuery` and pass it any options that fit your needs. -* When your component renders, `useGetSceneQuery` returns an object from Apollo Client that contains loading, error, and data properties -* you can use to render your UI. -* -* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; -* -* @example -* const { data, loading, error } = useGetSceneQuery({ - * variables: { - * input: // value for 'input' - * }, - * }); - */ + * __useGetSceneQuery__ + * + * To run a query within a React component, call `useGetSceneQuery` and pass it any options that fit your needs. + * When your component renders, `useGetSceneQuery` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. + * + * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useGetSceneQuery({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ export function useGetSceneQuery(baseOptions?: Apollo.QueryHookOptions) { return Apollo.useQuery(GetSceneDocument, baseOptions); } @@ -3342,33 +3343,33 @@ export type GetSceneQueryHookResult = ReturnType; export type GetSceneLazyQueryHookResult = ReturnType; export type GetSceneQueryResult = Apollo.QueryResult; export const GetIngestionItemsForSubjectsDocument = gql` - query getIngestionItemsForSubjects($input: GetIngestionItemsForSubjectsInput!) { - getIngestionItemsForSubjects(input: $input) { - Item { - idItem - EntireSubject - Name - } - } - } - `; + query getIngestionItemsForSubjects($input: GetIngestionItemsForSubjectsInput!) { + getIngestionItemsForSubjects(input: $input) { + Item { + idItem + EntireSubject + Name + } + } +} + `; /** -* __useGetIngestionItemsForSubjectsQuery__ -* -* To run a query within a React component, call `useGetIngestionItemsForSubjectsQuery` and pass it any options that fit your needs. -* When your component renders, `useGetIngestionItemsForSubjectsQuery` returns an object from Apollo Client that contains loading, error, and data properties -* you can use to render your UI. -* -* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; -* -* @example -* const { data, loading, error } = useGetIngestionItemsForSubjectsQuery({ - * variables: { - * input: // value for 'input' - * }, - * }); - */ + * __useGetIngestionItemsForSubjectsQuery__ + * + * To run a query within a React component, call `useGetIngestionItemsForSubjectsQuery` and pass it any options that fit your needs. + * When your component renders, `useGetIngestionItemsForSubjectsQuery` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. + * + * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useGetIngestionItemsForSubjectsQuery({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ export function useGetIngestionItemsForSubjectsQuery(baseOptions?: Apollo.QueryHookOptions) { return Apollo.useQuery(GetIngestionItemsForSubjectsDocument, baseOptions); } @@ -3379,32 +3380,32 @@ export type GetIngestionItemsForSubjectsQueryHookResult = ReturnType; export type GetIngestionItemsForSubjectsQueryResult = Apollo.QueryResult; export const GetIngestionProjectsForSubjectsDocument = gql` - query getIngestionProjectsForSubjects($input: GetIngestionProjectsForSubjectsInput!) { - getIngestionProjectsForSubjects(input: $input) { - Project { - idProject - Name - } - } - } - `; + query getIngestionProjectsForSubjects($input: GetIngestionProjectsForSubjectsInput!) { + getIngestionProjectsForSubjects(input: $input) { + Project { + idProject + Name + } + } +} + `; /** -* __useGetIngestionProjectsForSubjectsQuery__ -* -* To run a query within a React component, call `useGetIngestionProjectsForSubjectsQuery` and pass it any options that fit your needs. -* When your component renders, `useGetIngestionProjectsForSubjectsQuery` returns an object from Apollo Client that contains loading, error, and data properties -* you can use to render your UI. -* -* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; -* -* @example -* const { data, loading, error } = useGetIngestionProjectsForSubjectsQuery({ - * variables: { - * input: // value for 'input' - * }, - * }); - */ + * __useGetIngestionProjectsForSubjectsQuery__ + * + * To run a query within a React component, call `useGetIngestionProjectsForSubjectsQuery` and pass it any options that fit your needs. + * When your component renders, `useGetIngestionProjectsForSubjectsQuery` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. + * + * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useGetIngestionProjectsForSubjectsQuery({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ export function useGetIngestionProjectsForSubjectsQuery(baseOptions?: Apollo.QueryHookOptions) { return Apollo.useQuery(GetIngestionProjectsForSubjectsDocument, baseOptions); } @@ -3415,31 +3416,31 @@ export type GetIngestionProjectsForSubjectsQueryHookResult = ReturnType; export type GetIngestionProjectsForSubjectsQueryResult = Apollo.QueryResult; export const GetItemDocument = gql` - query getItem($input: GetItemInput!) { - getItem(input: $input) { - Item { - idItem - } - } - } - `; + query getItem($input: GetItemInput!) { + getItem(input: $input) { + Item { + idItem + } + } +} + `; /** -* __useGetItemQuery__ -* -* To run a query within a React component, call `useGetItemQuery` and pass it any options that fit your needs. -* When your component renders, `useGetItemQuery` returns an object from Apollo Client that contains loading, error, and data properties -* you can use to render your UI. -* -* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; -* -* @example -* const { data, loading, error } = useGetItemQuery({ - * variables: { - * input: // value for 'input' - * }, - * }); - */ + * __useGetItemQuery__ + * + * To run a query within a React component, call `useGetItemQuery` and pass it any options that fit your needs. + * When your component renders, `useGetItemQuery` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. + * + * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useGetItemQuery({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ export function useGetItemQuery(baseOptions?: Apollo.QueryHookOptions) { return Apollo.useQuery(GetItemDocument, baseOptions); } @@ -3450,32 +3451,32 @@ export type GetItemQueryHookResult = ReturnType; export type GetItemLazyQueryHookResult = ReturnType; export type GetItemQueryResult = Apollo.QueryResult; export const GetItemsForSubjectDocument = gql` - query getItemsForSubject($input: GetItemsForSubjectInput!) { - getItemsForSubject(input: $input) { - Item { - idItem - Name - } - } - } - `; + query getItemsForSubject($input: GetItemsForSubjectInput!) { + getItemsForSubject(input: $input) { + Item { + idItem + Name + } + } +} + `; /** -* __useGetItemsForSubjectQuery__ -* -* To run a query within a React component, call `useGetItemsForSubjectQuery` and pass it any options that fit your needs. -* When your component renders, `useGetItemsForSubjectQuery` returns an object from Apollo Client that contains loading, error, and data properties -* you can use to render your UI. -* -* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; -* -* @example -* const { data, loading, error } = useGetItemsForSubjectQuery({ - * variables: { - * input: // value for 'input' - * }, - * }); - */ + * __useGetItemsForSubjectQuery__ + * + * To run a query within a React component, call `useGetItemsForSubjectQuery` and pass it any options that fit your needs. + * When your component renders, `useGetItemsForSubjectQuery` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. + * + * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useGetItemsForSubjectQuery({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ export function useGetItemsForSubjectQuery(baseOptions?: Apollo.QueryHookOptions) { return Apollo.useQuery(GetItemsForSubjectDocument, baseOptions); } @@ -3486,53 +3487,53 @@ export type GetItemsForSubjectQueryHookResult = ReturnType; export type GetItemsForSubjectQueryResult = Apollo.QueryResult; export const GetObjectsForItemDocument = gql` - query getObjectsForItem($input: GetObjectsForItemInput!) { - getObjectsForItem(input: $input) { - CaptureData { - idCaptureData - DateCaptured - Description - } - Model { - idModel - Authoritative - DateCreated - } - Scene { - idScene - HasBeenQCd - IsOriented - Name - } - IntermediaryFile { - idIntermediaryFile - DateCreated - } - ProjectDocumentation { - idProjectDocumentation - Description - Name - } - } - } - `; + query getObjectsForItem($input: GetObjectsForItemInput!) { + getObjectsForItem(input: $input) { + CaptureData { + idCaptureData + DateCaptured + Description + } + Model { + idModel + Authoritative + DateCreated + } + Scene { + idScene + HasBeenQCd + IsOriented + Name + } + IntermediaryFile { + idIntermediaryFile + DateCreated + } + ProjectDocumentation { + idProjectDocumentation + Description + Name + } + } +} + `; /** -* __useGetObjectsForItemQuery__ -* -* To run a query within a React component, call `useGetObjectsForItemQuery` and pass it any options that fit your needs. -* When your component renders, `useGetObjectsForItemQuery` returns an object from Apollo Client that contains loading, error, and data properties -* you can use to render your UI. -* -* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; -* -* @example -* const { data, loading, error } = useGetObjectsForItemQuery({ - * variables: { - * input: // value for 'input' - * }, - * }); - */ + * __useGetObjectsForItemQuery__ + * + * To run a query within a React component, call `useGetObjectsForItemQuery` and pass it any options that fit your needs. + * When your component renders, `useGetObjectsForItemQuery` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. + * + * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useGetObjectsForItemQuery({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ export function useGetObjectsForItemQuery(baseOptions?: Apollo.QueryHookOptions) { return Apollo.useQuery(GetObjectsForItemDocument, baseOptions); } @@ -3543,31 +3544,31 @@ export type GetObjectsForItemQueryHookResult = ReturnType; export type GetObjectsForItemQueryResult = Apollo.QueryResult; export const GetProjectDocument = gql` - query getProject($input: GetProjectInput!) { - getProject(input: $input) { - Project { - idProject - } - } - } - `; + query getProject($input: GetProjectInput!) { + getProject(input: $input) { + Project { + idProject + } + } +} + `; /** -* __useGetProjectQuery__ -* -* To run a query within a React component, call `useGetProjectQuery` and pass it any options that fit your needs. -* When your component renders, `useGetProjectQuery` returns an object from Apollo Client that contains loading, error, and data properties -* you can use to render your UI. -* -* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; -* -* @example -* const { data, loading, error } = useGetProjectQuery({ - * variables: { - * input: // value for 'input' - * }, - * }); - */ + * __useGetProjectQuery__ + * + * To run a query within a React component, call `useGetProjectQuery` and pass it any options that fit your needs. + * When your component renders, `useGetProjectQuery` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. + * + * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useGetProjectQuery({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ export function useGetProjectQuery(baseOptions?: Apollo.QueryHookOptions) { return Apollo.useQuery(GetProjectDocument, baseOptions); } @@ -3578,31 +3579,31 @@ export type GetProjectQueryHookResult = ReturnType; export type GetProjectLazyQueryHookResult = ReturnType; export type GetProjectQueryResult = Apollo.QueryResult; export const GetProjectDocumentationDocument = gql` - query getProjectDocumentation($input: GetProjectDocumentationInput!) { - getProjectDocumentation(input: $input) { - ProjectDocumentation { - idProjectDocumentation - } - } - } - `; + query getProjectDocumentation($input: GetProjectDocumentationInput!) { + getProjectDocumentation(input: $input) { + ProjectDocumentation { + idProjectDocumentation + } + } +} + `; /** -* __useGetProjectDocumentationQuery__ -* -* To run a query within a React component, call `useGetProjectDocumentationQuery` and pass it any options that fit your needs. -* When your component renders, `useGetProjectDocumentationQuery` returns an object from Apollo Client that contains loading, error, and data properties -* you can use to render your UI. -* -* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; -* -* @example -* const { data, loading, error } = useGetProjectDocumentationQuery({ - * variables: { - * input: // value for 'input' - * }, - * }); - */ + * __useGetProjectDocumentationQuery__ + * + * To run a query within a React component, call `useGetProjectDocumentationQuery` and pass it any options that fit your needs. + * When your component renders, `useGetProjectDocumentationQuery` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. + * + * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useGetProjectDocumentationQuery({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ export function useGetProjectDocumentationQuery(baseOptions?: Apollo.QueryHookOptions) { return Apollo.useQuery(GetProjectDocumentationDocument, baseOptions); } @@ -3613,31 +3614,31 @@ export type GetProjectDocumentationQueryHookResult = ReturnType; export type GetProjectDocumentationQueryResult = Apollo.QueryResult; export const GetSubjectDocument = gql` - query getSubject($input: GetSubjectInput!) { - getSubject(input: $input) { - Subject { - idSubject - } - } - } - `; + query getSubject($input: GetSubjectInput!) { + getSubject(input: $input) { + Subject { + idSubject + } + } +} + `; /** -* __useGetSubjectQuery__ -* -* To run a query within a React component, call `useGetSubjectQuery` and pass it any options that fit your needs. -* When your component renders, `useGetSubjectQuery` returns an object from Apollo Client that contains loading, error, and data properties -* you can use to render your UI. -* -* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; -* -* @example -* const { data, loading, error } = useGetSubjectQuery({ - * variables: { - * input: // value for 'input' - * }, - * }); - */ + * __useGetSubjectQuery__ + * + * To run a query within a React component, call `useGetSubjectQuery` and pass it any options that fit your needs. + * When your component renders, `useGetSubjectQuery` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. + * + * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useGetSubjectQuery({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ export function useGetSubjectQuery(baseOptions?: Apollo.QueryHookOptions) { return Apollo.useQuery(GetSubjectDocument, baseOptions); } @@ -3648,32 +3649,32 @@ export type GetSubjectQueryHookResult = ReturnType; export type GetSubjectLazyQueryHookResult = ReturnType; export type GetSubjectQueryResult = Apollo.QueryResult; export const GetSubjectsForUnitDocument = gql` - query getSubjectsForUnit($input: GetSubjectsForUnitInput!) { - getSubjectsForUnit(input: $input) { - Subject { - idSubject - Name - } - } - } - `; + query getSubjectsForUnit($input: GetSubjectsForUnitInput!) { + getSubjectsForUnit(input: $input) { + Subject { + idSubject + Name + } + } +} + `; /** -* __useGetSubjectsForUnitQuery__ -* -* To run a query within a React component, call `useGetSubjectsForUnitQuery` and pass it any options that fit your needs. -* When your component renders, `useGetSubjectsForUnitQuery` returns an object from Apollo Client that contains loading, error, and data properties -* you can use to render your UI. -* -* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; -* -* @example -* const { data, loading, error } = useGetSubjectsForUnitQuery({ - * variables: { - * input: // value for 'input' - * }, - * }); - */ + * __useGetSubjectsForUnitQuery__ + * + * To run a query within a React component, call `useGetSubjectsForUnitQuery` and pass it any options that fit your needs. + * When your component renders, `useGetSubjectsForUnitQuery` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. + * + * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useGetSubjectsForUnitQuery({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ export function useGetSubjectsForUnitQuery(baseOptions?: Apollo.QueryHookOptions) { return Apollo.useQuery(GetSubjectsForUnitDocument, baseOptions); } @@ -3684,31 +3685,31 @@ export type GetSubjectsForUnitQueryHookResult = ReturnType; export type GetSubjectsForUnitQueryResult = Apollo.QueryResult; export const GetUnitDocument = gql` - query getUnit($input: GetUnitInput!) { - getUnit(input: $input) { - Unit { - idUnit - } - } - } - `; + query getUnit($input: GetUnitInput!) { + getUnit(input: $input) { + Unit { + idUnit + } + } +} + `; /** -* __useGetUnitQuery__ -* -* To run a query within a React component, call `useGetUnitQuery` and pass it any options that fit your needs. -* When your component renders, `useGetUnitQuery` returns an object from Apollo Client that contains loading, error, and data properties -* you can use to render your UI. -* -* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; -* -* @example -* const { data, loading, error } = useGetUnitQuery({ - * variables: { - * input: // value for 'input' - * }, - * }); - */ + * __useGetUnitQuery__ + * + * To run a query within a React component, call `useGetUnitQuery` and pass it any options that fit your needs. + * When your component renders, `useGetUnitQuery` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. + * + * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useGetUnitQuery({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ export function useGetUnitQuery(baseOptions?: Apollo.QueryHookOptions) { return Apollo.useQuery(GetUnitDocument, baseOptions); } @@ -3719,35 +3720,35 @@ export type GetUnitQueryHookResult = ReturnType; export type GetUnitLazyQueryHookResult = ReturnType; export type GetUnitQueryResult = Apollo.QueryResult; export const SearchIngestionSubjectsDocument = gql` - query searchIngestionSubjects($input: SearchIngestionSubjectsInput!) { - searchIngestionSubjects(input: $input) { - SubjectUnitIdentifier { - idSubject - SubjectName - UnitAbbreviation - IdentifierPublic - IdentifierCollection - } - } - } - `; + query searchIngestionSubjects($input: SearchIngestionSubjectsInput!) { + searchIngestionSubjects(input: $input) { + SubjectUnitIdentifier { + idSubject + SubjectName + UnitAbbreviation + IdentifierPublic + IdentifierCollection + } + } +} + `; /** -* __useSearchIngestionSubjectsQuery__ -* -* To run a query within a React component, call `useSearchIngestionSubjectsQuery` and pass it any options that fit your needs. -* When your component renders, `useSearchIngestionSubjectsQuery` returns an object from Apollo Client that contains loading, error, and data properties -* you can use to render your UI. -* -* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; -* -* @example -* const { data, loading, error } = useSearchIngestionSubjectsQuery({ - * variables: { - * input: // value for 'input' - * }, - * }); - */ + * __useSearchIngestionSubjectsQuery__ + * + * To run a query within a React component, call `useSearchIngestionSubjectsQuery` and pass it any options that fit your needs. + * When your component renders, `useSearchIngestionSubjectsQuery` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. + * + * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useSearchIngestionSubjectsQuery({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ export function useSearchIngestionSubjectsQuery(baseOptions?: Apollo.QueryHookOptions) { return Apollo.useQuery(SearchIngestionSubjectsDocument, baseOptions); } @@ -3758,38 +3759,38 @@ export type SearchIngestionSubjectsQueryHookResult = ReturnType; export type SearchIngestionSubjectsQueryResult = Apollo.QueryResult; export const GetCurrentUserDocument = gql` - query getCurrentUser { - getCurrentUser { - User { - idUser - Name - Active - DateActivated - DateDisabled - EmailAddress - EmailSettings - SecurityID - WorkflowNotificationTime - } - } - } - `; + query getCurrentUser { + getCurrentUser { + User { + idUser + Name + Active + DateActivated + DateDisabled + EmailAddress + EmailSettings + SecurityID + WorkflowNotificationTime + } + } +} + `; /** -* __useGetCurrentUserQuery__ -* -* To run a query within a React component, call `useGetCurrentUserQuery` and pass it any options that fit your needs. -* When your component renders, `useGetCurrentUserQuery` returns an object from Apollo Client that contains loading, error, and data properties -* you can use to render your UI. -* -* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; -* -* @example -* const { data, loading, error } = useGetCurrentUserQuery({ - * variables: { - * }, - * }); - */ + * __useGetCurrentUserQuery__ + * + * To run a query within a React component, call `useGetCurrentUserQuery` and pass it any options that fit your needs. + * When your component renders, `useGetCurrentUserQuery` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. + * + * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useGetCurrentUserQuery({ + * variables: { + * }, + * }); + */ export function useGetCurrentUserQuery(baseOptions?: Apollo.QueryHookOptions) { return Apollo.useQuery(GetCurrentUserDocument, baseOptions); } @@ -3800,34 +3801,34 @@ export type GetCurrentUserQueryHookResult = ReturnType; export type GetCurrentUserQueryResult = Apollo.QueryResult; export const GetUserDocument = gql` - query getUser($input: GetUserInput!) { - getUser(input: $input) { - User { - idUser - Name - Active - DateActivated - } - } - } - `; + query getUser($input: GetUserInput!) { + getUser(input: $input) { + User { + idUser + Name + Active + DateActivated + } + } +} + `; /** -* __useGetUserQuery__ -* -* To run a query within a React component, call `useGetUserQuery` and pass it any options that fit your needs. -* When your component renders, `useGetUserQuery` returns an object from Apollo Client that contains loading, error, and data properties -* you can use to render your UI. -* -* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; -* -* @example -* const { data, loading, error } = useGetUserQuery({ - * variables: { - * input: // value for 'input' - * }, - * }); - */ + * __useGetUserQuery__ + * + * To run a query within a React component, call `useGetUserQuery` and pass it any options that fit your needs. + * When your component renders, `useGetUserQuery` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. + * + * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useGetUserQuery({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ export function useGetUserQuery(baseOptions?: Apollo.QueryHookOptions) { return Apollo.useQuery(GetUserDocument, baseOptions); } @@ -3838,31 +3839,31 @@ export type GetUserQueryHookResult = ReturnType; export type GetUserLazyQueryHookResult = ReturnType; export type GetUserQueryResult = Apollo.QueryResult; export const GetVocabularyDocument = gql` - query getVocabulary($input: GetVocabularyInput!) { - getVocabulary(input: $input) { - Vocabulary { - idVocabulary - } - } - } - `; + query getVocabulary($input: GetVocabularyInput!) { + getVocabulary(input: $input) { + Vocabulary { + idVocabulary + } + } +} + `; /** -* __useGetVocabularyQuery__ -* -* To run a query within a React component, call `useGetVocabularyQuery` and pass it any options that fit your needs. -* When your component renders, `useGetVocabularyQuery` returns an object from Apollo Client that contains loading, error, and data properties -* you can use to render your UI. -* -* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; -* -* @example -* const { data, loading, error } = useGetVocabularyQuery({ - * variables: { - * input: // value for 'input' - * }, - * }); - */ + * __useGetVocabularyQuery__ + * + * To run a query within a React component, call `useGetVocabularyQuery` and pass it any options that fit your needs. + * When your component renders, `useGetVocabularyQuery` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. + * + * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useGetVocabularyQuery({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ export function useGetVocabularyQuery(baseOptions?: Apollo.QueryHookOptions) { return Apollo.useQuery(GetVocabularyDocument, baseOptions); } @@ -3873,35 +3874,35 @@ export type GetVocabularyQueryHookResult = ReturnType; export type GetVocabularyQueryResult = Apollo.QueryResult; export const GetVocabularyEntriesDocument = gql` - query getVocabularyEntries($input: GetVocabularyEntriesInput!) { - getVocabularyEntries(input: $input) { - VocabularyEntries { - eVocabSetID - Vocabulary { - idVocabulary - Term - } - } - } - } - `; + query getVocabularyEntries($input: GetVocabularyEntriesInput!) { + getVocabularyEntries(input: $input) { + VocabularyEntries { + eVocabSetID + Vocabulary { + idVocabulary + Term + } + } + } +} + `; /** -* __useGetVocabularyEntriesQuery__ -* -* To run a query within a React component, call `useGetVocabularyEntriesQuery` and pass it any options that fit your needs. -* When your component renders, `useGetVocabularyEntriesQuery` returns an object from Apollo Client that contains loading, error, and data properties -* you can use to render your UI. -* -* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; -* -* @example -* const { data, loading, error } = useGetVocabularyEntriesQuery({ - * variables: { - * input: // value for 'input' - * }, - * }); - */ + * __useGetVocabularyEntriesQuery__ + * + * To run a query within a React component, call `useGetVocabularyEntriesQuery` and pass it any options that fit your needs. + * When your component renders, `useGetVocabularyEntriesQuery` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. + * + * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useGetVocabularyEntriesQuery({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ export function useGetVocabularyEntriesQuery(baseOptions?: Apollo.QueryHookOptions) { return Apollo.useQuery(GetVocabularyEntriesDocument, baseOptions); } @@ -3912,31 +3913,31 @@ export type GetVocabularyEntriesQueryHookResult = ReturnType; export type GetVocabularyEntriesQueryResult = Apollo.QueryResult; export const GetWorkflowDocument = gql` - query getWorkflow($input: GetWorkflowInput!) { - getWorkflow(input: $input) { - Workflow { - idWorkflow - } - } - } - `; + query getWorkflow($input: GetWorkflowInput!) { + getWorkflow(input: $input) { + Workflow { + idWorkflow + } + } +} + `; /** -* __useGetWorkflowQuery__ -* -* To run a query within a React component, call `useGetWorkflowQuery` and pass it any options that fit your needs. -* When your component renders, `useGetWorkflowQuery` returns an object from Apollo Client that contains loading, error, and data properties -* you can use to render your UI. -* -* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; -* -* @example -* const { data, loading, error } = useGetWorkflowQuery({ - * variables: { - * input: // value for 'input' - * }, - * }); - */ + * __useGetWorkflowQuery__ + * + * To run a query within a React component, call `useGetWorkflowQuery` and pass it any options that fit your needs. + * When your component renders, `useGetWorkflowQuery` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. + * + * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useGetWorkflowQuery({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ export function useGetWorkflowQuery(baseOptions?: Apollo.QueryHookOptions) { return Apollo.useQuery(GetWorkflowDocument, baseOptions); } diff --git a/server/graphql/api/queries/asset/getUploadedAssetVersion.ts b/server/graphql/api/queries/asset/getUploadedAssetVersion.ts index 4243f8cad..80713fc9e 100644 --- a/server/graphql/api/queries/asset/getUploadedAssetVersion.ts +++ b/server/graphql/api/queries/asset/getUploadedAssetVersion.ts @@ -7,6 +7,7 @@ const getUploadedAssetVersion = gql` idAssetVersion StorageSize FileName + DateCreated Asset { idAsset VAssetType { From c888ce726940e16767288fe956b82f318a4d2494 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Tue, 13 Oct 2020 16:07:43 +0530 Subject: [PATCH 014/239] use clsx to improve mui classes --- client/package.json | 1 + client/src/components/controls/LoadingButton.tsx | 5 +++-- client/src/components/shared/Progress.tsx | 5 +++-- client/src/index.tsx | 2 +- yarn.lock | 2 +- 5 files changed, 9 insertions(+), 6 deletions(-) diff --git a/client/package.json b/client/package.json index 3e4d34f1c..25e5c27c2 100644 --- a/client/package.json +++ b/client/package.json @@ -41,6 +41,7 @@ "@types/react-router-dom": "5.1.5", "@types/yup": "0.29.7", "apollo-upload-client": "14.1.2", + "clsx": "1.1.1", "customize-cra": "1.0.0", "customize-cra-react-refresh": "1.1.0", "formik": "2.1.5", diff --git a/client/src/components/controls/LoadingButton.tsx b/client/src/components/controls/LoadingButton.tsx index d1d63413f..6403af5ba 100644 --- a/client/src/components/controls/LoadingButton.tsx +++ b/client/src/components/controls/LoadingButton.tsx @@ -1,6 +1,7 @@ -import React from 'react'; import { Button, ButtonProps } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; +import clsx from 'clsx'; +import React from 'react'; import Progress from '../shared/Progress'; type LoadingButtonProps = ButtonProps & { @@ -19,7 +20,7 @@ function LoadingButton(props: LoadingButtonProps): React.ReactElement { const classes = useStyles(); return ( - diff --git a/client/src/components/shared/Progress.tsx b/client/src/components/shared/Progress.tsx index e3d47449a..1177de9d5 100644 --- a/client/src/components/shared/Progress.tsx +++ b/client/src/components/shared/Progress.tsx @@ -1,6 +1,7 @@ -import React from 'react'; import { CircularProgress, CircularProgressProps } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; +import clsx from 'clsx'; +import React from 'react'; const useStyles = makeStyles(() => ({ container: { @@ -12,7 +13,7 @@ function Progress({ className, ...props }: CircularProgressProps): React.ReactEl const classes = useStyles(); return ( - + ); } diff --git a/client/src/index.tsx b/client/src/index.tsx index 6ce1a9bbb..7910000ac 100644 --- a/client/src/index.tsx +++ b/client/src/index.tsx @@ -28,7 +28,7 @@ function AppRouter(): React.ReactElement { initializeUser(); }, [initializeUser]); - let content: React.ReactNode = ; + let content: React.ReactNode = ; if (!loading) { content = ( diff --git a/yarn.lock b/yarn.lock index 204a2d9cf..4c5ec820b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5592,7 +5592,7 @@ clone@^1.0.2: resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" integrity sha1-2jCcwmPfFZlMaIypAheco8fNfH4= -clsx@^1.0.2, clsx@^1.0.4: +clsx@1.1.1, clsx@^1.0.2, clsx@^1.0.4: version "1.1.1" resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.1.1.tgz#98b3134f9abbdf23b2663491ace13c5c03a73188" integrity sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA== From cfa5c3b0c4d31bec4df6083bb7737c38cc3516d5 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Wed, 14 Oct 2020 17:22:53 +0530 Subject: [PATCH 015/239] re-implement tree view to improve render performance --- .../RepositoryTreeView/MetadataView.tsx | 63 +++++++ .../RepositoryTreeHeader.tsx | 2 +- .../RepositoryTreeView/RepositoryTreeNode.tsx | 121 -------------- .../RepositoryTreeView/StyledTreeItem.tsx | 154 +----------------- .../RepositoryTreeView/TreeLabel.tsx | 98 +++++++++++ .../RepositoryTreeView/TreeViewContents.tsx | 77 --------- .../components/RepositoryTreeView/index.tsx | 141 +++++++++------- .../pages/Repository/hooks/useRepository.ts | 81 +++------ client/src/pages/Repository/index.tsx | 4 +- .../utils/{repository.ts => repository.tsx} | 56 ++++++- 10 files changed, 326 insertions(+), 471 deletions(-) create mode 100644 client/src/pages/Repository/components/RepositoryTreeView/MetadataView.tsx delete mode 100644 client/src/pages/Repository/components/RepositoryTreeView/RepositoryTreeNode.tsx create mode 100644 client/src/pages/Repository/components/RepositoryTreeView/TreeLabel.tsx delete mode 100644 client/src/pages/Repository/components/RepositoryTreeView/TreeViewContents.tsx rename client/src/utils/{repository.ts => repository.tsx} (69%) diff --git a/client/src/pages/Repository/components/RepositoryTreeView/MetadataView.tsx b/client/src/pages/Repository/components/RepositoryTreeView/MetadataView.tsx new file mode 100644 index 000000000..cfba813af --- /dev/null +++ b/client/src/pages/Repository/components/RepositoryTreeView/MetadataView.tsx @@ -0,0 +1,63 @@ +import { makeStyles } from '@material-ui/core/styles'; +import React from 'react'; +import { eMetadata } from '../../../../types/server'; +import { computeMetadataViewWidth, trimmedMetadataField } from '../../../../utils/repository'; + +export type TreeViewColumn = { + metadataColumn: eMetadata; + label: string; + size: number; +}; + +const useStyles = makeStyles(({ palette, typography, breakpoints }) => ({ + metadata: { + display: 'flex' + }, + column: { + display: 'flex', + alignItems: 'center', + padding: '0px 10px', + fontSize: ({ header }: MetadataViewProps) => header ? typography.pxToRem(18) : undefined, + color: ({ header }: MetadataViewProps) => header ? palette.primary.dark : palette.grey[900], + fontWeight: ({ header }: MetadataViewProps) => header ? typography.fontWeightRegular : typography.fontWeightLight, + overflow: 'hidden', + textOverflow: 'ellipsis', + [breakpoints.down('lg')]: { + fontSize: ({ header }: MetadataViewProps) => header ? typography.pxToRem(14) : undefined, + } + } +})); + +interface MetadataViewProps { + header: boolean; + treeColumns: TreeViewColumn[]; +} + +function MetadataView(props: MetadataViewProps): React.ReactElement { + const { header, treeColumns } = props; + const classes = useStyles(props); + + const width = computeMetadataViewWidth(treeColumns); + + const renderTreeColumns = (treeColumns: TreeViewColumn[]) => + treeColumns.map((treeColumn: TreeViewColumn, index: number) => { + const { label, size } = treeColumn; + const width = `${size}vw`; + + return ( +
+ + {trimmedMetadataField(label, 20, 10)} + +
+ ); + }); + + return ( +
+ {renderTreeColumns(treeColumns)} +
+ ); +} + +export default MetadataView; diff --git a/client/src/pages/Repository/components/RepositoryTreeView/RepositoryTreeHeader.tsx b/client/src/pages/Repository/components/RepositoryTreeView/RepositoryTreeHeader.tsx index 6ef19365b..f3ff3dddd 100644 --- a/client/src/pages/Repository/components/RepositoryTreeView/RepositoryTreeHeader.tsx +++ b/client/src/pages/Repository/components/RepositoryTreeView/RepositoryTreeHeader.tsx @@ -3,7 +3,7 @@ import { makeStyles } from '@material-ui/core/styles'; import React from 'react'; import { eMetadata } from '../../../../types/server'; import { getTreeViewColumns, getTreeWidth } from '../../../../utils/repository'; -import { MetadataView } from './StyledTreeItem'; +import MetadataView from './MetadataView'; const useStyles = makeStyles(({ palette, typography, breakpoints }) => ({ container: { diff --git a/client/src/pages/Repository/components/RepositoryTreeView/RepositoryTreeNode.tsx b/client/src/pages/Repository/components/RepositoryTreeView/RepositoryTreeNode.tsx deleted file mode 100644 index b07ae7ac4..000000000 --- a/client/src/pages/Repository/components/RepositoryTreeView/RepositoryTreeNode.tsx +++ /dev/null @@ -1,121 +0,0 @@ -import React from 'react'; -import { AiOutlineFileText } from 'react-icons/ai'; -import { RepositoryFilter } from '../..'; -import { RepositoryIcon } from '../../../../components'; -import { Colors } from '../../../../theme'; -import { RepositoryColorVariant } from '../../../../theme/colors'; -import { NavigationResultEntry } from '../../../../types/graphql'; -import { eMetadata, eSystemObjectType } from '../../../../types/server'; -import { getRepositoryTreeNodeId, getSortedTreeEntries, getTreeColorVariant, getTreeViewColumns } from '../../../../utils/repository'; -import { useGetObjectChildren } from '../../hooks/useRepository'; -import StyledTreeItem, { TreeViewColumn } from './StyledTreeItem'; -import TreeViewContents from './TreeViewContents'; - -export type ExpandedNodeMap = Map; - -interface RepositoryTreeNodeProps { - idSystemObject: number; - idObject: number; - name: string; - color: string; - objectType: eSystemObjectType; - icon: React.ReactNode; - treeColumns: TreeViewColumn[]; - filter: RepositoryFilter; - expandedNodes: ExpandedNodeMap; -} - -function RepositoryTreeNode(props: RepositoryTreeNodeProps): React.ReactElement { - const { idSystemObject, idObject, name, objectType, icon, color, treeColumns, filter, expandedNodes } = props; - const { - getObjectChildren, - getObjectChildrenData, - getObjectChildrenLoading, - getObjectChildrenError, - } = useGetObjectChildren(idSystemObject, filter); - - const queryData = getObjectChildrenData?.getObjectChildren; - - const nodeId = getRepositoryTreeNodeId(idSystemObject, idObject, objectType); - const isEmpty = !queryData?.entries.length ?? true; - const entries = getSortedTreeEntries(queryData?.entries ?? []); - const metadataColumns = queryData?.metadataColumns ?? []; - const loading = getObjectChildrenLoading && !getObjectChildrenError; - - const loadData = expandedNodes.has(nodeId); - - React.useEffect(() => { - if (loadData && !getObjectChildrenLoading) { - getObjectChildren(); - } - }, [loadData, getObjectChildrenLoading, getObjectChildren]); - - return ( - - - {renderTreeNodes(expandedNodes, filter, entries, metadataColumns)} - - - ); -} - -export const renderTreeNodes = (expandedNodes: ExpandedNodeMap, filter: RepositoryFilter, entries: NavigationResultEntry[], metadataColumns: eMetadata[]): React.ReactNode => - entries.map((entry, index: number) => { - const { idSystemObject, name, objectType, idObject, metadata } = entry; - const variant = getTreeColorVariant(index); - const { icon, color } = getObjectInterfaceDetails(objectType, variant); - const treeColumns = getTreeViewColumns(metadataColumns, false, metadata); - - return ( - - ); - }); - -type ObjectInterfaceDetails = { - icon: React.ReactNode; - color: string; -}; - -export function getObjectInterfaceDetails(objectType: eSystemObjectType, variant: RepositoryColorVariant): ObjectInterfaceDetails { - const color: string = Colors.repository[objectType][variant]; - const textColor: string = Colors.defaults.white; - const backgroundColor: string = Colors.repository[objectType][RepositoryColorVariant.dark] || Colors.repository.default[RepositoryColorVariant.dark]; - - const iconProps = { objectType, backgroundColor, textColor }; - - switch (objectType) { - case eSystemObjectType.eUnit: - return { icon: , color }; - case eSystemObjectType.eProject: - return { icon: , color }; - case eSystemObjectType.eSubject: - return { icon: , color }; - case eSystemObjectType.eItem: - return { icon: , color }; - case eSystemObjectType.eCaptureData: - return { icon: , color }; - - default: - return { icon: , color: Colors.repository.default[variant] }; - } -} - -export default RepositoryTreeNode; diff --git a/client/src/pages/Repository/components/RepositoryTreeView/StyledTreeItem.tsx b/client/src/pages/Repository/components/RepositoryTreeView/StyledTreeItem.tsx index 1f6c97c42..e614c509a 100644 --- a/client/src/pages/Repository/components/RepositoryTreeView/StyledTreeItem.tsx +++ b/client/src/pages/Repository/components/RepositoryTreeView/StyledTreeItem.tsx @@ -1,41 +1,9 @@ -import { Box, Collapse, Tooltip } from '@material-ui/core'; -import { fade, withStyles, Theme, makeStyles } from '@material-ui/core/styles'; +import { fade, Theme, withStyles } from '@material-ui/core/styles'; import { TreeItem, TreeItemProps } from '@material-ui/lab'; import React from 'react'; -import { animated, useSpring } from 'react-spring'; -import { eMetadata } from '../../../../types/server'; -import { computeMetadataViewWidth, getTermForSystemObjectType, trimmedMetadataField } from '../../../../utils/repository'; - -interface TransitionComponentProps { - in?: boolean; -} - -function TransitionComponent(props: TransitionComponentProps): React.ReactElement { - const style = useSpring({ - from: { opacity: 0, transform: 'translate3d(20px,0,0)' }, - to: { - opacity: props.in ? 1 : 0, - transform: `translate3d(${props.in ? 0 : 20}px,0,0)` - } - }); - - return ( - - - - ); -} - -export type TreeViewColumn = { - metadataColumn: eMetadata; - label: string; - size: number; -}; interface StyledTreeItemProps { color: string; - objectType: number; - treeColumns: TreeViewColumn[]; } const StyledTreeItem = withStyles(({ palette, typography, breakpoints }: Theme) => ({ @@ -67,9 +35,9 @@ const StyledTreeItem = withStyles(({ palette, typography, breakpoints }: Theme) fontSize: 16, fontWeight: typography.fontWeightLight, borderRadius: 5, - padding: '5px 10px', + padding: '2.5px 5px', [breakpoints.down('lg')]: { - fontSize: 12 + fontSize: '0.7em' }, backgroundColor: 'transparent !important', '&:hover': { @@ -87,120 +55,6 @@ const StyledTreeItem = withStyles(({ palette, typography, breakpoints }: Theme) selected: { backgroundColor: 'transparent' } -}))((props: TreeItemProps & StyledTreeItemProps) => ( - - } - /> -)); - -interface TreeLabelProps { - label?: React.ReactNode; - objectType: number; - color: string; - treeColumns: TreeViewColumn[]; -} - -const useTreeLabelStyles = makeStyles(({ breakpoints }) => ({ - label: { - display: 'flex', - flex: 1, - alignItems: 'center', - position: 'sticky', - left: 45, - [breakpoints.down('lg')]: { - left: 35 - } - }, - labelText: { - width: '60%', - background: ({ color }: TreeLabelProps) => color, - zIndex: 10, - } -})); - -function TreeLabel(props: TreeLabelProps): React.ReactElement { - const classes = useTreeLabelStyles(props); - const { label, objectType, treeColumns } = props; - - const objectTitle = `${getTermForSystemObjectType(objectType)} ${label}`; - - return ( - - - - - {label} - - - - - - ); -} - -const useMetadataStyles = makeStyles(({ palette, typography, breakpoints }) => ({ - metadata: { - display: 'flex' - }, - column: { - display: 'flex', - alignItems: 'center', - padding: '0px 10px', - fontSize: ({ header }: MetadataViewProps) => header ? typography.pxToRem(18) : undefined, - color: ({ header }: MetadataViewProps) => header ? palette.primary.dark : palette.grey[900], - fontWeight: ({ header }: MetadataViewProps) => header ? typography.fontWeightRegular : typography.fontWeightLight, - overflow: 'hidden', - textOverflow: 'ellipsis', - [breakpoints.down('lg')]: { - fontSize: ({ header }: MetadataViewProps) => header ? typography.pxToRem(14) : undefined, - } - } -})); - -interface MetadataViewProps { - header: boolean; - treeColumns: TreeViewColumn[]; -} - -export function MetadataView(props: MetadataViewProps): React.ReactElement { - const { header, treeColumns } = props; - const classes = useMetadataStyles(props); - - const width = computeMetadataViewWidth(treeColumns); - - const renderTreeColumns = (treeColumns: TreeViewColumn[]) => - treeColumns.map((treeColumn: TreeViewColumn, index: number) => { - const { label, size } = treeColumn; - const width = `${size}vw`; - - return ( - - - {trimmedMetadataField(label, 20, 10)} - - - ); - }); - - return ( - - {renderTreeColumns(treeColumns)} - - ); -} +}))((props: TreeItemProps & StyledTreeItemProps) => ); export default StyledTreeItem; diff --git a/client/src/pages/Repository/components/RepositoryTreeView/TreeLabel.tsx b/client/src/pages/Repository/components/RepositoryTreeView/TreeLabel.tsx new file mode 100644 index 000000000..b9e74a1e0 --- /dev/null +++ b/client/src/pages/Repository/components/RepositoryTreeView/TreeLabel.tsx @@ -0,0 +1,98 @@ +import { Typography } from '@material-ui/core'; +import { makeStyles } from '@material-ui/core/styles'; +import React from 'react'; +import { Progress } from '../../../../components'; +import { getTermForSystemObjectType } from '../../../../utils/repository'; +import MetadataView, { TreeViewColumn } from './MetadataView'; + +const useStyles = makeStyles(({ breakpoints }) => ({ + container: { + display: 'flex', + }, + label: { + display: 'flex', + flex: 1, + alignItems: 'center', + position: 'sticky', + left: 45, + [breakpoints.down('lg')]: { + left: 35 + } + }, + labelText: { + width: '60%', + background: ({ color }: TreeLabelProps) => color, + zIndex: 10, + } +})); + +interface TreeLabelProps { + label?: React.ReactNode; + objectType: number; + color: string; + treeColumns: TreeViewColumn[]; +} + +function TreeLabel(props: TreeLabelProps): React.ReactElement { + const { label, treeColumns, objectType } = props; + const classes = useStyles(props); + const objectTitle = `${getTermForSystemObjectType(objectType)} ${label}`; + + return ( +
+
+
+ {label} +
+
+ +
+ ); +} + +const useLabelStyle = makeStyles(({ breakpoints, palette, typography }) => ({ + container: { + display: 'flex', + alignItems: 'center', + height: 40, + position: 'sticky', + left: 20, + [breakpoints.down('lg')]: { + height: 30, + } + }, + emptyText: { + fontSize: '0.75em', + fontWeight: typography.fontWeightLight, + color: palette.grey[500] + } +})); + +export function TreeLabelLoading(): React.ReactElement { + const classes = useLabelStyle(); + return ( +
+ +
+ ); +} + +interface TreeLabelEmptyProps { + label: string; + objectType: number; +} + +export function TreeLabelEmpty(props: TreeLabelEmptyProps): React.ReactElement { + const { label, objectType } = props; + const classes = useLabelStyle(); + const term = getTermForSystemObjectType(objectType); + const contentTerm = `No objects found for ${term} ${label}`; + + return ( +
+ {contentTerm} +
+ ); +} + +export default TreeLabel; \ No newline at end of file diff --git a/client/src/pages/Repository/components/RepositoryTreeView/TreeViewContents.tsx b/client/src/pages/Repository/components/RepositoryTreeView/TreeViewContents.tsx deleted file mode 100644 index cb8efe45c..000000000 --- a/client/src/pages/Repository/components/RepositoryTreeView/TreeViewContents.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import { Box, Typography } from '@material-ui/core'; -import { makeStyles } from '@material-ui/core/styles'; -import React from 'react'; -import { Progress } from '../../../../components'; -import { eSystemObjectType } from '../../../../types/server'; -import { getTermForSystemObjectType } from '../../../../utils/repository'; - -const useStyles = makeStyles(({ palette, breakpoints }) => ({ - container: { - display: 'flex', - height: 40, - marginLeft: 20, - position: 'sticky', - left: 20, - alignItems: 'center', - [breakpoints.down('lg')]: { - height: 30, - }, - }, - emptyList: { - display: 'flex', - height: 40, - alignItems: 'center', - color: palette.grey[400], - [breakpoints.down('lg')]: { - height: 30, - }, - }, - emptyListText: { - position: 'sticky', - left: 15, - [breakpoints.down('lg')]: { - fontSize: 12, - }, - } -})); - -interface TreeViewContentsProps { - name: string; - loading: boolean; - isEmpty: boolean; - objectType: eSystemObjectType; - children: React.ReactNode -} - -function TreeViewContents(props: TreeViewContentsProps): React.ReactElement { - const { name, loading, isEmpty, objectType, children } = props; - const classes = useStyles(); - - const contentTerm = getTermForSystemObjectType(objectType); - - let content: React.ReactNode = ( - - - - ); - - if (!loading) { - if (isEmpty) { - content = ( - - No objects found for {contentTerm} {name} - - ); - } else { - content = children; - } - } - - return ( - - {content} - - ); -} - -export default TreeViewContents; diff --git a/client/src/pages/Repository/components/RepositoryTreeView/index.tsx b/client/src/pages/Repository/components/RepositoryTreeView/index.tsx index 3447178e8..801cff3a5 100644 --- a/client/src/pages/Repository/components/RepositoryTreeView/index.tsx +++ b/client/src/pages/Repository/components/RepositoryTreeView/index.tsx @@ -1,18 +1,19 @@ -import { Box, Typography } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; +import ChevronRightIcon from '@material-ui/icons/ChevronRight'; +import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; import { TreeView } from '@material-ui/lab'; -import lodash from 'lodash'; -import React, { useState } from 'react'; -import { BsChevronDown, BsChevronRight } from 'react-icons/bs'; +import React, { useEffect, useState } from 'react'; import { Loader } from '../../../../components'; import { useRepositoryFilterStore } from '../../../../store'; -import { getSortedTreeEntries, getSystemObjectTypesForFilter, getTreeWidth } from '../../../../utils/repository'; -import { useGetRootObjects } from '../../hooks/useRepository'; -import { RepositoryFilter } from '../../index'; +import { NavigationResultEntry } from '../../../../types/graphql'; +import { eMetadata, eSystemObjectType } from '../../../../types/server'; +import { getObjectInterfaceDetails, getRepositoryTreeNodeId, getTreeColorVariant, getTreeViewColumns, parseRepositoryTreeNodeId } from '../../../../utils/repository'; +import { getObjectChildren, useGetRootObjects } from '../../hooks/useRepository'; import RepositoryTreeHeader from './RepositoryTreeHeader'; -import { ExpandedNodeMap, renderTreeNodes } from './RepositoryTreeNode'; +import StyledTreeItem from './StyledTreeItem'; +import TreeLabel, { TreeLabelEmpty, TreeLabelLoading } from './TreeLabel'; -const useStyles = makeStyles(({ palette, breakpoints }) => ({ +const useStyles = makeStyles(({ breakpoints }) => ({ container: { display: 'flex', flex: 5, @@ -30,72 +31,98 @@ const useStyles = makeStyles(({ palette, breakpoints }) => ({ display: 'flex', flexDirection: 'column', flex: 1 - }, - fullView: { - display: 'flex', - flex: 1, - maxWidth: '83.5vw', - alignItems: 'center', - justifyContent: 'center', - color: palette.primary.dark, - [breakpoints.down('lg')]: { - maxHeight: (isExpanded: boolean) => isExpanded ? '60vh' : '77vh', - } } })); -interface RepositoryTreeViewProps { - filter: RepositoryFilter; -} - -function RepositoryTreeView(props: RepositoryTreeViewProps): React.ReactElement { - const { filter } = props; +function RepositoryTreeView(): React.ReactElement { const { isExpanded } = useRepositoryFilterStore(); const classes = useStyles(isExpanded); - const [expandedNodes, setExpandedNodes] = useState(new Map() as ExpandedNodeMap); + const metadataColumns: eMetadata[] = [eMetadata.eUnitAbbreviation, eMetadata.eSubjectIdentifier, eMetadata.eItemName]; - const objectTypes = getSystemObjectTypesForFilter(filter); - const { getRootObjectsData, getRootObjectsLoading, getRootObjectsError } = useGetRootObjects(objectTypes, filter); + const { data, loading, error } = useGetRootObjects([eSystemObjectType.eUnit], metadataColumns); - const noFilter = !filter.units && !filter.projects; + const [tree, setTree] = useState>(() => new Map([['root', []]])); - const entries = getSortedTreeEntries(getRootObjectsData?.getObjectChildren?.entries ?? []); - const metadataColumns = getRootObjectsData?.getObjectChildren?.metadataColumns ?? []; - const width = getTreeWidth(metadataColumns.length); + useEffect(() => { + if (data && !loading && !error) { + const { getObjectChildren } = data; + const { entries } = getObjectChildren; + const updatedRootMap = new Map([['root', entries]]); + setTree(updatedRootMap); + } + }, [data, loading, error]); - let content: React.ReactNode = null; + const onNodeToggle = async (_, nodeIds: string[]) => { + if (!nodeIds.length) return; + const [nodeId] = nodeIds.slice(); + const { idSystemObject } = parseRepositoryTreeNodeId(nodeId); + const { data, error } = await getObjectChildren(idSystemObject, metadataColumns); - if (!getRootObjectsLoading && !getRootObjectsError) { - content = renderTreeNodes(expandedNodes, filter, entries, metadataColumns); - } else if (!noFilter) { - content = ; - } + if (data && !error) { + const { getObjectChildren } = data; + const { entries } = getObjectChildren; + const updatedTree = new Map(tree); + updatedTree.set(nodeId, entries); + setTree(updatedTree); + } + }; - const onNodeToggle = (_, nodeIds: string[]) => { - const keyValueArray: [string, undefined][] = lodash.map(nodeIds, (id: string) => [id, undefined]); - const updatedMap: ExpandedNodeMap = new Map(keyValueArray); - setExpandedNodes(updatedMap); + const renderTree = (children: NavigationResultEntry[] | undefined) => { + if (!children) return null; + return children.map((child: NavigationResultEntry, index: number) => { + const { idSystemObject, objectType, idObject, name, metadata } = child; + const nodeId: string = getRepositoryTreeNodeId(idSystemObject, objectType, idObject); + const childNodes = tree.get(nodeId); + + let childNodesContent: React.ReactNode = ; + + if (childNodes) { + if (childNodes.length > 0) { + childNodesContent = renderTree(childNodes); + } else { + childNodesContent = ; + } + } + + const variant = getTreeColorVariant(index); + const { icon, color } = getObjectInterfaceDetails(objectType, variant); + const treeColumns = getTreeViewColumns(metadataColumns, false, metadata); + + return ( + } + > + {childNodesContent} + + ); + }); }; - return ( - + let content: React.ReactNode = ; + + if (!loading) { + content = ( } - defaultExpandIcon={} + defaultCollapseIcon={} + defaultExpandIcon={} + onNodeToggle={onNodeToggle} > - {noFilter && ( - - Please select a valid filter - - )} - {content} + {renderTree(tree.get('root'))} - + ); + } + + return ( +
+ {content} +
); } -export default RepositoryTreeView; \ No newline at end of file +export default RepositoryTreeView; diff --git a/client/src/pages/Repository/hooks/useRepository.ts b/client/src/pages/Repository/hooks/useRepository.ts index 55e3db62f..8d8af64e0 100644 --- a/client/src/pages/Repository/hooks/useRepository.ts +++ b/client/src/pages/Repository/hooks/useRepository.ts @@ -1,72 +1,31 @@ -import { ApolloError, useLazyQuery, useQuery } from '@apollo/client'; -import { RepositoryFilter } from '../index'; +import { ApolloQueryResult, QueryResult, useQuery } from '@apollo/client'; +import { apolloClient } from '../../../graphql'; import { GetObjectChildrenDocument, GetObjectChildrenQuery, GetObjectChildrenQueryVariables } from '../../../types/graphql'; import { eMetadata, eSystemObjectType } from '../../../types/server'; -interface UseGetRootObjects { - getRootObjectsData: GetObjectChildrenQuery | undefined; - getRootObjectsLoading: boolean; - getRootObjectsError: ApolloError | undefined; -} - -function useGetRootObjects(objectTypes: eSystemObjectType[], filter: RepositoryFilter): UseGetRootObjects { - const { data: getRootObjectsData, loading: getRootObjectsLoading, error: getRootObjectsError } = useQuery( - GetObjectChildrenDocument, - { - variables: { - input: { - idRoot: 0, - objectTypes, - metadataColumns: getMetadataColumnsForFilter(filter) - } +function useGetRootObjects(objectTypes: eSystemObjectType[], metadataColumns: eMetadata[]): QueryResult { + return useQuery(GetObjectChildrenDocument, { + variables: { + input: { + idRoot: 0, + objectTypes, + metadataColumns } } - ); - - return { - getRootObjectsData, - getRootObjectsLoading, - getRootObjectsError - }; -} - -interface UseGetObjectChildren { - getObjectChildren: () => void; - getObjectChildrenData: GetObjectChildrenQuery | undefined; - getObjectChildrenLoading: boolean; - getObjectChildrenError: ApolloError | undefined; + }); } -function useGetObjectChildren(idRoot: number, filter: RepositoryFilter): UseGetObjectChildren { - const [getObjectChildren, { data: getObjectChildrenData, loading: getObjectChildrenLoading, error: getObjectChildrenError }] = useLazyQuery( - GetObjectChildrenDocument, - { - variables: { - input: { - idRoot, - objectTypes: [], - metadataColumns: getMetadataColumnsForFilter(filter) - } +function getObjectChildren(idRoot: number, metadataColumns: eMetadata[]): Promise> { + return apolloClient.query({ + query: GetObjectChildrenDocument, + variables: { + input: { + idRoot, + objectTypes: [], + metadataColumns } } - ); - - return { - getObjectChildren, - getObjectChildrenData, - getObjectChildrenLoading, - getObjectChildrenError, - }; -} - -function getMetadataColumnsForFilter(filter: RepositoryFilter): eMetadata[] { - const metadataColumns: eMetadata[] = [eMetadata.eSubjectIdentifier, eMetadata.eItemName]; - - if (filter.units || filter.projects) { - metadataColumns.unshift(eMetadata.eUnitAbbreviation); - } - - return metadataColumns; + }); } -export { useGetRootObjects, useGetObjectChildren }; +export { useGetRootObjects, getObjectChildren }; diff --git a/client/src/pages/Repository/index.tsx b/client/src/pages/Repository/index.tsx index 6424eb2ad..9e8f64921 100644 --- a/client/src/pages/Repository/index.tsx +++ b/client/src/pages/Repository/index.tsx @@ -40,7 +40,7 @@ function Repository(): React.ReactElement { const defaultFilterState = Object.keys(queries).length ? queries : initialFilterState; - const [filter,] = useState(defaultFilterState); + const [filter] = useState(defaultFilterState); useEffect(() => { const route = generateRepositoryUrl(filter); @@ -50,7 +50,7 @@ function Repository(): React.ReactElement { return ( - + ); } diff --git a/client/src/utils/repository.ts b/client/src/utils/repository.tsx similarity index 69% rename from client/src/utils/repository.ts rename to client/src/utils/repository.tsx index 84c433d97..29eb44bbf 100644 --- a/client/src/utils/repository.ts +++ b/client/src/utils/repository.tsx @@ -1,10 +1,13 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import lodash from 'lodash'; import * as qs from 'query-string'; +import React from 'react'; +import { AiOutlineFileText } from 'react-icons/ai'; +import { RepositoryIcon } from '../components'; import { HOME_ROUTES } from '../constants'; import { RepositoryFilter } from '../pages/Repository'; -import { TreeViewColumn } from '../pages/Repository/components/RepositoryTreeView/StyledTreeItem'; -import { RepositoryColorVariant } from '../theme/colors'; +import { TreeViewColumn } from '../pages/Repository/components/RepositoryTreeView/MetadataView'; +import Colors, { RepositoryColorVariant } from '../theme/colors'; import { NavigationResultEntry } from '../types/graphql'; import { eMetadata, eSystemObjectType } from '../types/server'; @@ -59,6 +62,25 @@ export function getRepositoryTreeNodeId(idSystemObject: number, idObject: number return `${idSystemObject}-${eSystemObjectType[objectType]}-${idObject}`; } +type ParsedNodeId = { + idSystemObject: number; + idObject: number; + objectType: eSystemObjectType; +}; + +export function parseRepositoryTreeNodeId(nodeId: string): ParsedNodeId { + const [nodeSystemObjectId, nodeObjectType, nodeObjectId] = nodeId.split('-'); + const idSystemObject = Number.parseInt(nodeSystemObjectId, 10); + const objectType = Number.parseInt(nodeObjectType, 10); + const idObject = Number.parseInt(nodeObjectId, 10); + + return { + idSystemObject, + objectType, + idObject + }; +} + export function getSortedTreeEntries(entries: NavigationResultEntry[]): NavigationResultEntry[] { return lodash.orderBy(entries, [(entry: NavigationResultEntry) => entry.name.toLowerCase()], 'asc'); } @@ -141,3 +163,33 @@ export function getTreeViewColumns(metadataColumns: eMetadata[], isHeader: boole export function computeMetadataViewWidth(treeColumns: TreeViewColumn[]): string { return `${treeColumns.reduce((prev, current) => prev + current.size, 0)}vw`; } + + +type ObjectInterfaceDetails = { + icon: React.ReactNode; + color: string; +}; + +export function getObjectInterfaceDetails(objectType: eSystemObjectType, variant: RepositoryColorVariant): ObjectInterfaceDetails { + const color: string = Colors.repository[objectType][variant]; + const textColor: string = Colors.defaults.white; + const backgroundColor: string = Colors.repository[objectType][RepositoryColorVariant.dark] || Colors.repository.default[RepositoryColorVariant.dark]; + + const iconProps = { objectType, backgroundColor, textColor }; + + switch (objectType) { + case eSystemObjectType.eUnit: + return { icon: , color }; + case eSystemObjectType.eProject: + return { icon: , color }; + case eSystemObjectType.eSubject: + return { icon: , color }; + case eSystemObjectType.eItem: + return { icon: , color }; + case eSystemObjectType.eCaptureData: + return { icon: , color }; + + default: + return { icon: , color: Colors.repository.default[variant] }; + } +} \ No newline at end of file From 25f86a37d32e11cf3f3a76037b9493c466b11db6 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Wed, 14 Oct 2020 17:23:55 +0530 Subject: [PATCH 016/239] updated treeview labels --- client/src/utils/repository.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/utils/repository.tsx b/client/src/utils/repository.tsx index 29eb44bbf..a0ff66985 100644 --- a/client/src/utils/repository.tsx +++ b/client/src/utils/repository.tsx @@ -142,12 +142,12 @@ export function getTreeViewColumns(metadataColumns: eMetadata[], isHeader: boole break; case eMetadata.eSubjectIdentifier: - if (isHeader) treeColumn.label = 'SubjectId'; + if (isHeader) treeColumn.label = 'Subject'; treeColumn.size = MIN_SIZE * 2; break; case eMetadata.eItemName: - if (isHeader) treeColumn.label = 'Item name'; + if (isHeader) treeColumn.label = 'Item'; break; default: From a24c8d61da329e7811e067b49d3d2388084da196 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Wed, 14 Oct 2020 18:08:44 +0530 Subject: [PATCH 017/239] Fix all metadata side panel preselected bug --- client/src/pages/Ingestion/index.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/client/src/pages/Ingestion/index.tsx b/client/src/pages/Ingestion/index.tsx index 815358b76..2c07029e7 100644 --- a/client/src/pages/Ingestion/index.tsx +++ b/client/src/pages/Ingestion/index.tsx @@ -37,11 +37,12 @@ function Ingestion(): React.ReactElement { enabled: false }); - metadatas.forEach(({ file: { name } }) => { + metadatas.forEach(({ file: { id, name, type } }) => { + const route = `${INGESTION_ROUTE.ROUTES.METADATA}?fileId=${id}&type=${type}`; updatedOptions.push({ title: 'Metadata', subtitle: name, - route: INGESTION_ROUTE.ROUTES.METADATA, + route, enabled: false }); }); From be8502726423654d12d4477d5b38a56cd11bb8ac Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Wed, 14 Oct 2020 18:49:22 +0530 Subject: [PATCH 018/239] treeview responsive fixes --- .../components/controls/RepositoryIcon.tsx | 6 ++--- client/src/components/shared/Progress.tsx | 2 +- .../RepositoryTreeView/MetadataView.tsx | 5 ++++- .../RepositoryTreeView/StyledTreeItem.tsx | 9 ++++---- .../RepositoryTreeView/TreeLabel.tsx | 22 ++++++++++++------- .../components/RepositoryTreeView/index.tsx | 14 +++++++----- client/src/pages/Repository/index.tsx | 8 +++---- client/src/utils/repository.tsx | 2 +- 8 files changed, 41 insertions(+), 27 deletions(-) diff --git a/client/src/components/controls/RepositoryIcon.tsx b/client/src/components/controls/RepositoryIcon.tsx index b9ebfabbb..1ceb7e37e 100644 --- a/client/src/components/controls/RepositoryIcon.tsx +++ b/client/src/components/controls/RepositoryIcon.tsx @@ -9,8 +9,8 @@ const useStyles = makeStyles(({ typography, breakpoints }) => ({ display: 'flex', alignItems: 'center', justifyContent: 'center', - height: 20, - width: 20, + height: 18, + width: 18, borderRadius: 2.5, backgroundColor: ({ backgroundColor }: RepositoryIconProps) => backgroundColor, [breakpoints.down('lg')]: { @@ -20,7 +20,7 @@ const useStyles = makeStyles(({ typography, breakpoints }) => ({ }, initial: { fontSize: 10, - fontWeight: typography.fontWeightBold, + fontWeight: typography.fontWeightMedium, color: ({ textColor }: RepositoryIconProps) => textColor, } })); diff --git a/client/src/components/shared/Progress.tsx b/client/src/components/shared/Progress.tsx index 1177de9d5..f17e6ae8f 100644 --- a/client/src/components/shared/Progress.tsx +++ b/client/src/components/shared/Progress.tsx @@ -5,7 +5,7 @@ import React from 'react'; const useStyles = makeStyles(() => ({ container: { - animationDuration: '750ms' + animationDuration: '650ms' } })); diff --git a/client/src/pages/Repository/components/RepositoryTreeView/MetadataView.tsx b/client/src/pages/Repository/components/RepositoryTreeView/MetadataView.tsx index cfba813af..68cfa80ec 100644 --- a/client/src/pages/Repository/components/RepositoryTreeView/MetadataView.tsx +++ b/client/src/pages/Repository/components/RepositoryTreeView/MetadataView.tsx @@ -25,6 +25,9 @@ const useStyles = makeStyles(({ palette, typography, breakpoints }) => ({ [breakpoints.down('lg')]: { fontSize: ({ header }: MetadataViewProps) => header ? typography.pxToRem(14) : undefined, } + }, + text: { + fontSize: '0.8em' } })); @@ -46,7 +49,7 @@ function MetadataView(props: MetadataViewProps): React.ReactElement { return (
- + {trimmedMetadataField(label, 20, 10)}
diff --git a/client/src/pages/Repository/components/RepositoryTreeView/StyledTreeItem.tsx b/client/src/pages/Repository/components/RepositoryTreeView/StyledTreeItem.tsx index e614c509a..1a63648f2 100644 --- a/client/src/pages/Repository/components/RepositoryTreeView/StyledTreeItem.tsx +++ b/client/src/pages/Repository/components/RepositoryTreeView/StyledTreeItem.tsx @@ -9,13 +9,13 @@ interface StyledTreeItemProps { const StyledTreeItem = withStyles(({ palette, typography, breakpoints }: Theme) => ({ iconContainer: { width: 25, - marginLeft: 5, position: 'sticky', - left: 10, + left: 2.5, zIndex: 10, [breakpoints.down('lg')]: { width: 15, - marginLeft: 8 + left: 4, + marginLeft: 4 }, '& .close': { opacity: 0.3 @@ -37,7 +37,8 @@ const StyledTreeItem = withStyles(({ palette, typography, breakpoints }: Theme) borderRadius: 5, padding: '2.5px 5px', [breakpoints.down('lg')]: { - fontSize: '0.7em' + fontSize: '0.7em', + padding: '4px 8px' }, backgroundColor: 'transparent !important', '&:hover': { diff --git a/client/src/pages/Repository/components/RepositoryTreeView/TreeLabel.tsx b/client/src/pages/Repository/components/RepositoryTreeView/TreeLabel.tsx index b9e74a1e0..9142a1d02 100644 --- a/client/src/pages/Repository/components/RepositoryTreeView/TreeLabel.tsx +++ b/client/src/pages/Repository/components/RepositoryTreeView/TreeLabel.tsx @@ -1,5 +1,5 @@ -import { Typography } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; +import clsx from 'clsx'; import React from 'react'; import { Progress } from '../../../../components'; import { getTermForSystemObjectType } from '../../../../utils/repository'; @@ -16,11 +16,12 @@ const useStyles = makeStyles(({ breakpoints }) => ({ position: 'sticky', left: 45, [breakpoints.down('lg')]: { - left: 35 + left: 30 } }, labelText: { width: '60%', + fontSize: '0.8em', background: ({ color }: TreeLabelProps) => color, zIndex: 10, } @@ -55,16 +56,21 @@ const useLabelStyle = makeStyles(({ breakpoints, palette, typography }) => ({ display: 'flex', alignItems: 'center', height: 40, - position: 'sticky', - left: 20, [breakpoints.down('lg')]: { height: 30, } }, emptyText: { - fontSize: '0.75em', + fontSize: '0.8em', fontWeight: typography.fontWeightLight, - color: palette.grey[500] + color: palette.grey[500], + [breakpoints.down('lg')]: { + fontSize: '0.65em', + } + }, + stickyItem: { + position: 'sticky', + left: 0, } })); @@ -72,7 +78,7 @@ export function TreeLabelLoading(): React.ReactElement { const classes = useLabelStyle(); return (
- +
); } @@ -90,7 +96,7 @@ export function TreeLabelEmpty(props: TreeLabelEmptyProps): React.ReactElement { return (
- {contentTerm} + {contentTerm}
); } diff --git a/client/src/pages/Repository/components/RepositoryTreeView/index.tsx b/client/src/pages/Repository/components/RepositoryTreeView/index.tsx index 801cff3a5..850dab1a8 100644 --- a/client/src/pages/Repository/components/RepositoryTreeView/index.tsx +++ b/client/src/pages/Repository/components/RepositoryTreeView/index.tsx @@ -7,7 +7,7 @@ import { Loader } from '../../../../components'; import { useRepositoryFilterStore } from '../../../../store'; import { NavigationResultEntry } from '../../../../types/graphql'; import { eMetadata, eSystemObjectType } from '../../../../types/server'; -import { getObjectInterfaceDetails, getRepositoryTreeNodeId, getTreeColorVariant, getTreeViewColumns, parseRepositoryTreeNodeId } from '../../../../utils/repository'; +import { getObjectInterfaceDetails, getRepositoryTreeNodeId, getTreeColorVariant, getTreeViewColumns, getTreeWidth, parseRepositoryTreeNodeId } from '../../../../utils/repository'; import { getObjectChildren, useGetRootObjects } from '../../hooks/useRepository'; import RepositoryTreeHeader from './RepositoryTreeHeader'; import StyledTreeItem from './StyledTreeItem'; @@ -17,8 +17,8 @@ const useStyles = makeStyles(({ breakpoints }) => ({ container: { display: 'flex', flex: 5, - maxHeight: (isExpanded: boolean) => isExpanded ? '64vh' : '82vh', - maxWidth: '83.5vw', + maxHeight: (isExpanded: boolean) => isExpanded ? '60vh' : '82vh', + maxWidth: '85vw', flexDirection: 'column', overflow: 'auto', transition: '250ms height ease', @@ -30,7 +30,7 @@ const useStyles = makeStyles(({ breakpoints }) => ({ tree: { display: 'flex', flexDirection: 'column', - flex: 1 + flex: 1, } })); @@ -102,15 +102,19 @@ function RepositoryTreeView(): React.ReactElement { }); }; - let content: React.ReactNode = ; + let content: React.ReactNode = ; if (!loading) { + const treeColumns = getTreeViewColumns(metadataColumns, false); + const width = getTreeWidth(treeColumns.length); + content = ( } defaultExpandIcon={} onNodeToggle={onNodeToggle} + style={{ width }} > {renderTree(tree.get('root'))} diff --git a/client/src/pages/Repository/index.tsx b/client/src/pages/Repository/index.tsx index 9e8f64921..9d92ecdf7 100644 --- a/client/src/pages/Repository/index.tsx +++ b/client/src/pages/Repository/index.tsx @@ -10,13 +10,13 @@ const useStyles = makeStyles(({ breakpoints }) => ({ container: { display: 'flex', flex: 1, - maxWidth: '100vw', + maxWidth: '85vw', flexDirection: 'column', - padding: 40, + padding: 20, paddingBottom: 0, + paddingRight: 0, [breakpoints.down('lg')]: { - padding: 20, - paddingBottom: 0, + paddingRight: 20, } } })); diff --git a/client/src/utils/repository.tsx b/client/src/utils/repository.tsx index a0ff66985..f6a920134 100644 --- a/client/src/utils/repository.tsx +++ b/client/src/utils/repository.tsx @@ -115,7 +115,7 @@ export function generateRepositoryUrl(filter: RepositoryFilter): string { export function getTreeWidth(columnSize: number): string { const width = 50 + columnSize * 10; if (width <= 80) { - return '83.5vw'; + return '85vw'; } return `${width}vw`; From 7b81671ccfa23a64c1d16d331d215301c90491ab Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Thu, 15 Oct 2020 15:18:39 +0530 Subject: [PATCH 019/239] Fix metadata view breaking on more columns --- .../IngestionSidebar/IngestionSidebarMenuOption.tsx | 2 +- client/src/pages/Ingestion/components/Uploads/index.tsx | 2 +- .../components/RepositoryTreeView/MetadataView.tsx | 5 ++++- .../Repository/components/RepositoryTreeView/TreeLabel.tsx | 5 ++++- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/client/src/pages/Ingestion/components/IngestionSidebar/IngestionSidebarMenuOption.tsx b/client/src/pages/Ingestion/components/IngestionSidebar/IngestionSidebarMenuOption.tsx index 65a35aa23..634f8e716 100644 --- a/client/src/pages/Ingestion/components/IngestionSidebar/IngestionSidebarMenuOption.tsx +++ b/client/src/pages/Ingestion/components/IngestionSidebar/IngestionSidebarMenuOption.tsx @@ -11,7 +11,7 @@ const useStyles = makeStyles(({ palette }) => ({ alignItems: 'flex-start', justifyContent: 'center', flexDirection: 'column', - padding: '0.8rem 1rem', + padding: '0.8rem', width: 150, transition: 'all 250ms ease-in', textDecoration: 'none', diff --git a/client/src/pages/Ingestion/components/Uploads/index.tsx b/client/src/pages/Ingestion/components/Uploads/index.tsx index c5f46948a..9a6962fb0 100644 --- a/client/src/pages/Ingestion/components/Uploads/index.tsx +++ b/client/src/pages/Ingestion/components/Uploads/index.tsx @@ -115,7 +115,7 @@ function Uploads(): React.ReactElement { } }; - let content: React.ReactNode = ; + let content: React.ReactNode = ; if (!loadingVocabulary) { content = ( diff --git a/client/src/pages/Repository/components/RepositoryTreeView/MetadataView.tsx b/client/src/pages/Repository/components/RepositoryTreeView/MetadataView.tsx index 68cfa80ec..1cd7758a3 100644 --- a/client/src/pages/Repository/components/RepositoryTreeView/MetadataView.tsx +++ b/client/src/pages/Repository/components/RepositoryTreeView/MetadataView.tsx @@ -27,7 +27,10 @@ const useStyles = makeStyles(({ palette, typography, breakpoints }) => ({ } }, text: { - fontSize: '0.8em' + fontSize: '0.8em', + [breakpoints.down('lg')]: { + fontSize: '0.9em', + } } })); diff --git a/client/src/pages/Repository/components/RepositoryTreeView/TreeLabel.tsx b/client/src/pages/Repository/components/RepositoryTreeView/TreeLabel.tsx index 9142a1d02..a2adc2ef2 100644 --- a/client/src/pages/Repository/components/RepositoryTreeView/TreeLabel.tsx +++ b/client/src/pages/Repository/components/RepositoryTreeView/TreeLabel.tsx @@ -24,6 +24,9 @@ const useStyles = makeStyles(({ breakpoints }) => ({ fontSize: '0.8em', background: ({ color }: TreeLabelProps) => color, zIndex: 10, + [breakpoints.down('lg')]: { + fontSize: '0.9em', + } } })); @@ -65,7 +68,7 @@ const useLabelStyle = makeStyles(({ breakpoints, palette, typography }) => ({ fontWeight: typography.fontWeightLight, color: palette.grey[500], [breakpoints.down('lg')]: { - fontSize: '0.65em', + fontSize: '0.7em', } }, stickyItem: { From d0eabb31f732fbffa3fa6db867372a5d6d5704d2 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Thu, 15 Oct 2020 16:07:30 +0530 Subject: [PATCH 020/239] - Move repository tree state to store --- client/src/components/shared/Header.tsx | 4 +- .../components/RepositoryFilterView/index.tsx | 4 +- .../components/RepositoryTreeView/index.tsx | 38 +++++----------- .../pages/Repository/hooks/useRepository.ts | 11 ++--- client/src/store/repository.ts | 43 ++++++++++++++++++- 5 files changed, 62 insertions(+), 38 deletions(-) diff --git a/client/src/components/shared/Header.tsx b/client/src/components/shared/Header.tsx index 7142e0ed4..8ba9fc3fd 100644 --- a/client/src/components/shared/Header.tsx +++ b/client/src/components/shared/Header.tsx @@ -7,7 +7,7 @@ import { Link, useHistory, useLocation } from 'react-router-dom'; import { toast } from 'react-toastify'; import Logo from '../../assets/images/logo-packrat.square.png'; import { HOME_ROUTES, resolveRoute, ROUTES } from '../../constants'; -import { useRepositoryFilterStore, useUserStore } from '../../store'; +import { useRepositoryStore, useUserStore } from '../../store'; import { Colors } from '../../theme'; const useStyles = makeStyles(({ palette, spacing, typography, breakpoints }) => ({ @@ -79,7 +79,7 @@ function Header(): React.ReactElement { const history = useHistory(); const { pathname } = useLocation(); const { user, logout } = useUserStore(); - const [search, updateSearch] = useRepositoryFilterStore(state => [state.search, state.updateSearch]); + const [search, updateSearch] = useRepositoryStore(state => [state.search, state.updateSearch]); const onSearch = (): void => { const route: string = resolveRoute(HOME_ROUTES.REPOSITORY); diff --git a/client/src/pages/Repository/components/RepositoryFilterView/index.tsx b/client/src/pages/Repository/components/RepositoryFilterView/index.tsx index a71309cfb..c9f9a438a 100644 --- a/client/src/pages/Repository/components/RepositoryFilterView/index.tsx +++ b/client/src/pages/Repository/components/RepositoryFilterView/index.tsx @@ -5,7 +5,7 @@ import { FaChevronDown, FaChevronUp } from 'react-icons/fa'; import { FiLink2 } from 'react-icons/fi'; import { IoIosRemoveCircle } from 'react-icons/io'; import { toast } from 'react-toastify'; -import { useRepositoryFilterStore } from '../../../../store'; +import { useRepositoryStore } from '../../../../store'; import { Colors, palette } from '../../../../theme'; import FilterDate from './FilterDate'; import FilterSelect from './FilterSelect'; @@ -91,7 +91,7 @@ const StyledChip = withStyles(({ palette }) => ({ }))(Chip); function RepositoryFilterView(): React.ReactElement { - const [isExpanded, toggleFilter] = useRepositoryFilterStore(state => [state.isExpanded, state.toggleFilter]); + const [isExpanded, toggleFilter] = useRepositoryStore(state => [state.isExpanded, state.toggleFilter]); const classes = useStyles(isExpanded); const [chips] = useState([ { diff --git a/client/src/pages/Repository/components/RepositoryTreeView/index.tsx b/client/src/pages/Repository/components/RepositoryTreeView/index.tsx index 850dab1a8..b8277f8c9 100644 --- a/client/src/pages/Repository/components/RepositoryTreeView/index.tsx +++ b/client/src/pages/Repository/components/RepositoryTreeView/index.tsx @@ -2,13 +2,11 @@ import { makeStyles } from '@material-ui/core/styles'; import ChevronRightIcon from '@material-ui/icons/ChevronRight'; import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; import { TreeView } from '@material-ui/lab'; -import React, { useEffect, useState } from 'react'; +import React, { useEffect } from 'react'; import { Loader } from '../../../../components'; -import { useRepositoryFilterStore } from '../../../../store'; +import { treeRootKey, useRepositoryStore } from '../../../../store'; import { NavigationResultEntry } from '../../../../types/graphql'; -import { eMetadata, eSystemObjectType } from '../../../../types/server'; -import { getObjectInterfaceDetails, getRepositoryTreeNodeId, getTreeColorVariant, getTreeViewColumns, getTreeWidth, parseRepositoryTreeNodeId } from '../../../../utils/repository'; -import { getObjectChildren, useGetRootObjects } from '../../hooks/useRepository'; +import { getObjectInterfaceDetails, getRepositoryTreeNodeId, getTreeColorVariant, getTreeViewColumns, getTreeWidth } from '../../../../utils/repository'; import RepositoryTreeHeader from './RepositoryTreeHeader'; import StyledTreeItem from './StyledTreeItem'; import TreeLabel, { TreeLabelEmpty, TreeLabelLoading } from './TreeLabel'; @@ -35,36 +33,20 @@ const useStyles = makeStyles(({ breakpoints }) => ({ })); function RepositoryTreeView(): React.ReactElement { - const { isExpanded } = useRepositoryFilterStore(); + const [loading, isExpanded] = useRepositoryStore(state => [state.loading, state.isExpanded]); const classes = useStyles(isExpanded); - const metadataColumns: eMetadata[] = [eMetadata.eUnitAbbreviation, eMetadata.eSubjectIdentifier, eMetadata.eItemName]; - const { data, loading, error } = useGetRootObjects([eSystemObjectType.eUnit], metadataColumns); - - const [tree, setTree] = useState>(() => new Map([['root', []]])); + const [tree, initializeTree, getChildren] = useRepositoryStore(state => [state.tree, state.initializeTree, state.getChildren]); + const metadataColumns = useRepositoryStore(state => state.metadataToDisplay); useEffect(() => { - if (data && !loading && !error) { - const { getObjectChildren } = data; - const { entries } = getObjectChildren; - const updatedRootMap = new Map([['root', entries]]); - setTree(updatedRootMap); - } - }, [data, loading, error]); + initializeTree(); + }, [initializeTree]); const onNodeToggle = async (_, nodeIds: string[]) => { if (!nodeIds.length) return; const [nodeId] = nodeIds.slice(); - const { idSystemObject } = parseRepositoryTreeNodeId(nodeId); - const { data, error } = await getObjectChildren(idSystemObject, metadataColumns); - - if (data && !error) { - const { getObjectChildren } = data; - const { entries } = getObjectChildren; - const updatedTree = new Map(tree); - updatedTree.set(nodeId, entries); - setTree(updatedTree); - } + getChildren(nodeId); }; const renderTree = (children: NavigationResultEntry[] | undefined) => { @@ -117,7 +99,7 @@ function RepositoryTreeView(): React.ReactElement { style={{ width }} > - {renderTree(tree.get('root'))} + {renderTree(tree.get(treeRootKey))} ); } diff --git a/client/src/pages/Repository/hooks/useRepository.ts b/client/src/pages/Repository/hooks/useRepository.ts index 8d8af64e0..852bece5a 100644 --- a/client/src/pages/Repository/hooks/useRepository.ts +++ b/client/src/pages/Repository/hooks/useRepository.ts @@ -1,10 +1,11 @@ -import { ApolloQueryResult, QueryResult, useQuery } from '@apollo/client'; +import { ApolloQueryResult } from '@apollo/client'; import { apolloClient } from '../../../graphql'; -import { GetObjectChildrenDocument, GetObjectChildrenQuery, GetObjectChildrenQueryVariables } from '../../../types/graphql'; +import { GetObjectChildrenDocument, GetObjectChildrenQuery } from '../../../types/graphql'; import { eMetadata, eSystemObjectType } from '../../../types/server'; -function useGetRootObjects(objectTypes: eSystemObjectType[], metadataColumns: eMetadata[]): QueryResult { - return useQuery(GetObjectChildrenDocument, { +function getObjectChildrenForRoot(objectTypes: eSystemObjectType[], metadataColumns: eMetadata[]): Promise> { + return apolloClient.query({ + query: GetObjectChildrenDocument, variables: { input: { idRoot: 0, @@ -28,4 +29,4 @@ function getObjectChildren(idRoot: number, metadataColumns: eMetadata[]): Promis }); } -export { useGetRootObjects, getObjectChildren }; +export { getObjectChildrenForRoot, getObjectChildren }; diff --git a/client/src/store/repository.ts b/client/src/store/repository.ts index 1779e09f3..9b5720033 100644 --- a/client/src/store/repository.ts +++ b/client/src/store/repository.ts @@ -1,20 +1,61 @@ import create, { GetState, SetState } from 'zustand'; +import { getObjectChildren, getObjectChildrenForRoot } from '../pages/Repository/hooks/useRepository'; +import { NavigationResultEntry } from '../types/graphql'; +import { eMetadata, eSystemObjectType } from '../types/server'; +import { parseRepositoryTreeNodeId } from '../utils/repository'; type RepositoryStore = { isExpanded: boolean; search: string; + tree: Map; + loading: boolean; updateSearch: (value: string) => void; toggleFilter: () => void; + repositoryRootType: eSystemObjectType[]; + metadataToDisplay: eMetadata[]; + initializeTree: () => Promise; + getChildren: (nodeId: string) => Promise; }; -export const useRepositoryFilterStore = create((set: SetState, get: GetState) => ({ +export const treeRootKey: string = 'root'; + +export const useRepositoryStore = create((set: SetState, get: GetState) => ({ isExpanded: true, search: '', + tree: new Map([[treeRootKey, []]]), + loading: true, + repositoryRootType: [eSystemObjectType.eUnit], + metadataToDisplay: [eMetadata.eUnitAbbreviation, eMetadata.eSubjectIdentifier, eMetadata.eItemName], updateSearch: (value: string): void => { set({ search: value }); }, toggleFilter: (): void => { const { isExpanded } = get(); set({ isExpanded: !isExpanded }); + }, + initializeTree: async (): Promise => { + const { repositoryRootType, metadataToDisplay } = get(); + const { data, error } = await getObjectChildrenForRoot(repositoryRootType, metadataToDisplay); + + if (data && !error) { + const { getObjectChildren } = data; + const { entries } = getObjectChildren; + const entry: [string, NavigationResultEntry[]] = [treeRootKey, entries]; + const updatedTree = new Map([entry]); + set({ tree: updatedTree, loading: false }); + } + }, + getChildren: async (nodeId: string): Promise => { + const { tree, metadataToDisplay } = get(); + const { idSystemObject } = parseRepositoryTreeNodeId(nodeId); + const { data, error } = await getObjectChildren(idSystemObject, metadataToDisplay); + + if (data && !error) { + const { getObjectChildren } = data; + const { entries } = getObjectChildren; + const updatedTree: Map = new Map(tree); + updatedTree.set(nodeId, entries); + set({ tree: updatedTree }); + } } })); From 7b4b0c3b0305efb0320f4013cd69b62d08d82837 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Fri, 16 Oct 2020 12:22:44 +0530 Subject: [PATCH 021/239] added filter fields to repository store --- .../RepositoryFilterView/FilterSelect.tsx | 17 ++++++---- .../components/RepositoryFilterView/index.tsx | 26 ++++++++------- client/src/store/repository.ts | 32 +++++++++++++++++-- client/src/utils/repository.tsx | 4 +++ 4 files changed, 59 insertions(+), 20 deletions(-) diff --git a/client/src/pages/Repository/components/RepositoryFilterView/FilterSelect.tsx b/client/src/pages/Repository/components/RepositoryFilterView/FilterSelect.tsx index 530356d39..5d8c56b7e 100644 --- a/client/src/pages/Repository/components/RepositoryFilterView/FilterSelect.tsx +++ b/client/src/pages/Repository/components/RepositoryFilterView/FilterSelect.tsx @@ -1,6 +1,7 @@ import { Box, MenuItem, Select, Typography } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; import React from 'react'; +import { useRepositoryStore } from '../../../../store'; const useStyles = makeStyles(({ palette, breakpoints }) => ({ label: { @@ -28,15 +29,18 @@ const useStyles = makeStyles(({ palette, breakpoints }) => ({ interface FilterSelectProps { label: string; name: string; + options: number[]; + multiple?: boolean; } function FilterSelect(props: FilterSelectProps): React.ReactElement { - const { label, name } = props; - + const { label, name, multiple, options } = props; const classes = useStyles(); - const onChange = (v) => { - console.log(v); + const [value, updateFilterValue] = useRepositoryStore(state => [state[name], state.updateFilterValue]); + + const onChange = ({ target }) => { + updateFilterValue(name, target.value); }; const inputProps = { @@ -49,14 +53,15 @@ function FilterSelect(props: FilterSelectProps): React.ReactElement { {label} ); diff --git a/client/src/pages/Repository/components/RepositoryFilterView/index.tsx b/client/src/pages/Repository/components/RepositoryFilterView/index.tsx index c9f9a438a..203e8cf72 100644 --- a/client/src/pages/Repository/components/RepositoryFilterView/index.tsx +++ b/client/src/pages/Repository/components/RepositoryFilterView/index.tsx @@ -7,6 +7,7 @@ import { IoIosRemoveCircle } from 'react-icons/io'; import { toast } from 'react-toastify'; import { useRepositoryStore } from '../../../../store'; import { Colors, palette } from '../../../../theme'; +import { eSystemObjectType } from '../../../../types/server'; import FilterDate from './FilterDate'; import FilterSelect from './FilterSelect'; @@ -90,6 +91,9 @@ const StyledChip = withStyles(({ palette }) => ({ } }))(Chip); +const mockOptions: number[] = [0, 1, 2, 3]; +const repositoryRootTypes: eSystemObjectType[] = [eSystemObjectType.eUnit, eSystemObjectType.eProject]; + function RepositoryFilterView(): React.ReactElement { const [isExpanded, toggleFilter] = useRepositoryStore(state => [state.isExpanded, state.toggleFilter]); const classes = useStyles(isExpanded); @@ -145,24 +149,24 @@ function RepositoryFilterView(): React.ReactElement { - - - + + + - - - - + + + + - - - - + + + + diff --git a/client/src/store/repository.ts b/client/src/store/repository.ts index 9b5720033..597ec206d 100644 --- a/client/src/store/repository.ts +++ b/client/src/store/repository.ts @@ -2,7 +2,7 @@ import create, { GetState, SetState } from 'zustand'; import { getObjectChildren, getObjectChildrenForRoot } from '../pages/Repository/hooks/useRepository'; import { NavigationResultEntry } from '../types/graphql'; import { eMetadata, eSystemObjectType } from '../types/server'; -import { parseRepositoryTreeNodeId } from '../utils/repository'; +import { parseRepositoryTreeNodeId, sortEntriesAlphabetically } from '../utils/repository'; type RepositoryStore = { isExpanded: boolean; @@ -12,7 +12,17 @@ type RepositoryStore = { updateSearch: (value: string) => void; toggleFilter: () => void; repositoryRootType: eSystemObjectType[]; + objectsToDisplay: number[]; metadataToDisplay: eMetadata[]; + units: number[]; + projects: number[]; + has: number; + missing: number; + captureMethod: number; + variantType: number; + modelPurpose: number; + modelFileType: number; + updateFilterValue: (name: string, value: number | number[]) => void; initializeTree: () => Promise; getChildren: (nodeId: string) => Promise; }; @@ -25,7 +35,21 @@ export const useRepositoryStore = create((set: SetState([[treeRootKey, []]]), loading: true, repositoryRootType: [eSystemObjectType.eUnit], + objectsToDisplay: [0], metadataToDisplay: [eMetadata.eUnitAbbreviation, eMetadata.eSubjectIdentifier, eMetadata.eItemName], + units: [0], + projects: [0], + has: 0, + missing: 0, + captureMethod: 0, + variantType: 0, + modelPurpose: 0, + modelFileType: 0, + updateFilterValue: (name: string, value: number | number[]): void => { + const { initializeTree } = get(); + set({ [name]: value, loading: true }); + initializeTree(); + }, updateSearch: (value: string): void => { set({ search: value }); }, @@ -40,7 +64,8 @@ export const useRepositoryStore = create((set: SetState((set: SetState = new Map(tree); - updatedTree.set(nodeId, entries); + updatedTree.set(nodeId, sortedEntries); set({ tree: updatedTree }); } } diff --git a/client/src/utils/repository.tsx b/client/src/utils/repository.tsx index f6a920134..3cf5a4f50 100644 --- a/client/src/utils/repository.tsx +++ b/client/src/utils/repository.tsx @@ -192,4 +192,8 @@ export function getObjectInterfaceDetails(objectType: eSystemObjectType, variant default: return { icon: , color: Colors.repository.default[variant] }; } +} + +export function sortEntriesAlphabetically(entries: NavigationResultEntry[]): NavigationResultEntry[] { + return lodash.orderBy(entries, [entry => entry.name.toLowerCase().trim()], ['asc']); } \ No newline at end of file From 3b98e709a1a3c29d238c888ec45f20460e92f5a0 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Fri, 16 Oct 2020 12:44:25 +0530 Subject: [PATCH 022/239] memoized repository tree --- .../components/RepositoryFilterView/index.tsx | 63 +++++++++---------- .../RepositoryTreeView/MetadataView.tsx | 2 +- .../RepositoryTreeHeader.tsx | 4 +- .../RepositoryTreeView/StyledTreeItem.tsx | 2 +- .../RepositoryTreeView/TreeLabel.tsx | 2 +- .../components/RepositoryTreeView/index.tsx | 6 +- 6 files changed, 39 insertions(+), 40 deletions(-) diff --git a/client/src/pages/Repository/components/RepositoryFilterView/index.tsx b/client/src/pages/Repository/components/RepositoryFilterView/index.tsx index 203e8cf72..b553af85b 100644 --- a/client/src/pages/Repository/components/RepositoryFilterView/index.tsx +++ b/client/src/pages/Repository/components/RepositoryFilterView/index.tsx @@ -14,14 +14,14 @@ import FilterSelect from './FilterSelect'; const useStyles = makeStyles(({ palette, typography, breakpoints }) => ({ container: { display: 'flex', - height: (isExpanded: boolean) => isExpanded ? 250 : 35, + height: (isExpanded: boolean) => isExpanded ? 235 : 35, background: palette.primary.light, borderRadius: 5, padding: 10, marginBottom: 10, transition: '250ms height ease', [breakpoints.down('lg')]: { - height: (isExpanded: boolean) => isExpanded ? 230 : 20, + height: (isExpanded: boolean) => isExpanded ? 215 : 30 } }, content: { @@ -63,6 +63,7 @@ const useStyles = makeStyles(({ palette, typography, breakpoints }) => ({ width: 280, padding: '5px 8px', borderRadius: 5, + color: palette.primary.dark, backgroundColor: Colors.defaults.white, fontSize: '0.8em', cursor: 'pointer' @@ -78,7 +79,7 @@ const useStyles = makeStyles(({ palette, typography, breakpoints }) => ({ }, options: { display: 'flex', - alignItems: 'flex-end', + alignItems: (isExpanded: boolean) => isExpanded ? 'flex-end' : 'center', justifyContent: 'center' } })); @@ -115,39 +116,37 @@ function RepositoryFilterView(): React.ReactElement { } }; - let content: React.ReactNode = null; + let content: React.ReactNode = ( + + + Unit: All + + + {chips.map((chip, index: number) => { + const { type, name } = chip; + const handleDelete = () => null; + const label = `${type}: ${name}`; + + return ( + } + className={classes.chip} + onDelete={handleDelete} + variant='outlined' + /> + ); + })} + + ); if (isExpanded) { content = ( - - - - Unit: All - - click to select - - - {chips.map((chip, index: number) => { - const { type, name } = chip; - const handleDelete = () => null; - const label = `${type}: ${name}`; - - return ( - } - className={classes.chip} - onDelete={handleDelete} - variant='outlined' - /> - ); - })} - - - + {content} + diff --git a/client/src/pages/Repository/components/RepositoryTreeView/MetadataView.tsx b/client/src/pages/Repository/components/RepositoryTreeView/MetadataView.tsx index 1cd7758a3..4fd8a3bc5 100644 --- a/client/src/pages/Repository/components/RepositoryTreeView/MetadataView.tsx +++ b/client/src/pages/Repository/components/RepositoryTreeView/MetadataView.tsx @@ -66,4 +66,4 @@ function MetadataView(props: MetadataViewProps): React.ReactElement { ); } -export default MetadataView; +export default React.memo(MetadataView); diff --git a/client/src/pages/Repository/components/RepositoryTreeView/RepositoryTreeHeader.tsx b/client/src/pages/Repository/components/RepositoryTreeView/RepositoryTreeHeader.tsx index f3ff3dddd..a74c233b5 100644 --- a/client/src/pages/Repository/components/RepositoryTreeView/RepositoryTreeHeader.tsx +++ b/client/src/pages/Repository/components/RepositoryTreeView/RepositoryTreeHeader.tsx @@ -11,7 +11,7 @@ const useStyles = makeStyles(({ palette, typography, breakpoints }) => ({ height: 50, backgroundColor: palette.primary.light, borderRadius: 5, - margin: '0px 0px 5px 0px', + marginBottom: 5, position: 'sticky', top: 0, zIndex: 20, @@ -66,4 +66,4 @@ function RepositoryTreeHeader(props: RepositoryTreeHeaderProps): React.ReactElem ); } -export default RepositoryTreeHeader; \ No newline at end of file +export default React.memo(RepositoryTreeHeader); \ No newline at end of file diff --git a/client/src/pages/Repository/components/RepositoryTreeView/StyledTreeItem.tsx b/client/src/pages/Repository/components/RepositoryTreeView/StyledTreeItem.tsx index 1a63648f2..75a9582ad 100644 --- a/client/src/pages/Repository/components/RepositoryTreeView/StyledTreeItem.tsx +++ b/client/src/pages/Repository/components/RepositoryTreeView/StyledTreeItem.tsx @@ -58,4 +58,4 @@ const StyledTreeItem = withStyles(({ palette, typography, breakpoints }: Theme) } }))((props: TreeItemProps & StyledTreeItemProps) => ); -export default StyledTreeItem; +export default React.memo(StyledTreeItem); diff --git a/client/src/pages/Repository/components/RepositoryTreeView/TreeLabel.tsx b/client/src/pages/Repository/components/RepositoryTreeView/TreeLabel.tsx index a2adc2ef2..cd783f8e1 100644 --- a/client/src/pages/Repository/components/RepositoryTreeView/TreeLabel.tsx +++ b/client/src/pages/Repository/components/RepositoryTreeView/TreeLabel.tsx @@ -104,4 +104,4 @@ export function TreeLabelEmpty(props: TreeLabelEmptyProps): React.ReactElement { ); } -export default TreeLabel; \ No newline at end of file +export default React.memo(TreeLabel); \ No newline at end of file diff --git a/client/src/pages/Repository/components/RepositoryTreeView/index.tsx b/client/src/pages/Repository/components/RepositoryTreeView/index.tsx index b8277f8c9..65d320889 100644 --- a/client/src/pages/Repository/components/RepositoryTreeView/index.tsx +++ b/client/src/pages/Repository/components/RepositoryTreeView/index.tsx @@ -15,13 +15,13 @@ const useStyles = makeStyles(({ breakpoints }) => ({ container: { display: 'flex', flex: 5, - maxHeight: (isExpanded: boolean) => isExpanded ? '60vh' : '82vh', + maxHeight: (isExpanded: boolean) => isExpanded ? '62vh' : '82vh', maxWidth: '85vw', flexDirection: 'column', overflow: 'auto', transition: '250ms height ease', [breakpoints.down('lg')]: { - maxHeight: (isExpanded: boolean) => isExpanded ? '50vh' : '80vh', + maxHeight: (isExpanded: boolean) => isExpanded ? '54vh' : '79vh', maxWidth: '81.5vw' } }, @@ -111,4 +111,4 @@ function RepositoryTreeView(): React.ReactElement { ); } -export default RepositoryTreeView; +export default React.memo(RepositoryTreeView); From 5ec5e849692049cb6d0959c6aeaed6a4950a8efe Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Fri, 16 Oct 2020 13:09:48 +0530 Subject: [PATCH 023/239] - Size fixes to ingestion and repository --- .../shared/SidebarBottomNavigator.tsx | 7 ++-- .../Photogrammetry/IdentifierList.tsx | 9 +++-- .../Ingestion/components/Metadata/index.tsx | 12 +++---- .../components/SubjectItem/index.tsx | 12 +++---- .../Ingestion/components/Uploads/index.tsx | 8 ++--- .../components/RepositoryFilterView/index.tsx | 34 +++++++++++-------- 6 files changed, 36 insertions(+), 46 deletions(-) diff --git a/client/src/components/shared/SidebarBottomNavigator.tsx b/client/src/components/shared/SidebarBottomNavigator.tsx index d445b38a5..670a7ee5a 100644 --- a/client/src/components/shared/SidebarBottomNavigator.tsx +++ b/client/src/components/shared/SidebarBottomNavigator.tsx @@ -11,13 +11,10 @@ const useStyles = makeStyles(({ palette, breakpoints }) => ({ bottom: 0, alignItems: 'center', justifyContent: 'space-between', - width: '51vw', + width: '53vw', padding: '20px 0px', - marginLeft: 40, + marginLeft: 20, background: palette.background.paper, - [breakpoints.down('lg')]: { - marginLeft: 20 - } }, navButton: { minHeight: 35, diff --git a/client/src/pages/Ingestion/components/Metadata/Photogrammetry/IdentifierList.tsx b/client/src/pages/Ingestion/components/Metadata/Photogrammetry/IdentifierList.tsx index 093d6e086..bdc5ddc99 100644 --- a/client/src/pages/Ingestion/components/Metadata/Photogrammetry/IdentifierList.tsx +++ b/client/src/pages/Ingestion/components/Metadata/Photogrammetry/IdentifierList.tsx @@ -57,11 +57,10 @@ const useStyles = makeStyles(({ palette, typography, spacing, breakpoints }) => cursor: 'pointer' }, addIdentifier: { - color: palette.background.paper, + height: 30, width: 80, - [breakpoints.down('lg')]: { - fontSize: '0.8em', - } + fontSize: '0.8em', + color: palette.background.paper, } })); @@ -117,7 +116,7 @@ function IdentifierList(props: IdentifierListProps): React.ReactElement { > {identifierTypes.map(({ idVocabulary, Term }, index) => {Term})} - + ); })} diff --git a/client/src/pages/Ingestion/components/Metadata/index.tsx b/client/src/pages/Ingestion/components/Metadata/index.tsx index d84924224..a9f2e05bc 100644 --- a/client/src/pages/Ingestion/components/Metadata/index.tsx +++ b/client/src/pages/Ingestion/components/Metadata/index.tsx @@ -11,7 +11,7 @@ import { FileId, StateItem, StateMetadata, StateProject, useItemStore, useMetada import useIngest from '../../hooks/useIngest'; import Photogrammetry from './Photogrammetry'; -const useStyles = makeStyles(({ palette, breakpoints }) => ({ +const useStyles = makeStyles(({ palette }) => ({ container: { display: 'flex', flex: 1, @@ -23,13 +23,9 @@ const useStyles = makeStyles(({ palette, breakpoints }) => ({ display: 'flex', flex: 1, flexDirection: 'column', - width: '50vw', - padding: 40, - paddingBottom: 0, - [breakpoints.down('lg')]: { - padding: 20, - paddingBottom: 0, - } + width: '52vw', + padding: 20, + paddingBottom: 0 }, breadcrumbs: { marginBottom: 10, diff --git a/client/src/pages/Ingestion/components/SubjectItem/index.tsx b/client/src/pages/Ingestion/components/SubjectItem/index.tsx index fe42359dd..0a613adb4 100644 --- a/client/src/pages/Ingestion/components/SubjectItem/index.tsx +++ b/client/src/pages/Ingestion/components/SubjectItem/index.tsx @@ -11,7 +11,7 @@ import ProjectList from './ProjectList'; import SearchList from './SearchList'; import SubjectList from './SubjectList'; -const useStyles = makeStyles(({ palette, breakpoints }) => ({ +const useStyles = makeStyles(({ palette }) => ({ container: { display: 'flex', flex: 1, @@ -22,14 +22,10 @@ const useStyles = makeStyles(({ palette, breakpoints }) => ({ content: { display: 'flex', flex: 1, - width: '50vw', + width: '52vw', flexDirection: 'column', - padding: 40, - paddingBottom: 0, - [breakpoints.down('lg')]: { - padding: 20, - paddingBottom: 0, - } + padding: 20, + paddingBottom: 0 }, filesLabel: { color: palette.primary.dark, diff --git a/client/src/pages/Ingestion/components/Uploads/index.tsx b/client/src/pages/Ingestion/components/Uploads/index.tsx index 9a6962fb0..eb2cff182 100644 --- a/client/src/pages/Ingestion/components/Uploads/index.tsx +++ b/client/src/pages/Ingestion/components/Uploads/index.tsx @@ -13,7 +13,7 @@ import UploadCompleteList from './UploadCompleteList'; import UploadFilesPicker from './UploadFilesPicker'; import UploadList from './UploadList'; -const useStyles = makeStyles(({ palette, typography, spacing, breakpoints }) => ({ +const useStyles = makeStyles(({ palette, typography, spacing }) => ({ container: { display: 'flex', flex: 1, @@ -25,12 +25,8 @@ const useStyles = makeStyles(({ palette, typography, spacing, breakpoints }) => display: 'flex', flex: 1, flexDirection: 'column', - padding: 40, + padding: 20, paddingBottom: 0, - [breakpoints.down('lg')]: { - padding: 20, - paddingBottom: 0, - } }, fileDrop: { display: 'flex', diff --git a/client/src/pages/Repository/components/RepositoryFilterView/index.tsx b/client/src/pages/Repository/components/RepositoryFilterView/index.tsx index b553af85b..4a1f7d0a0 100644 --- a/client/src/pages/Repository/components/RepositoryFilterView/index.tsx +++ b/client/src/pages/Repository/components/RepositoryFilterView/index.tsx @@ -87,7 +87,7 @@ const useStyles = makeStyles(({ palette, typography, breakpoints }) => ({ const StyledChip = withStyles(({ palette }) => ({ outlined: { height: 30, - fontSize: '0.8em', + fontSize: '0.75em', border: `0.5px solid ${palette.primary.contrastText}` } }))(Chip); @@ -142,6 +142,15 @@ function RepositoryFilterView(): React.ReactElement { ); + let expandIcon: React.ReactNode = ( + + ); + if (isExpanded) { content = ( @@ -172,6 +181,15 @@ function RepositoryFilterView(): React.ReactElement { ); + + expandIcon = ( + + ); } return ( @@ -182,19 +200,7 @@ function RepositoryFilterView(): React.ReactElement { - {isExpanded ? - : - } + {expandIcon} From 27f7638261ed1242d11555a048a5a66dfd5b0796 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Mon, 19 Oct 2020 12:25:21 +0530 Subject: [PATCH 024/239] minor fixes --- .../pages/Ingestion/components/Uploads/UploadFilesPicker.tsx | 2 +- client/src/pages/Ingestion/components/Uploads/UploadList.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/pages/Ingestion/components/Uploads/UploadFilesPicker.tsx b/client/src/pages/Ingestion/components/Uploads/UploadFilesPicker.tsx index bcf4505a4..19a811ffb 100644 --- a/client/src/pages/Ingestion/components/Uploads/UploadFilesPicker.tsx +++ b/client/src/pages/Ingestion/components/Uploads/UploadFilesPicker.tsx @@ -13,7 +13,7 @@ const useStyles = makeStyles(({ palette, typography, spacing }) => ({ alignItems: 'center', justifyContent: 'center', height: '20vh', - width: '50vw', + width: '52vw', border: `1px dashed ${palette.primary.main}`, borderRadius: 10, padding: 10, diff --git a/client/src/pages/Ingestion/components/Uploads/UploadList.tsx b/client/src/pages/Ingestion/components/Uploads/UploadList.tsx index d6ac46721..ac7a8029f 100644 --- a/client/src/pages/Ingestion/components/Uploads/UploadList.tsx +++ b/client/src/pages/Ingestion/components/Uploads/UploadList.tsx @@ -13,7 +13,7 @@ export const useUploadListStyles = makeStyles(({ palette, breakpoints }) => ({ flexDirection: 'column', marginTop: 20, maxHeight: 'auto', - width: '50vw', + width: '52vw', }, list: { display: 'flex', From 42144b1da43758a3d3149716087ec08d6a9b5cc3 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Mon, 19 Oct 2020 12:59:17 +0530 Subject: [PATCH 025/239] added useCallback hook to optimize treeview --- .../RepositoryFilterView/FilterSelect.tsx | 8 ++- .../components/RepositoryTreeView/index.tsx | 15 ++--- yarn.lock | 63 ++++++++++++++++++- 3 files changed, 75 insertions(+), 11 deletions(-) diff --git a/client/src/pages/Repository/components/RepositoryFilterView/FilterSelect.tsx b/client/src/pages/Repository/components/RepositoryFilterView/FilterSelect.tsx index 5d8c56b7e..14610a1ed 100644 --- a/client/src/pages/Repository/components/RepositoryFilterView/FilterSelect.tsx +++ b/client/src/pages/Repository/components/RepositoryFilterView/FilterSelect.tsx @@ -40,7 +40,13 @@ function FilterSelect(props: FilterSelectProps): React.ReactElement { const [value, updateFilterValue] = useRepositoryStore(state => [state[name], state.updateFilterValue]); const onChange = ({ target }) => { - updateFilterValue(name, target.value); + let { value } = target; + + if (multiple) { + value = value.sort(); + } + + updateFilterValue(name, value); }; const inputProps = { diff --git a/client/src/pages/Repository/components/RepositoryTreeView/index.tsx b/client/src/pages/Repository/components/RepositoryTreeView/index.tsx index 65d320889..e1278264f 100644 --- a/client/src/pages/Repository/components/RepositoryTreeView/index.tsx +++ b/client/src/pages/Repository/components/RepositoryTreeView/index.tsx @@ -2,7 +2,7 @@ import { makeStyles } from '@material-ui/core/styles'; import ChevronRightIcon from '@material-ui/icons/ChevronRight'; import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; import { TreeView } from '@material-ui/lab'; -import React, { useEffect } from 'react'; +import React, { useCallback, useEffect } from 'react'; import { Loader } from '../../../../components'; import { treeRootKey, useRepositoryStore } from '../../../../store'; import { NavigationResultEntry } from '../../../../types/graphql'; @@ -33,7 +33,7 @@ const useStyles = makeStyles(({ breakpoints }) => ({ })); function RepositoryTreeView(): React.ReactElement { - const [loading, isExpanded] = useRepositoryStore(state => [state.loading, state.isExpanded]); + const [loading, isExpanded] = useRepositoryStore(useCallback(state => [state.loading, state.isExpanded], [])); const classes = useStyles(isExpanded); const [tree, initializeTree, getChildren] = useRepositoryStore(state => [state.tree, state.initializeTree, state.getChildren]); @@ -43,13 +43,13 @@ function RepositoryTreeView(): React.ReactElement { initializeTree(); }, [initializeTree]); - const onNodeToggle = async (_, nodeIds: string[]) => { + const onNodeToggle = useCallback(async (_, nodeIds: string[]) => { if (!nodeIds.length) return; const [nodeId] = nodeIds.slice(); getChildren(nodeId); - }; + }, [getChildren]); - const renderTree = (children: NavigationResultEntry[] | undefined) => { + const renderTree = useCallback((children: NavigationResultEntry[] | undefined) => { if (!children) return null; return children.map((child: NavigationResultEntry, index: number) => { const { idSystemObject, objectType, idObject, name, metadata } = child; @@ -82,13 +82,14 @@ function RepositoryTreeView(): React.ReactElement { ); }); - }; + }, [tree, metadataColumns]); let content: React.ReactNode = ; if (!loading) { const treeColumns = getTreeViewColumns(metadataColumns, false); const width = getTreeWidth(treeColumns.length); + const children = tree.get(treeRootKey); content = ( - {renderTree(tree.get(treeRootKey))} + {renderTree(children)} ); } diff --git a/yarn.lock b/yarn.lock index 4c5ec820b..20c998465 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5479,7 +5479,7 @@ class-utils@^0.3.5: isobject "^3.0.0" static-extend "^0.1.1" -classnames@^2.2.6: +classnames@^2.2.5, classnames@^2.2.6: version "2.2.6" resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce" integrity sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q== @@ -6883,7 +6883,7 @@ dom-converter@^0.2: dependencies: utila "~0.4" -dom-helpers@^5.0.1: +dom-helpers@^5.0.1, dom-helpers@^5.1.3: version "5.2.0" resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.0.tgz#57fd054c5f8f34c52a3eeffdb7e7e93cd357d95b" integrity sha512-Ru5o9+V8CpunKnz5LGgWXkmrH/20cGKwcHwS4m73zIvs54CN9epEmT/HLqFJW3kXpakAFkEdzgy1hzlJe3E4OQ== @@ -10947,6 +10947,11 @@ lodash.debounce@^4, lodash.debounce@^4.0.8: resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168= +lodash.findindex@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.findindex/-/lodash.findindex-4.6.0.tgz#a3245dee61fb9b6e0624b535125624bb69c11106" + integrity sha1-oyRd7mH7m24GJLU1ElYku2nBEQY= + lodash.flow@^3.5.0: version "3.5.0" resolved "https://registry.yarnpkg.com/lodash.flow/-/lodash.flow-3.5.0.tgz#87bf40292b8cf83e4e8ce1a3ae4209e20071675a" @@ -10967,6 +10972,11 @@ lodash.isboolean@^3.0.3: resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" integrity sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY= +lodash.isequal@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" + integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA= + lodash.isinteger@^4.0.4: version "4.0.4" resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" @@ -10997,6 +11007,11 @@ lodash.memoize@4.x, lodash.memoize@^4.1.2: resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4= +lodash.omit@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.omit/-/lodash.omit-4.5.0.tgz#6eb19ae5a1ee1dd9df0b969e66ce0b7fa30b5e60" + integrity sha1-brGa5aHuHdnfC5aeZs4Lf6MLXmA= + lodash.once@^4.0.0: version "4.1.1" resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" @@ -11032,7 +11047,7 @@ lodash.uniq@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= -lodash@4.17.20, "lodash@>=3.5 <5", lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.5, lodash@^4.2.1, lodash@~4.17.15: +lodash@4.17.20, "lodash@>=3.5 <5", lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.1, lodash@~4.17.15: version "4.17.20" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== @@ -11223,6 +11238,11 @@ map-visit@^1.0.0: dependencies: object-visit "^1.0.0" +material-icons@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/material-icons/-/material-icons-0.1.0.tgz#af3d9117767bd7cefd1f29493fdd7ab4a931cbc8" + integrity sha1-rz2RF3Z71879HylJP916tKkxy8g= + math-random@^1.0.1: version "1.0.4" resolved "https://registry.yarnpkg.com/math-random/-/math-random-1.0.4.tgz#5dd6943c938548267016d4e34f057583080c514c" @@ -13950,6 +13970,11 @@ react-is@^16.12.0, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.0, react-i resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== +react-lifecycles-compat@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" + integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== + react-node-key@^0.1.7: version "0.1.7" resolved "https://registry.yarnpkg.com/react-node-key/-/react-node-key-0.1.7.tgz#702d756ce9c04c881d47aefe3b40e7f026042c71" @@ -14080,6 +14105,33 @@ react-transition-group@^4.0.0, react-transition-group@^4.4.0, react-transition-g loose-envify "^1.4.0" prop-types "^15.6.2" +react-virtualized-tree@3.4.1: + version "3.4.1" + resolved "https://registry.yarnpkg.com/react-virtualized-tree/-/react-virtualized-tree-3.4.1.tgz#422952f51e2c1e4eae5ab926f33ed476f26efa94" + integrity sha512-MolDiG9XgmflPPX9uPzf7iSWLqOHDlCZEiyA4FVYjv2pHfu6zV6/SIEFVyHdurWD80IjUuZ3Er12gq2bQQak2Q== + dependencies: + classnames "^2.2.5" + lodash "^4.17.4" + lodash.debounce "^4.0.8" + lodash.findindex "^4.6.0" + lodash.isequal "^4.5.0" + lodash.omit "^4.5.0" + material-icons "^0.1.0" + react-lifecycles-compat "^3.0.4" + reselect "^3.0.1" + +react-virtualized@9.22.2: + version "9.22.2" + resolved "https://registry.yarnpkg.com/react-virtualized/-/react-virtualized-9.22.2.tgz#217a870bad91e5438f46f01a009e1d8ce1060a5a" + integrity sha512-5j4h4FhxTdOpBKtePSs1yk6LDNT4oGtUwjT7Nkh61Z8vv3fTG/XeOf8J4li1AYaexOwTXnw0HFVxsV0GBUqwRw== + dependencies: + "@babel/runtime" "^7.7.2" + clsx "^1.0.4" + dom-helpers "^5.1.3" + loose-envify "^1.4.0" + prop-types "^15.7.2" + react-lifecycles-compat "^3.0.4" + react@16.13.1: version "16.13.1" resolved "https://registry.yarnpkg.com/react/-/react-16.13.1.tgz#2e818822f1a9743122c063d6410d85c1e3afe48e" @@ -14548,6 +14600,11 @@ requires-port@^1.0.0: resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8= +reselect@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/reselect/-/reselect-3.0.1.tgz#efdaa98ea7451324d092b2b2163a6a1d7a9a2147" + integrity sha1-79qpjqdFEyTQkrKyFjpqHXqaIUc= + resolve-cwd@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a" From c9887e3c96debb408030142f9ab9553022787151 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Mon, 19 Oct 2020 16:32:36 +0530 Subject: [PATCH 026/239] treeview memo fixes --- .../RepositoryTreeView/MetadataView.tsx | 3 +- .../RepositoryTreeView/TreeLabel.tsx | 7 ++- .../components/RepositoryTreeView/index.tsx | 6 +- yarn.lock | 63 +------------------ 4 files changed, 13 insertions(+), 66 deletions(-) diff --git a/client/src/pages/Repository/components/RepositoryTreeView/MetadataView.tsx b/client/src/pages/Repository/components/RepositoryTreeView/MetadataView.tsx index 4fd8a3bc5..57eec2175 100644 --- a/client/src/pages/Repository/components/RepositoryTreeView/MetadataView.tsx +++ b/client/src/pages/Repository/components/RepositoryTreeView/MetadataView.tsx @@ -1,4 +1,5 @@ import { makeStyles } from '@material-ui/core/styles'; +import lodash from 'lodash'; import React from 'react'; import { eMetadata } from '../../../../types/server'; import { computeMetadataViewWidth, trimmedMetadataField } from '../../../../utils/repository'; @@ -66,4 +67,4 @@ function MetadataView(props: MetadataViewProps): React.ReactElement { ); } -export default React.memo(MetadataView); +export default React.memo(MetadataView, lodash.isEqual); diff --git a/client/src/pages/Repository/components/RepositoryTreeView/TreeLabel.tsx b/client/src/pages/Repository/components/RepositoryTreeView/TreeLabel.tsx index cd783f8e1..7ad9adefa 100644 --- a/client/src/pages/Repository/components/RepositoryTreeView/TreeLabel.tsx +++ b/client/src/pages/Repository/components/RepositoryTreeView/TreeLabel.tsx @@ -1,6 +1,7 @@ import { makeStyles } from '@material-ui/core/styles'; import clsx from 'clsx'; -import React from 'react'; +import lodash from 'lodash'; +import React, { useMemo } from 'react'; import { Progress } from '../../../../components'; import { getTermForSystemObjectType } from '../../../../utils/repository'; import MetadataView, { TreeViewColumn } from './MetadataView'; @@ -40,7 +41,7 @@ interface TreeLabelProps { function TreeLabel(props: TreeLabelProps): React.ReactElement { const { label, treeColumns, objectType } = props; const classes = useStyles(props); - const objectTitle = `${getTermForSystemObjectType(objectType)} ${label}`; + const objectTitle = useMemo(() => `${getTermForSystemObjectType(objectType)} ${label}`, [objectType, label]); return (
@@ -104,4 +105,4 @@ export function TreeLabelEmpty(props: TreeLabelEmptyProps): React.ReactElement { ); } -export default React.memo(TreeLabel); \ No newline at end of file +export default React.memo(TreeLabel, lodash.isEqual); \ No newline at end of file diff --git a/client/src/pages/Repository/components/RepositoryTreeView/index.tsx b/client/src/pages/Repository/components/RepositoryTreeView/index.tsx index e1278264f..7c9aa5c82 100644 --- a/client/src/pages/Repository/components/RepositoryTreeView/index.tsx +++ b/client/src/pages/Repository/components/RepositoryTreeView/index.tsx @@ -59,7 +59,7 @@ function RepositoryTreeView(): React.ReactElement { let childNodesContent: React.ReactNode = ; if (childNodes) { - if (childNodes.length > 0) { + if (childNodes.length) { childNodesContent = renderTree(childNodes); } else { childNodesContent = ; @@ -70,13 +70,15 @@ function RepositoryTreeView(): React.ReactElement { const { icon, color } = getObjectInterfaceDetails(objectType, variant); const treeColumns = getTreeViewColumns(metadataColumns, false, metadata); + const label: React.ReactNode = ; + return ( } + label={label} > {childNodesContent} diff --git a/yarn.lock b/yarn.lock index 20c998465..4c5ec820b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5479,7 +5479,7 @@ class-utils@^0.3.5: isobject "^3.0.0" static-extend "^0.1.1" -classnames@^2.2.5, classnames@^2.2.6: +classnames@^2.2.6: version "2.2.6" resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce" integrity sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q== @@ -6883,7 +6883,7 @@ dom-converter@^0.2: dependencies: utila "~0.4" -dom-helpers@^5.0.1, dom-helpers@^5.1.3: +dom-helpers@^5.0.1: version "5.2.0" resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.0.tgz#57fd054c5f8f34c52a3eeffdb7e7e93cd357d95b" integrity sha512-Ru5o9+V8CpunKnz5LGgWXkmrH/20cGKwcHwS4m73zIvs54CN9epEmT/HLqFJW3kXpakAFkEdzgy1hzlJe3E4OQ== @@ -10947,11 +10947,6 @@ lodash.debounce@^4, lodash.debounce@^4.0.8: resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168= -lodash.findindex@^4.6.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/lodash.findindex/-/lodash.findindex-4.6.0.tgz#a3245dee61fb9b6e0624b535125624bb69c11106" - integrity sha1-oyRd7mH7m24GJLU1ElYku2nBEQY= - lodash.flow@^3.5.0: version "3.5.0" resolved "https://registry.yarnpkg.com/lodash.flow/-/lodash.flow-3.5.0.tgz#87bf40292b8cf83e4e8ce1a3ae4209e20071675a" @@ -10972,11 +10967,6 @@ lodash.isboolean@^3.0.3: resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" integrity sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY= -lodash.isequal@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" - integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA= - lodash.isinteger@^4.0.4: version "4.0.4" resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" @@ -11007,11 +10997,6 @@ lodash.memoize@4.x, lodash.memoize@^4.1.2: resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4= -lodash.omit@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.omit/-/lodash.omit-4.5.0.tgz#6eb19ae5a1ee1dd9df0b969e66ce0b7fa30b5e60" - integrity sha1-brGa5aHuHdnfC5aeZs4Lf6MLXmA= - lodash.once@^4.0.0: version "4.1.1" resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" @@ -11047,7 +11032,7 @@ lodash.uniq@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= -lodash@4.17.20, "lodash@>=3.5 <5", lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.1, lodash@~4.17.15: +lodash@4.17.20, "lodash@>=3.5 <5", lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.5, lodash@^4.2.1, lodash@~4.17.15: version "4.17.20" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== @@ -11238,11 +11223,6 @@ map-visit@^1.0.0: dependencies: object-visit "^1.0.0" -material-icons@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/material-icons/-/material-icons-0.1.0.tgz#af3d9117767bd7cefd1f29493fdd7ab4a931cbc8" - integrity sha1-rz2RF3Z71879HylJP916tKkxy8g= - math-random@^1.0.1: version "1.0.4" resolved "https://registry.yarnpkg.com/math-random/-/math-random-1.0.4.tgz#5dd6943c938548267016d4e34f057583080c514c" @@ -13970,11 +13950,6 @@ react-is@^16.12.0, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.0, react-i resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== -react-lifecycles-compat@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" - integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== - react-node-key@^0.1.7: version "0.1.7" resolved "https://registry.yarnpkg.com/react-node-key/-/react-node-key-0.1.7.tgz#702d756ce9c04c881d47aefe3b40e7f026042c71" @@ -14105,33 +14080,6 @@ react-transition-group@^4.0.0, react-transition-group@^4.4.0, react-transition-g loose-envify "^1.4.0" prop-types "^15.6.2" -react-virtualized-tree@3.4.1: - version "3.4.1" - resolved "https://registry.yarnpkg.com/react-virtualized-tree/-/react-virtualized-tree-3.4.1.tgz#422952f51e2c1e4eae5ab926f33ed476f26efa94" - integrity sha512-MolDiG9XgmflPPX9uPzf7iSWLqOHDlCZEiyA4FVYjv2pHfu6zV6/SIEFVyHdurWD80IjUuZ3Er12gq2bQQak2Q== - dependencies: - classnames "^2.2.5" - lodash "^4.17.4" - lodash.debounce "^4.0.8" - lodash.findindex "^4.6.0" - lodash.isequal "^4.5.0" - lodash.omit "^4.5.0" - material-icons "^0.1.0" - react-lifecycles-compat "^3.0.4" - reselect "^3.0.1" - -react-virtualized@9.22.2: - version "9.22.2" - resolved "https://registry.yarnpkg.com/react-virtualized/-/react-virtualized-9.22.2.tgz#217a870bad91e5438f46f01a009e1d8ce1060a5a" - integrity sha512-5j4h4FhxTdOpBKtePSs1yk6LDNT4oGtUwjT7Nkh61Z8vv3fTG/XeOf8J4li1AYaexOwTXnw0HFVxsV0GBUqwRw== - dependencies: - "@babel/runtime" "^7.7.2" - clsx "^1.0.4" - dom-helpers "^5.1.3" - loose-envify "^1.4.0" - prop-types "^15.7.2" - react-lifecycles-compat "^3.0.4" - react@16.13.1: version "16.13.1" resolved "https://registry.yarnpkg.com/react/-/react-16.13.1.tgz#2e818822f1a9743122c063d6410d85c1e3afe48e" @@ -14600,11 +14548,6 @@ requires-port@^1.0.0: resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8= -reselect@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/reselect/-/reselect-3.0.1.tgz#efdaa98ea7451324d092b2b2163a6a1d7a9a2147" - integrity sha1-79qpjqdFEyTQkrKyFjpqHXqaIUc= - resolve-cwd@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a" From 2b4c1fb03f3a5fd9713a840a4813e3664148a029 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Mon, 19 Oct 2020 16:38:46 +0530 Subject: [PATCH 027/239] fix undefined node id --- client/src/utils/repository.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/utils/repository.tsx b/client/src/utils/repository.tsx index 3cf5a4f50..e6a190e40 100644 --- a/client/src/utils/repository.tsx +++ b/client/src/utils/repository.tsx @@ -58,7 +58,7 @@ export function getTermForSystemObjectType(objectType: eSystemObjectType): strin } } -export function getRepositoryTreeNodeId(idSystemObject: number, idObject: number, objectType: eSystemObjectType): string { +export function getRepositoryTreeNodeId(idSystemObject: number, objectType: eSystemObjectType, idObject: number): string { return `${idSystemObject}-${eSystemObjectType[objectType]}-${idObject}`; } From 8bdf95de10a5ff3cf9272fd62b92d3ead8c54150 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Mon, 19 Oct 2020 18:39:17 +0530 Subject: [PATCH 028/239] added controls store, adjust metadata column widths --- client/src/global/root.css | 24 +++++++++++-------- client/src/pages/Home/index.tsx | 17 ++++++------- .../components/Uploads/UploadList.tsx | 13 ++-------- .../RepositoryTreeHeader.tsx | 4 +++- .../components/RepositoryTreeView/index.tsx | 23 +++++++++++------- client/src/pages/Repository/index.tsx | 7 ++++-- client/src/store/control.ts | 13 ++++++++++ client/src/store/index.ts | 1 + client/src/utils/repository.tsx | 23 ++++++++++++------ client/src/utils/shared.ts | 16 +++++++++++++ 10 files changed, 94 insertions(+), 47 deletions(-) create mode 100644 client/src/store/control.ts diff --git a/client/src/global/root.css b/client/src/global/root.css index 83da58798..8d563be31 100644 --- a/client/src/global/root.css +++ b/client/src/global/root.css @@ -1,18 +1,22 @@ +html { + scroll-behavior: smooth; +} + body { - margin: 0; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; } code { - font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace; + font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace; } #root { - display: flex; - flex: 1; - min-height: 100vh; - width: 100vw; - flex-direction: column; + display: flex; + flex: 1; + min-height: 100vh; + width: 100vw; + flex-direction: column; } \ No newline at end of file diff --git a/client/src/pages/Home/index.tsx b/client/src/pages/Home/index.tsx index c7f1c1ce8..3efc18c95 100644 --- a/client/src/pages/Home/index.tsx +++ b/client/src/pages/Home/index.tsx @@ -1,12 +1,13 @@ -import React, { useState } from 'react'; -import { Box, } from '@material-ui/core'; +import { Box } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; -import SidePanel from './components/SidePanel'; +import React from 'react'; import { Redirect, useRouteMatch } from 'react-router'; +import { Header, PrivateRoute } from '../../components'; +import { HOME_ROUTE, HOME_ROUTES, resolveRoute } from '../../constants'; +import { useControlStore } from '../../store'; import Ingestion from '../Ingestion'; import Repository from '../Repository'; -import { Header, PrivateRoute } from '../../components'; -import { resolveRoute, HOME_ROUTES, HOME_ROUTE } from '../../constants'; +import SidePanel from './components/SidePanel'; const useStyles = makeStyles(() => ({ container: { @@ -22,10 +23,10 @@ const useStyles = makeStyles(() => ({ function Home(): React.ReactElement { const classes = useStyles(); - const [isExpanded, setExpanded] = useState(true); + const [sideBarExpanded, toggleSidebar] = useControlStore(state => [state.sideBarExpanded, state.toggleSidebar]); const { path } = useRouteMatch(); - const onToggle = (): void => setExpanded(setExpanded => !setExpanded); + const onToggle = (): void => toggleSidebar(!sideBarExpanded); return ( @@ -35,7 +36,7 @@ function Home(): React.ReactElement {
- + diff --git a/client/src/pages/Ingestion/components/Uploads/UploadList.tsx b/client/src/pages/Ingestion/components/Uploads/UploadList.tsx index ac7a8029f..5c2fd15d7 100644 --- a/client/src/pages/Ingestion/components/Uploads/UploadList.tsx +++ b/client/src/pages/Ingestion/components/Uploads/UploadList.tsx @@ -3,6 +3,7 @@ import { makeStyles } from '@material-ui/core/styles'; import React from 'react'; import { FieldType } from '../../../../components'; import { useUploadStore } from '../../../../store'; +import { scrollBarProperties } from '../../../../utils/shared'; import FileList from './FileList'; import UploadListHeader from './UploadListHeader'; @@ -24,17 +25,7 @@ export const useUploadListStyles = makeStyles(({ palette, breakpoints }) => ({ 'overflow-y': 'auto', 'overflow-x': 'hidden', width: '100%', - '&::-webkit-scrollbar': { - '-webkit-appearance': 'none' - }, - '&::-webkit-scrollbar:vertical': { - width: 12 - }, - '&::-webkit-scrollbar-thumb': { - borderRadius: 8, - border: '2px solid white', - backgroundColor: palette.text.disabled - }, + ...scrollBarProperties(true, false, palette.text.disabled), [breakpoints.down('lg')]: { minHeight: '20vh', maxHeight: '20vh', diff --git a/client/src/pages/Repository/components/RepositoryTreeView/RepositoryTreeHeader.tsx b/client/src/pages/Repository/components/RepositoryTreeView/RepositoryTreeHeader.tsx index a74c233b5..93df55d75 100644 --- a/client/src/pages/Repository/components/RepositoryTreeView/RepositoryTreeHeader.tsx +++ b/client/src/pages/Repository/components/RepositoryTreeView/RepositoryTreeHeader.tsx @@ -1,6 +1,7 @@ import { Box } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; import React from 'react'; +import { useControlStore } from '../../../../store'; import { eMetadata } from '../../../../types/server'; import { getTreeViewColumns, getTreeWidth } from '../../../../utils/repository'; import MetadataView from './MetadataView'; @@ -53,8 +54,9 @@ function RepositoryTreeHeader(props: RepositoryTreeHeaderProps): React.ReactElem const { metadataColumns } = props; const classes = useStyles(); + const sideBarExpanded = useControlStore(state => state.sideBarExpanded); const treeColumns = getTreeViewColumns(metadataColumns, true); - const width = getTreeWidth(treeColumns.length); + const width = getTreeWidth(treeColumns.length, sideBarExpanded); return ( diff --git a/client/src/pages/Repository/components/RepositoryTreeView/index.tsx b/client/src/pages/Repository/components/RepositoryTreeView/index.tsx index 7c9aa5c82..76ba07cbe 100644 --- a/client/src/pages/Repository/components/RepositoryTreeView/index.tsx +++ b/client/src/pages/Repository/components/RepositoryTreeView/index.tsx @@ -4,7 +4,7 @@ import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; import { TreeView } from '@material-ui/lab'; import React, { useCallback, useEffect } from 'react'; import { Loader } from '../../../../components'; -import { treeRootKey, useRepositoryStore } from '../../../../store'; +import { treeRootKey, useControlStore, useRepositoryStore } from '../../../../store'; import { NavigationResultEntry } from '../../../../types/graphql'; import { getObjectInterfaceDetails, getRepositoryTreeNodeId, getTreeColorVariant, getTreeViewColumns, getTreeWidth } from '../../../../utils/repository'; import RepositoryTreeHeader from './RepositoryTreeHeader'; @@ -15,14 +15,14 @@ const useStyles = makeStyles(({ breakpoints }) => ({ container: { display: 'flex', flex: 5, - maxHeight: (isExpanded: boolean) => isExpanded ? '62vh' : '82vh', - maxWidth: '85vw', + maxHeight: ({ isExpanded }: StyleProps) => isExpanded ? '62vh' : '82vh', + maxWidth: ({ sideBarExpanded }: StyleProps) => sideBarExpanded ? '85vw' : '93vw', flexDirection: 'column', overflow: 'auto', - transition: '250ms height ease', + transition: '250ms height, width ease', [breakpoints.down('lg')]: { - maxHeight: (isExpanded: boolean) => isExpanded ? '54vh' : '79vh', - maxWidth: '81.5vw' + maxHeight: ({ isExpanded }: StyleProps) => isExpanded ? '54vh' : '79vh', + maxWidth: ({ sideBarExpanded }: StyleProps) => sideBarExpanded ? '81.5vw' : '92vw' } }, tree: { @@ -32,9 +32,16 @@ const useStyles = makeStyles(({ breakpoints }) => ({ } })); +type StyleProps = { + sideBarExpanded: boolean; + isExpanded: boolean; +}; + function RepositoryTreeView(): React.ReactElement { const [loading, isExpanded] = useRepositoryStore(useCallback(state => [state.loading, state.isExpanded], [])); - const classes = useStyles(isExpanded); + const sideBarExpanded = useControlStore(state => state.sideBarExpanded); + + const classes = useStyles({ isExpanded, sideBarExpanded }); const [tree, initializeTree, getChildren] = useRepositoryStore(state => [state.tree, state.initializeTree, state.getChildren]); const metadataColumns = useRepositoryStore(state => state.metadataToDisplay); @@ -90,7 +97,7 @@ function RepositoryTreeView(): React.ReactElement { if (!loading) { const treeColumns = getTreeViewColumns(metadataColumns, false); - const width = getTreeWidth(treeColumns.length); + const width = getTreeWidth(treeColumns.length, sideBarExpanded); const children = tree.get(treeRootKey); content = ( diff --git a/client/src/pages/Repository/index.tsx b/client/src/pages/Repository/index.tsx index 9d92ecdf7..ca731d522 100644 --- a/client/src/pages/Repository/index.tsx +++ b/client/src/pages/Repository/index.tsx @@ -2,6 +2,7 @@ import { Box } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; import React, { useEffect, useState } from 'react'; import { useHistory, useLocation } from 'react-router'; +import { useControlStore } from '../../store'; import { generateRepositoryUrl, parseRepositoryUrl } from '../../utils/repository'; import RepositoryFilterView from './components/RepositoryFilterView'; import RepositoryTreeView from './components/RepositoryTreeView'; @@ -10,13 +11,14 @@ const useStyles = makeStyles(({ breakpoints }) => ({ container: { display: 'flex', flex: 1, - maxWidth: '85vw', + maxWidth: (sideBarExpanded: boolean) => sideBarExpanded ? '85vw' : '93vw', flexDirection: 'column', padding: 20, paddingBottom: 0, paddingRight: 0, [breakpoints.down('lg')]: { paddingRight: 20, + maxWidth: (sideBarExpanded: boolean) => sideBarExpanded ? '85vw' : '92vw', } } })); @@ -27,7 +29,8 @@ export type RepositoryFilter = { }; function Repository(): React.ReactElement { - const classes = useStyles(); + const sideBarExpanded = useControlStore(state => state.sideBarExpanded); + const classes = useStyles(sideBarExpanded); const history = useHistory(); const { search } = useLocation(); diff --git a/client/src/store/control.ts b/client/src/store/control.ts new file mode 100644 index 000000000..ae78d0b45 --- /dev/null +++ b/client/src/store/control.ts @@ -0,0 +1,13 @@ +import create, { SetState } from 'zustand'; + +type ControlStore = { + sideBarExpanded: boolean; + toggleSidebar: (sideBarExpanded: boolean) => void; +}; + +export const useControlStore = create((set: SetState) => ({ + sideBarExpanded: true, + toggleSidebar: (sideBarExpanded: boolean): void => { + set({ sideBarExpanded }); + } +})); diff --git a/client/src/store/index.ts b/client/src/store/index.ts index 9800b379c..a98e7d363 100644 --- a/client/src/store/index.ts +++ b/client/src/store/index.ts @@ -7,3 +7,4 @@ export * from './item'; export * from './metadata'; export * from './repository'; export * from './utils'; +export * from './control'; diff --git a/client/src/utils/repository.tsx b/client/src/utils/repository.tsx index e6a190e40..530101bdf 100644 --- a/client/src/utils/repository.tsx +++ b/client/src/utils/repository.tsx @@ -112,13 +112,21 @@ export function generateRepositoryUrl(filter: RepositoryFilter): string { return `${HOME_ROUTES.REPOSITORY}?${qs.stringify(queryResult)}`; } -export function getTreeWidth(columnSize: number): string { - const width = 50 + columnSize * 10; - if (width <= 80) { - return '85vw'; +export function getTreeWidth(columnSize: number, sideBarExpanded: boolean): string { + const computedWidth = 50 + columnSize * 10; + const isXLScreen = window.innerWidth >= 1600; + + if (computedWidth <= 80) { + if (isXLScreen) { + if (sideBarExpanded) return '85vw'; + return '93vw'; + } else { + if (sideBarExpanded) return '81.5vw'; + return '91vw'; + } } - return `${width}vw`; + return `${computedWidth}vw`; } export function getTreeColorVariant(index: number): RepositoryColorVariant { @@ -127,7 +135,7 @@ export function getTreeColorVariant(index: number): RepositoryColorVariant { export function getTreeViewColumns(metadataColumns: eMetadata[], isHeader: boolean, values?: string[]): TreeViewColumn[] { const treeColumns: TreeViewColumn[] = []; - const MIN_SIZE = 10; + const MIN_SIZE = 5; metadataColumns.forEach((metadataColumn, index: number) => { const treeColumn: TreeViewColumn = { @@ -143,11 +151,12 @@ export function getTreeViewColumns(metadataColumns: eMetadata[], isHeader: boole case eMetadata.eSubjectIdentifier: if (isHeader) treeColumn.label = 'Subject'; - treeColumn.size = MIN_SIZE * 2; + treeColumn.size = MIN_SIZE * 3; break; case eMetadata.eItemName: if (isHeader) treeColumn.label = 'Item'; + treeColumn.size = MIN_SIZE * 3; break; default: diff --git a/client/src/utils/shared.ts b/client/src/utils/shared.ts index 41d9e99d7..29212de9e 100644 --- a/client/src/utils/shared.ts +++ b/client/src/utils/shared.ts @@ -1,5 +1,21 @@ +import { CSSProperties } from '@material-ui/core/styles/withStyles'; + export const actionOnKeyPress = (key: string, actionKey: string, func: () => void): void => { if (key === actionKey) { func(); } }; + +export const scrollBarProperties = (vertical: boolean, horizontal: boolean, backgroundColor: string): CSSProperties => ({ + scrollBehavior: 'smooth', + '&::-webkit-scrollbar': { + '-webkit-appearance': 'none' + }, + '&::-webkit-scrollbar:vertical': vertical ? { width: 12 } : null, + '&::-webkit-scrollbar:horizontal': horizontal ? { height: 12 } : null, + '&::-webkit-scrollbar-thumb': { + borderRadius: 8, + border: '2px solid white', + backgroundColor + } +}); From e117ac2f34b9d4f615d26075ac933ec4b8560e83 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Tue, 20 Oct 2020 11:47:42 +0530 Subject: [PATCH 029/239] minor fixes --- .../Repository/components/RepositoryTreeView/index.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/client/src/pages/Repository/components/RepositoryTreeView/index.tsx b/client/src/pages/Repository/components/RepositoryTreeView/index.tsx index 76ba07cbe..d3dad4860 100644 --- a/client/src/pages/Repository/components/RepositoryTreeView/index.tsx +++ b/client/src/pages/Repository/components/RepositoryTreeView/index.tsx @@ -53,8 +53,12 @@ function RepositoryTreeView(): React.ReactElement { const onNodeToggle = useCallback(async (_, nodeIds: string[]) => { if (!nodeIds.length) return; const [nodeId] = nodeIds.slice(); - getChildren(nodeId); - }, [getChildren]); + const alreadyLoaded = tree.has(nodeId); + + if (!alreadyLoaded) { + getChildren(nodeId); + } + }, [tree, getChildren]); const renderTree = useCallback((children: NavigationResultEntry[] | undefined) => { if (!children) return null; From afbe8187ea03b532f9ea3b072d81903d0f7cb0fa Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Tue, 20 Oct 2020 12:25:44 +0530 Subject: [PATCH 030/239] e2e setup: added configs and packages --- e2e/README.md | 7 + e2e/jest.config.js | 7 + e2e/package.json | 33 + e2e/yarn.lock | 3779 ++++++++++++++++++++++++++++++++++++++++++++ package.json | 3 +- 5 files changed, 3828 insertions(+), 1 deletion(-) create mode 100644 e2e/README.md create mode 100644 e2e/jest.config.js create mode 100644 e2e/package.json create mode 100644 e2e/yarn.lock diff --git a/e2e/README.md b/e2e/README.md new file mode 100644 index 000000000..adff6524a --- /dev/null +++ b/e2e/README.md @@ -0,0 +1,7 @@ +#### E2E tests + +##### Steps to run: + +- Make sure client and server are running +- `CLIENT_ENDPOINT` should point to the client +- use `yarn test` to launch the e2e tests \ No newline at end of file diff --git a/e2e/jest.config.js b/e2e/jest.config.js new file mode 100644 index 000000000..67580df4d --- /dev/null +++ b/e2e/jest.config.js @@ -0,0 +1,7 @@ +module.exports = { + rootDir: '.', + preset: 'ts-jest', + testEnvironment: 'node', + testMatch: ['**/tests/*.test.ts'], + testPathIgnorePatterns: ['/node_modules/'] +}; diff --git a/e2e/package.json b/e2e/package.json new file mode 100644 index 000000000..fd4ead584 --- /dev/null +++ b/e2e/package.json @@ -0,0 +1,33 @@ +{ + "name": "@dpo-packrat/e2e", + "version": "0.4.0", + "private": true, + "license": "Apache-2.0", + "description": "E2E tests for packrat", + "homepage": "https://github.com/Smithsonian/dpo-packrat#readme", + "repository": { + "type": "git", + "url": "git+https://github.com/Smithsonian/dpo-packrat.git" + }, + "bugs": { + "url": "https://github.com/Smithsonian/dpo-packrat/issues" + }, + "contributors": [ + { + "name": "Jon Tyson", + "url": "https://github.com/jahjedtieson" + }, + { + "name": "Karan Pratap Singh", + "url": "https://github.com/karanpratapsingh" + } + ], + "scripts": { + "test": "jest --passWithNoTests --detectOpenHandles --forceExit" + }, + "devDependencies": { + "jest": "26.6.0", + "playwright": "1.5.1", + "ts-jest": "26.4.1" + } +} diff --git a/e2e/yarn.lock b/e2e/yarn.lock new file mode 100644 index 000000000..80748467a --- /dev/null +++ b/e2e/yarn.lock @@ -0,0 +1,3779 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.10.4.tgz#168da1a36e90da68ae8d49c0f1b48c7c6249213a" + integrity sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg== + dependencies: + "@babel/highlight" "^7.10.4" + +"@babel/core@^7.1.0", "@babel/core@^7.7.5": + version "7.12.3" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.12.3.tgz#1b436884e1e3bff6fb1328dc02b208759de92ad8" + integrity sha512-0qXcZYKZp3/6N2jKYVxZv0aNCsxTSVCiK72DTiTYZAu7sjg73W0/aynWjMbiGd87EQL4WyA8reiJVh92AVla9g== + dependencies: + "@babel/code-frame" "^7.10.4" + "@babel/generator" "^7.12.1" + "@babel/helper-module-transforms" "^7.12.1" + "@babel/helpers" "^7.12.1" + "@babel/parser" "^7.12.3" + "@babel/template" "^7.10.4" + "@babel/traverse" "^7.12.1" + "@babel/types" "^7.12.1" + convert-source-map "^1.7.0" + debug "^4.1.0" + gensync "^1.0.0-beta.1" + json5 "^2.1.2" + lodash "^4.17.19" + resolve "^1.3.2" + semver "^5.4.1" + source-map "^0.5.0" + +"@babel/generator@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.12.1.tgz#0d70be32bdaa03d7c51c8597dda76e0df1f15468" + integrity sha512-DB+6rafIdc9o72Yc3/Ph5h+6hUjeOp66pF0naQBgUFFuPqzQwIlPTm3xZR7YNvduIMtkDIj2t21LSQwnbCrXvg== + dependencies: + "@babel/types" "^7.12.1" + jsesc "^2.5.1" + source-map "^0.5.0" + +"@babel/helper-function-name@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz#d2d3b20c59ad8c47112fa7d2a94bc09d5ef82f1a" + integrity sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ== + dependencies: + "@babel/helper-get-function-arity" "^7.10.4" + "@babel/template" "^7.10.4" + "@babel/types" "^7.10.4" + +"@babel/helper-get-function-arity@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz#98c1cbea0e2332f33f9a4661b8ce1505b2c19ba2" + integrity sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A== + dependencies: + "@babel/types" "^7.10.4" + +"@babel/helper-member-expression-to-functions@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.12.1.tgz#fba0f2fcff3fba00e6ecb664bb5e6e26e2d6165c" + integrity sha512-k0CIe3tXUKTRSoEx1LQEPFU9vRQfqHtl+kf8eNnDqb4AUJEy5pz6aIiog+YWtVm2jpggjS1laH68bPsR+KWWPQ== + dependencies: + "@babel/types" "^7.12.1" + +"@babel/helper-module-imports@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.12.1.tgz#1644c01591a15a2f084dd6d092d9430eb1d1216c" + integrity sha512-ZeC1TlMSvikvJNy1v/wPIazCu3NdOwgYZLIkmIyAsGhqkNpiDoQQRmaCK8YP4Pq3GPTLPV9WXaPCJKvx06JxKA== + dependencies: + "@babel/types" "^7.12.1" + +"@babel/helper-module-transforms@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.12.1.tgz#7954fec71f5b32c48e4b303b437c34453fd7247c" + integrity sha512-QQzehgFAZ2bbISiCpmVGfiGux8YVFXQ0abBic2Envhej22DVXV9nCFaS5hIQbkyo1AdGb+gNME2TSh3hYJVV/w== + dependencies: + "@babel/helper-module-imports" "^7.12.1" + "@babel/helper-replace-supers" "^7.12.1" + "@babel/helper-simple-access" "^7.12.1" + "@babel/helper-split-export-declaration" "^7.11.0" + "@babel/helper-validator-identifier" "^7.10.4" + "@babel/template" "^7.10.4" + "@babel/traverse" "^7.12.1" + "@babel/types" "^7.12.1" + lodash "^4.17.19" + +"@babel/helper-optimise-call-expression@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.4.tgz#50dc96413d594f995a77905905b05893cd779673" + integrity sha512-n3UGKY4VXwXThEiKrgRAoVPBMqeoPgHVqiHZOanAJCG9nQUL2pLRQirUzl0ioKclHGpGqRgIOkgcIJaIWLpygg== + dependencies: + "@babel/types" "^7.10.4" + +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.8.0": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz#2f75a831269d4f677de49986dff59927533cf375" + integrity sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg== + +"@babel/helper-replace-supers@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.12.1.tgz#f15c9cc897439281891e11d5ce12562ac0cf3fa9" + integrity sha512-zJjTvtNJnCFsCXVi5rUInstLd/EIVNmIKA1Q9ynESmMBWPWd+7sdR+G4/wdu+Mppfep0XLyG2m7EBPvjCeFyrw== + dependencies: + "@babel/helper-member-expression-to-functions" "^7.12.1" + "@babel/helper-optimise-call-expression" "^7.10.4" + "@babel/traverse" "^7.12.1" + "@babel/types" "^7.12.1" + +"@babel/helper-simple-access@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.12.1.tgz#32427e5aa61547d38eb1e6eaf5fd1426fdad9136" + integrity sha512-OxBp7pMrjVewSSC8fXDFrHrBcJATOOFssZwv16F3/6Xtc138GHybBfPbm9kfiqQHKhYQrlamWILwlDCeyMFEaA== + dependencies: + "@babel/types" "^7.12.1" + +"@babel/helper-split-export-declaration@^7.11.0": + version "7.11.0" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz#f8a491244acf6a676158ac42072911ba83ad099f" + integrity sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg== + dependencies: + "@babel/types" "^7.11.0" + +"@babel/helper-validator-identifier@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz#a78c7a7251e01f616512d31b10adcf52ada5e0d2" + integrity sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw== + +"@babel/helpers@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.12.1.tgz#8a8261c1d438ec18cb890434df4ec768734c1e79" + integrity sha512-9JoDSBGoWtmbay98efmT2+mySkwjzeFeAL9BuWNoVQpkPFQF8SIIFUfY5os9u8wVzglzoiPRSW7cuJmBDUt43g== + dependencies: + "@babel/template" "^7.10.4" + "@babel/traverse" "^7.12.1" + "@babel/types" "^7.12.1" + +"@babel/highlight@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.10.4.tgz#7d1bdfd65753538fabe6c38596cdb76d9ac60143" + integrity sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA== + dependencies: + "@babel/helper-validator-identifier" "^7.10.4" + chalk "^2.0.0" + js-tokens "^4.0.0" + +"@babel/parser@^7.1.0", "@babel/parser@^7.10.4", "@babel/parser@^7.12.1", "@babel/parser@^7.12.3": + version "7.12.3" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.12.3.tgz#a305415ebe7a6c7023b40b5122a0662d928334cd" + integrity sha512-kFsOS0IbsuhO5ojF8Hc8z/8vEIOkylVBrjiZUbLTE3XFe0Qi+uu6HjzQixkFaqr0ZPAMZcBVxEwmsnsLPZ2Xsw== + +"@babel/plugin-syntax-async-generators@^7.8.4": + version "7.8.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" + integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-bigint@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz#4c9a6f669f5d0cdf1b90a1671e9a146be5300cea" + integrity sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-class-properties@^7.8.3": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.1.tgz#bcb297c5366e79bebadef509549cd93b04f19978" + integrity sha512-U40A76x5gTwmESz+qiqssqmeEsKvcSyvtgktrm0uzcARAmM9I1jR221f6Oq+GmHrcD+LvZDag1UTOTe2fL3TeA== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-import-meta@^7.8.3": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" + integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-json-strings@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" + integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-logical-assignment-operators@^7.8.3": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" + integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" + integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-numeric-separator@^7.8.3": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" + integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-object-rest-spread@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" + integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-catch-binding@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" + integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-chaining@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" + integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/template@^7.10.4", "@babel/template@^7.3.3": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.10.4.tgz#3251996c4200ebc71d1a8fc405fba940f36ba278" + integrity sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA== + dependencies: + "@babel/code-frame" "^7.10.4" + "@babel/parser" "^7.10.4" + "@babel/types" "^7.10.4" + +"@babel/traverse@^7.1.0", "@babel/traverse@^7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.12.1.tgz#941395e0c5cc86d5d3e75caa095d3924526f0c1e" + integrity sha512-MA3WPoRt1ZHo2ZmoGKNqi20YnPt0B1S0GTZEPhhd+hw2KGUzBlHuVunj6K4sNuK+reEvyiPwtp0cpaqLzJDmAw== + dependencies: + "@babel/code-frame" "^7.10.4" + "@babel/generator" "^7.12.1" + "@babel/helper-function-name" "^7.10.4" + "@babel/helper-split-export-declaration" "^7.11.0" + "@babel/parser" "^7.12.1" + "@babel/types" "^7.12.1" + debug "^4.1.0" + globals "^11.1.0" + lodash "^4.17.19" + +"@babel/types@^7.0.0", "@babel/types@^7.10.4", "@babel/types@^7.11.0", "@babel/types@^7.12.1", "@babel/types@^7.3.0", "@babel/types@^7.3.3": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.12.1.tgz#e109d9ab99a8de735be287ee3d6a9947a190c4ae" + integrity sha512-BzSY3NJBKM4kyatSOWh3D/JJ2O3CVzBybHWxtgxnggaxEuaSTTDqeiSb/xk9lrkw2Tbqyivw5ZU4rT+EfznQsA== + dependencies: + "@babel/helper-validator-identifier" "^7.10.4" + lodash "^4.17.19" + to-fast-properties "^2.0.0" + +"@bcoe/v8-coverage@^0.2.3": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" + integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== + +"@cnakazawa/watch@^1.0.3": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@cnakazawa/watch/-/watch-1.0.4.tgz#f864ae85004d0fcab6f50be9141c4da368d1656a" + integrity sha512-v9kIhKwjeZThiWrLmj0y17CWoyddASLj9O2yvbZkbvw/N3rWOYy9zkV66ursAoVr0mV15bL8g0c4QZUE6cdDoQ== + dependencies: + exec-sh "^0.3.2" + minimist "^1.2.0" + +"@istanbuljs/load-nyc-config@^1.0.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" + integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== + dependencies: + camelcase "^5.3.1" + find-up "^4.1.0" + get-package-type "^0.1.0" + js-yaml "^3.13.1" + resolve-from "^5.0.0" + +"@istanbuljs/schema@^0.1.2": + version "0.1.2" + resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.2.tgz#26520bf09abe4a5644cd5414e37125a8954241dd" + integrity sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw== + +"@jest/console@^26.6.0": + version "26.6.0" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-26.6.0.tgz#fd4a4733df3c50260aefb227414296aee96e682f" + integrity sha512-ArGcZWAEYMWmWnc/QvxLDvFmGRPvmHeulhS7FUUAlUGR5vS/SqMfArsGaYmIFEThSotCMnEihwx1h62I1eg5lg== + dependencies: + "@jest/types" "^26.6.0" + "@types/node" "*" + chalk "^4.0.0" + jest-message-util "^26.6.0" + jest-util "^26.6.0" + slash "^3.0.0" + +"@jest/core@^26.6.0": + version "26.6.0" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-26.6.0.tgz#04dd3e046e9ebbe06a4f330e05a67f21f7bb314a" + integrity sha512-7wbunxosnC5zXjxrEtTQSblFjRVOT8qz1eSytw8riEeWgegy3ct91NLPEP440CDuWrmW3cOLcEGxIf9q2u6O9Q== + dependencies: + "@jest/console" "^26.6.0" + "@jest/reporters" "^26.6.0" + "@jest/test-result" "^26.6.0" + "@jest/transform" "^26.6.0" + "@jest/types" "^26.6.0" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + exit "^0.1.2" + graceful-fs "^4.2.4" + jest-changed-files "^26.6.0" + jest-config "^26.6.0" + jest-haste-map "^26.6.0" + jest-message-util "^26.6.0" + jest-regex-util "^26.0.0" + jest-resolve "^26.6.0" + jest-resolve-dependencies "^26.6.0" + jest-runner "^26.6.0" + jest-runtime "^26.6.0" + jest-snapshot "^26.6.0" + jest-util "^26.6.0" + jest-validate "^26.6.0" + jest-watcher "^26.6.0" + micromatch "^4.0.2" + p-each-series "^2.1.0" + rimraf "^3.0.0" + slash "^3.0.0" + strip-ansi "^6.0.0" + +"@jest/environment@^26.6.0": + version "26.6.0" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-26.6.0.tgz#695ee24cbf110456272caa9debbbf7e01afb2f78" + integrity sha512-l+5MSdiC4rUUrz8xPdj0TwHBwuoqMcAbFnsYDTn5FkenJl8b+lvC5NdJl1tVICGHWnx0fnjdd1luRZ7u3U4xyg== + dependencies: + "@jest/fake-timers" "^26.6.0" + "@jest/types" "^26.6.0" + "@types/node" "*" + jest-mock "^26.6.0" + +"@jest/fake-timers@^26.6.0": + version "26.6.0" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-26.6.0.tgz#5b4cc83fab91029963c53e6e2716f02544323b22" + integrity sha512-7VQpjChrwlwvGNysS10lDBLOVLxMvMtpx0Xo6aIotzNVyojYk0NN0CR8R4T6h/eu7Zva/LB3P71jqwGdtADoag== + dependencies: + "@jest/types" "^26.6.0" + "@sinonjs/fake-timers" "^6.0.1" + "@types/node" "*" + jest-message-util "^26.6.0" + jest-mock "^26.6.0" + jest-util "^26.6.0" + +"@jest/globals@^26.6.0": + version "26.6.0" + resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-26.6.0.tgz#da2f58d17105b6a7531ee3c8724acb5f233400e2" + integrity sha512-rs3a/a8Lq8FgTx11SxbqIU2bDjsFU2PApl2oK2oUVlo84RSF76afFm2nLojW93AGssr715GHUwhq5b6mpCI5BQ== + dependencies: + "@jest/environment" "^26.6.0" + "@jest/types" "^26.6.0" + expect "^26.6.0" + +"@jest/reporters@^26.6.0": + version "26.6.0" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-26.6.0.tgz#2a8d631ad3b19a722fd0fae58ce9fa25e8aac1cf" + integrity sha512-PXbvHhdci5Rj1VFloolgLb+0kkdtzswhG8MzVENKJRI3O1ndwr52G6E/2QupjwrRcYnApZOelFf4nNpf5+SDxA== + dependencies: + "@bcoe/v8-coverage" "^0.2.3" + "@jest/console" "^26.6.0" + "@jest/test-result" "^26.6.0" + "@jest/transform" "^26.6.0" + "@jest/types" "^26.6.0" + chalk "^4.0.0" + collect-v8-coverage "^1.0.0" + exit "^0.1.2" + glob "^7.1.2" + graceful-fs "^4.2.4" + istanbul-lib-coverage "^3.0.0" + istanbul-lib-instrument "^4.0.3" + istanbul-lib-report "^3.0.0" + istanbul-lib-source-maps "^4.0.0" + istanbul-reports "^3.0.2" + jest-haste-map "^26.6.0" + jest-resolve "^26.6.0" + jest-util "^26.6.0" + jest-worker "^26.5.0" + slash "^3.0.0" + source-map "^0.6.0" + string-length "^4.0.1" + terminal-link "^2.0.0" + v8-to-istanbul "^6.0.1" + optionalDependencies: + node-notifier "^8.0.0" + +"@jest/source-map@^26.5.0": + version "26.5.0" + resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-26.5.0.tgz#98792457c85bdd902365cd2847b58fff05d96367" + integrity sha512-jWAw9ZwYHJMe9eZq/WrsHlwF8E3hM9gynlcDpOyCb9bR8wEd9ZNBZCi7/jZyzHxC7t3thZ10gO2IDhu0bPKS5g== + dependencies: + callsites "^3.0.0" + graceful-fs "^4.2.4" + source-map "^0.6.0" + +"@jest/test-result@^26.6.0": + version "26.6.0" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-26.6.0.tgz#79705c8a57165777af5ef1d45c65dcc4a5965c11" + integrity sha512-LV6X1ry+sKjseQsIFz3e6XAZYxwidvmeJFnVF08fq98q08dF1mJYI0lDq/LmH/jas+R4s0pwnNGiz1hfC4ZUBw== + dependencies: + "@jest/console" "^26.6.0" + "@jest/types" "^26.6.0" + "@types/istanbul-lib-coverage" "^2.0.0" + collect-v8-coverage "^1.0.0" + +"@jest/test-sequencer@^26.6.0": + version "26.6.0" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-26.6.0.tgz#a9dbc6545b1c59e7f375b05466e172126609906d" + integrity sha512-rWPTMa+8rejvePZnJmnKkmKWh0qILFDPpN0qbSif+KNGvFxqqDGafMo4P2Y8+I9XWrZQBeXL9IxPL4ZzDgRlbw== + dependencies: + "@jest/test-result" "^26.6.0" + graceful-fs "^4.2.4" + jest-haste-map "^26.6.0" + jest-runner "^26.6.0" + jest-runtime "^26.6.0" + +"@jest/transform@^26.6.0": + version "26.6.0" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-26.6.0.tgz#1a6b95d0c7f9b4f96dd3aab9d28422a9e5e4043e" + integrity sha512-NUNA1NMCyVV9g5NIQF1jzW7QutQhB/HAocteCiUyH0VhmLXnGMTfPYQu1G6IjPk+k1SWdh2PD+Zs1vMqbavWzg== + dependencies: + "@babel/core" "^7.1.0" + "@jest/types" "^26.6.0" + babel-plugin-istanbul "^6.0.0" + chalk "^4.0.0" + convert-source-map "^1.4.0" + fast-json-stable-stringify "^2.0.0" + graceful-fs "^4.2.4" + jest-haste-map "^26.6.0" + jest-regex-util "^26.0.0" + jest-util "^26.6.0" + micromatch "^4.0.2" + pirates "^4.0.1" + slash "^3.0.0" + source-map "^0.6.1" + write-file-atomic "^3.0.0" + +"@jest/types@^25.5.0": + version "25.5.0" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-25.5.0.tgz#4d6a4793f7b9599fc3680877b856a97dbccf2a9d" + integrity sha512-OXD0RgQ86Tu3MazKo8bnrkDRaDXXMGUqd+kTtLtK1Zb7CRzQcaSRPPPV37SvYTdevXEBVxe0HXylEjs8ibkmCw== + dependencies: + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^1.1.1" + "@types/yargs" "^15.0.0" + chalk "^3.0.0" + +"@jest/types@^26.6.0": + version "26.6.0" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-26.6.0.tgz#2c045f231bfd79d52514cda3fbc93ef46157fa6a" + integrity sha512-8pDeq/JVyAYw7jBGU83v8RMYAkdrRxLG3BGnAJuqaQAUd6GWBmND2uyl+awI88+hit48suLoLjNFtR+ZXxWaYg== + dependencies: + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^15.0.0" + chalk "^4.0.0" + +"@sinonjs/commons@^1.7.0": + version "1.8.1" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.1.tgz#e7df00f98a203324f6dc7cc606cad9d4a8ab2217" + integrity sha512-892K+kWUUi3cl+LlqEWIDrhvLgdL79tECi8JZUyq6IviKy/DNhuzCRlbHUjxK89f4ypPMMaFnFuR9Ie6DoIMsw== + dependencies: + type-detect "4.0.8" + +"@sinonjs/fake-timers@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz#293674fccb3262ac782c7aadfdeca86b10c75c40" + integrity sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA== + dependencies: + "@sinonjs/commons" "^1.7.0" + +"@types/babel__core@^7.0.0", "@types/babel__core@^7.1.7": + version "7.1.10" + resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.10.tgz#ca58fc195dd9734e77e57c6f2df565623636ab40" + integrity sha512-x8OM8XzITIMyiwl5Vmo2B1cR1S1Ipkyv4mdlbJjMa1lmuKvKY9FrBbEANIaMlnWn5Rf7uO+rC/VgYabNkE17Hw== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + "@types/babel__generator" "*" + "@types/babel__template" "*" + "@types/babel__traverse" "*" + +"@types/babel__generator@*": + version "7.6.2" + resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.2.tgz#f3d71178e187858f7c45e30380f8f1b7415a12d8" + integrity sha512-MdSJnBjl+bdwkLskZ3NGFp9YcXGx5ggLpQQPqtgakVhsWK0hTtNYhjpZLlWQTviGTvF8at+Bvli3jV7faPdgeQ== + dependencies: + "@babel/types" "^7.0.0" + +"@types/babel__template@*": + version "7.0.3" + resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.0.3.tgz#b8aaeba0a45caca7b56a5de9459872dde3727214" + integrity sha512-uCoznIPDmnickEi6D0v11SBpW0OuVqHJCa7syXqQHy5uktSCreIlt0iglsCnmvz8yCb38hGcWeseA8cWJSwv5Q== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + +"@types/babel__traverse@*", "@types/babel__traverse@^7.0.4", "@types/babel__traverse@^7.0.6": + version "7.0.15" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.0.15.tgz#db9e4238931eb69ef8aab0ad6523d4d4caa39d03" + integrity sha512-Pzh9O3sTK8V6I1olsXpCfj2k/ygO2q1X0vhhnDrEQyYLHZesWz+zMZMVcwXLCYf0U36EtmyYaFGPfXlTtDHe3A== + dependencies: + "@babel/types" "^7.3.0" + +"@types/graceful-fs@^4.1.2": + version "4.1.3" + resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.3.tgz#039af35fe26bec35003e8d86d2ee9c586354348f" + integrity sha512-AiHRaEB50LQg0pZmm659vNBb9f4SJ0qrAnteuzhSeAUcJKxoYgEnprg/83kppCnc2zvtCKbdZry1a5pVY3lOTQ== + dependencies: + "@types/node" "*" + +"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz#4ba8ddb720221f432e443bd5f9117fd22cfd4762" + integrity sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw== + +"@types/istanbul-lib-report@*": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#c14c24f18ea8190c118ee7562b7ff99a36552686" + integrity sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg== + dependencies: + "@types/istanbul-lib-coverage" "*" + +"@types/istanbul-reports@^1.1.1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-1.1.2.tgz#e875cc689e47bce549ec81f3df5e6f6f11cfaeb2" + integrity sha512-P/W9yOX/3oPZSpaYOCQzGqgCQRXn0FFO/V8bWrCQs+wLmvVVxk6CRBXALEvNs9OHIatlnlFokfhuDo2ug01ciw== + dependencies: + "@types/istanbul-lib-coverage" "*" + "@types/istanbul-lib-report" "*" + +"@types/istanbul-reports@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz#508b13aa344fa4976234e75dddcc34925737d821" + integrity sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA== + dependencies: + "@types/istanbul-lib-report" "*" + +"@types/jest@26.x": + version "26.0.14" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-26.0.14.tgz#078695f8f65cb55c5a98450d65083b2b73e5a3f3" + integrity sha512-Hz5q8Vu0D288x3iWXePSn53W7hAjP0H7EQ6QvDO9c7t46mR0lNOLlfuwQ+JkVxuhygHzlzPX+0jKdA3ZgSh+Vg== + dependencies: + jest-diff "^25.2.1" + pretty-format "^25.2.1" + +"@types/node@*": + version "14.11.10" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.11.10.tgz#8c102aba13bf5253f35146affbf8b26275069bef" + integrity sha512-yV1nWZPlMFpoXyoknm4S56y2nlTAuFYaJuQtYRAOU7xA/FJ9RY0Xm7QOkaYMMmr8ESdHIuUb6oQgR/0+2NqlyA== + +"@types/normalize-package-data@^2.4.0": + version "2.4.0" + resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz#e486d0d97396d79beedd0a6e33f4534ff6b4973e" + integrity sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA== + +"@types/prettier@^2.0.0": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.1.5.tgz#b6ab3bba29e16b821d84e09ecfaded462b816b00" + integrity sha512-UEyp8LwZ4Dg30kVU2Q3amHHyTn1jEdhCIE59ANed76GaT1Vp76DD3ZWSAxgCrw6wJ0TqeoBpqmfUHiUDPs//HQ== + +"@types/stack-utils@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.0.tgz#7036640b4e21cc2f259ae826ce843d277dad8cff" + integrity sha512-RJJrrySY7A8havqpGObOB4W92QXKJo63/jFLLgpvOtsGUqbQZ9Sbgl35KMm1DjC6j7AvmmU2bIno+3IyEaemaw== + +"@types/yargs-parser@*": + version "15.0.0" + resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-15.0.0.tgz#cb3f9f741869e20cce330ffbeb9271590483882d" + integrity sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw== + +"@types/yargs@^15.0.0": + version "15.0.9" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-15.0.9.tgz#524cd7998fe810cdb02f26101b699cccd156ff19" + integrity sha512-HmU8SeIRhZCWcnRskCs36Q1Q00KBV6Cqh/ora8WN1+22dY07AZdn6Gel8QZ3t26XYPImtcL8WV/eqjhVmMEw4g== + dependencies: + "@types/yargs-parser" "*" + +"@types/yauzl@^2.9.1": + version "2.9.1" + resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.9.1.tgz#d10f69f9f522eef3cf98e30afb684a1e1ec923af" + integrity sha512-A1b8SU4D10uoPjwb0lnHmmu8wZhR9d+9o2PKBQT2jU5YPTKsxac6M2qGAdY7VcL+dHHhARVUDmeg0rOrcd9EjA== + dependencies: + "@types/node" "*" + +abab@^2.0.3: + version "2.0.5" + resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.5.tgz#c0b678fb32d60fc1219c784d6a826fe385aeb79a" + integrity sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q== + +acorn-globals@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-6.0.0.tgz#46cdd39f0f8ff08a876619b55f5ac8a6dc770b45" + integrity sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg== + dependencies: + acorn "^7.1.1" + acorn-walk "^7.1.1" + +acorn-walk@^7.1.1: + version "7.2.0" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc" + integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA== + +acorn@^7.1.1: + version "7.4.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" + integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== + +agent-base@6: + version "6.0.1" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.1.tgz#808007e4e5867decb0ab6ab2f928fbdb5a596db4" + integrity sha512-01q25QQDwLSsyfhrKbn8yuur+JNw0H+0Y4JiGIKd3z9aYk/w/2kxD/Upc+t2ZBBSUNff50VjPsSW2YxM8QYKVg== + dependencies: + debug "4" + +ajv@^6.12.3: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ansi-escapes@^4.2.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.1.tgz#a5c47cc43181f1f38ffd7076837700d395522a61" + integrity sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA== + dependencies: + type-fest "^0.11.0" + +ansi-regex@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75" + integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg== + +ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +ansi-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +anymatch@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb" + integrity sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw== + dependencies: + micromatch "^3.1.4" + normalize-path "^2.1.1" + +anymatch@^3.0.3: + version "3.1.1" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.1.tgz#c55ecf02185e2469259399310c173ce31233b142" + integrity sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +arr-diff@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" + integrity sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA= + +arr-flatten@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" + integrity sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg== + +arr-union@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" + integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ= + +array-unique@^0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" + integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= + +asn1@~0.2.3: + version "0.2.4" + resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" + integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg== + dependencies: + safer-buffer "~2.1.0" + +assert-plus@1.0.0, assert-plus@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" + integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= + +assign-symbols@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" + integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c= + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= + +atob@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" + integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== + +aws-sign2@~0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" + integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg= + +aws4@^1.8.0: + version "1.10.1" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.10.1.tgz#e1e82e4f3e999e2cfd61b161280d16a111f86428" + integrity sha512-zg7Hz2k5lI8kb7U32998pRRFin7zJlkfezGJjUc2heaD4Pw2wObakCDVzkKztTm/Ln7eiVvYsjqak0Ed4LkMDA== + +babel-jest@^26.6.0: + version "26.6.0" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-26.6.0.tgz#eca57ac8af99d6e06047e595b1faf0b5adf8a7bb" + integrity sha512-JI66yILI7stzjHccAoQtRKcUwJrJb4oMIxLTirL3GdAjGpaUBQSjZDFi9LsPkN4gftsS4R2AThAJwOjJxadwbg== + dependencies: + "@jest/transform" "^26.6.0" + "@jest/types" "^26.6.0" + "@types/babel__core" "^7.1.7" + babel-plugin-istanbul "^6.0.0" + babel-preset-jest "^26.5.0" + chalk "^4.0.0" + graceful-fs "^4.2.4" + slash "^3.0.0" + +babel-plugin-istanbul@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.0.0.tgz#e159ccdc9af95e0b570c75b4573b7c34d671d765" + integrity sha512-AF55rZXpe7trmEylbaE1Gv54wn6rwU03aptvRoVIGP8YykoSxqdVLV1TfwflBCE/QtHmqtP8SWlTENqbK8GCSQ== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@istanbuljs/load-nyc-config" "^1.0.0" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-instrument "^4.0.0" + test-exclude "^6.0.0" + +babel-plugin-jest-hoist@^26.5.0: + version "26.5.0" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-26.5.0.tgz#3916b3a28129c29528de91e5784a44680db46385" + integrity sha512-ck17uZFD3CDfuwCLATWZxkkuGGFhMij8quP8CNhwj8ek1mqFgbFzRJ30xwC04LLscj/aKsVFfRST+b5PT7rSuw== + dependencies: + "@babel/template" "^7.3.3" + "@babel/types" "^7.3.3" + "@types/babel__core" "^7.0.0" + "@types/babel__traverse" "^7.0.6" + +babel-preset-current-node-syntax@^0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-0.1.4.tgz#826f1f8e7245ad534714ba001f84f7e906c3b615" + integrity sha512-5/INNCYhUGqw7VbVjT/hb3ucjgkVHKXY7lX3ZjlN4gm565VyFmJUrJ/h+h16ECVB38R/9SF6aACydpKMLZ/c9w== + dependencies: + "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/plugin-syntax-bigint" "^7.8.3" + "@babel/plugin-syntax-class-properties" "^7.8.3" + "@babel/plugin-syntax-import-meta" "^7.8.3" + "@babel/plugin-syntax-json-strings" "^7.8.3" + "@babel/plugin-syntax-logical-assignment-operators" "^7.8.3" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + "@babel/plugin-syntax-numeric-separator" "^7.8.3" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + +babel-preset-jest@^26.5.0: + version "26.5.0" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-26.5.0.tgz#f1b166045cd21437d1188d29f7fba470d5bdb0e7" + integrity sha512-F2vTluljhqkiGSJGBg/jOruA8vIIIL11YrxRcO7nviNTMbbofPSHwnm8mgP7d/wS7wRSexRoI6X1A6T74d4LQA== + dependencies: + babel-plugin-jest-hoist "^26.5.0" + babel-preset-current-node-syntax "^0.1.3" + +balanced-match@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" + integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= + +base@^0.11.1: + version "0.11.2" + resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" + integrity sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg== + dependencies: + cache-base "^1.0.1" + class-utils "^0.3.5" + component-emitter "^1.2.1" + define-property "^1.0.0" + isobject "^3.0.1" + mixin-deep "^1.2.0" + pascalcase "^0.1.1" + +bcrypt-pbkdf@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" + integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4= + dependencies: + tweetnacl "^0.14.3" + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +braces@^2.3.1: + version "2.3.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" + integrity sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w== + dependencies: + arr-flatten "^1.1.0" + array-unique "^0.3.2" + extend-shallow "^2.0.1" + fill-range "^4.0.0" + isobject "^3.0.1" + repeat-element "^1.1.2" + snapdragon "^0.8.1" + snapdragon-node "^2.0.1" + split-string "^3.0.2" + to-regex "^3.0.1" + +braces@^3.0.1: + version "3.0.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + dependencies: + fill-range "^7.0.1" + +browser-process-hrtime@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626" + integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow== + +bs-logger@0.x: + version "0.2.6" + resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8" + integrity sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog== + dependencies: + fast-json-stable-stringify "2.x" + +bser@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" + integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ== + dependencies: + node-int64 "^0.4.0" + +buffer-crc32@~0.2.3: + version "0.2.13" + resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" + integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI= + +buffer-from@1.x, buffer-from@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" + integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== + +cache-base@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" + integrity sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ== + dependencies: + collection-visit "^1.0.0" + component-emitter "^1.2.1" + get-value "^2.0.6" + has-value "^1.0.0" + isobject "^3.0.1" + set-value "^2.0.0" + to-object-path "^0.3.0" + union-value "^1.0.0" + unset-value "^1.0.0" + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +camelcase@^5.0.0, camelcase@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" + integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== + +camelcase@^6.0.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.1.0.tgz#27dc176173725fb0adf8a48b647f4d7871944d78" + integrity sha512-WCMml9ivU60+8rEJgELlFp1gxFcEGxwYleE3bziHEDeqsqAWGHdimB7beBFGjLzVNgPGyDsfgXLQEYMpmIFnVQ== + +capture-exit@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/capture-exit/-/capture-exit-2.0.0.tgz#fb953bfaebeb781f62898239dabb426d08a509a4" + integrity sha512-PiT/hQmTonHhl/HFGN+Lx3JJUznrVYJ3+AQsnthneZbvW7x+f08Tk7yLJTLEOUvBTbduLeeBkxEaYXUOUrRq6g== + dependencies: + rsvp "^4.8.4" + +caseless@~0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" + integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= + +chalk@^2.0.0: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chalk@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4" + integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +chalk@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a" + integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +char-regex@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" + integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== + +ci-info@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" + integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== + +class-utils@^0.3.5: + version "0.3.6" + resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" + integrity sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg== + dependencies: + arr-union "^3.1.0" + define-property "^0.2.5" + isobject "^3.0.0" + static-extend "^0.1.1" + +cliui@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1" + integrity sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^6.2.0" + +co@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" + integrity sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ= + +collect-v8-coverage@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz#cc2c8e94fc18bbdffe64d6534570c8a673b27f59" + integrity sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg== + +collection-visit@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" + integrity sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA= + dependencies: + map-visit "^1.0.0" + object-visit "^1.0.0" + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +combined-stream@^1.0.6, combined-stream@~1.0.6: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + +component-emitter@^1.2.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" + integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + +convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.7.0.tgz#17a2cb882d7f77d3490585e2ce6c524424a3a442" + integrity sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA== + dependencies: + safe-buffer "~5.1.1" + +copy-descriptor@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" + integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= + +core-util-is@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= + +cross-spawn@^6.0.0: + version "6.0.5" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" + integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== + dependencies: + nice-try "^1.0.4" + path-key "^2.0.1" + semver "^5.5.0" + shebang-command "^1.2.0" + which "^1.2.9" + +cross-spawn@^7.0.0: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +cssom@^0.4.4: + version "0.4.4" + resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.4.4.tgz#5a66cf93d2d0b661d80bf6a44fb65f5c2e4e0a10" + integrity sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw== + +cssom@~0.3.6: + version "0.3.8" + resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.8.tgz#9f1276f5b2b463f2114d3f2c75250af8c1a36f4a" + integrity sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg== + +cssstyle@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-2.3.0.tgz#ff665a0ddbdc31864b09647f34163443d90b0852" + integrity sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A== + dependencies: + cssom "~0.3.6" + +dashdash@^1.12.0: + version "1.14.1" + resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" + integrity sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA= + dependencies: + assert-plus "^1.0.0" + +data-urls@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-2.0.0.tgz#156485a72963a970f5d5821aaf642bef2bf2db9b" + integrity sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ== + dependencies: + abab "^2.0.3" + whatwg-mimetype "^2.3.0" + whatwg-url "^8.0.0" + +debug@4, debug@^4.1.0, debug@^4.1.1: + version "4.2.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.2.0.tgz#7f150f93920e94c58f5574c2fd01a3110effe7f1" + integrity sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg== + dependencies: + ms "2.1.2" + +debug@^2.2.0, debug@^2.3.3: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + +decamelize@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" + integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= + +decimal.js@^10.2.0: + version "10.2.1" + resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.2.1.tgz#238ae7b0f0c793d3e3cea410108b35a2c01426a3" + integrity sha512-KaL7+6Fw6i5A2XSnsbhm/6B+NuEA7TZ4vqxnd5tXz9sbKtrN9Srj8ab4vKVdK8YAqZO9P1kg45Y6YLoduPf+kw== + +decode-uri-component@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" + integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= + +deep-is@~0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" + integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= + +deepmerge@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" + integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg== + +define-property@^0.2.5: + version "0.2.5" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116" + integrity sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY= + dependencies: + is-descriptor "^0.1.0" + +define-property@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-1.0.0.tgz#769ebaaf3f4a63aad3af9e8d304c9bbe79bfb0e6" + integrity sha1-dp66rz9KY6rTr56NMEybvnm/sOY= + dependencies: + is-descriptor "^1.0.0" + +define-property@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-2.0.2.tgz#d459689e8d654ba77e02a817f8710d702cb16e9d" + integrity sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ== + dependencies: + is-descriptor "^1.0.2" + isobject "^3.0.1" + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= + +detect-newline@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" + integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== + +diff-sequences@^25.2.6: + version "25.2.6" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-25.2.6.tgz#5f467c00edd35352b7bca46d7927d60e687a76dd" + integrity sha512-Hq8o7+6GaZeoFjtpgvRBUknSXNeJiCx7V9Fr94ZMljNiCr9n9L8H8aJqgWOQiDDGdyn29fRNcDdRVJ5fdyihfg== + +diff-sequences@^26.5.0: + version "26.5.0" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-26.5.0.tgz#ef766cf09d43ed40406611f11c6d8d9dd8b2fefd" + integrity sha512-ZXx86srb/iYy6jG71k++wBN9P9J05UNQ5hQHQd9MtMPvcqXPx/vKU69jfHV637D00Q2gSgPk2D+jSx3l1lDW/Q== + +domexception@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/domexception/-/domexception-2.0.1.tgz#fb44aefba793e1574b0af6aed2801d057529f304" + integrity sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg== + dependencies: + webidl-conversions "^5.0.0" + +ecc-jsbn@~0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" + integrity sha1-OoOpBOVDUyh4dMVkt1SThoSamMk= + dependencies: + jsbn "~0.1.0" + safer-buffer "^2.1.0" + +emittery@^0.7.1: + version "0.7.2" + resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.7.2.tgz#25595908e13af0f5674ab419396e2fb394cdfa82" + integrity sha512-A8OG5SR/ij3SsJdWDJdkkSYUjQdCUx6APQXem0SaEePBSRg4eymGYwBkKo1Y6DU+af/Jn2dBQqDBvjnr9Vi8nQ== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +end-of-stream@^1.1.0: + version "1.4.4" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" + integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== + dependencies: + once "^1.4.0" + +error-ex@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + dependencies: + is-arrayish "^0.2.1" + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= + +escape-string-regexp@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" + integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== + +escodegen@^1.14.1: + version "1.14.3" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.14.3.tgz#4e7b81fba61581dc97582ed78cab7f0e8d63f503" + integrity sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw== + dependencies: + esprima "^4.0.1" + estraverse "^4.2.0" + esutils "^2.0.2" + optionator "^0.8.1" + optionalDependencies: + source-map "~0.6.1" + +esprima@^4.0.0, esprima@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +estraverse@^4.2.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" + integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +exec-sh@^0.3.2: + version "0.3.4" + resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.3.4.tgz#3a018ceb526cc6f6df2bb504b2bfe8e3a4934ec5" + integrity sha512-sEFIkc61v75sWeOe72qyrqg2Qg0OuLESziUDk/O/z2qgS15y2gWVFrI6f2Qn/qw/0/NCfCEsmNA4zOjkwEZT1A== + +execa@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8" + integrity sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA== + dependencies: + cross-spawn "^6.0.0" + get-stream "^4.0.0" + is-stream "^1.1.0" + npm-run-path "^2.0.0" + p-finally "^1.0.0" + signal-exit "^3.0.0" + strip-eof "^1.0.0" + +execa@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/execa/-/execa-4.0.3.tgz#0a34dabbad6d66100bd6f2c576c8669403f317f2" + integrity sha512-WFDXGHckXPWZX19t1kCsXzOpqX9LWYNqn4C+HqZlk/V0imTkzJZqf87ZBhvpHaftERYknpk0fjSylnXVlVgI0A== + dependencies: + cross-spawn "^7.0.0" + get-stream "^5.0.0" + human-signals "^1.1.1" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.0" + onetime "^5.1.0" + signal-exit "^3.0.2" + strip-final-newline "^2.0.0" + +exit@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" + integrity sha1-BjJjj42HfMghB9MKD/8aF8uhzQw= + +expand-brackets@^2.1.4: + version "2.1.4" + resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622" + integrity sha1-t3c14xXOMPa27/D4OwQVGiJEliI= + dependencies: + debug "^2.3.3" + define-property "^0.2.5" + extend-shallow "^2.0.1" + posix-character-classes "^0.1.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + +expect@^26.6.0: + version "26.6.0" + resolved "https://registry.yarnpkg.com/expect/-/expect-26.6.0.tgz#f48861317f62bb9f1248eaab7ae9e50a9a5a8339" + integrity sha512-EzhbZ1tbwcaa5Ok39BI11flIMeIUSlg1QsnXOrleaMvltwHsvIQPBtL710l+ma+qDFLUgktCXK4YuQzmHdm7cg== + dependencies: + "@jest/types" "^26.6.0" + ansi-styles "^4.0.0" + jest-get-type "^26.3.0" + jest-matcher-utils "^26.6.0" + jest-message-util "^26.6.0" + jest-regex-util "^26.0.0" + +extend-shallow@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" + integrity sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8= + dependencies: + is-extendable "^0.1.0" + +extend-shallow@^3.0.0, extend-shallow@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8" + integrity sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg= + dependencies: + assign-symbols "^1.0.0" + is-extendable "^1.0.1" + +extend@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" + integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== + +extglob@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543" + integrity sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw== + dependencies: + array-unique "^0.3.2" + define-property "^1.0.0" + expand-brackets "^2.1.4" + extend-shallow "^2.0.1" + fragment-cache "^0.2.1" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + +extract-zip@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-2.0.1.tgz#663dca56fe46df890d5f131ef4a06d22bb8ba13a" + integrity sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg== + dependencies: + debug "^4.1.1" + get-stream "^5.1.0" + yauzl "^2.10.0" + optionalDependencies: + "@types/yauzl" "^2.9.1" + +extsprintf@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" + integrity sha1-lpGEQOMEGnpBT4xS48V06zw+HgU= + +extsprintf@^1.2.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" + integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= + +fast-deep-equal@^3.1.1: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-levenshtein@~2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= + +fb-watchman@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.1.tgz#fc84fb39d2709cf3ff6d743706157bb5708a8a85" + integrity sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg== + dependencies: + bser "2.1.1" + +fd-slicer@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e" + integrity sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4= + dependencies: + pend "~1.2.0" + +fill-range@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" + integrity sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc= + dependencies: + extend-shallow "^2.0.1" + is-number "^3.0.0" + repeat-string "^1.6.1" + to-regex-range "^2.1.0" + +fill-range@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== + dependencies: + to-regex-range "^5.0.1" + +find-up@^4.0.0, find-up@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== + dependencies: + locate-path "^5.0.0" + path-exists "^4.0.0" + +for-in@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" + integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA= + +forever-agent@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" + integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= + +form-data@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" + integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.6" + mime-types "^2.1.12" + +fragment-cache@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" + integrity sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk= + dependencies: + map-cache "^0.2.2" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + +fsevents@^2.1.2: + version "2.1.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.3.tgz#fb738703ae8d2f9fe900c33836ddebee8b97f23e" + integrity sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ== + +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + +gensync@^1.0.0-beta.1: + version "1.0.0-beta.1" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.1.tgz#58f4361ff987e5ff6e1e7a210827aa371eaac269" + integrity sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg== + +get-caller-file@^2.0.1: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +get-package-type@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" + integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== + +get-stream@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" + integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== + dependencies: + pump "^3.0.0" + +get-stream@^5.0.0, get-stream@^5.1.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" + integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== + dependencies: + pump "^3.0.0" + +get-value@^2.0.3, get-value@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" + integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg= + +getpass@^0.1.1: + version "0.1.7" + resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" + integrity sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo= + dependencies: + assert-plus "^1.0.0" + +glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4: + version "7.1.6" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" + integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +globals@^11.1.0: + version "11.12.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== + +graceful-fs@^4.1.11, graceful-fs@^4.2.4: + version "4.2.4" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" + integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== + +growly@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" + integrity sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE= + +har-schema@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" + integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= + +har-validator@~5.1.3: + version "5.1.5" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.5.tgz#1f0803b9f8cb20c0fa13822df1ecddb36bde1efd" + integrity sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w== + dependencies: + ajv "^6.12.3" + har-schema "^2.0.0" + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has-value@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" + integrity sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8= + dependencies: + get-value "^2.0.3" + has-values "^0.1.4" + isobject "^2.0.0" + +has-value@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-value/-/has-value-1.0.0.tgz#18b281da585b1c5c51def24c930ed29a0be6b177" + integrity sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc= + dependencies: + get-value "^2.0.6" + has-values "^1.0.0" + isobject "^3.0.0" + +has-values@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/has-values/-/has-values-0.1.4.tgz#6d61de95d91dfca9b9a02089ad384bff8f62b771" + integrity sha1-bWHeldkd/Km5oCCJrThL/49it3E= + +has-values@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-values/-/has-values-1.0.0.tgz#95b0b63fec2146619a6fe57fe75628d5a39efe4f" + integrity sha1-lbC2P+whRmGab+V/51Yo1aOe/k8= + dependencies: + is-number "^3.0.0" + kind-of "^4.0.0" + +has@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + dependencies: + function-bind "^1.1.1" + +hosted-git-info@^2.1.4: + version "2.8.8" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488" + integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg== + +html-encoding-sniffer@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz#42a6dc4fd33f00281176e8b23759ca4e4fa185f3" + integrity sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ== + dependencies: + whatwg-encoding "^1.0.5" + +html-escaper@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" + integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== + +http-signature@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" + integrity sha1-muzZJRFHcvPZW2WmCruPfBj7rOE= + dependencies: + assert-plus "^1.0.0" + jsprim "^1.2.2" + sshpk "^1.7.0" + +https-proxy-agent@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2" + integrity sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA== + dependencies: + agent-base "6" + debug "4" + +human-signals@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3" + integrity sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw== + +iconv-lite@0.4.24: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +import-local@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.0.2.tgz#a8cfd0431d1de4a2199703d003e3e62364fa6db6" + integrity sha512-vjL3+w0oulAVZ0hBHnxa/Nm5TAurf9YLQJDhqRZyqb+VKGOB6LU8t9H1Nr5CIo16vh9XfJTOoHwU0B71S557gA== + dependencies: + pkg-dir "^4.2.0" + resolve-cwd "^3.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +ip-regex@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9" + integrity sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk= + +is-accessor-descriptor@^0.1.6: + version "0.1.6" + resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" + integrity sha1-qeEss66Nh2cn7u84Q/igiXtcmNY= + dependencies: + kind-of "^3.0.2" + +is-accessor-descriptor@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz#169c2f6d3df1f992618072365c9b0ea1f6878656" + integrity sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ== + dependencies: + kind-of "^6.0.0" + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= + +is-buffer@^1.1.5: + version "1.1.6" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" + integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== + +is-ci@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-2.0.0.tgz#6bc6334181810e04b5c22b3d589fdca55026404c" + integrity sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w== + dependencies: + ci-info "^2.0.0" + +is-core-module@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.0.0.tgz#58531b70aed1db7c0e8d4eb1a0a2d1ddd64bd12d" + integrity sha512-jq1AH6C8MuteOoBPwkxHafmByhL9j5q4OaPGdbuD+ZtQJVzH+i6E3BJDQcBA09k57i2Hh2yQbEG8yObZ0jdlWw== + dependencies: + has "^1.0.3" + +is-data-descriptor@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" + integrity sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y= + dependencies: + kind-of "^3.0.2" + +is-data-descriptor@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz#d84876321d0e7add03990406abbbbd36ba9268c7" + integrity sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ== + dependencies: + kind-of "^6.0.0" + +is-descriptor@^0.1.0: + version "0.1.6" + resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca" + integrity sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg== + dependencies: + is-accessor-descriptor "^0.1.6" + is-data-descriptor "^0.1.4" + kind-of "^5.0.0" + +is-descriptor@^1.0.0, is-descriptor@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-1.0.2.tgz#3b159746a66604b04f8c81524ba365c5f14d86ec" + integrity sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg== + dependencies: + is-accessor-descriptor "^1.0.0" + is-data-descriptor "^1.0.0" + kind-of "^6.0.2" + +is-docker@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.1.1.tgz#4125a88e44e450d384e09047ede71adc2d144156" + integrity sha512-ZOoqiXfEwtGknTiuDEy8pN2CfE3TxMHprvNer1mXiqwkOT77Rw3YVrUQ52EqAOU3QAWDQ+bQdx7HJzrv7LS2Hw== + +is-extendable@^0.1.0, is-extendable@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" + integrity sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik= + +is-extendable@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-1.0.1.tgz#a7470f9e426733d81bd81e1155264e3a3507cab4" + integrity sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA== + dependencies: + is-plain-object "^2.0.4" + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-generator-fn@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" + integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== + +is-number@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" + integrity sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU= + dependencies: + kind-of "^3.0.2" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-plain-object@^2.0.3, is-plain-object@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" + integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== + dependencies: + isobject "^3.0.1" + +is-potential-custom-element-name@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.0.tgz#0c52e54bcca391bb2c494b21e8626d7336c6e397" + integrity sha1-DFLlS8yjkbssSUsh6GJtczbG45c= + +is-stream@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" + integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= + +is-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.0.tgz#bde9c32680d6fae04129d6ac9d921ce7815f78e3" + integrity sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw== + +is-typedarray@^1.0.0, is-typedarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" + integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= + +is-windows@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" + integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== + +is-wsl@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" + integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== + dependencies: + is-docker "^2.0.0" + +isarray@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= + +isobject@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" + integrity sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk= + dependencies: + isarray "1.0.0" + +isobject@^3.0.0, isobject@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" + integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= + +isstream@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" + integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= + +istanbul-lib-coverage@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz#f5944a37c70b550b02a78a5c3b2055b280cec8ec" + integrity sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg== + +istanbul-lib-instrument@^4.0.0, istanbul-lib-instrument@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz#873c6fff897450118222774696a3f28902d77c1d" + integrity sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ== + dependencies: + "@babel/core" "^7.7.5" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-coverage "^3.0.0" + semver "^6.3.0" + +istanbul-lib-report@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#7518fe52ea44de372f460a76b5ecda9ffb73d8a6" + integrity sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw== + dependencies: + istanbul-lib-coverage "^3.0.0" + make-dir "^3.0.0" + supports-color "^7.1.0" + +istanbul-lib-source-maps@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.0.tgz#75743ce6d96bb86dc7ee4352cf6366a23f0b1ad9" + integrity sha512-c16LpFRkR8vQXyHZ5nLpY35JZtzj1PQY1iZmesUbf1FZHbIupcWfjgOXBY9YHkLEQ6puz1u4Dgj6qmU/DisrZg== + dependencies: + debug "^4.1.1" + istanbul-lib-coverage "^3.0.0" + source-map "^0.6.1" + +istanbul-reports@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.0.2.tgz#d593210e5000683750cb09fc0644e4b6e27fd53b" + integrity sha512-9tZvz7AiR3PEDNGiV9vIouQ/EAcqMXFmkcA1CDFTwOB98OZVDL0PH9glHotf5Ugp6GCOTypfzGWI/OqjWNCRUw== + dependencies: + html-escaper "^2.0.0" + istanbul-lib-report "^3.0.0" + +jest-changed-files@^26.6.0: + version "26.6.0" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-26.6.0.tgz#63b04aa261b5733c6ade96b7dd24784d12d8bb2d" + integrity sha512-k8PZzlp3cRWDe0fDc/pYs+c4w36+hiWXe1PpW/pW1UJmu1TNTAcQfZUrVYleij+uEqlY6z4mPv7Iff3kY0o5SQ== + dependencies: + "@jest/types" "^26.6.0" + execa "^4.0.0" + throat "^5.0.0" + +jest-cli@^26.6.0: + version "26.6.0" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-26.6.0.tgz#dc3ae34fd5937310493ed07dc79c5ffba2bf6671" + integrity sha512-lJAMZGpmML+y3Kfln6L5DGRTfKGQ+n1JDM1RQstojSLUhe/EaXWR8vmcx70v4CyJKvFZs7c/0QDkPX5ra/aDew== + dependencies: + "@jest/core" "^26.6.0" + "@jest/test-result" "^26.6.0" + "@jest/types" "^26.6.0" + chalk "^4.0.0" + exit "^0.1.2" + graceful-fs "^4.2.4" + import-local "^3.0.2" + is-ci "^2.0.0" + jest-config "^26.6.0" + jest-util "^26.6.0" + jest-validate "^26.6.0" + prompts "^2.0.1" + yargs "^15.4.1" + +jest-config@^26.6.0: + version "26.6.0" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-26.6.0.tgz#cb879a37002f881edb66d673fd40b6704595de89" + integrity sha512-RCR1Kf7MGJ5waVCvrj/k3nCAJKquWZlzs8rkskzj0KlG392hNBOaYd5FQ4cCac08j6pwfIDOwNvMcy0/FqguJg== + dependencies: + "@babel/core" "^7.1.0" + "@jest/test-sequencer" "^26.6.0" + "@jest/types" "^26.6.0" + babel-jest "^26.6.0" + chalk "^4.0.0" + deepmerge "^4.2.2" + glob "^7.1.1" + graceful-fs "^4.2.4" + jest-environment-jsdom "^26.6.0" + jest-environment-node "^26.6.0" + jest-get-type "^26.3.0" + jest-jasmine2 "^26.6.0" + jest-regex-util "^26.0.0" + jest-resolve "^26.6.0" + jest-util "^26.6.0" + jest-validate "^26.6.0" + micromatch "^4.0.2" + pretty-format "^26.6.0" + +jest-diff@^25.2.1: + version "25.5.0" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-25.5.0.tgz#1dd26ed64f96667c068cef026b677dfa01afcfa9" + integrity sha512-z1kygetuPiREYdNIumRpAHY6RXiGmp70YHptjdaxTWGmA085W3iCnXNx0DhflK3vwrKmrRWyY1wUpkPMVxMK7A== + dependencies: + chalk "^3.0.0" + diff-sequences "^25.2.6" + jest-get-type "^25.2.6" + pretty-format "^25.5.0" + +jest-diff@^26.6.0: + version "26.6.0" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-26.6.0.tgz#5e5bbbaf93ec5017fae2b3ef12fc895e29988379" + integrity sha512-IH09rKsdWY8YEY7ii2BHlSq59oXyF2pK3GoK+hOK9eD/x6009eNB5Jv1shLMKgxekodPzLlV7eZP1jPFQYds8w== + dependencies: + chalk "^4.0.0" + diff-sequences "^26.5.0" + jest-get-type "^26.3.0" + pretty-format "^26.6.0" + +jest-docblock@^26.0.0: + version "26.0.0" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-26.0.0.tgz#3e2fa20899fc928cb13bd0ff68bd3711a36889b5" + integrity sha512-RDZ4Iz3QbtRWycd8bUEPxQsTlYazfYn/h5R65Fc6gOfwozFhoImx+affzky/FFBuqISPTqjXomoIGJVKBWoo0w== + dependencies: + detect-newline "^3.0.0" + +jest-each@^26.6.0: + version "26.6.0" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-26.6.0.tgz#9e9d90a4fc5a79e1d99a008897038325a6c7fbbf" + integrity sha512-7LzSNwNviYnm4FWK46itIE03NqD/8O8/7tVQ5rwTdTNrmPMQoQ1Z7hEFQ1uzRReluOFislpurpnQ0QsclSiDkA== + dependencies: + "@jest/types" "^26.6.0" + chalk "^4.0.0" + jest-get-type "^26.3.0" + jest-util "^26.6.0" + pretty-format "^26.6.0" + +jest-environment-jsdom@^26.6.0: + version "26.6.0" + resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-26.6.0.tgz#2ce353fb82d27a9066bfea3ff2c27d9405076c69" + integrity sha512-bXO9IG7a3YlyiHxwfKF+OWoTA+GIw4FrD+Y0pb6CC+nKs5JuSRZmR2ovEX6PWo6KY42ka3JoZOp3KEnXiFPPCg== + dependencies: + "@jest/environment" "^26.6.0" + "@jest/fake-timers" "^26.6.0" + "@jest/types" "^26.6.0" + "@types/node" "*" + jest-mock "^26.6.0" + jest-util "^26.6.0" + jsdom "^16.4.0" + +jest-environment-node@^26.6.0: + version "26.6.0" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-26.6.0.tgz#97f6e48085e67bda43b97f48e678ce78d760cd14" + integrity sha512-kWU6ZD1h6fs7sIl6ufuK0sXW/3d6WLaj48iow0NxhgU6eY89d9K+0MVmE0cRcVlh53yMyxTK6b+TnhLOnlGp/A== + dependencies: + "@jest/environment" "^26.6.0" + "@jest/fake-timers" "^26.6.0" + "@jest/types" "^26.6.0" + "@types/node" "*" + jest-mock "^26.6.0" + jest-util "^26.6.0" + +jest-get-type@^25.2.6: + version "25.2.6" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-25.2.6.tgz#0b0a32fab8908b44d508be81681487dbabb8d877" + integrity sha512-DxjtyzOHjObRM+sM1knti6or+eOgcGU4xVSb2HNP1TqO4ahsT+rqZg+nyqHWJSvWgKC5cG3QjGFBqxLghiF/Ig== + +jest-get-type@^26.3.0: + version "26.3.0" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-26.3.0.tgz#e97dc3c3f53c2b406ca7afaed4493b1d099199e0" + integrity sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig== + +jest-haste-map@^26.6.0: + version "26.6.0" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-26.6.0.tgz#4cd392bc51109bd8e0f765b2d5afa746bebb5ce2" + integrity sha512-RpNqAGMR58uG9E9vWITorX2/R7he/tSbHWldX5upt1ymEcmCaXczqXxjqI6xOtRR8Ev6ZEYDfgSA5Fy7WHUL5w== + dependencies: + "@jest/types" "^26.6.0" + "@types/graceful-fs" "^4.1.2" + "@types/node" "*" + anymatch "^3.0.3" + fb-watchman "^2.0.0" + graceful-fs "^4.2.4" + jest-regex-util "^26.0.0" + jest-serializer "^26.5.0" + jest-util "^26.6.0" + jest-worker "^26.5.0" + micromatch "^4.0.2" + sane "^4.0.3" + walker "^1.0.7" + optionalDependencies: + fsevents "^2.1.2" + +jest-jasmine2@^26.6.0: + version "26.6.0" + resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-26.6.0.tgz#1b59e26aa56651bae3d4637965c8cd4d3851de6d" + integrity sha512-2E3c+0A9y2OIK5caw5qlcm3b4doaf8FSfXKTX3xqKTUJoR4zXh0xvERBNWxZP9xMNXEi/2Z3LVsZpR2hROgixA== + dependencies: + "@babel/traverse" "^7.1.0" + "@jest/environment" "^26.6.0" + "@jest/source-map" "^26.5.0" + "@jest/test-result" "^26.6.0" + "@jest/types" "^26.6.0" + "@types/node" "*" + chalk "^4.0.0" + co "^4.6.0" + expect "^26.6.0" + is-generator-fn "^2.0.0" + jest-each "^26.6.0" + jest-matcher-utils "^26.6.0" + jest-message-util "^26.6.0" + jest-runtime "^26.6.0" + jest-snapshot "^26.6.0" + jest-util "^26.6.0" + pretty-format "^26.6.0" + throat "^5.0.0" + +jest-leak-detector@^26.6.0: + version "26.6.0" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-26.6.0.tgz#a211c4c7627743e8d87b392bf92502cd64275df3" + integrity sha512-3oMv34imWTl1/nwKnmE/DxYo3QqHnZeF3nO6UzldppkhW0Za7OY2DYyWiamqVzwdUrjhoQkY5g+aF6Oc3alYEQ== + dependencies: + jest-get-type "^26.3.0" + pretty-format "^26.6.0" + +jest-matcher-utils@^26.6.0: + version "26.6.0" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-26.6.0.tgz#8f57d78353275bfa7a3ccea128c1030b347138e2" + integrity sha512-BUy/dQYb7ELGRazmK4ZVkbfPYCaNnrMtw1YljVhcKzWUxBM0xQ+bffrfnMLdRZp4wUUcT4ahaVnA3VWZtXWP9Q== + dependencies: + chalk "^4.0.0" + jest-diff "^26.6.0" + jest-get-type "^26.3.0" + pretty-format "^26.6.0" + +jest-message-util@^26.6.0: + version "26.6.0" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-26.6.0.tgz#c3499053022e05765f71b8c2535af63009e2d4be" + integrity sha512-WPAeS38Kza29f04I0iOIQrXeiebRXjmn6cFehzI7KKJOgT0NmqYAcLgjWnIAfKs5FBmEQgje1kXab0DaLKCl2w== + dependencies: + "@babel/code-frame" "^7.0.0" + "@jest/types" "^26.6.0" + "@types/stack-utils" "^2.0.0" + chalk "^4.0.0" + graceful-fs "^4.2.4" + micromatch "^4.0.2" + slash "^3.0.0" + stack-utils "^2.0.2" + +jest-mock@^26.6.0: + version "26.6.0" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-26.6.0.tgz#5d13a41f3662a98a55c7742ac67c482e232ded13" + integrity sha512-HsNmL8vVIn1rL1GWA21Drpy9Cl+7GImwbWz/0fkWHrUXVzuaG7rP0vwLtE+/n70Mt0U8nPkz8fxioi3SC0wqhw== + dependencies: + "@jest/types" "^26.6.0" + "@types/node" "*" + +jest-pnp-resolver@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz#b704ac0ae028a89108a4d040b3f919dfddc8e33c" + integrity sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w== + +jest-regex-util@^26.0.0: + version "26.0.0" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-26.0.0.tgz#d25e7184b36e39fd466c3bc41be0971e821fee28" + integrity sha512-Gv3ZIs/nA48/Zvjrl34bf+oD76JHiGDUxNOVgUjh3j890sblXryjY4rss71fPtD/njchl6PSE2hIhvyWa1eT0A== + +jest-resolve-dependencies@^26.6.0: + version "26.6.0" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-26.6.0.tgz#05bfecc977a3a48929fc7d9876f03d93a16b7df0" + integrity sha512-4di+XUT7LwJJ8b8qFEEDQssC5+aeVjLhvRICCaS4alh/EVS9JCT1armfJ3pnSS8t4o6659WbMmKVo82H4LuUVw== + dependencies: + "@jest/types" "^26.6.0" + jest-regex-util "^26.0.0" + jest-snapshot "^26.6.0" + +jest-resolve@^26.6.0: + version "26.6.0" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-26.6.0.tgz#070fe7159af87b03e50f52ea5e17ee95bbee40e1" + integrity sha512-tRAz2bwraHufNp+CCmAD8ciyCpXCs1NQxB5EJAmtCFy6BN81loFEGWKzYu26Y62lAJJe4X4jg36Kf+NsQyiStQ== + dependencies: + "@jest/types" "^26.6.0" + chalk "^4.0.0" + graceful-fs "^4.2.4" + jest-pnp-resolver "^1.2.2" + jest-util "^26.6.0" + read-pkg-up "^7.0.1" + resolve "^1.17.0" + slash "^3.0.0" + +jest-runner@^26.6.0: + version "26.6.0" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-26.6.0.tgz#465a76efc9ec12cfd83a2af3a6cfb695b13a3efe" + integrity sha512-QpeN6pje8PQvFgT+wYOlzeycKd67qAvSw5FgYBiX2cTW+QTiObTzv/k09qRvT09rcCntFxUhy9VB1mgNGFLYIA== + dependencies: + "@jest/console" "^26.6.0" + "@jest/environment" "^26.6.0" + "@jest/test-result" "^26.6.0" + "@jest/types" "^26.6.0" + "@types/node" "*" + chalk "^4.0.0" + emittery "^0.7.1" + exit "^0.1.2" + graceful-fs "^4.2.4" + jest-config "^26.6.0" + jest-docblock "^26.0.0" + jest-haste-map "^26.6.0" + jest-leak-detector "^26.6.0" + jest-message-util "^26.6.0" + jest-resolve "^26.6.0" + jest-runtime "^26.6.0" + jest-util "^26.6.0" + jest-worker "^26.5.0" + source-map-support "^0.5.6" + throat "^5.0.0" + +jest-runtime@^26.6.0: + version "26.6.0" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-26.6.0.tgz#90f80ea5eb0d97a1089120f582fb84bd36ca5491" + integrity sha512-JEz4YGnybFvtN4NLID6lsZf0bcd8jccwjWcG5TRE3fYVnxoX1egTthPjnC4btIwWJ6QaaHhtOQ/E3AGn8iClAw== + dependencies: + "@jest/console" "^26.6.0" + "@jest/environment" "^26.6.0" + "@jest/fake-timers" "^26.6.0" + "@jest/globals" "^26.6.0" + "@jest/source-map" "^26.5.0" + "@jest/test-result" "^26.6.0" + "@jest/transform" "^26.6.0" + "@jest/types" "^26.6.0" + "@types/yargs" "^15.0.0" + chalk "^4.0.0" + collect-v8-coverage "^1.0.0" + exit "^0.1.2" + glob "^7.1.3" + graceful-fs "^4.2.4" + jest-config "^26.6.0" + jest-haste-map "^26.6.0" + jest-message-util "^26.6.0" + jest-mock "^26.6.0" + jest-regex-util "^26.0.0" + jest-resolve "^26.6.0" + jest-snapshot "^26.6.0" + jest-util "^26.6.0" + jest-validate "^26.6.0" + slash "^3.0.0" + strip-bom "^4.0.0" + yargs "^15.4.1" + +jest-serializer@^26.5.0: + version "26.5.0" + resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-26.5.0.tgz#f5425cc4c5f6b4b355f854b5f0f23ec6b962bc13" + integrity sha512-+h3Gf5CDRlSLdgTv7y0vPIAoLgX/SI7T4v6hy+TEXMgYbv+ztzbg5PSN6mUXAT/hXYHvZRWm+MaObVfqkhCGxA== + dependencies: + "@types/node" "*" + graceful-fs "^4.2.4" + +jest-snapshot@^26.6.0: + version "26.6.0" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-26.6.0.tgz#457aa9c1761efc781ac9c02b021a0b21047c6a38" + integrity sha512-mcqJZeIZqxomvBcsaiIbiEe2g7K1UxnUpTwjMoHb+DX4uFGnuZoZ6m28YOYRyCfZsdU9mmq73rNBnEH2atTR4Q== + dependencies: + "@babel/types" "^7.0.0" + "@jest/types" "^26.6.0" + "@types/babel__traverse" "^7.0.4" + "@types/prettier" "^2.0.0" + chalk "^4.0.0" + expect "^26.6.0" + graceful-fs "^4.2.4" + jest-diff "^26.6.0" + jest-get-type "^26.3.0" + jest-haste-map "^26.6.0" + jest-matcher-utils "^26.6.0" + jest-message-util "^26.6.0" + jest-resolve "^26.6.0" + natural-compare "^1.4.0" + pretty-format "^26.6.0" + semver "^7.3.2" + +jest-util@^26.1.0, jest-util@^26.6.0: + version "26.6.0" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-26.6.0.tgz#a81547f6d38738b505c5a594b37d911335dea60f" + integrity sha512-/cUGqcnKeZMjvTQLfJo65nBOEZ/k0RB/8usv2JpfYya05u0XvBmKkIH5o5c4nCh9DD61B1YQjMGGqh1Ha0aXdg== + dependencies: + "@jest/types" "^26.6.0" + "@types/node" "*" + chalk "^4.0.0" + graceful-fs "^4.2.4" + is-ci "^2.0.0" + micromatch "^4.0.2" + +jest-validate@^26.6.0: + version "26.6.0" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-26.6.0.tgz#b95e2076cca1a58b183e5bcce2bf43af52eebf10" + integrity sha512-FKHNqvh1Pgs4NWas56gsTPmjcIoGAAzSVUCK1+g8euzuCGbmdEr8LRTtOEFjd29uMZUk0PhzmzKGlHPe6j3UWw== + dependencies: + "@jest/types" "^26.6.0" + camelcase "^6.0.0" + chalk "^4.0.0" + jest-get-type "^26.3.0" + leven "^3.1.0" + pretty-format "^26.6.0" + +jest-watcher@^26.6.0: + version "26.6.0" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-26.6.0.tgz#06001c22831583a16f9ccb388ee33316a7f4200f" + integrity sha512-gw5BvcgPi0PKpMlNWQjUet5C5A4JOYrT7gexdP6+DR/f7mRm7wE0o1GqwPwcTsTwo0/FNf9c/kIDXTRaSAYwlw== + dependencies: + "@jest/test-result" "^26.6.0" + "@jest/types" "^26.6.0" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + jest-util "^26.6.0" + string-length "^4.0.1" + +jest-worker@^26.5.0: + version "26.5.0" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.5.0.tgz#87deee86dbbc5f98d9919e0dadf2c40e3152fa30" + integrity sha512-kTw66Dn4ZX7WpjZ7T/SUDgRhapFRKWmisVAF0Rv4Fu8SLFD7eLbqpLvbxVqYhSgaWa7I+bW7pHnbyfNsH6stug== + dependencies: + "@types/node" "*" + merge-stream "^2.0.0" + supports-color "^7.0.0" + +jest@26.6.0: + version "26.6.0" + resolved "https://registry.yarnpkg.com/jest/-/jest-26.6.0.tgz#546b25a1d8c888569dbbe93cae131748086a4a25" + integrity sha512-jxTmrvuecVISvKFFhOkjsWRZV7sFqdSUAd1ajOKY+/QE/aLBVstsJ/dX8GczLzwiT6ZEwwmZqtCUHLHHQVzcfA== + dependencies: + "@jest/core" "^26.6.0" + import-local "^3.0.2" + jest-cli "^26.6.0" + +jpeg-js@^0.4.2: + version "0.4.2" + resolved "https://registry.yarnpkg.com/jpeg-js/-/jpeg-js-0.4.2.tgz#8b345b1ae4abde64c2da2fe67ea216a114ac279d" + integrity sha512-+az2gi/hvex7eLTMTlbRLOhH6P6WFdk2ITI8HJsaH2VqYO0I594zXSYEP+tf4FW+8Cy68ScDXoAsQdyQanv3sw== + +js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@^3.13.1: + version "3.14.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.0.tgz#a7a34170f26a21bb162424d8adacb4113a69e482" + integrity sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +jsbn@~0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" + integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= + +jsdom@^16.4.0: + version "16.4.0" + resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-16.4.0.tgz#36005bde2d136f73eee1a830c6d45e55408edddb" + integrity sha512-lYMm3wYdgPhrl7pDcRmvzPhhrGVBeVhPIqeHjzeiHN3DFmD1RBpbExbi8vU7BJdH8VAZYovR8DMt0PNNDM7k8w== + dependencies: + abab "^2.0.3" + acorn "^7.1.1" + acorn-globals "^6.0.0" + cssom "^0.4.4" + cssstyle "^2.2.0" + data-urls "^2.0.0" + decimal.js "^10.2.0" + domexception "^2.0.1" + escodegen "^1.14.1" + html-encoding-sniffer "^2.0.1" + is-potential-custom-element-name "^1.0.0" + nwsapi "^2.2.0" + parse5 "5.1.1" + request "^2.88.2" + request-promise-native "^1.0.8" + saxes "^5.0.0" + symbol-tree "^3.2.4" + tough-cookie "^3.0.1" + w3c-hr-time "^1.0.2" + w3c-xmlserializer "^2.0.0" + webidl-conversions "^6.1.0" + whatwg-encoding "^1.0.5" + whatwg-mimetype "^2.3.0" + whatwg-url "^8.0.0" + ws "^7.2.3" + xml-name-validator "^3.0.0" + +jsesc@^2.5.1: + version "2.5.2" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" + integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== + +json-parse-even-better-errors@^2.3.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-schema@0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" + integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM= + +json-stringify-safe@~5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" + integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= + +json5@2.x, json5@^2.1.2: + version "2.1.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.3.tgz#c9b0f7fa9233bfe5807fe66fcf3a5617ed597d43" + integrity sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA== + dependencies: + minimist "^1.2.5" + +jsprim@^1.2.2: + version "1.4.1" + resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" + integrity sha1-MT5mvB5cwG5Di8G3SZwuXFastqI= + dependencies: + assert-plus "1.0.0" + extsprintf "1.3.0" + json-schema "0.2.3" + verror "1.10.0" + +kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: + version "3.2.2" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" + integrity sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ= + dependencies: + is-buffer "^1.1.5" + +kind-of@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57" + integrity sha1-IIE989cSkosgc3hpGkUGb65y3Vc= + dependencies: + is-buffer "^1.1.5" + +kind-of@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d" + integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw== + +kind-of@^6.0.0, kind-of@^6.0.2: + version "6.0.3" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" + integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== + +kleur@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" + integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== + +leven@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" + integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== + +levn@~0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" + integrity sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4= + dependencies: + prelude-ls "~1.1.2" + type-check "~0.3.2" + +lines-and-columns@^1.1.6: + version "1.1.6" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" + integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA= + +locate-path@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" + integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== + dependencies: + p-locate "^4.1.0" + +lodash.memoize@4.x: + version "4.1.2" + resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" + integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4= + +lodash.sortby@^4.7.0: + version "4.7.0" + resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" + integrity sha1-7dFMgk4sycHgsKG0K7UhBRakJDg= + +lodash@^4.17.19: + version "4.17.20" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" + integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== + +make-dir@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" + integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== + dependencies: + semver "^6.0.0" + +make-error@1.x: + version "1.3.6" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" + integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== + +makeerror@1.0.x: + version "1.0.11" + resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.11.tgz#e01a5c9109f2af79660e4e8b9587790184f5a96c" + integrity sha1-4BpckQnyr3lmDk6LlYd5AYT1qWw= + dependencies: + tmpl "1.0.x" + +map-cache@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" + integrity sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8= + +map-visit@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" + integrity sha1-7Nyo8TFE5mDxtb1B8S80edmN+48= + dependencies: + object-visit "^1.0.0" + +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + +micromatch@^3.1.4: + version "3.1.10" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" + integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg== + dependencies: + arr-diff "^4.0.0" + array-unique "^0.3.2" + braces "^2.3.1" + define-property "^2.0.2" + extend-shallow "^3.0.2" + extglob "^2.0.4" + fragment-cache "^0.2.1" + kind-of "^6.0.2" + nanomatch "^1.2.9" + object.pick "^1.3.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.2" + +micromatch@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259" + integrity sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q== + dependencies: + braces "^3.0.1" + picomatch "^2.0.5" + +mime-db@1.44.0: + version "1.44.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.44.0.tgz#fa11c5eb0aca1334b4233cb4d52f10c5a6272f92" + integrity sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg== + +mime-types@^2.1.12, mime-types@~2.1.19: + version "2.1.27" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.27.tgz#47949f98e279ea53119f5722e0f34e529bec009f" + integrity sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w== + dependencies: + mime-db "1.44.0" + +mime@^2.4.6: + version "2.4.6" + resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.6.tgz#e5b407c90db442f2beb5b162373d07b69affa4d1" + integrity sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA== + +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + +minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== + dependencies: + brace-expansion "^1.1.7" + +minimist@^1.1.1, minimist@^1.2.0, minimist@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" + integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== + +mixin-deep@^1.2.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566" + integrity sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA== + dependencies: + for-in "^1.0.2" + is-extendable "^1.0.1" + +mkdirp@1.x: + version "1.0.4" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" + integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +nanomatch@^1.2.9: + version "1.2.13" + resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" + integrity sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA== + dependencies: + arr-diff "^4.0.0" + array-unique "^0.3.2" + define-property "^2.0.2" + extend-shallow "^3.0.2" + fragment-cache "^0.2.1" + is-windows "^1.0.2" + kind-of "^6.0.2" + object.pick "^1.3.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= + +nice-try@^1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" + integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== + +node-int64@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" + integrity sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs= + +node-modules-regexp@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz#8d9dbe28964a4ac5712e9131642107c71e90ec40" + integrity sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA= + +node-notifier@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-8.0.0.tgz#a7eee2d51da6d0f7ff5094bc7108c911240c1620" + integrity sha512-46z7DUmcjoYdaWyXouuFNNfUo6eFa94t23c53c+lG/9Cvauk4a98rAUp9672X5dxGdQmLpPzTxzu8f/OeEPaFA== + dependencies: + growly "^1.3.0" + is-wsl "^2.2.0" + semver "^7.3.2" + shellwords "^0.1.1" + uuid "^8.3.0" + which "^2.0.2" + +normalize-package-data@^2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" + integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== + dependencies: + hosted-git-info "^2.1.4" + resolve "^1.10.0" + semver "2 || 3 || 4 || 5" + validate-npm-package-license "^3.0.1" + +normalize-path@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" + integrity sha1-GrKLVW4Zg2Oowab35vogE3/mrtk= + dependencies: + remove-trailing-separator "^1.0.1" + +normalize-path@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +npm-run-path@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" + integrity sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8= + dependencies: + path-key "^2.0.0" + +npm-run-path@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" + integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== + dependencies: + path-key "^3.0.0" + +nwsapi@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.0.tgz#204879a9e3d068ff2a55139c2c772780681a38b7" + integrity sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ== + +oauth-sign@~0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" + integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== + +object-copy@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" + integrity sha1-fn2Fi3gb18mRpBupde04EnVOmYw= + dependencies: + copy-descriptor "^0.1.0" + define-property "^0.2.5" + kind-of "^3.0.3" + +object-visit@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" + integrity sha1-95xEk68MU3e1n+OdOV5BBC3QRbs= + dependencies: + isobject "^3.0.0" + +object.pick@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" + integrity sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c= + dependencies: + isobject "^3.0.1" + +once@^1.3.0, once@^1.3.1, once@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + dependencies: + wrappy "1" + +onetime@^5.1.0: + version "5.1.2" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== + dependencies: + mimic-fn "^2.1.0" + +optionator@^0.8.1: + version "0.8.3" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" + integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA== + dependencies: + deep-is "~0.1.3" + fast-levenshtein "~2.0.6" + levn "~0.3.0" + prelude-ls "~1.1.2" + type-check "~0.3.2" + word-wrap "~1.2.3" + +p-each-series@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/p-each-series/-/p-each-series-2.1.0.tgz#961c8dd3f195ea96c747e636b262b800a6b1af48" + integrity sha512-ZuRs1miPT4HrjFa+9fRfOFXxGJfORgelKV9f9nNOWw2gl6gVsRaVDOQP0+MI0G0wGKns1Yacsu0GjOFbTK0JFQ== + +p-finally@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" + integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= + +p-limit@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== + dependencies: + p-try "^2.0.0" + +p-locate@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" + integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== + dependencies: + p-limit "^2.2.0" + +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + +parse-json@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.1.0.tgz#f96088cdf24a8faa9aea9a009f2d9d942c999646" + integrity sha512-+mi/lmVVNKFNVyLXV31ERiy2CY5E1/F6QtJFEzoChPRwwngMNXRDQ9GJ5WdE2Z2P4AujsOi0/+2qHID68KwfIQ== + dependencies: + "@babel/code-frame" "^7.0.0" + error-ex "^1.3.1" + json-parse-even-better-errors "^2.3.0" + lines-and-columns "^1.1.6" + +parse5@5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-5.1.1.tgz#f68e4e5ba1852ac2cadc00f4555fff6c2abb6178" + integrity sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug== + +pascalcase@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" + integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ= + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + +path-key@^2.0.0, path-key@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" + integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= + +path-key@^3.0.0, path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-parse@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" + integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== + +pend@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" + integrity sha1-elfrVQpng/kRUzH89GY9XI4AelA= + +performance-now@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" + integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= + +picomatch@^2.0.4, picomatch@^2.0.5: + version "2.2.2" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" + integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== + +pirates@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.1.tgz#643a92caf894566f91b2b986d2c66950a8e2fb87" + integrity sha512-WuNqLTbMI3tmfef2TKxlQmAiLHKtFhlsCZnPIpuv2Ow0RDVO8lfy1Opf4NUzlMXLjPl+Men7AuVdX6TA+s+uGA== + dependencies: + node-modules-regexp "^1.0.0" + +pkg-dir@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" + integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== + dependencies: + find-up "^4.0.0" + +playwright@1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.5.1.tgz#f5333cbfefaf9cb7213b0c9ca8bc9f3f18efcf16" + integrity sha512-FYxrQ2TAmvp5ZnBnc6EJHc1Vt3DmXx8xVDq6rxVVG4YVoNKHYMarHI92zGyDlueN2kKCavrhk4Et9w6jJ5XWaA== + dependencies: + debug "^4.1.1" + extract-zip "^2.0.1" + https-proxy-agent "^5.0.0" + jpeg-js "^0.4.2" + mime "^2.4.6" + pngjs "^5.0.0" + progress "^2.0.3" + proper-lockfile "^4.1.1" + proxy-from-env "^1.1.0" + rimraf "^3.0.2" + ws "^7.3.1" + +pngjs@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-5.0.0.tgz#e79dd2b215767fd9c04561c01236df960bce7fbb" + integrity sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw== + +posix-character-classes@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" + integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= + +prelude-ls@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" + integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= + +pretty-format@^25.2.1, pretty-format@^25.5.0: + version "25.5.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-25.5.0.tgz#7873c1d774f682c34b8d48b6743a2bf2ac55791a" + integrity sha512-kbo/kq2LQ/A/is0PQwsEHM7Ca6//bGPPvU6UnsdDRSKTWxT/ru/xb88v4BJf6a69H+uTytOEsTusT9ksd/1iWQ== + dependencies: + "@jest/types" "^25.5.0" + ansi-regex "^5.0.0" + ansi-styles "^4.0.0" + react-is "^16.12.0" + +pretty-format@^26.6.0: + version "26.6.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-26.6.0.tgz#1e1030e3c70e3ac1c568a5fd15627671ea159391" + integrity sha512-Uumr9URVB7bm6SbaByXtx+zGlS+0loDkFMHP0kHahMjmfCtmFY03iqd++5v3Ld6iB5TocVXlBN/T+DXMn9d4BA== + dependencies: + "@jest/types" "^26.6.0" + ansi-regex "^5.0.0" + ansi-styles "^4.0.0" + react-is "^16.12.0" + +progress@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" + integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== + +prompts@^2.0.1: + version "2.3.2" + resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.3.2.tgz#480572d89ecf39566d2bd3fe2c9fccb7c4c0b068" + integrity sha512-Q06uKs2CkNYVID0VqwfAl9mipo99zkBv/n2JtWY89Yxa3ZabWSrs0e2KTudKVa3peLUvYXMefDqIleLPVUBZMA== + dependencies: + kleur "^3.0.3" + sisteransi "^1.0.4" + +proper-lockfile@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/proper-lockfile/-/proper-lockfile-4.1.1.tgz#284cf9db9e30a90e647afad69deb7cb06881262c" + integrity sha512-1w6rxXodisVpn7QYvLk706mzprPTAPCYAqxMvctmPN3ekuRk/kuGkGc82pangZiAt4R3lwSuUzheTTn0/Yb7Zg== + dependencies: + graceful-fs "^4.1.11" + retry "^0.12.0" + signal-exit "^3.0.2" + +proxy-from-env@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" + integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== + +psl@^1.1.28: + version "1.8.0" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" + integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ== + +pump@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" + integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + +punycode@^2.1.0, punycode@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" + integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== + +qs@~6.5.2: + version "6.5.2" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" + integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== + +react-is@^16.12.0: + version "16.13.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" + integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== + +read-pkg-up@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-7.0.1.tgz#f3a6135758459733ae2b95638056e1854e7ef507" + integrity sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg== + dependencies: + find-up "^4.1.0" + read-pkg "^5.2.0" + type-fest "^0.8.1" + +read-pkg@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-5.2.0.tgz#7bf295438ca5a33e56cd30e053b34ee7250c93cc" + integrity sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg== + dependencies: + "@types/normalize-package-data" "^2.4.0" + normalize-package-data "^2.5.0" + parse-json "^5.0.0" + type-fest "^0.6.0" + +regex-not@^1.0.0, regex-not@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c" + integrity sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A== + dependencies: + extend-shallow "^3.0.2" + safe-regex "^1.1.0" + +remove-trailing-separator@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" + integrity sha1-wkvOKig62tW8P1jg1IJJuSN52O8= + +repeat-element@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.3.tgz#782e0d825c0c5a3bb39731f84efee6b742e6b1ce" + integrity sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g== + +repeat-string@^1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" + integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= + +request-promise-core@1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.4.tgz#3eedd4223208d419867b78ce815167d10593a22f" + integrity sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw== + dependencies: + lodash "^4.17.19" + +request-promise-native@^1.0.8: + version "1.0.9" + resolved "https://registry.yarnpkg.com/request-promise-native/-/request-promise-native-1.0.9.tgz#e407120526a5efdc9a39b28a5679bf47b9d9dc28" + integrity sha512-wcW+sIUiWnKgNY0dqCpOZkUbF/I+YPi+f09JZIDa39Ec+q82CpSYniDp+ISgTTbKmnpJWASeJBPZmoxH84wt3g== + dependencies: + request-promise-core "1.1.4" + stealthy-require "^1.1.1" + tough-cookie "^2.3.3" + +request@^2.88.2: + version "2.88.2" + resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" + integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== + dependencies: + aws-sign2 "~0.7.0" + aws4 "^1.8.0" + caseless "~0.12.0" + combined-stream "~1.0.6" + extend "~3.0.2" + forever-agent "~0.6.1" + form-data "~2.3.2" + har-validator "~5.1.3" + http-signature "~1.2.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.19" + oauth-sign "~0.9.0" + performance-now "^2.1.0" + qs "~6.5.2" + safe-buffer "^5.1.2" + tough-cookie "~2.5.0" + tunnel-agent "^0.6.0" + uuid "^3.3.2" + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= + +require-main-filename@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" + integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== + +resolve-cwd@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" + integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== + dependencies: + resolve-from "^5.0.0" + +resolve-from@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" + integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== + +resolve-url@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" + integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= + +resolve@^1.10.0, resolve@^1.17.0, resolve@^1.3.2: + version "1.18.1" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.18.1.tgz#018fcb2c5b207d2a6424aee361c5a266da8f4130" + integrity sha512-lDfCPaMKfOJXjy0dPayzPdF1phampNWr3qFCjAu+rw/qbQmr5jWH5xN2hwh9QKfw9E5v4hwV7A+jrCmL8yjjqA== + dependencies: + is-core-module "^2.0.0" + path-parse "^1.0.6" + +ret@~0.1.10: + version "0.1.15" + resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" + integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== + +retry@^0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" + integrity sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs= + +rimraf@^3.0.0, rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + +rsvp@^4.8.4: + version "4.8.5" + resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-4.8.5.tgz#c8f155311d167f68f21e168df71ec5b083113734" + integrity sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA== + +safe-buffer@^5.0.1, safe-buffer@^5.1.2: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +safe-regex@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" + integrity sha1-QKNmnzsHfR6UPURinhV91IAjvy4= + dependencies: + ret "~0.1.10" + +"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +sane@^4.0.3: + version "4.1.0" + resolved "https://registry.yarnpkg.com/sane/-/sane-4.1.0.tgz#ed881fd922733a6c461bc189dc2b6c006f3ffded" + integrity sha512-hhbzAgTIX8O7SHfp2c8/kREfEn4qO/9q8C9beyY6+tvZ87EpoZ3i1RIEvp27YBswnNbY9mWd6paKVmKbAgLfZA== + dependencies: + "@cnakazawa/watch" "^1.0.3" + anymatch "^2.0.0" + capture-exit "^2.0.0" + exec-sh "^0.3.2" + execa "^1.0.0" + fb-watchman "^2.0.0" + micromatch "^3.1.4" + minimist "^1.1.1" + walker "~1.0.5" + +saxes@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/saxes/-/saxes-5.0.1.tgz#eebab953fa3b7608dbe94e5dadb15c888fa6696d" + integrity sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw== + dependencies: + xmlchars "^2.2.0" + +"semver@2 || 3 || 4 || 5", semver@^5.4.1, semver@^5.5.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + +semver@7.x, semver@^7.3.2: + version "7.3.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938" + integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ== + +semver@^6.0.0, semver@^6.3.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" + integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== + +set-blocking@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" + integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= + +set-value@^2.0.0, set-value@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b" + integrity sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw== + dependencies: + extend-shallow "^2.0.1" + is-extendable "^0.1.1" + is-plain-object "^2.0.3" + split-string "^3.0.1" + +shebang-command@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" + integrity sha1-RKrGW2lbAzmJaMOfNj/uXer98eo= + dependencies: + shebang-regex "^1.0.0" + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" + integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +shellwords@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b" + integrity sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww== + +signal-exit@^3.0.0, signal-exit@^3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" + integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA== + +sisteransi@^1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" + integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== + +slash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + +snapdragon-node@^2.0.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" + integrity sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw== + dependencies: + define-property "^1.0.0" + isobject "^3.0.0" + snapdragon-util "^3.0.1" + +snapdragon-util@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/snapdragon-util/-/snapdragon-util-3.0.1.tgz#f956479486f2acd79700693f6f7b805e45ab56e2" + integrity sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ== + dependencies: + kind-of "^3.2.0" + +snapdragon@^0.8.1: + version "0.8.2" + resolved "https://registry.yarnpkg.com/snapdragon/-/snapdragon-0.8.2.tgz#64922e7c565b0e14204ba1aa7d6964278d25182d" + integrity sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg== + dependencies: + base "^0.11.1" + debug "^2.2.0" + define-property "^0.2.5" + extend-shallow "^2.0.1" + map-cache "^0.2.2" + source-map "^0.5.6" + source-map-resolve "^0.5.0" + use "^3.1.0" + +source-map-resolve@^0.5.0: + version "0.5.3" + resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a" + integrity sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw== + dependencies: + atob "^2.1.2" + decode-uri-component "^0.2.0" + resolve-url "^0.2.1" + source-map-url "^0.4.0" + urix "^0.1.0" + +source-map-support@^0.5.6: + version "0.5.19" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61" + integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map-url@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" + integrity sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM= + +source-map@^0.5.0, source-map@^0.5.6: + version "0.5.7" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" + integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= + +source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +source-map@^0.7.3: + version "0.7.3" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" + integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== + +spdx-correct@^3.0.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9" + integrity sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w== + dependencies: + spdx-expression-parse "^3.0.0" + spdx-license-ids "^3.0.0" + +spdx-exceptions@^2.1.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz#3f28ce1a77a00372683eade4a433183527a2163d" + integrity sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A== + +spdx-expression-parse@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz#cf70f50482eefdc98e3ce0a6833e4a53ceeba679" + integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q== + dependencies: + spdx-exceptions "^2.1.0" + spdx-license-ids "^3.0.0" + +spdx-license-ids@^3.0.0: + version "3.0.6" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.6.tgz#c80757383c28abf7296744998cbc106ae8b854ce" + integrity sha512-+orQK83kyMva3WyPf59k1+Y525csj5JejicWut55zeTWANuN17qSiSLUXWtzHeNWORSvT7GLDJ/E/XiIWoXBTw== + +split-string@^3.0.1, split-string@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" + integrity sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw== + dependencies: + extend-shallow "^3.0.0" + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= + +sshpk@^1.7.0: + version "1.16.1" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" + integrity sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg== + dependencies: + asn1 "~0.2.3" + assert-plus "^1.0.0" + bcrypt-pbkdf "^1.0.0" + dashdash "^1.12.0" + ecc-jsbn "~0.1.1" + getpass "^0.1.1" + jsbn "~0.1.0" + safer-buffer "^2.0.2" + tweetnacl "~0.14.0" + +stack-utils@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.2.tgz#5cf48b4557becb4638d0bc4f21d23f5d19586593" + integrity sha512-0H7QK2ECz3fyZMzQ8rH0j2ykpfbnd20BFtfg/SqVC2+sCTtcw0aDTGB7dk+de4U4uUeuz6nOtJcrkFFLG1B0Rg== + dependencies: + escape-string-regexp "^2.0.0" + +static-extend@^0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" + integrity sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY= + dependencies: + define-property "^0.2.5" + object-copy "^0.1.0" + +stealthy-require@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/stealthy-require/-/stealthy-require-1.1.1.tgz#35b09875b4ff49f26a777e509b3090a3226bf24b" + integrity sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks= + +string-length@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.1.tgz#4a973bf31ef77c4edbceadd6af2611996985f8a1" + integrity sha512-PKyXUd0LK0ePjSOnWn34V2uD6acUWev9uy0Ft05k0E8xRW+SKcA0F7eMr7h5xlzfn+4O3N+55rduYyet3Jk+jw== + dependencies: + char-regex "^1.0.2" + strip-ansi "^6.0.0" + +string-width@^4.1.0, string-width@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.0.tgz#952182c46cc7b2c313d1596e623992bd163b72b5" + integrity sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.0" + +strip-ansi@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532" + integrity sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w== + dependencies: + ansi-regex "^5.0.0" + +strip-bom@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" + integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== + +strip-eof@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" + integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8= + +strip-final-newline@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" + integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== + +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^7.0.0, supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +supports-hyperlinks@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-2.1.0.tgz#f663df252af5f37c5d49bbd7eeefa9e0b9e59e47" + integrity sha512-zoE5/e+dnEijk6ASB6/qrK+oYdm2do1hjoLWrqUC/8WEIW1gbxFcKuBof7sW8ArN6e+AYvsE8HBGiVRWL/F5CA== + dependencies: + has-flag "^4.0.0" + supports-color "^7.0.0" + +symbol-tree@^3.2.4: + version "3.2.4" + resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" + integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== + +terminal-link@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/terminal-link/-/terminal-link-2.1.1.tgz#14a64a27ab3c0df933ea546fba55f2d078edc994" + integrity sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ== + dependencies: + ansi-escapes "^4.2.1" + supports-hyperlinks "^2.0.0" + +test-exclude@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" + integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== + dependencies: + "@istanbuljs/schema" "^0.1.2" + glob "^7.1.4" + minimatch "^3.0.4" + +throat@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/throat/-/throat-5.0.0.tgz#c5199235803aad18754a667d659b5e72ce16764b" + integrity sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA== + +tmpl@1.0.x: + version "1.0.4" + resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.4.tgz#23640dd7b42d00433911140820e5cf440e521dd1" + integrity sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE= + +to-fast-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" + integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4= + +to-object-path@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" + integrity sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68= + dependencies: + kind-of "^3.0.2" + +to-regex-range@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" + integrity sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg= + dependencies: + is-number "^3.0.0" + repeat-string "^1.6.1" + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +to-regex@^3.0.1, to-regex@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" + integrity sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw== + dependencies: + define-property "^2.0.2" + extend-shallow "^3.0.2" + regex-not "^1.0.2" + safe-regex "^1.1.0" + +tough-cookie@^2.3.3, tough-cookie@~2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" + integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== + dependencies: + psl "^1.1.28" + punycode "^2.1.1" + +tough-cookie@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-3.0.1.tgz#9df4f57e739c26930a018184887f4adb7dca73b2" + integrity sha512-yQyJ0u4pZsv9D4clxO69OEjLWYw+jbgspjTue4lTQZLfV0c5l1VmK2y1JK8E9ahdpltPOaAThPcp5nKPUgSnsg== + dependencies: + ip-regex "^2.1.0" + psl "^1.1.28" + punycode "^2.1.1" + +tr46@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-2.0.2.tgz#03273586def1595ae08fedb38d7733cee91d2479" + integrity sha512-3n1qG+/5kg+jrbTzwAykB5yRYtQCTqOGKq5U5PE3b0a1/mzo6snDhjGS0zJVJunO0NrT3Dg1MLy5TjWP/UJppg== + dependencies: + punycode "^2.1.1" + +ts-jest@26.4.1: + version "26.4.1" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-26.4.1.tgz#08ec0d3fc2c3a39e4a46eae5610b69fafa6babd0" + integrity sha512-F4aFq01aS6mnAAa0DljNmKr/Kk9y4HVZ1m6/rtJ0ED56cuxINGq3Q9eVAh+z5vcYKe5qnTMvv90vE8vUMFxomg== + dependencies: + "@types/jest" "26.x" + bs-logger "0.x" + buffer-from "1.x" + fast-json-stable-stringify "2.x" + jest-util "^26.1.0" + json5 "2.x" + lodash.memoize "4.x" + make-error "1.x" + mkdirp "1.x" + semver "7.x" + yargs-parser "20.x" + +tunnel-agent@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" + integrity sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0= + dependencies: + safe-buffer "^5.0.1" + +tweetnacl@^0.14.3, tweetnacl@~0.14.0: + version "0.14.5" + resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" + integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= + +type-check@~0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" + integrity sha1-WITKtRLPHTVeP7eE8wgEsrUg23I= + dependencies: + prelude-ls "~1.1.2" + +type-detect@4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" + integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== + +type-fest@^0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.11.0.tgz#97abf0872310fed88a5c466b25681576145e33f1" + integrity sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ== + +type-fest@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b" + integrity sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg== + +type-fest@^0.8.1: + version "0.8.1" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" + integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== + +typedarray-to-buffer@^3.1.5: + version "3.1.5" + resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" + integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q== + dependencies: + is-typedarray "^1.0.0" + +union-value@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847" + integrity sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg== + dependencies: + arr-union "^3.1.0" + get-value "^2.0.6" + is-extendable "^0.1.1" + set-value "^2.0.1" + +unset-value@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559" + integrity sha1-g3aHP30jNRef+x5vw6jtDfyKtVk= + dependencies: + has-value "^0.3.1" + isobject "^3.0.0" + +uri-js@^4.2.2: + version "4.4.0" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.0.tgz#aa714261de793e8a82347a7bcc9ce74e86f28602" + integrity sha512-B0yRTzYdUCCn9n+F4+Gh4yIDtMQcaJsmYBDsTSG8g/OejKBodLQ2IHfN3bM7jUsRXndopT7OIXWdYqc1fjmV6g== + dependencies: + punycode "^2.1.0" + +urix@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" + integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= + +use@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" + integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== + +uuid@^3.3.2: + version "3.4.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" + integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== + +uuid@^8.3.0: + version "8.3.1" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.1.tgz#2ba2e6ca000da60fce5a196954ab241131e05a31" + integrity sha512-FOmRr+FmWEIG8uhZv6C2bTgEVXsHk08kE7mPlrBbEe+c3r9pjceVPgupIfNIhc4yx55H69OXANrUaSuu9eInKg== + +v8-to-istanbul@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-6.0.1.tgz#7ef0e32faa10f841fe4c1b0f8de96ed067c0be1e" + integrity sha512-PzM1WlqquhBvsV+Gco6WSFeg1AGdD53ccMRkFeyHRE/KRZaVacPOmQYP3EeVgDBtKD2BJ8kgynBQ5OtKiHCH+w== + dependencies: + "@types/istanbul-lib-coverage" "^2.0.1" + convert-source-map "^1.6.0" + source-map "^0.7.3" + +validate-npm-package-license@^3.0.1: + version "3.0.4" + resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" + integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew== + dependencies: + spdx-correct "^3.0.0" + spdx-expression-parse "^3.0.0" + +verror@1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" + integrity sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA= + dependencies: + assert-plus "^1.0.0" + core-util-is "1.0.2" + extsprintf "^1.2.0" + +w3c-hr-time@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd" + integrity sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ== + dependencies: + browser-process-hrtime "^1.0.0" + +w3c-xmlserializer@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz#3e7104a05b75146cc60f564380b7f683acf1020a" + integrity sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA== + dependencies: + xml-name-validator "^3.0.0" + +walker@^1.0.7, walker@~1.0.5: + version "1.0.7" + resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.7.tgz#2f7f9b8fd10d677262b18a884e28d19618e028fb" + integrity sha1-L3+bj9ENZ3JisYqITijRlhjgKPs= + dependencies: + makeerror "1.0.x" + +webidl-conversions@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-5.0.0.tgz#ae59c8a00b121543a2acc65c0434f57b0fc11aff" + integrity sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA== + +webidl-conversions@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-6.1.0.tgz#9111b4d7ea80acd40f5270d666621afa78b69514" + integrity sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w== + +whatwg-encoding@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz#5abacf777c32166a51d085d6b4f3e7d27113ddb0" + integrity sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw== + dependencies: + iconv-lite "0.4.24" + +whatwg-mimetype@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf" + integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g== + +whatwg-url@^8.0.0: + version "8.4.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-8.4.0.tgz#50fb9615b05469591d2b2bd6dfaed2942ed72837" + integrity sha512-vwTUFf6V4zhcPkWp/4CQPr1TW9Ml6SF4lVyaIMBdJw5i6qUUJ1QWM4Z6YYVkfka0OUIzVo/0aNtGVGk256IKWw== + dependencies: + lodash.sortby "^4.7.0" + tr46 "^2.0.2" + webidl-conversions "^6.1.0" + +which-module@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" + integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= + +which@^1.2.9: + version "1.3.1" + resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" + integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== + dependencies: + isexe "^2.0.0" + +which@^2.0.1, which@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +word-wrap@~1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" + integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== + +wrap-ansi@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" + integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= + +write-file-atomic@^3.0.0: + version "3.0.3" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8" + integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q== + dependencies: + imurmurhash "^0.1.4" + is-typedarray "^1.0.0" + signal-exit "^3.0.2" + typedarray-to-buffer "^3.1.5" + +ws@^7.2.3, ws@^7.3.1: + version "7.3.1" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.3.1.tgz#d0547bf67f7ce4f12a72dfe31262c68d7dc551c8" + integrity sha512-D3RuNkynyHmEJIpD2qrgVkc9DQ23OrN/moAwZX4L8DfvszsJxpjQuUq3LMx6HoYji9fbIOBY18XWBsAux1ZZUA== + +xml-name-validator@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" + integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw== + +xmlchars@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" + integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== + +y18n@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b" + integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w== + +yargs-parser@20.x: + version "20.2.3" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.3.tgz#92419ba867b858c868acf8bae9bf74af0dd0ce26" + integrity sha512-emOFRT9WVHw03QSvN5qor9QQT9+sw5vwxfYweivSMHTcAXPefwVae2FjO7JJjj8hCE4CzPOPeFM83VwT29HCww== + +yargs-parser@^18.1.2: + version "18.1.3" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0" + integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ== + dependencies: + camelcase "^5.0.0" + decamelize "^1.2.0" + +yargs@^15.4.1: + version "15.4.1" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8" + integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A== + dependencies: + cliui "^6.0.0" + decamelize "^1.2.0" + find-up "^4.1.0" + get-caller-file "^2.0.1" + require-directory "^2.1.1" + require-main-filename "^2.0.0" + set-blocking "^2.0.0" + string-width "^4.2.0" + which-module "^2.0.0" + y18n "^4.0.0" + yargs-parser "^18.1.2" + +yauzl@^2.10.0: + version "2.10.0" + resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" + integrity sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk= + dependencies: + buffer-crc32 "~0.2.3" + fd-slicer "~1.1.0" diff --git a/package.json b/package.json index 1accd4cb9..fa2cf9c64 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,8 @@ "postinstall": "lerna run postinstall", "lint": "yarn eslint --ext .js,.jsx,.ts,.tsx .", "lint:fix": "yarn prettier --write . && yarn lint --fix", - "test": "yarn workspaces run test" + "test": "yarn workspaces run test", + "test:e2e": "cd e2e && yarn test" }, "dependencies": { "@typescript-eslint/eslint-plugin": "4.1.0", From e7f18f4252568bfec7523c1fce3679c8a8fc3742 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Tue, 20 Oct 2020 12:26:11 +0530 Subject: [PATCH 031/239] added E2E test utils --- e2e/utils.ts | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 e2e/utils.ts diff --git a/e2e/utils.ts b/e2e/utils.ts new file mode 100644 index 000000000..f363458eb --- /dev/null +++ b/e2e/utils.ts @@ -0,0 +1,38 @@ +import playwright, { ChromiumBrowser, ChromiumBrowserContext, LaunchOptions, Page } from 'playwright'; + +class E2ETestUtils { + public page: Page | null = null; + private browser: ChromiumBrowser | null = null; + private context: ChromiumBrowserContext | null = null; + + public setupJest(): void { + global.beforeEach(this.beforeEach.bind(this)); + global.afterEach(this.afterEach.bind(this)); + } + + private async beforeEach(): Promise { + jest.setTimeout(60000); + const options: LaunchOptions = { + headless: true, + args: ['--no-sandbox', '--disable-gpu'] + }; + + this.browser = await playwright.chromium.launch(options); + this.context = await this.browser.newContext(); + this.page = await this.context.newPage(); + + const { CLIENT_ENDPOINT } = process.env; + + if (!CLIENT_ENDPOINT) { + throw new Error('E2E tests: CLIENT_ENDPOINT was not provided'); + } + + await this.page.goto(CLIENT_ENDPOINT); + } + + private async afterEach(): Promise { + await this.browser?.close(); + } +} + +export default E2ETestUtils; From d95c21d7aff8be35e1c1be5b9702605a7ca3a290 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Tue, 20 Oct 2020 12:51:57 +0530 Subject: [PATCH 032/239] added test selectors in client --- client/src/components/shared/Header.tsx | 3 ++- client/src/config/test.selectors.ts | 10 ++++++++++ client/src/pages/Login/index.tsx | 5 ++++- 3 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 client/src/config/test.selectors.ts diff --git a/client/src/components/shared/Header.tsx b/client/src/components/shared/Header.tsx index 8ba9fc3fd..30fd3fd00 100644 --- a/client/src/components/shared/Header.tsx +++ b/client/src/components/shared/Header.tsx @@ -6,6 +6,7 @@ import { IoIosLogOut, IoIosNotifications, IoIosSearch } from 'react-icons/io'; import { Link, useHistory, useLocation } from 'react-router-dom'; import { toast } from 'react-toastify'; import Logo from '../../assets/images/logo-packrat.square.png'; +import { Selectors } from '../../config'; import { HOME_ROUTES, resolveRoute, ROUTES } from '../../constants'; import { useRepositoryStore, useUserStore } from '../../store'; import { Colors } from '../../theme'; @@ -136,7 +137,7 @@ function Header(): React.ReactElement { - + diff --git a/client/src/config/test.selectors.ts b/client/src/config/test.selectors.ts new file mode 100644 index 000000000..402a61215 --- /dev/null +++ b/client/src/config/test.selectors.ts @@ -0,0 +1,10 @@ +const Selectors = { + AUTH: { + EMAIL_FIELD: 'auth-email-field', + PASSWORD_FIELD: 'auth-password-field', + LOGIN_BUTTON: 'auth-login-button', + LOGOUT_BUTTON: 'auth-logout-button' + } +}; + +export { Selectors }; diff --git a/client/src/pages/Login/index.tsx b/client/src/pages/Login/index.tsx index ea99980d4..22deb8c9c 100644 --- a/client/src/pages/Login/index.tsx +++ b/client/src/pages/Login/index.tsx @@ -7,7 +7,7 @@ import { useHistory } from 'react-router-dom'; import { toast } from 'react-toastify'; import LoginBackground from '../../assets/images/login-background.png'; import { LoadingButton } from '../../components'; -import Config from '../../config'; +import Config, { Selectors } from '../../config'; import { ROUTES } from '../../constants'; import { useUserStore } from '../../store'; import { actionOnKeyPress } from '../../utils/shared'; @@ -141,6 +141,7 @@ function Login(): React.ReactElement { {({ handleSubmit, handleChange, values, isSubmitting, submitForm }) => (
Date: Tue, 20 Oct 2020 13:02:19 +0530 Subject: [PATCH 033/239] added e2e test for auth --- client/src/config/index.ts | 1 + e2e/tests/login.test.ts | 31 +++++++++++++++++++++++++++++++ e2e/utils.ts | 22 +++++++++++++++++++++- 3 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 e2e/tests/login.test.ts diff --git a/client/src/config/index.ts b/client/src/config/index.ts index c8ee90f45..cab206f33 100644 --- a/client/src/config/index.ts +++ b/client/src/config/index.ts @@ -10,3 +10,4 @@ const Config = { }; export default Config; +export * from './test.selectors'; diff --git a/e2e/tests/login.test.ts b/e2e/tests/login.test.ts new file mode 100644 index 000000000..743901b09 --- /dev/null +++ b/e2e/tests/login.test.ts @@ -0,0 +1,31 @@ +import { Page } from 'playwright'; +import E2EUtils, { ID, Selectors } from '../utils'; + +const utils = new E2EUtils(); +utils.setupJest(); + +let page: Page | null; +let login: () => Promise; + +beforeEach(() => { + page = utils.page; + login = utils.login; +}); + +describe('Login E2E tests', () => { + test('User should be able to login', async () => { + await login(); + }); + + test('User should be able to logout', async () => { + await login(); + page?.on('dialog', dialog => { + dialog.accept(); + }); + await page?.click(ID(Selectors.AUTH.LOGOUT_BUTTON)); + await page?.waitForNavigation({ + timeout: 20000, + waitUntil: 'domcontentloaded' + }); + }); +}); diff --git a/e2e/utils.ts b/e2e/utils.ts index f363458eb..292bf244b 100644 --- a/e2e/utils.ts +++ b/e2e/utils.ts @@ -1,4 +1,5 @@ import playwright, { ChromiumBrowser, ChromiumBrowserContext, LaunchOptions, Page } from 'playwright'; +import { Selectors } from '../client/src/config'; class E2ETestUtils { public page: Page | null = null; @@ -33,6 +34,25 @@ class E2ETestUtils { private async afterEach(): Promise { await this.browser?.close(); } + + public login = async (): Promise => { + if (!this.page) { + throw new Error('E2E tests: page was not initialized'); + } + + const TEST_USER_EMAIL: string = 'karan.pratapsingh686@gmail.com'; + const TEST_USER_PASSWORD: string = 'karan.pratapsingh686@gmail.com'; + + await this.page.type(ID(Selectors.AUTH.EMAIL_FIELD), TEST_USER_EMAIL); + await this.page.type(ID(Selectors.AUTH.PASSWORD_FIELD), TEST_USER_PASSWORD); + await this.page.click(ID(Selectors.AUTH.LOGIN_BUTTON)); + await this.page.waitForNavigation({ + timeout: 20000, + waitUntil: 'domcontentloaded' + }); + }; } -export default E2ETestUtils; +const ID = (selector: string): string => `#${selector}`; + +export { E2ETestUtils as default, Selectors, ID }; From 3d15991d40aa2092f4ee538a43db821c82700b16 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Tue, 20 Oct 2020 13:02:31 +0530 Subject: [PATCH 034/239] minor refactor --- client/src/config/{test.selectors.ts => Selectors.ts} | 6 ++++++ client/src/config/index.ts | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) rename client/src/config/{test.selectors.ts => Selectors.ts} (66%) diff --git a/client/src/config/test.selectors.ts b/client/src/config/Selectors.ts similarity index 66% rename from client/src/config/test.selectors.ts rename to client/src/config/Selectors.ts index 402a61215..5364d01cd 100644 --- a/client/src/config/test.selectors.ts +++ b/client/src/config/Selectors.ts @@ -1,3 +1,9 @@ +/** + * Selectors + * + * These are the selectors for various elements in the client, + * and are shared with E2E testing + */ const Selectors = { AUTH: { EMAIL_FIELD: 'auth-email-field', diff --git a/client/src/config/index.ts b/client/src/config/index.ts index cab206f33..025613b94 100644 --- a/client/src/config/index.ts +++ b/client/src/config/index.ts @@ -10,4 +10,4 @@ const Config = { }; export default Config; -export * from './test.selectors'; +export * from './Selectors'; From b26f512fa8bd59640dc6c1dace8c6de50e88adcb Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Wed, 21 Oct 2020 11:34:22 +0530 Subject: [PATCH 035/239] added error boundary --- client/src/components/index.ts | 3 +- .../src/components/shared/ErrorBoundary.tsx | 71 +++++++++++++++++++ client/src/index.tsx | 6 +- 3 files changed, 76 insertions(+), 4 deletions(-) create mode 100644 client/src/components/shared/ErrorBoundary.tsx diff --git a/client/src/components/index.ts b/client/src/components/index.ts index ebfc74743..83a025e7c 100644 --- a/client/src/components/index.ts +++ b/client/src/components/index.ts @@ -11,5 +11,6 @@ import Progress from './shared/Progress'; import SidebarBottomNavigator from './shared/SidebarBottomNavigator'; import LoadingButton from './controls/LoadingButton'; import RepositoryIcon from './controls/RepositoryIcon'; +import ErrorBoundary from './shared/ErrorBoundary'; -export { Header, PrivateRoute, PublicRoute, FieldType, LoadingButton, Loader, Progress, SidebarBottomNavigator, RepositoryIcon }; +export { Header, PrivateRoute, PublicRoute, FieldType, LoadingButton, Loader, Progress, SidebarBottomNavigator, RepositoryIcon, ErrorBoundary }; diff --git a/client/src/components/shared/ErrorBoundary.tsx b/client/src/components/shared/ErrorBoundary.tsx new file mode 100644 index 000000000..8de73335a --- /dev/null +++ b/client/src/components/shared/ErrorBoundary.tsx @@ -0,0 +1,71 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { Box, Button, Typography } from '@material-ui/core'; +import { Theme, withStyles } from '@material-ui/core/styles'; +import React, { Component } from 'react'; +import { BiMessageRoundedError } from 'react-icons/bi'; +import { IoMdArrowBack } from 'react-icons/io'; +import { withRouter } from 'react-router'; +import { ROUTES } from '../../constants'; +import { palette } from '../../theme'; + +interface ErrorBoundaryState { + hasError: boolean; +} + +const styles = ({ palette }: Theme) => ({ + container: { + display: 'flex', + flex: 1, + flexDirection: 'column', + alignItems: 'center', + paddingTop: '35vh' + }, + title: { + margin: '10px 0px 5px 0px', + }, + subtitle: { + color: palette.grey[600], + marginBottom: 10 + }, + button: { + color: palette.primary.main, + fontSize: '0.8em' + } +}); + +class ErrorBoundary extends Component { + state: ErrorBoundaryState = { + hasError: false + }; + + static getDerivedStateFromError(): ErrorBoundaryState { + return { hasError: true }; + } + + render(): React.ReactNode { + const { hasError } = this.state; + const { children, classes, history } = this.props; + + const startIcon: React.ReactNode = ; + + const onGoBack = (): void => { + history.push(ROUTES.HOME); + this.setState({ hasError: false }); + }; + + if (hasError) { + return ( + + + Something went wrong + We are working on getting it fixed + + + ); + } + + return children; + } +} + +export default withStyles(styles as any)(withRouter(ErrorBoundary)); \ No newline at end of file diff --git a/client/src/index.tsx b/client/src/index.tsx index 7910000ac..47b5f04cd 100644 --- a/client/src/index.tsx +++ b/client/src/index.tsx @@ -6,7 +6,7 @@ import ReactDOM from 'react-dom'; import { BrowserRouter as Router, Switch } from 'react-router-dom'; import { Slide, ToastContainer } from 'react-toastify'; import 'react-toastify/dist/ReactToastify.css'; -import { Loader, PrivateRoute, PublicRoute } from './components'; +import { ErrorBoundary, Loader, PrivateRoute, PublicRoute } from './components'; import { ROUTES } from './constants'; import './global/root.css'; import { apolloClient } from './graphql'; @@ -32,7 +32,7 @@ function AppRouter(): React.ReactElement { if (!loading) { content = ( - + @@ -42,7 +42,7 @@ function AppRouter(): React.ReactElement { - + ); } From 4139decf014d0e3c3abaa846a2091a248dde9142 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Mon, 26 Oct 2020 13:22:56 +0530 Subject: [PATCH 036/239] Document utils and api in client --- client/src/api/index.ts | 6 ++++++ client/src/constants/routes.ts | 6 ++++++ client/src/graphql/index.ts | 5 +++++ client/src/graphql/utils/index.ts | 9 +++++++-- client/src/index.tsx | 6 ++++++ client/src/theme/colors.ts | 5 +++++ client/src/theme/index.ts | 7 ++++++- client/src/theme/typography.ts | 7 ++++++- client/src/utils/repository.tsx | 5 +++++ client/src/utils/shared.ts | 5 +++++ client/src/utils/upload.ts | 5 +++++ 11 files changed, 62 insertions(+), 4 deletions(-) diff --git a/client/src/api/index.ts b/client/src/api/index.ts index 02bea68a6..648226375 100644 --- a/client/src/api/index.ts +++ b/client/src/api/index.ts @@ -1,4 +1,10 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ +/** + * API (REST) + * + * This class is responsible for performing REST operations such + * as login and logout. + */ enum API_ROUTES { LOGIN = 'auth/login', LOGOUT = 'auth/logout' diff --git a/client/src/constants/routes.ts b/client/src/constants/routes.ts index 33d4d42e5..a6b4a91f5 100644 --- a/client/src/constants/routes.ts +++ b/client/src/constants/routes.ts @@ -1,3 +1,9 @@ +/** + * Routes + * + * This file exports all the route, types associated with + * routing. + */ export enum ROUTES { HOME = '/', LOGIN = '/login', diff --git a/client/src/graphql/index.ts b/client/src/graphql/index.ts index 4f906d3d9..70b30dd0b 100644 --- a/client/src/graphql/index.ts +++ b/client/src/graphql/index.ts @@ -1,4 +1,9 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ +/** + * GraphQL Client + * + * This file configures and exports apollo client and apollo uploader client. + */ import { ApolloClient, InMemoryCache, NormalizedCacheObject } from '@apollo/client'; import { createUploadLink } from 'apollo-upload-client'; import { apolloFetch } from './utils'; diff --git a/client/src/graphql/utils/index.ts b/client/src/graphql/utils/index.ts index 3d982c7ef..6a036b3b5 100644 --- a/client/src/graphql/utils/index.ts +++ b/client/src/graphql/utils/index.ts @@ -1,5 +1,10 @@ -/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ -/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any */ +/** + * GraphQL Utils + * + * These utility provides custom implementation for upload functionality in + * apollo client. + */ const parseHeaders = (rawHeaders: any) => { const headers = new Headers(); const preProcessedHeaders = rawHeaders.replace(/\r?\n[\t ]+/g, ' '); diff --git a/client/src/index.tsx b/client/src/index.tsx index 47b5f04cd..67ca940be 100644 --- a/client/src/index.tsx +++ b/client/src/index.tsx @@ -1,3 +1,9 @@ +/** + * Packrat Client + * + * This is the root component of the client where we mount apollo, theme provider and + * router. + */ import { ApolloProvider } from '@apollo/client'; import { ThemeProvider } from '@material-ui/core'; import React, { useCallback, useEffect, useState } from 'react'; diff --git a/client/src/theme/colors.ts b/client/src/theme/colors.ts index c5b08039d..4a5b8b359 100644 --- a/client/src/theme/colors.ts +++ b/client/src/theme/colors.ts @@ -1,3 +1,8 @@ +/** + * Colors + * + * Custom colors used in client. + */ import { eSystemObjectType } from '../types/server'; type ColorMap = { [key: string]: string }; diff --git a/client/src/theme/index.ts b/client/src/theme/index.ts index d6142e1e5..b1081573f 100644 --- a/client/src/theme/index.ts +++ b/client/src/theme/index.ts @@ -1,9 +1,14 @@ +/** + * Theme + * + * Material UI theme palette for packrat client. + * https://material-ui.com/customization/palette + */ import { createMuiTheme, Theme } from '@material-ui/core/styles'; import createBreakpoints from '@material-ui/core/styles/createBreakpoints'; import Colors from './colors'; import { createTypographyOverrides } from './typography'; -// https://material-ui.com/customization/palette/ export const palette = { primary: { light: '#ECF5FD', diff --git a/client/src/theme/typography.ts b/client/src/theme/typography.ts index 01f6a7e96..21981dbfa 100644 --- a/client/src/theme/typography.ts +++ b/client/src/theme/typography.ts @@ -1,3 +1,9 @@ +/** + * Typography + * + * Material UI typography overrides for packrat client. + * https://material-ui.com/customization/breakpoints + */ import { grey } from '@material-ui/core/colors'; import { Breakpoints } from '@material-ui/core/styles/createBreakpoints'; import { Overrides } from '@material-ui/core/styles/overrides'; @@ -6,7 +12,6 @@ function pxToRem(value: number): string { return `${value / 16}rem`; } -// https://material-ui.com/customization/breakpoints/ function createTypographyOverrides(breakpoints: Breakpoints): Overrides { return { MuiTableCell: { diff --git a/client/src/utils/repository.tsx b/client/src/utils/repository.tsx index 530101bdf..ca7cc22b3 100644 --- a/client/src/utils/repository.tsx +++ b/client/src/utils/repository.tsx @@ -1,4 +1,9 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ +/** + * Repository utilities + * + * Utilities for components associated with Repository UI. + */ import lodash from 'lodash'; import * as qs from 'query-string'; import React from 'react'; diff --git a/client/src/utils/shared.ts b/client/src/utils/shared.ts index 29212de9e..8f0f0ded6 100644 --- a/client/src/utils/shared.ts +++ b/client/src/utils/shared.ts @@ -1,3 +1,8 @@ +/** + * Shared utilities + * + * Shared utilities for components, functionality. + */ import { CSSProperties } from '@material-ui/core/styles/withStyles'; export const actionOnKeyPress = (key: string, actionKey: string, func: () => void): void => { diff --git a/client/src/utils/upload.ts b/client/src/utils/upload.ts index 200658a38..98c269b82 100644 --- a/client/src/utils/upload.ts +++ b/client/src/utils/upload.ts @@ -1,3 +1,8 @@ +/** + * Upload utilities + * + * Utils for upload specific components, functionality. + */ import randomize from 'randomatic'; export function formatBytes(bytes: number, decimals: number = 2): string { From cee7486154f35027f2e738c26ee3802103923b29 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Mon, 26 Oct 2020 14:33:35 +0530 Subject: [PATCH 037/239] document pages and components --- client/src/components/controls/LoadingButton.tsx | 5 +++++ client/src/components/controls/RepositoryIcon.tsx | 5 +++++ client/src/components/shared/ErrorBoundary.tsx | 6 ++++++ client/src/components/shared/FieldType.tsx | 5 +++++ client/src/components/shared/Header.tsx | 5 +++++ client/src/components/shared/Loader.tsx | 5 +++++ client/src/components/shared/PrivateRoute.tsx | 4 ++-- client/src/components/shared/Progress.tsx | 5 +++++ client/src/components/shared/PublicRoute.tsx | 3 ++- .../src/components/shared/SidebarBottomNavigator.tsx | 6 ++++++ client/src/pages/Home/components/SidePanel.tsx | 5 +++++ client/src/pages/Home/components/SidePanelOption.tsx | 5 +++++ client/src/pages/Home/index.tsx | 6 ++++++ .../IngestionSidebar/IngestionSidebarMenu.tsx | 5 +++++ .../IngestionSidebar/IngestionSidebarMenuOption.tsx | 5 +++++ .../Metadata/Photogrammetry/AssetContents.tsx | 6 ++++++ .../Metadata/Photogrammetry/Description.tsx | 5 +++++ .../Metadata/Photogrammetry/IdInputField.tsx | 5 +++++ .../Metadata/Photogrammetry/IdentifierList.tsx | 5 +++++ .../Metadata/Photogrammetry/SelectField.tsx | 5 +++++ .../components/Metadata/Photogrammetry/index.tsx | 5 +++++ .../pages/Ingestion/components/Metadata/index.tsx | 5 +++++ .../Ingestion/components/SubjectItem/ItemList.tsx | 5 +++++ .../Ingestion/components/SubjectItem/ProjectList.tsx | 5 +++++ .../Ingestion/components/SubjectItem/SearchList.tsx | 5 +++++ .../Ingestion/components/SubjectItem/SubjectList.tsx | 5 +++++ .../components/SubjectItem/SubjectListItem.tsx | 5 +++++ .../pages/Ingestion/components/SubjectItem/index.tsx | 5 +++++ .../pages/Ingestion/components/Uploads/FileList.tsx | 5 +++++ .../Ingestion/components/Uploads/FileListItem.tsx | 5 +++++ .../components/Uploads/UploadCompleteList.tsx | 5 +++++ .../components/Uploads/UploadFilesPicker.tsx | 5 +++++ .../Ingestion/components/Uploads/UploadList.tsx | 5 +++++ .../components/Uploads/UploadListHeader.tsx | 5 +++++ .../src/pages/Ingestion/components/Uploads/index.tsx | 5 +++++ client/src/pages/Ingestion/hooks/useIngest.ts | 5 +++++ client/src/pages/Ingestion/index.tsx | 6 ++++++ client/src/pages/Login/hooks/useLoginForm.ts | 12 +++++++++++- client/src/pages/Login/index.tsx | 5 +++++ .../components/RepositoryFilterView/FilterDate.tsx | 5 +++++ .../components/RepositoryFilterView/FilterSelect.tsx | 5 +++++ .../components/RepositoryFilterView/index.tsx | 5 +++++ .../components/RepositoryTreeView/MetadataView.tsx | 5 +++++ .../RepositoryTreeView/RepositoryTreeHeader.tsx | 5 +++++ .../components/RepositoryTreeView/StyledTreeItem.tsx | 5 +++++ .../components/RepositoryTreeView/TreeLabel.tsx | 5 +++++ .../components/RepositoryTreeView/index.tsx | 6 ++++++ client/src/pages/Repository/hooks/useDebounce.ts | 5 +++++ client/src/pages/Repository/hooks/useRepository.ts | 5 +++++ client/src/pages/Repository/index.tsx | 6 ++++++ client/src/pages/index.ts | 2 +- 51 files changed, 258 insertions(+), 5 deletions(-) diff --git a/client/src/components/controls/LoadingButton.tsx b/client/src/components/controls/LoadingButton.tsx index 6403af5ba..5c1fa9823 100644 --- a/client/src/components/controls/LoadingButton.tsx +++ b/client/src/components/controls/LoadingButton.tsx @@ -1,3 +1,8 @@ +/** + * LoadingButton + * + * This is a button component that supports loading behavior. + */ import { Button, ButtonProps } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; import clsx from 'clsx'; diff --git a/client/src/components/controls/RepositoryIcon.tsx b/client/src/components/controls/RepositoryIcon.tsx index 1ceb7e37e..f5d844971 100644 --- a/client/src/components/controls/RepositoryIcon.tsx +++ b/client/src/components/controls/RepositoryIcon.tsx @@ -1,3 +1,8 @@ +/** + * RepositoryIcon + * + * This component renders the icons for the repository tree view item. + */ import React from 'react'; import { Box, Typography } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; diff --git a/client/src/components/shared/ErrorBoundary.tsx b/client/src/components/shared/ErrorBoundary.tsx index 8de73335a..49e4dbd93 100644 --- a/client/src/components/shared/ErrorBoundary.tsx +++ b/client/src/components/shared/ErrorBoundary.tsx @@ -1,4 +1,10 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ +/** + * ErrorBoundary + * + * This component catch errors during rendering, in lifecycle methods, + * and in constructors of the whole tree below them. + */ import { Box, Button, Typography } from '@material-ui/core'; import { Theme, withStyles } from '@material-ui/core/styles'; import React, { Component } from 'react'; diff --git a/client/src/components/shared/FieldType.tsx b/client/src/components/shared/FieldType.tsx index b0d90fae4..5e4efbbcd 100644 --- a/client/src/components/shared/FieldType.tsx +++ b/client/src/components/shared/FieldType.tsx @@ -1,3 +1,8 @@ +/** + * FieldType + * + * This component wraps content and highlights it as required field or not. + */ import { Box, BoxProps, PropTypes, Typography } from '@material-ui/core'; import { fade, makeStyles } from '@material-ui/core/styles'; import React from 'react'; diff --git a/client/src/components/shared/Header.tsx b/client/src/components/shared/Header.tsx index 30fd3fd00..0e29529e0 100644 --- a/client/src/components/shared/Header.tsx +++ b/client/src/components/shared/Header.tsx @@ -1,3 +1,8 @@ +/** + * Header + * + * This component renders the dashboard's header. + */ import { Box, Typography } from '@material-ui/core'; import { fade, makeStyles } from '@material-ui/core/styles'; import React from 'react'; diff --git a/client/src/components/shared/Loader.tsx b/client/src/components/shared/Loader.tsx index 98d7cc767..87113b9fa 100644 --- a/client/src/components/shared/Loader.tsx +++ b/client/src/components/shared/Loader.tsx @@ -1,3 +1,8 @@ +/** + * Loader + * + * This component wraps the Progress in a view. + */ import React from 'react'; import { Box } from '@material-ui/core'; import Progress from './Progress'; diff --git a/client/src/components/shared/PrivateRoute.tsx b/client/src/components/shared/PrivateRoute.tsx index bd5242eea..96c034398 100644 --- a/client/src/components/shared/PrivateRoute.tsx +++ b/client/src/components/shared/PrivateRoute.tsx @@ -1,6 +1,7 @@ /** * PrivateRoute - * Renders a route only if the user is authenticated else redirects to login + * + * Renders a route only if the user is authenticated else redirects to login. */ import React from 'react'; import { Redirect, Route, RouteProps } from 'react-router-dom'; @@ -16,7 +17,6 @@ function PrivateRoute({ component: Component, children, ...rest }: PrivateRouteP const user = useUserStore(state => state.user); const render = props => { - if (!user) { return ; } else { diff --git a/client/src/components/shared/Progress.tsx b/client/src/components/shared/Progress.tsx index f17e6ae8f..86efddf89 100644 --- a/client/src/components/shared/Progress.tsx +++ b/client/src/components/shared/Progress.tsx @@ -1,3 +1,8 @@ +/** + * Progress + * + * Simple circular progress component. + */ import { CircularProgress, CircularProgressProps } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; import clsx from 'clsx'; diff --git a/client/src/components/shared/PublicRoute.tsx b/client/src/components/shared/PublicRoute.tsx index c18b1fba0..b526db2cf 100644 --- a/client/src/components/shared/PublicRoute.tsx +++ b/client/src/components/shared/PublicRoute.tsx @@ -1,6 +1,7 @@ /** * PublicRoute - * Renders a route based on authentication and restriction specified + * + * Renders a route based on authentication and restriction specified. */ import React from 'react'; import { Redirect, Route, RouteProps } from 'react-router-dom'; diff --git a/client/src/components/shared/SidebarBottomNavigator.tsx b/client/src/components/shared/SidebarBottomNavigator.tsx index 670a7ee5a..6ea3972cb 100644 --- a/client/src/components/shared/SidebarBottomNavigator.tsx +++ b/client/src/components/shared/SidebarBottomNavigator.tsx @@ -1,3 +1,9 @@ +/** + * SidebarBottomNavigator + * + * This component renders bottom navigation view, used in data upload + * and ingestion flow + */ import { Box } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; import React from 'react'; diff --git a/client/src/pages/Home/components/SidePanel.tsx b/client/src/pages/Home/components/SidePanel.tsx index 8ef1b57c9..d78502b85 100644 --- a/client/src/pages/Home/components/SidePanel.tsx +++ b/client/src/pages/Home/components/SidePanel.tsx @@ -1,3 +1,8 @@ +/** + * SidePanel + * + * This component renders the collapsable left side panel in homepage UI. + */ import { Box, Grid } from '@material-ui/core'; import { makeStyles, fade } from '@material-ui/core/styles'; import React, { memo, useState, useEffect } from 'react'; diff --git a/client/src/pages/Home/components/SidePanelOption.tsx b/client/src/pages/Home/components/SidePanelOption.tsx index af8296679..cf373ded2 100644 --- a/client/src/pages/Home/components/SidePanelOption.tsx +++ b/client/src/pages/Home/components/SidePanelOption.tsx @@ -1,3 +1,8 @@ +/** + * SidePanelOption + * + * This component renders options for collapsable SidePanel in then homepage UI. + */ import { Box, Typography } from '@material-ui/core'; import { makeStyles, fade } from '@material-ui/core/styles'; import React, { memo } from 'react'; diff --git a/client/src/pages/Home/index.tsx b/client/src/pages/Home/index.tsx index 3efc18c95..9bcc446de 100644 --- a/client/src/pages/Home/index.tsx +++ b/client/src/pages/Home/index.tsx @@ -1,3 +1,9 @@ +/** + * Home + * + * This component renders Home page UI and all the sub routes like Dashboard, Ingestion, + * Repository, Workflow. + */ import { Box } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; import React from 'react'; diff --git a/client/src/pages/Ingestion/components/IngestionSidebar/IngestionSidebarMenu.tsx b/client/src/pages/Ingestion/components/IngestionSidebar/IngestionSidebarMenu.tsx index d5dfd4beb..c332d2944 100644 --- a/client/src/pages/Ingestion/components/IngestionSidebar/IngestionSidebarMenu.tsx +++ b/client/src/pages/Ingestion/components/IngestionSidebar/IngestionSidebarMenu.tsx @@ -1,3 +1,8 @@ +/** + * IngestionSidebarMenu + * + * This component renders sidebar menu for Ingestion flow. + */ import { Box, Grid, Typography } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; import React, { useState, useEffect } from 'react'; diff --git a/client/src/pages/Ingestion/components/IngestionSidebar/IngestionSidebarMenuOption.tsx b/client/src/pages/Ingestion/components/IngestionSidebar/IngestionSidebarMenuOption.tsx index 634f8e716..5e54f9efb 100644 --- a/client/src/pages/Ingestion/components/IngestionSidebar/IngestionSidebarMenuOption.tsx +++ b/client/src/pages/Ingestion/components/IngestionSidebar/IngestionSidebarMenuOption.tsx @@ -1,3 +1,8 @@ +/** + * IngestionSidebarMenuOption + * + * This component renders sidebar menu option for IngestionSidebarMenu component. + */ import { Box, Typography } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; import React, { memo } from 'react'; diff --git a/client/src/pages/Ingestion/components/Metadata/Photogrammetry/AssetContents.tsx b/client/src/pages/Ingestion/components/Metadata/Photogrammetry/AssetContents.tsx index f54534a74..81e5e7d6c 100644 --- a/client/src/pages/Ingestion/components/Metadata/Photogrammetry/AssetContents.tsx +++ b/client/src/pages/Ingestion/components/Metadata/Photogrammetry/AssetContents.tsx @@ -1,3 +1,9 @@ +/** + * AssetContents + * + * This component renders the folder type selector for contents present in + * the uploaded assets + */ import { Box, MenuItem, Select, Typography } from '@material-ui/core'; import { fade, makeStyles } from '@material-ui/core/styles'; import React from 'react'; diff --git a/client/src/pages/Ingestion/components/Metadata/Photogrammetry/Description.tsx b/client/src/pages/Ingestion/components/Metadata/Photogrammetry/Description.tsx index 5fae9d408..86e267508 100644 --- a/client/src/pages/Ingestion/components/Metadata/Photogrammetry/Description.tsx +++ b/client/src/pages/Ingestion/components/Metadata/Photogrammetry/Description.tsx @@ -1,3 +1,8 @@ +/** + * Description + * + * This component renders description field used in photogrammetry metadata component. + */ import { fade, makeStyles } from '@material-ui/core/styles'; import React from 'react'; import { DebounceInput } from 'react-debounce-input'; diff --git a/client/src/pages/Ingestion/components/Metadata/Photogrammetry/IdInputField.tsx b/client/src/pages/Ingestion/components/Metadata/Photogrammetry/IdInputField.tsx index bcbe993ee..d3b3edb7b 100644 --- a/client/src/pages/Ingestion/components/Metadata/Photogrammetry/IdInputField.tsx +++ b/client/src/pages/Ingestion/components/Metadata/Photogrammetry/IdInputField.tsx @@ -1,4 +1,9 @@ /* eslint-disable @typescript-eslint/ban-ts-comment */ +/** + * IdInputField + * + * This component renders id input fields used in photogrammetry metadata component. + */ import { fade, makeStyles } from '@material-ui/core/styles'; import React from 'react'; import { DebounceInput } from 'react-debounce-input'; diff --git a/client/src/pages/Ingestion/components/Metadata/Photogrammetry/IdentifierList.tsx b/client/src/pages/Ingestion/components/Metadata/Photogrammetry/IdentifierList.tsx index bdc5ddc99..4be218871 100644 --- a/client/src/pages/Ingestion/components/Metadata/Photogrammetry/IdentifierList.tsx +++ b/client/src/pages/Ingestion/components/Metadata/Photogrammetry/IdentifierList.tsx @@ -1,3 +1,8 @@ +/** + * IdentifierList + * + * This component renders identifier list used in photogrammetry metadata component. + */ import { Box, Button, Checkbox, MenuItem, Select } from '@material-ui/core'; import { fade, makeStyles } from '@material-ui/core/styles'; import React from 'react'; diff --git a/client/src/pages/Ingestion/components/Metadata/Photogrammetry/SelectField.tsx b/client/src/pages/Ingestion/components/Metadata/Photogrammetry/SelectField.tsx index 8ce4cd63e..aa6ee86e4 100644 --- a/client/src/pages/Ingestion/components/Metadata/Photogrammetry/SelectField.tsx +++ b/client/src/pages/Ingestion/components/Metadata/Photogrammetry/SelectField.tsx @@ -1,3 +1,8 @@ +/** + * SelectField + * + * This component renders select input fields used in photogrammetry metadata component. + */ import { MenuItem, Select } from '@material-ui/core'; import { fade, makeStyles } from '@material-ui/core/styles'; import React from 'react'; diff --git a/client/src/pages/Ingestion/components/Metadata/Photogrammetry/index.tsx b/client/src/pages/Ingestion/components/Metadata/Photogrammetry/index.tsx index 80d237261..5aa0e4e43 100644 --- a/client/src/pages/Ingestion/components/Metadata/Photogrammetry/index.tsx +++ b/client/src/pages/Ingestion/components/Metadata/Photogrammetry/index.tsx @@ -1,4 +1,9 @@ /* eslint-disable react-hooks/exhaustive-deps */ +/** + * Metadata - Photogrammetry + * + * This component renders the metadata fields specific to photogrammetry asset. + */ import DateFnsUtils from '@date-io/date-fns'; import { Box, Checkbox, Typography } from '@material-ui/core'; import { fade, makeStyles, withStyles } from '@material-ui/core/styles'; diff --git a/client/src/pages/Ingestion/components/Metadata/index.tsx b/client/src/pages/Ingestion/components/Metadata/index.tsx index a9f2e05bc..75debabfe 100644 --- a/client/src/pages/Ingestion/components/Metadata/index.tsx +++ b/client/src/pages/Ingestion/components/Metadata/index.tsx @@ -1,3 +1,8 @@ +/** + * Metadata + * + * This component renders the metadata specific components for Ingestion UI. + */ import { Box, Breadcrumbs, Typography } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; import * as qs from 'query-string'; diff --git a/client/src/pages/Ingestion/components/SubjectItem/ItemList.tsx b/client/src/pages/Ingestion/components/SubjectItem/ItemList.tsx index 48295eb98..7a3a172ba 100644 --- a/client/src/pages/Ingestion/components/SubjectItem/ItemList.tsx +++ b/client/src/pages/Ingestion/components/SubjectItem/ItemList.tsx @@ -1,3 +1,8 @@ +/** + * ItemList + * + * This component renders item list used in SubjectItem component. + */ import { Table, TableBody, TableCell, TableContainer, TableHead, TableRow } from '@material-ui/core'; import { grey } from '@material-ui/core/colors'; import { makeStyles } from '@material-ui/core/styles'; diff --git a/client/src/pages/Ingestion/components/SubjectItem/ProjectList.tsx b/client/src/pages/Ingestion/components/SubjectItem/ProjectList.tsx index d30d68829..67ca7a77f 100644 --- a/client/src/pages/Ingestion/components/SubjectItem/ProjectList.tsx +++ b/client/src/pages/Ingestion/components/SubjectItem/ProjectList.tsx @@ -1,3 +1,8 @@ +/** + * ProjectList + * + * This component renders project list used in SubjectItem component. + */ import { MenuItem, Select } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; import lodash from 'lodash'; diff --git a/client/src/pages/Ingestion/components/SubjectItem/SearchList.tsx b/client/src/pages/Ingestion/components/SubjectItem/SearchList.tsx index dc55d5437..bb36612d5 100644 --- a/client/src/pages/Ingestion/components/SubjectItem/SearchList.tsx +++ b/client/src/pages/Ingestion/components/SubjectItem/SearchList.tsx @@ -1,3 +1,8 @@ +/** + * SearchList + * + * This component renders search list used in SubjectItem component. + */ import { useLazyQuery } from '@apollo/client'; import { Box, TextField } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; diff --git a/client/src/pages/Ingestion/components/SubjectItem/SubjectList.tsx b/client/src/pages/Ingestion/components/SubjectItem/SubjectList.tsx index d4a87f2f7..17fb08d2b 100644 --- a/client/src/pages/Ingestion/components/SubjectItem/SubjectList.tsx +++ b/client/src/pages/Ingestion/components/SubjectItem/SubjectList.tsx @@ -1,3 +1,8 @@ +/** + * SubjectList + * + * This component renders subject list table used in SubjectItem component. + */ import { Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Typography } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; import React from 'react'; diff --git a/client/src/pages/Ingestion/components/SubjectItem/SubjectListItem.tsx b/client/src/pages/Ingestion/components/SubjectItem/SubjectListItem.tsx index f1dff97c4..67896f16b 100644 --- a/client/src/pages/Ingestion/components/SubjectItem/SubjectListItem.tsx +++ b/client/src/pages/Ingestion/components/SubjectItem/SubjectListItem.tsx @@ -1,3 +1,8 @@ +/** + * SubjectListItem + * + * This component renders subject list item for SubjectList component. + */ import React from 'react'; import { TableRow, TableCell, Typography, Box } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; diff --git a/client/src/pages/Ingestion/components/SubjectItem/index.tsx b/client/src/pages/Ingestion/components/SubjectItem/index.tsx index 0a613adb4..2a5bcc077 100644 --- a/client/src/pages/Ingestion/components/SubjectItem/index.tsx +++ b/client/src/pages/Ingestion/components/SubjectItem/index.tsx @@ -1,3 +1,8 @@ +/** + * SubjectItem + * + * This component renders the subject and item specific components for Ingestion UI. + */ import { Box, Chip, Typography } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; import React, { useEffect, useState } from 'react'; diff --git a/client/src/pages/Ingestion/components/Uploads/FileList.tsx b/client/src/pages/Ingestion/components/Uploads/FileList.tsx index 211f4c569..610700206 100644 --- a/client/src/pages/Ingestion/components/Uploads/FileList.tsx +++ b/client/src/pages/Ingestion/components/Uploads/FileList.tsx @@ -1,3 +1,8 @@ +/** + * FileList + * + * This component renders file list used in UploadList and UploadCompleteList components. + */ import { AnimatePresence } from 'framer-motion'; import React from 'react'; import { FileId, FileUploadStatus, IngestionFile, useUploadStore, useVocabularyStore, VocabularyOption } from '../../../../store'; diff --git a/client/src/pages/Ingestion/components/Uploads/FileListItem.tsx b/client/src/pages/Ingestion/components/Uploads/FileListItem.tsx index a7eb1065e..58faf497c 100644 --- a/client/src/pages/Ingestion/components/Uploads/FileListItem.tsx +++ b/client/src/pages/Ingestion/components/Uploads/FileListItem.tsx @@ -1,3 +1,8 @@ +/** + * FileListItem + * + * This component renders file list item used in FileListItem component. + */ import { Box, MenuItem, Select, Typography } from '@material-ui/core'; import { green, grey, red, yellow } from '@material-ui/core/colors'; import { fade, makeStyles } from '@material-ui/core/styles'; diff --git a/client/src/pages/Ingestion/components/Uploads/UploadCompleteList.tsx b/client/src/pages/Ingestion/components/Uploads/UploadCompleteList.tsx index b402c4511..9717e09f4 100644 --- a/client/src/pages/Ingestion/components/Uploads/UploadCompleteList.tsx +++ b/client/src/pages/Ingestion/components/Uploads/UploadCompleteList.tsx @@ -1,4 +1,9 @@ /* eslint-disable react-hooks/exhaustive-deps */ +/** + * UploadCompleteList + * + * This component renders upload list for completed files only. + */ import { useQuery } from '@apollo/client'; import { Box, Typography } from '@material-ui/core'; import React, { useEffect } from 'react'; diff --git a/client/src/pages/Ingestion/components/Uploads/UploadFilesPicker.tsx b/client/src/pages/Ingestion/components/Uploads/UploadFilesPicker.tsx index 19a811ffb..7c9904d75 100644 --- a/client/src/pages/Ingestion/components/Uploads/UploadFilesPicker.tsx +++ b/client/src/pages/Ingestion/components/Uploads/UploadFilesPicker.tsx @@ -1,3 +1,8 @@ +/** + * UploadFilesPicker + * + * This component renders file picker with drag and drop functionality. + */ import { Button, Typography } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; import React from 'react'; diff --git a/client/src/pages/Ingestion/components/Uploads/UploadList.tsx b/client/src/pages/Ingestion/components/Uploads/UploadList.tsx index 5c2fd15d7..aa565fb80 100644 --- a/client/src/pages/Ingestion/components/Uploads/UploadList.tsx +++ b/client/src/pages/Ingestion/components/Uploads/UploadList.tsx @@ -1,3 +1,8 @@ +/** + * UploadList + * + * This component renders upload list for pending files only. + */ import { Box, Typography } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; import React from 'react'; diff --git a/client/src/pages/Ingestion/components/Uploads/UploadListHeader.tsx b/client/src/pages/Ingestion/components/Uploads/UploadListHeader.tsx index 26bafc78c..2b7d7f76e 100644 --- a/client/src/pages/Ingestion/components/Uploads/UploadListHeader.tsx +++ b/client/src/pages/Ingestion/components/Uploads/UploadListHeader.tsx @@ -1,3 +1,8 @@ +/** + * UploadListHeader + * + * This component renders upload list header for FileList component. + */ import { Box, Typography } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; import React from 'react'; diff --git a/client/src/pages/Ingestion/components/Uploads/index.tsx b/client/src/pages/Ingestion/components/Uploads/index.tsx index eb2cff182..fe7094792 100644 --- a/client/src/pages/Ingestion/components/Uploads/index.tsx +++ b/client/src/pages/Ingestion/components/Uploads/index.tsx @@ -1,4 +1,9 @@ /* eslint-disable react-hooks/exhaustive-deps */ +/** + * Uploads + * + * This component renders the upload specific components for Ingestion UI. + */ import { Box } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; import React, { useEffect, useState } from 'react'; diff --git a/client/src/pages/Ingestion/hooks/useIngest.ts b/client/src/pages/Ingestion/hooks/useIngest.ts index b4ce351e8..e2e869ab1 100644 --- a/client/src/pages/Ingestion/hooks/useIngest.ts +++ b/client/src/pages/Ingestion/hooks/useIngest.ts @@ -1,3 +1,8 @@ +/** + * Ingest Hook + * + * This custom hooks provides easy access ingestion functionality. + */ import { FetchResult } from '@apollo/client'; import lodash from 'lodash'; import { useHistory } from 'react-router'; diff --git a/client/src/pages/Ingestion/index.tsx b/client/src/pages/Ingestion/index.tsx index 2c07029e7..85c596b50 100644 --- a/client/src/pages/Ingestion/index.tsx +++ b/client/src/pages/Ingestion/index.tsx @@ -1,3 +1,9 @@ +/** + * Ingestion + * + * This component renders Ingestion UI and all the sub routes like Uploads, Subject Item + * and Metadata. + */ import { Box } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; import React, { useEffect, useState } from 'react'; diff --git a/client/src/pages/Login/hooks/useLoginForm.ts b/client/src/pages/Login/hooks/useLoginForm.ts index 558a2bc36..b0e7d40f6 100644 --- a/client/src/pages/Login/hooks/useLoginForm.ts +++ b/client/src/pages/Login/hooks/useLoginForm.ts @@ -1,3 +1,8 @@ +/** + * Login Form Hook + * + * This hook provides easy validation and initial values for login form. + */ import * as Yup from 'yup'; export interface ILoginForm { @@ -16,7 +21,12 @@ const initialValues = { password: '' }; -function useLoginForm(): { initialValues: ILoginForm; loginValidationSchema: typeof loginValidationSchema } { +interface UseLoginForm { + initialValues: ILoginForm; + loginValidationSchema: typeof loginValidationSchema; +} + +function useLoginForm(): UseLoginForm { return { initialValues, loginValidationSchema diff --git a/client/src/pages/Login/index.tsx b/client/src/pages/Login/index.tsx index 22deb8c9c..18304976a 100644 --- a/client/src/pages/Login/index.tsx +++ b/client/src/pages/Login/index.tsx @@ -1,3 +1,8 @@ +/** + * Login + * + * This component renders Login page UI. + */ import { Box, Container, Typography } from '@material-ui/core'; import { fade, makeStyles } from '@material-ui/core/styles'; import { Field, Formik, FormikHelpers } from 'formik'; diff --git a/client/src/pages/Repository/components/RepositoryFilterView/FilterDate.tsx b/client/src/pages/Repository/components/RepositoryFilterView/FilterDate.tsx index 7c3201d3b..2a7f6e4c0 100644 --- a/client/src/pages/Repository/components/RepositoryFilterView/FilterDate.tsx +++ b/client/src/pages/Repository/components/RepositoryFilterView/FilterDate.tsx @@ -1,3 +1,8 @@ +/** + * FilterDate + * + * This component renders date input fields used in RepositoryFilterView component. + */ import DateFnsUtils from '@date-io/date-fns'; import { Box, Typography } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; diff --git a/client/src/pages/Repository/components/RepositoryFilterView/FilterSelect.tsx b/client/src/pages/Repository/components/RepositoryFilterView/FilterSelect.tsx index 14610a1ed..0f08fd7aa 100644 --- a/client/src/pages/Repository/components/RepositoryFilterView/FilterSelect.tsx +++ b/client/src/pages/Repository/components/RepositoryFilterView/FilterSelect.tsx @@ -1,3 +1,8 @@ +/** + * FilterSelect + * + * This component renders select input fields used in RepositoryFilterView component. + */ import { Box, MenuItem, Select, Typography } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; import React from 'react'; diff --git a/client/src/pages/Repository/components/RepositoryFilterView/index.tsx b/client/src/pages/Repository/components/RepositoryFilterView/index.tsx index 4a1f7d0a0..38e9824ab 100644 --- a/client/src/pages/Repository/components/RepositoryFilterView/index.tsx +++ b/client/src/pages/Repository/components/RepositoryFilterView/index.tsx @@ -1,3 +1,8 @@ +/** + * RepositoryFilterView + * + * This component renders repository filter view for the Repository UI. + */ import { Box, Chip, Typography } from '@material-ui/core'; import { fade, makeStyles, withStyles } from '@material-ui/core/styles'; import React, { useState } from 'react'; diff --git a/client/src/pages/Repository/components/RepositoryTreeView/MetadataView.tsx b/client/src/pages/Repository/components/RepositoryTreeView/MetadataView.tsx index 57eec2175..9fe9c944d 100644 --- a/client/src/pages/Repository/components/RepositoryTreeView/MetadataView.tsx +++ b/client/src/pages/Repository/components/RepositoryTreeView/MetadataView.tsx @@ -1,3 +1,8 @@ +/** + * MetadataView + * + * This component renders metadata view used in RepositoryTreeView and RepositoryTreeHeader. + */ import { makeStyles } from '@material-ui/core/styles'; import lodash from 'lodash'; import React from 'react'; diff --git a/client/src/pages/Repository/components/RepositoryTreeView/RepositoryTreeHeader.tsx b/client/src/pages/Repository/components/RepositoryTreeView/RepositoryTreeHeader.tsx index 93df55d75..fdc602a92 100644 --- a/client/src/pages/Repository/components/RepositoryTreeView/RepositoryTreeHeader.tsx +++ b/client/src/pages/Repository/components/RepositoryTreeView/RepositoryTreeHeader.tsx @@ -1,3 +1,8 @@ +/** + * RepositoryTreeHeader + * + * This component renders header for RepositoryTreeView. + */ import { Box } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; import React from 'react'; diff --git a/client/src/pages/Repository/components/RepositoryTreeView/StyledTreeItem.tsx b/client/src/pages/Repository/components/RepositoryTreeView/StyledTreeItem.tsx index 75a9582ad..e036b5539 100644 --- a/client/src/pages/Repository/components/RepositoryTreeView/StyledTreeItem.tsx +++ b/client/src/pages/Repository/components/RepositoryTreeView/StyledTreeItem.tsx @@ -1,3 +1,8 @@ +/** + * StyledTreeItem + * + * This component renders a custom tree item for RepositoryTreeView. + */ import { fade, Theme, withStyles } from '@material-ui/core/styles'; import { TreeItem, TreeItemProps } from '@material-ui/lab'; import React from 'react'; diff --git a/client/src/pages/Repository/components/RepositoryTreeView/TreeLabel.tsx b/client/src/pages/Repository/components/RepositoryTreeView/TreeLabel.tsx index 7ad9adefa..16ff387e3 100644 --- a/client/src/pages/Repository/components/RepositoryTreeView/TreeLabel.tsx +++ b/client/src/pages/Repository/components/RepositoryTreeView/TreeLabel.tsx @@ -1,3 +1,8 @@ +/** + * TreeLabel + * + * This component renders a tree label for StyledTreeItem. + */ import { makeStyles } from '@material-ui/core/styles'; import clsx from 'clsx'; import lodash from 'lodash'; diff --git a/client/src/pages/Repository/components/RepositoryTreeView/index.tsx b/client/src/pages/Repository/components/RepositoryTreeView/index.tsx index d3dad4860..4d145e21f 100644 --- a/client/src/pages/Repository/components/RepositoryTreeView/index.tsx +++ b/client/src/pages/Repository/components/RepositoryTreeView/index.tsx @@ -1,3 +1,9 @@ +/** + * RepositoryTreeView + * + * This component renders repository tree view along with metadata view + * for the Repository UI. + */ import { makeStyles } from '@material-ui/core/styles'; import ChevronRightIcon from '@material-ui/icons/ChevronRight'; import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; diff --git a/client/src/pages/Repository/hooks/useDebounce.ts b/client/src/pages/Repository/hooks/useDebounce.ts index cb008e16d..60f4e52d4 100644 --- a/client/src/pages/Repository/hooks/useDebounce.ts +++ b/client/src/pages/Repository/hooks/useDebounce.ts @@ -1,4 +1,9 @@ /* eslint-disable react-hooks/exhaustive-deps */ +/** + * Debounce hook + * + * This custom hook provides de-bouncing functionality. + */ import { useState, useEffect } from 'react'; export default function useDebounce(value: T, delay: number): T { diff --git a/client/src/pages/Repository/hooks/useRepository.ts b/client/src/pages/Repository/hooks/useRepository.ts index 852bece5a..d11363b3f 100644 --- a/client/src/pages/Repository/hooks/useRepository.ts +++ b/client/src/pages/Repository/hooks/useRepository.ts @@ -1,3 +1,8 @@ +/** + * Repository hook + * + * This custom hook provides reusable functions for getting repository tree data. + */ import { ApolloQueryResult } from '@apollo/client'; import { apolloClient } from '../../../graphql'; import { GetObjectChildrenDocument, GetObjectChildrenQuery } from '../../../types/graphql'; diff --git a/client/src/pages/Repository/index.tsx b/client/src/pages/Repository/index.tsx index ca731d522..d4f3a93d5 100644 --- a/client/src/pages/Repository/index.tsx +++ b/client/src/pages/Repository/index.tsx @@ -1,3 +1,9 @@ +/** + * Repository + * + * This component renders Repository UI and all the sub-components like Filter and + * TreeView. + */ import { Box } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; import React, { useEffect, useState } from 'react'; diff --git a/client/src/pages/index.ts b/client/src/pages/index.ts index 50ea9faef..5c72e195a 100644 --- a/client/src/pages/index.ts +++ b/client/src/pages/index.ts @@ -1,6 +1,6 @@ /** * Pages - * User facing pages are organized here + * User facing pages are organized here. */ import Home from './Home'; import Login from './Login'; From 7b75aa9ed4bf72e499cbed9bbd0a74bf1cfec632 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Mon, 26 Oct 2020 14:51:03 +0530 Subject: [PATCH 038/239] document client stores --- client/config-overrides.js | 6 ++++++ client/src/store/control.ts | 5 +++++ client/src/store/index.ts | 5 +++++ client/src/store/item.ts | 5 +++++ client/src/store/metadata.ts | 5 +++++ client/src/store/project.ts | 5 +++++ client/src/store/repository.ts | 5 +++++ client/src/store/subject.ts | 5 +++++ client/src/store/upload.ts | 5 +++++ client/src/store/user.ts | 5 +++++ client/src/store/utils.ts | 5 +++++ client/src/store/vocabulary.ts | 5 +++++ 12 files changed, 61 insertions(+) diff --git a/client/config-overrides.js b/client/config-overrides.js index 0b5224a62..579fa29c6 100644 --- a/client/config-overrides.js +++ b/client/config-overrides.js @@ -1,3 +1,9 @@ +/** + * Config Overrides + * + * This config is used for overriding default webpack config of CRA without + * ejecting. + */ const { override, addExternalBabelPlugin } = require('customize-cra'); const { addReactRefresh } = require('customize-cra-react-refresh'); diff --git a/client/src/store/control.ts b/client/src/store/control.ts index ae78d0b45..1e141a6b3 100644 --- a/client/src/store/control.ts +++ b/client/src/store/control.ts @@ -1,3 +1,8 @@ +/** + * Control Store + * + * This store manages state for root level sidebar. + */ import create, { SetState } from 'zustand'; type ControlStore = { diff --git a/client/src/store/index.ts b/client/src/store/index.ts index a98e7d363..37e99e8b7 100644 --- a/client/src/store/index.ts +++ b/client/src/store/index.ts @@ -1,3 +1,8 @@ +/** + * Store + * + * This file exports all the client side stores. + */ export * from './user'; export * from './vocabulary'; export * from './upload'; diff --git a/client/src/store/item.ts b/client/src/store/item.ts index 27bd0adb7..5ab383eb0 100644 --- a/client/src/store/item.ts +++ b/client/src/store/item.ts @@ -1,3 +1,8 @@ +/** + * Item Store + * + * This store manages state for items used in Ingestion flow. + */ import create, { SetState, GetState } from 'zustand'; import lodash from 'lodash'; diff --git a/client/src/store/metadata.ts b/client/src/store/metadata.ts index 3a53c2a78..19ba37a8d 100644 --- a/client/src/store/metadata.ts +++ b/client/src/store/metadata.ts @@ -1,3 +1,8 @@ +/** + * Metadata Store + * + * This store manages state for metadata used in Ingestion flow. + */ import { ApolloQueryResult } from '@apollo/client'; import lodash from 'lodash'; import { toast } from 'react-toastify'; diff --git a/client/src/store/project.ts b/client/src/store/project.ts index 6ab64fb6a..496e29a8e 100644 --- a/client/src/store/project.ts +++ b/client/src/store/project.ts @@ -1,3 +1,8 @@ +/** + * Project Store + * + * This store manages state for project used in Ingestion flow. + */ import create, { SetState, GetState } from 'zustand'; import lodash from 'lodash'; diff --git a/client/src/store/repository.ts b/client/src/store/repository.ts index 597ec206d..69ed6f1c8 100644 --- a/client/src/store/repository.ts +++ b/client/src/store/repository.ts @@ -1,3 +1,8 @@ +/** + * Repository Store + * + * This store manages state for Repository filter and tree view. + */ import create, { GetState, SetState } from 'zustand'; import { getObjectChildren, getObjectChildrenForRoot } from '../pages/Repository/hooks/useRepository'; import { NavigationResultEntry } from '../types/graphql'; diff --git a/client/src/store/subject.ts b/client/src/store/subject.ts index 61fcaf7a8..b87cd4843 100644 --- a/client/src/store/subject.ts +++ b/client/src/store/subject.ts @@ -1,3 +1,8 @@ +/** + * Subject Store + * + * This store manages state for subject used in Ingestion flow. + */ import create, { SetState, GetState } from 'zustand'; import lodash from 'lodash'; import { ApolloQueryResult } from '@apollo/client'; diff --git a/client/src/store/upload.ts b/client/src/store/upload.ts index b20768e0b..5cf059522 100644 --- a/client/src/store/upload.ts +++ b/client/src/store/upload.ts @@ -1,3 +1,8 @@ +/** + * Upload Store + * + * This store manages state for file uploads used in Ingestion flow. + */ import create, { SetState, GetState } from 'zustand'; import lodash from 'lodash'; import { toast } from 'react-toastify'; diff --git a/client/src/store/user.ts b/client/src/store/user.ts index ea9e3179d..65717f1fa 100644 --- a/client/src/store/user.ts +++ b/client/src/store/user.ts @@ -1,3 +1,8 @@ +/** + * User Store + * + * This store manages state for user. + */ import create, { SetState, GetState } from 'zustand'; import { User, GetCurrentUserDocument } from '../types/graphql'; import { apolloClient } from '../graphql'; diff --git a/client/src/store/utils.ts b/client/src/store/utils.ts index 8739ad86e..ad79db26b 100644 --- a/client/src/store/utils.ts +++ b/client/src/store/utils.ts @@ -1,3 +1,8 @@ +/** + * Utils + * + * These are store specific utilities. + */ import { Item, Project, SubjectUnitIdentifier, AssetVersion, Vocabulary } from '../types/graphql'; import { StateSubject } from './subject'; import { StateItem } from './item'; diff --git a/client/src/store/vocabulary.ts b/client/src/store/vocabulary.ts index 0f1d33add..c90a2cf2f 100644 --- a/client/src/store/vocabulary.ts +++ b/client/src/store/vocabulary.ts @@ -1,3 +1,8 @@ +/** + * Vocabulary Store + * + * This store manages state for vocabularies used in Ingestion flow. + */ import create, { SetState, GetState } from 'zustand'; import { apolloClient } from '../graphql'; import { GetVocabularyEntriesDocument, Vocabulary } from '../types/graphql'; From 6d47873258acce9f364101bcc0e6c9bec185a415 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Mon, 26 Oct 2020 14:53:38 +0530 Subject: [PATCH 039/239] remove unused components --- client/src/index.tsx | 3 +-- client/src/pages/About/index.tsx | 24 ------------------------ client/src/pages/index.ts | 3 +-- 3 files changed, 2 insertions(+), 28 deletions(-) delete mode 100644 client/src/pages/About/index.tsx diff --git a/client/src/index.tsx b/client/src/index.tsx index 67ca940be..c56bde406 100644 --- a/client/src/index.tsx +++ b/client/src/index.tsx @@ -16,7 +16,7 @@ import { ErrorBoundary, Loader, PrivateRoute, PublicRoute } from './components'; import { ROUTES } from './constants'; import './global/root.css'; import { apolloClient } from './graphql'; -import { About, Home, Login } from './pages'; +import { Home, Login } from './pages'; import * as serviceWorker from './serviceWorker'; import { useUserStore } from './store'; import theme from './theme'; @@ -41,7 +41,6 @@ function AppRouter(): React.ReactElement { - diff --git a/client/src/pages/About/index.tsx b/client/src/pages/About/index.tsx deleted file mode 100644 index 46ea73d6d..000000000 --- a/client/src/pages/About/index.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import React from 'react'; -import { Box, Typography } from '@material-ui/core'; -import { makeStyles } from '@material-ui/core/styles'; - -const useStyles = makeStyles(() => ({ - container: { - display: 'flex', - flex: 1, - alignItems: 'center', - justifyContent: 'center', - }, -})); - -function About(): React.ReactElement { - const classes = useStyles(); - - return ( - - About - - ); -} - -export default About; \ No newline at end of file diff --git a/client/src/pages/index.ts b/client/src/pages/index.ts index 5c72e195a..67d6dfdef 100644 --- a/client/src/pages/index.ts +++ b/client/src/pages/index.ts @@ -4,6 +4,5 @@ */ import Home from './Home'; import Login from './Login'; -import About from './About'; -export { Home, Login, About }; +export { Home, Login }; From 58a7e02e3a18fc709220599d1012892dc727ca67 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Mon, 26 Oct 2020 15:16:22 +0530 Subject: [PATCH 040/239] reactor use ingest hook --- client/src/pages/Ingestion/hooks/useIngest.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/client/src/pages/Ingestion/hooks/useIngest.ts b/client/src/pages/Ingestion/hooks/useIngest.ts index e2e869ab1..1bd152638 100644 --- a/client/src/pages/Ingestion/hooks/useIngest.ts +++ b/client/src/pages/Ingestion/hooks/useIngest.ts @@ -31,12 +31,12 @@ interface UseIngest { } function useIngest(): UseIngest { - const [{ removeSelectedUploads }, resetUploads] = useUploadStore(state => [state, state.reset]); - const [{ subjects }, resetSubjects] = useSubjectStore(state => [state, state.reset]); - const [{ getSelectedProject }, resetProjects] = useProjectStore(state => [state, state.reset]); - const [{ getSelectedItem }, resetItems] = useItemStore(state => [state, state.reset]); - const [{ metadatas, getSelectedIdentifiers }, resetMetadatas] = useMetadataStore(state => [state, state.reset]); - const { getAssetType } = useVocabularyStore(); + const [removeSelectedUploads, resetUploads] = useUploadStore(state => [state.removeSelectedUploads, state.reset]); + const [subjects, resetSubjects] = useSubjectStore(state => [state.subjects, state.reset]); + const [getSelectedProject, resetProjects] = useProjectStore(state => [state.getSelectedProject, state.reset]); + const [getSelectedItem, resetItems] = useItemStore(state => [state.getSelectedItem, state.reset]); + const [metadatas, getSelectedIdentifiers, resetMetadatas] = useMetadataStore(state => [state.metadatas, state.getSelectedIdentifiers, state.reset]); + const getAssetType = useVocabularyStore(state => state.getAssetType); const history = useHistory(); From c0fdc613d8d217e5ed265560eefd8a7ae432e379 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Tue, 27 Oct 2020 13:43:37 +0530 Subject: [PATCH 041/239] - Add metadata containers for model, scenes and other --- .../components/Metadata/Model/index.tsx | 25 +++++++++++++++++++ .../components/Metadata/Other/index.tsx | 25 +++++++++++++++++++ .../components/Metadata/Scene/index.tsx | 25 +++++++++++++++++++ .../Ingestion/components/Metadata/index.tsx | 17 +++++++++---- client/src/store/vocabulary.ts | 6 +++++ 5 files changed, 93 insertions(+), 5 deletions(-) create mode 100644 client/src/pages/Ingestion/components/Metadata/Model/index.tsx create mode 100644 client/src/pages/Ingestion/components/Metadata/Other/index.tsx create mode 100644 client/src/pages/Ingestion/components/Metadata/Scene/index.tsx diff --git a/client/src/pages/Ingestion/components/Metadata/Model/index.tsx b/client/src/pages/Ingestion/components/Metadata/Model/index.tsx new file mode 100644 index 000000000..d6155ef5d --- /dev/null +++ b/client/src/pages/Ingestion/components/Metadata/Model/index.tsx @@ -0,0 +1,25 @@ +/** + * Metadata - Model + * + * This component renders the metadata fields specific to model asset. + */ +import { Box, Typography } from '@material-ui/core'; +import React from 'react'; +import { useMetadataStore } from '../../../../../store'; + +interface ModelProps { + metadataIndex: number; +} + +function Model(props: ModelProps): React.ReactElement { + const { metadataIndex } = props; + const metadata = useMetadataStore(state => state.metadatas[metadataIndex]); + + return ( + + Metadata For Model {metadata.file.name} + + ); +} + +export default Model; \ No newline at end of file diff --git a/client/src/pages/Ingestion/components/Metadata/Other/index.tsx b/client/src/pages/Ingestion/components/Metadata/Other/index.tsx new file mode 100644 index 000000000..050b98ba5 --- /dev/null +++ b/client/src/pages/Ingestion/components/Metadata/Other/index.tsx @@ -0,0 +1,25 @@ +/** + * Metadata - Other + * + * This component renders the metadata fields for other asset types. + */ +import { Box, Typography } from '@material-ui/core'; +import React from 'react'; +import { useMetadataStore } from '../../../../../store'; + +interface OtherProps { + metadataIndex: number; +} + +function Other(props: OtherProps): React.ReactElement { + const { metadataIndex } = props; + const metadata = useMetadataStore(state => state.metadatas[metadataIndex]); + + return ( + + Metadata Not yet implemented for {metadata.file.name} + + ); +} + +export default Other; \ No newline at end of file diff --git a/client/src/pages/Ingestion/components/Metadata/Scene/index.tsx b/client/src/pages/Ingestion/components/Metadata/Scene/index.tsx new file mode 100644 index 000000000..0aaf4d25c --- /dev/null +++ b/client/src/pages/Ingestion/components/Metadata/Scene/index.tsx @@ -0,0 +1,25 @@ +/** + * Metadata - Scene + * + * This component renders the metadata fields specific to scene asset. + */ +import { Box, Typography } from '@material-ui/core'; +import React from 'react'; +import { useMetadataStore } from '../../../../../store'; + +interface SceneProps { + metadataIndex: number; +} + +function Scene(props: SceneProps): React.ReactElement { + const { metadataIndex } = props; + const metadata = useMetadataStore(state => state.metadatas[metadataIndex]); + + return ( + + Metadata For Scene {metadata.file.name} + + ); +} + +export default Scene; \ No newline at end of file diff --git a/client/src/pages/Ingestion/components/Metadata/index.tsx b/client/src/pages/Ingestion/components/Metadata/index.tsx index 75debabfe..9a51e81a5 100644 --- a/client/src/pages/Ingestion/components/Metadata/index.tsx +++ b/client/src/pages/Ingestion/components/Metadata/index.tsx @@ -14,7 +14,10 @@ import { SidebarBottomNavigator } from '../../../../components'; import { HOME_ROUTES, INGESTION_ROUTE, resolveSubRoute } from '../../../../constants'; import { FileId, StateItem, StateMetadata, StateProject, useItemStore, useMetadataStore, useProjectStore, useVocabularyStore } from '../../../../store'; import useIngest from '../../hooks/useIngest'; +import Other from './Other'; +import Model from './Model'; import Photogrammetry from './Photogrammetry'; +import Scene from './Scene'; const useStyles = makeStyles(({ palette }) => ({ container: { @@ -140,11 +143,15 @@ function Metadata(): React.ReactElement { return ; } - return ( - - Metadata type not yet implemented - - ); + if (assetType.scene) { + return ; + } + + if (assetType.model) { + return ; + } + + return ; }; return ( diff --git a/client/src/store/vocabulary.ts b/client/src/store/vocabulary.ts index c90a2cf2f..de6bee438 100644 --- a/client/src/store/vocabulary.ts +++ b/client/src/store/vocabulary.ts @@ -14,6 +14,8 @@ export type StateVocabulary = Map; type AssetType = { photogrammetry: boolean; + scene: boolean; + model: boolean; bagit: boolean; }; @@ -89,6 +91,8 @@ export const useVocabularyStore = create((set: SetState((set: SetState Date: Tue, 27 Oct 2020 19:42:35 +0530 Subject: [PATCH 042/239] Replace manual validation with yup package --- .../Ingestion/components/Metadata/index.tsx | 44 +--------- client/src/store/metadata.ts | 83 +++++++++++++++++-- 2 files changed, 82 insertions(+), 45 deletions(-) diff --git a/client/src/pages/Ingestion/components/Metadata/index.tsx b/client/src/pages/Ingestion/components/Metadata/index.tsx index 9a51e81a5..90577fb2e 100644 --- a/client/src/pages/Ingestion/components/Metadata/index.tsx +++ b/client/src/pages/Ingestion/components/Metadata/index.tsx @@ -14,8 +14,8 @@ import { SidebarBottomNavigator } from '../../../../components'; import { HOME_ROUTES, INGESTION_ROUTE, resolveSubRoute } from '../../../../constants'; import { FileId, StateItem, StateMetadata, StateProject, useItemStore, useMetadataStore, useProjectStore, useVocabularyStore } from '../../../../store'; import useIngest from '../../hooks/useIngest'; -import Other from './Other'; import Model from './Model'; +import Other from './Other'; import Photogrammetry from './Photogrammetry'; import Scene from './Scene'; @@ -55,7 +55,7 @@ function Metadata(): React.ReactElement { const getSelectedProject = useProjectStore(state => state.getSelectedProject); const getSelectedItem = useItemStore(state => state.getSelectedItem); - const [metadatas, getFieldErrors, getMetadataInfo] = useMetadataStore(state => [state.metadatas, state.getFieldErrors, state.getMetadataInfo]); + const [metadatas, validatePhotogrammetryFields, getMetadataInfo] = useMetadataStore(state => [state.metadatas, state.validatePhotogrammetryFields, state.getMetadataInfo]); const { ingestPhotogrammetryData, ingestionComplete } = useIngest(); const getAssetType = useVocabularyStore(state => state.getAssetType); @@ -76,45 +76,9 @@ function Metadata(): React.ReactElement { history.goBack(); }; - const onNext = async () => { + const onNext = async (): Promise => { if (assetType.photogrammetry) { - const { photogrammetry } = getFieldErrors(metadata); - const { photogrammetry: { datasetType, description, systemCreated, identifiers } } = metadata; - let hasError: boolean = false; - - if (!datasetType) { - toast.warn('Please select a valid dataset type', { autoClose: false }); - } - - if (!systemCreated) { - hasError = true; - } - - identifiers.forEach(({ identifier, selected }) => { - if (selected) { - hasError = false; - if (identifier.trim() === '') { - toast.warn('Please provide a valid identifier', { autoClose: false }); - hasError = true; - } - } - }); - - if (hasError && !systemCreated) { - toast.warn('Should select/provide at least 1 identifier', { autoClose: false }); - } - - if (description.trim() === '') { - toast.warn('Description cannot be empty', { autoClose: false }); - hasError = true; - } - - for (const fieldValue of Object.values(photogrammetry)) { - if (fieldValue) { - hasError = true; - } - } - + const hasError: boolean = validatePhotogrammetryFields(metadata.photogrammetry); if (hasError) return; } diff --git a/client/src/store/metadata.ts b/client/src/store/metadata.ts index 19ba37a8d..58b171c2a 100644 --- a/client/src/store/metadata.ts +++ b/client/src/store/metadata.ts @@ -6,7 +6,8 @@ import { ApolloQueryResult } from '@apollo/client'; import lodash from 'lodash'; import { toast } from 'react-toastify'; -import create, { SetState, GetState } from 'zustand'; +import * as yup from 'yup'; +import create, { GetState, SetState } from 'zustand'; import { apolloClient } from '../graphql'; import { AreCameraSettingsUniformDocument, @@ -18,10 +19,10 @@ import { Project } from '../types/graphql'; import { eVocabularySetID } from '../types/server'; -import { useItemStore, StateItem } from './item'; -import { useProjectStore, StateProject } from './project'; -import { useSubjectStore, StateSubject } from './subject'; -import { useUploadStore, FileId, IngestionFile } from './upload'; +import { StateItem, useItemStore } from './item'; +import { StateProject, useProjectStore } from './project'; +import { StateSubject, useSubjectStore } from './subject'; +import { FileId, IngestionFile, useUploadStore } from './upload'; import { parseFileId, parseItemToState, parseProjectToState, parseSubjectUnitIdentifierToState } from './utils'; import { useVocabularyStore } from './vocabulary'; @@ -98,6 +99,57 @@ export const defaultPhotogrammetryFields: PhotogrammetryFields = { directory: '' }; +const identifierSchema = yup.object().shape({ + id: yup.number().required(), + identifier: yup + .string() + .trim() + .when('selected', { + is: true, + then: yup.string().trim().required('Enter a valid identifier'), + otherwise: yup.string().trim() + }), + identifierType: yup.number().nullable(true), + selected: yup.boolean().required() +}); + +const folderSchema = yup.object().shape({ + id: yup.number().required(), + name: yup.string().required(), + variantType: yup.number().nullable(true) +}); + +const identifierValidation = { + test: array => !!lodash.filter(array as StateIdentifier[], { selected: true }).length, + message: 'Should select/provide at least 1 identifier' +}; + +const photogrammetryFieldsSchema = yup.object().shape({ + systemCreated: yup.boolean().required(), + identifiers: yup + .array() + .of(identifierSchema) + .when('systemCreated', { + is: false, + then: yup.array().of(identifierSchema).test(identifierValidation) + }), + folders: yup.array().of(folderSchema), + description: yup.string().required('Description cannot be empty'), + dateCaptured: yup.date().required(), + datasetType: yup.number().required('Please select a valid dataset type'), + datasetFieldId: yup.number().nullable(true), + itemPositionType: yup.number().nullable(true), + itemPositionFieldId: yup.number().nullable(true), + itemArrangementFieldId: yup.number().nullable(true), + focusType: yup.number().nullable(true), + lightsourceType: yup.number().nullable(true), + backgroundRemovalMethod: yup.number().nullable(true), + clusterType: yup.number().nullable(true), + clusterGeometryFieldId: yup.number().nullable(true), + cameraSettingUniform: yup.boolean().required(), + directory: yup.string().required() +}); + export type StateMetadata = { photogrammetry: PhotogrammetryFields; file: IngestionFile; @@ -109,6 +161,7 @@ type MetadataStore = { getInitialStateFolders: (folders: string[]) => StateFolder[]; getSelectedIdentifiers: (metadata: StateMetadata) => StateIdentifier[] | undefined; getFieldErrors: (metadata: StateMetadata) => FieldErrors; + validatePhotogrammetryFields: (fields: PhotogrammetryFields) => boolean; getCurrentMetadata: (id: FileId) => StateMetadata | undefined; getMetadataInfo: (id: FileId) => MetadataInfo; updateMetadataSteps: () => Promise; @@ -142,6 +195,26 @@ export const useMetadataStore = create((set: SetState { + let hasError: boolean = false; + const options: yup.ValidateOptions = { + abortEarly: false + }; + + toast.dismiss(); + + try { + photogrammetryFieldsSchema.validateSync(fields, options); + } catch (error) { + hasError = true; + console.log(error); + for (const message of error.errors) { + toast.warn(message, { autoClose: false }); + } + } + + return hasError; + }, getCurrentMetadata: (id: FileId): StateMetadata | undefined => { const { metadatas } = get(); return metadatas.find(({ file }) => file.id === id); From bf549de483cbed265ea17dc40f7ad16905bc9d9d Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Thu, 29 Oct 2020 17:44:21 +0530 Subject: [PATCH 043/239] update default react icons --- client/public/favicon.ico | Bin 3150 -> 2462 bytes client/public/logo192.png | Bin 5347 -> 5059 bytes client/public/logo512.png | Bin 9664 -> 11836 bytes 3 files changed, 0 insertions(+), 0 deletions(-) diff --git a/client/public/favicon.ico b/client/public/favicon.ico index bcd5dfd67cd0361b78123e95c2dd96031f27f743..3b69d8ecf866dc84d06a56c2cf3b025162c477fb 100644 GIT binary patch literal 2462 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 MDeX2GIKR{Qf5okjU;qFB diff --git a/client/public/logo192.png b/client/public/logo192.png index fc44b0a3796c0e0a64c3d858ca038bd4570465d9..f4dd28018c70ad8b89235bb31fea0dee5745797e 100644 GIT binary patch literal 5059 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 R?fiR~rYx_9u97tm`44L(IKKb@ literal 5347 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 literal 9664 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 b{|{}U^kRn2wo}j7|0ATu<;8xA7zX}7|B6mN From e0a34bb2e0005589edee1d19d289690e21687e2a Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Thu, 29 Oct 2020 18:59:32 +0530 Subject: [PATCH 044/239] fix github workflow --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fc96adce7..2d3fc9484 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -61,11 +61,11 @@ jobs: - name: Setup node version uses: actions/setup-node@v1 with: - node-version: 12 + node-version: 12.18.4 # Install dependencies in CI mode - name: Install dependencies - run: yarn install --frozen-lockfile + run: yarn install --frozen-lockfile && cd e2e && yarn install --frozen-lockfile && cd .. # Run tests using test DB - name: Run Tests From 45ba6bcef992148615a3c5aa7ad6c40c9d8e5d61 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Thu, 29 Oct 2020 19:05:15 +0530 Subject: [PATCH 045/239] e2e deps in ci fix --- .github/workflows/ci.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2d3fc9484..31d63defd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -67,6 +67,10 @@ jobs: - name: Install dependencies run: yarn install --frozen-lockfile && cd e2e && yarn install --frozen-lockfile && cd .. + # Install E2E dependencies in CI mode + - name: Install dependencies + run: cd e2e && yarn install --frozen-lockfile && cd .. + # Run tests using test DB - name: Run Tests run: yarn test From 15d0d8e4888e590e41e219cf43dcafcb084727a9 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Thu, 29 Oct 2020 19:08:20 +0530 Subject: [PATCH 046/239] retry fix for e2e deps --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 31d63defd..e3ed74d37 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -65,10 +65,10 @@ jobs: # Install dependencies in CI mode - name: Install dependencies - run: yarn install --frozen-lockfile && cd e2e && yarn install --frozen-lockfile && cd .. + run: yarn install --frozen-lockfile # Install E2E dependencies in CI mode - - name: Install dependencies + - name: Install E2E dependencies run: cd e2e && yarn install --frozen-lockfile && cd .. # Run tests using test DB From 703b2618e1064a0966fbf48ce92b60914376c2fb Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Thu, 29 Oct 2020 19:11:19 +0530 Subject: [PATCH 047/239] fix lint step --- .github/workflows/ci.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e3ed74d37..04337f0b8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,11 +25,14 @@ jobs: - name: Setup node version uses: actions/setup-node@v1 with: - node-version: 12 + node-version: 12.18.4 - name: Install dependencies run: yarn install --frozen-lockfile + - name: Install E2E dependencies + run: cd e2e && yarn install --frozen-lockfile && cd .. + - name: Check for lint errors run: yarn lint From f6bab2eb26e9dc14ef9190e781e922b619dab492 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Thu, 29 Oct 2020 22:19:21 +0530 Subject: [PATCH 048/239] refactor identifier list to be reusable --- client/src/components/index.ts | 3 +- .../components/shared/AssetIdentifiers.tsx | 91 +++++++++++++++++++ 2 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 client/src/components/shared/AssetIdentifiers.tsx diff --git a/client/src/components/index.ts b/client/src/components/index.ts index 83a025e7c..c6ba07cef 100644 --- a/client/src/components/index.ts +++ b/client/src/components/index.ts @@ -12,5 +12,6 @@ import SidebarBottomNavigator from './shared/SidebarBottomNavigator'; import LoadingButton from './controls/LoadingButton'; import RepositoryIcon from './controls/RepositoryIcon'; import ErrorBoundary from './shared/ErrorBoundary'; +import AssetIdentifiers from './shared/AssetIdentifiers'; -export { Header, PrivateRoute, PublicRoute, FieldType, LoadingButton, Loader, Progress, SidebarBottomNavigator, RepositoryIcon, ErrorBoundary }; +export { Header, PrivateRoute, PublicRoute, FieldType, LoadingButton, Loader, Progress, SidebarBottomNavigator, RepositoryIcon, ErrorBoundary, AssetIdentifiers }; diff --git a/client/src/components/shared/AssetIdentifiers.tsx b/client/src/components/shared/AssetIdentifiers.tsx new file mode 100644 index 000000000..22bdeb683 --- /dev/null +++ b/client/src/components/shared/AssetIdentifiers.tsx @@ -0,0 +1,91 @@ +import { Box, Checkbox, Typography } from '@material-ui/core'; +import { makeStyles } from '@material-ui/core/styles'; +import lodash from 'lodash'; +import React from 'react'; +import { StateIdentifier, useVocabularyStore } from '../../store'; +import { eVocabularySetID } from '../../types/server'; +import FieldType from './FieldType'; +import IdentifierList from './IdentifierList'; + +const useStyles = makeStyles(({ palette, spacing }) => ({ + assetIdentifier: { + display: 'flex', + alignItems: 'center', + marginBottom: 10, + }, + systemCreatedText: { + marginLeft: spacing(2), + fontStyle: 'italic', + color: palette.primary.contrastText + } +})); + +interface AssetIdentifiersProps { + systemCreated: boolean; + identifiers: StateIdentifier[]; + onSystemCreatedChange: (event: React.ChangeEvent) => void; + onAddIdentifer: (identifiers: StateIdentifier[]) => void; + onUpdateIdentifer: (identifiers: StateIdentifier[]) => void; + onRemoveIdentifer: (identifiers: StateIdentifier[]) => void; +} + +function AssetIdentifiers(props: AssetIdentifiersProps): React.ReactElement { + const { systemCreated, identifiers, onSystemCreatedChange, onAddIdentifer, onUpdateIdentifer, onRemoveIdentifer } = props; + const classes = useStyles(); + const [getEntries, getInitialEntry] = useVocabularyStore(state => [state.getEntries, state.getInitialEntry]); + + const addIdentifer = (initialEntry: number | null) => { + const newIdentifier: StateIdentifier = { + id: identifiers.length + 1, + identifier: '', + identifierType: getInitialEntry(eVocabularySetID.eIdentifierIdentifierType) || initialEntry, + selected: false + }; + + const updatedIdentifiers = lodash.concat(identifiers, [newIdentifier]); + onAddIdentifer(updatedIdentifiers); + }; + + const removeIdentifier = (id: number) => { + const updatedIdentifiers = lodash.filter(identifiers, identifier => identifier.id !== id); + onUpdateIdentifer(updatedIdentifiers); + }; + + const updateIdentifierFields = (id: number, name: string, value: string | number | boolean) => { + const updatedIdentifiers = identifiers.map(identifier => { + if (identifier.id === id) { + return { + ...identifier, + [name]: value + }; + } + return identifier; + }); + onRemoveIdentifer(updatedIdentifiers); + }; + + return ( + + + + + System will create an identifier + + + + + ); +} + +export default AssetIdentifiers; \ No newline at end of file From 3e7d11613bc00bdcd4e37b716d1713775314a962 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Thu, 29 Oct 2020 22:32:45 +0530 Subject: [PATCH 049/239] Setup initial model fields, validation for metadata --- .../shared}/IdentifierList.tsx | 4 +- .../components/Metadata/Model/index.tsx | 35 +++++- .../Metadata/Photogrammetry/index.tsx | 113 +++++------------- .../Ingestion/components/Metadata/index.tsx | 22 +++- client/src/store/metadata.ts | 110 ++++++++++++----- 5 files changed, 165 insertions(+), 119 deletions(-) rename client/src/{pages/Ingestion/components/Metadata/Photogrammetry => components/shared}/IdentifierList.tsx (97%) diff --git a/client/src/pages/Ingestion/components/Metadata/Photogrammetry/IdentifierList.tsx b/client/src/components/shared/IdentifierList.tsx similarity index 97% rename from client/src/pages/Ingestion/components/Metadata/Photogrammetry/IdentifierList.tsx rename to client/src/components/shared/IdentifierList.tsx index 4be218871..933f0db1f 100644 --- a/client/src/pages/Ingestion/components/Metadata/Photogrammetry/IdentifierList.tsx +++ b/client/src/components/shared/IdentifierList.tsx @@ -8,8 +8,8 @@ import { fade, makeStyles } from '@material-ui/core/styles'; import React from 'react'; import { DebounceInput } from 'react-debounce-input'; import { MdRemoveCircleOutline } from 'react-icons/md'; -import { FieldType } from '../../../../../components'; -import { StateIdentifier, VocabularyOption } from '../../../../../store'; +import FieldType from './FieldType'; +import { StateIdentifier, VocabularyOption } from '../../store'; const useStyles = makeStyles(({ palette, typography, spacing, breakpoints }) => ({ container: { diff --git a/client/src/pages/Ingestion/components/Metadata/Model/index.tsx b/client/src/pages/Ingestion/components/Metadata/Model/index.tsx index d6155ef5d..417bd5ac8 100644 --- a/client/src/pages/Ingestion/components/Metadata/Model/index.tsx +++ b/client/src/pages/Ingestion/components/Metadata/Model/index.tsx @@ -3,9 +3,17 @@ * * This component renders the metadata fields specific to model asset. */ -import { Box, Typography } from '@material-ui/core'; +import { Box } from '@material-ui/core'; +import { makeStyles } from '@material-ui/core/styles'; import React from 'react'; -import { useMetadataStore } from '../../../../../store'; +import { AssetIdentifiers } from '../../../../../components'; +import { StateIdentifier, useMetadataStore } from '../../../../../store'; + +const useStyles = makeStyles(() => ({ + container: { + marginTop: 20 + } +})); interface ModelProps { metadataIndex: number; @@ -13,11 +21,30 @@ interface ModelProps { function Model(props: ModelProps): React.ReactElement { const { metadataIndex } = props; + const classes = useStyles(); const metadata = useMetadataStore(state => state.metadatas[metadataIndex]); + const { model } = metadata; + const updateModelField = useMetadataStore(state => state.updateModelField); + + const setCheckboxField = ({ target }): void => { + const { name, checked } = target; + updateModelField(metadataIndex, name, checked); + }; + + const onIdentifersChange = (identifiers: StateIdentifier[]): void => { + updateModelField(metadataIndex, 'identifiers', identifiers); + }; return ( - - Metadata For Model {metadata.file.name} + + ); } diff --git a/client/src/pages/Ingestion/components/Metadata/Photogrammetry/index.tsx b/client/src/pages/Ingestion/components/Metadata/Photogrammetry/index.tsx index 5aa0e4e43..348b3ba52 100644 --- a/client/src/pages/Ingestion/components/Metadata/Photogrammetry/index.tsx +++ b/client/src/pages/Ingestion/components/Metadata/Photogrammetry/index.tsx @@ -5,35 +5,23 @@ * This component renders the metadata fields specific to photogrammetry asset. */ import DateFnsUtils from '@date-io/date-fns'; -import { Box, Checkbox, Typography } from '@material-ui/core'; +import { Box, Checkbox } from '@material-ui/core'; import { fade, makeStyles, withStyles } from '@material-ui/core/styles'; import { KeyboardDatePicker, MuiPickersUtilsProvider } from '@material-ui/pickers'; -import lodash from 'lodash'; import React from 'react'; -import { FieldType } from '../../../../../components'; +import { AssetIdentifiers, FieldType } from '../../../../../components'; import { StateIdentifier, StateMetadata, useMetadataStore, useVocabularyStore } from '../../../../../store'; import { Colors } from '../../../../../theme'; import { eVocabularySetID } from '../../../../../types/server'; import AssetContents from './AssetContents'; import Description from './Description'; -import IdentifierList from './IdentifierList'; import IdInputField from './IdInputField'; import SelectField from './SelectField'; -const useStyles = makeStyles(({ palette, typography, spacing, breakpoints }) => ({ +const useStyles = makeStyles(({ palette, typography, breakpoints }) => ({ container: { marginTop: 20 }, - assetIdentifier: { - display: 'flex', - alignItems: 'center', - marginBottom: 10, - }, - systemCreatedText: { - marginLeft: spacing(2), - fontStyle: 'italic', - color: palette.primary.contrastText - }, fieldsContainer: { display: 'flex', marginTop: 10 @@ -61,22 +49,6 @@ const useStyles = makeStyles(({ palette, typography, spacing, breakpoints }) => } })); -const checkboxStyles = ({ palette }) => ({ - root: { - color: palette.primary.main, - '&$checked': { - color: palette.primary.main, - }, - '&$disabled': { - color: palette.primary.main, - } - }, - checked: {}, - disabled: {} -}); - -const CustomCheckbox = withStyles(checkboxStyles)(Checkbox); - interface PhotogrammetryProps { metadataIndex: number; } @@ -100,6 +72,7 @@ function Photogrammetry(props: PhotogrammetryProps): React.ReactElement { const setIdField = ({ target }): void => { const { name, value } = target; let idFieldValue: number | null = null; + if (value) { idFieldValue = Number.parseInt(value, 10); } @@ -116,41 +89,11 @@ function Photogrammetry(props: PhotogrammetryProps): React.ReactElement { const setCheckboxField = ({ target }): void => { const { name, checked } = target; - updatePhotogrammetryField(metadataIndex, name, checked); }; - const addIdentifer = (initialEntry: number | null) => { - const { identifiers } = photogrammetry; - const newIdentifier: StateIdentifier = { - id: identifiers.length + 1, - identifier: '', - identifierType: getInitialEntry(eVocabularySetID.eIdentifierIdentifierType) || initialEntry, - selected: false - }; - - const updatedIdentifiers = lodash.concat(identifiers, [newIdentifier]); - updatePhotogrammetryField(metadataIndex, 'identifiers', updatedIdentifiers); - }; - - const removeIdentifier = (id: number) => { - const { identifiers } = photogrammetry; - const updatedIdentifiers = lodash.filter(identifiers, identifier => identifier.id !== id); - updatePhotogrammetryField(metadataIndex, 'identifiers', updatedIdentifiers); - }; - - const updateIdentifierFields = (id: number, name: string, value: string | number | boolean) => { - const { identifiers } = photogrammetry; - const updatedIdentifiers = identifiers.map(identifier => { - if (identifier.id === id) { - return { - ...identifier, - [name]: value - }; - } - return identifier; - }); - updatePhotogrammetryField(metadataIndex, 'identifiers', updatedIdentifiers); + const onIdentifersChange = (identifiers: StateIdentifier[]): void => { + updatePhotogrammetryField(metadataIndex, 'identifiers', identifiers); }; const updateFolderVariant = (folderId: number, variantType: number) => { @@ -171,26 +114,14 @@ function Photogrammetry(props: PhotogrammetryProps): React.ReactElement { return ( - - - - - System will create an identifier - - - - + @@ -292,4 +223,20 @@ function Photogrammetry(props: PhotogrammetryProps): React.ReactElement { ); } +const checkboxStyles = ({ palette }) => ({ + root: { + color: palette.primary.main, + '&$checked': { + color: palette.primary.main, + }, + '&$disabled': { + color: palette.primary.main, + } + }, + checked: {}, + disabled: {} +}); + +const CustomCheckbox = withStyles(checkboxStyles)(Checkbox); + export default Photogrammetry; \ No newline at end of file diff --git a/client/src/pages/Ingestion/components/Metadata/index.tsx b/client/src/pages/Ingestion/components/Metadata/index.tsx index 90577fb2e..93cbd4a5c 100644 --- a/client/src/pages/Ingestion/components/Metadata/index.tsx +++ b/client/src/pages/Ingestion/components/Metadata/index.tsx @@ -12,7 +12,18 @@ import { Redirect, useHistory, useLocation } from 'react-router'; import { toast } from 'react-toastify'; import { SidebarBottomNavigator } from '../../../../components'; import { HOME_ROUTES, INGESTION_ROUTE, resolveSubRoute } from '../../../../constants'; -import { FileId, StateItem, StateMetadata, StateProject, useItemStore, useMetadataStore, useProjectStore, useVocabularyStore } from '../../../../store'; +import { + FileId, + modelFieldsSchema, + photogrammetryFieldsSchema, + StateItem, + StateMetadata, + StateProject, + useItemStore, + useMetadataStore, + useProjectStore, + useVocabularyStore +} from '../../../../store'; import useIngest from '../../hooks/useIngest'; import Model from './Model'; import Other from './Other'; @@ -55,7 +66,7 @@ function Metadata(): React.ReactElement { const getSelectedProject = useProjectStore(state => state.getSelectedProject); const getSelectedItem = useItemStore(state => state.getSelectedItem); - const [metadatas, validatePhotogrammetryFields, getMetadataInfo] = useMetadataStore(state => [state.metadatas, state.validatePhotogrammetryFields, state.getMetadataInfo]); + const [metadatas, getMetadataInfo, validateFields] = useMetadataStore(state => [state.metadatas, state.getMetadataInfo, state.validateFields]); const { ingestPhotogrammetryData, ingestionComplete } = useIngest(); const getAssetType = useVocabularyStore(state => state.getAssetType); @@ -78,7 +89,12 @@ function Metadata(): React.ReactElement { const onNext = async (): Promise => { if (assetType.photogrammetry) { - const hasError: boolean = validatePhotogrammetryFields(metadata.photogrammetry); + const hasError: boolean = validateFields(metadata.photogrammetry, photogrammetryFieldsSchema); + if (hasError) return; + } + + if (assetType.model) { + const hasError: boolean = validateFields(metadata.model, modelFieldsSchema); if (hasError) return; } diff --git a/client/src/store/metadata.ts b/client/src/store/metadata.ts index 58b171c2a..83b1a61c0 100644 --- a/client/src/store/metadata.ts +++ b/client/src/store/metadata.ts @@ -12,6 +12,7 @@ import { apolloClient } from '../graphql'; import { AreCameraSettingsUniformDocument, AssetVersionContent, + GetAssetVersionDetailResult, GetAssetVersionsDetailsDocument, GetAssetVersionsDetailsQuery, GetContentsForAssetVersionsDocument, @@ -124,15 +125,16 @@ const identifierValidation = { message: 'Should select/provide at least 1 identifier' }; -const photogrammetryFieldsSchema = yup.object().shape({ +const identifiersWhenValidation = { + is: false, + then: yup.array().of(identifierSchema).test(identifierValidation) +}; + +type PhotogrammetrySchemaType = typeof photogrammetryFieldsSchema; + +export const photogrammetryFieldsSchema = yup.object().shape({ systemCreated: yup.boolean().required(), - identifiers: yup - .array() - .of(identifierSchema) - .when('systemCreated', { - is: false, - then: yup.array().of(identifierSchema).test(identifierValidation) - }), + identifiers: yup.array().of(identifierSchema).when('systemCreated', identifiersWhenValidation), folders: yup.array().of(folderSchema), description: yup.string().required('Description cannot be empty'), dateCaptured: yup.date().required(), @@ -150,8 +152,26 @@ const photogrammetryFieldsSchema = yup.object().shape({ directory: yup.string().required() }); +export type ModelFields = { + systemCreated: boolean; + identifiers: StateIdentifier[]; +}; + +export const defaultModelFields: ModelFields = { + systemCreated: true, + identifiers: [] +}; + +type ModelSchemaType = typeof modelFieldsSchema; + +export const modelFieldsSchema = yup.object().shape({ + systemCreated: yup.boolean().required(), + identifiers: yup.array().of(identifierSchema).when('systemCreated', identifiersWhenValidation) +}); + export type StateMetadata = { photogrammetry: PhotogrammetryFields; + model: ModelFields; file: IngestionFile; }; @@ -161,11 +181,12 @@ type MetadataStore = { getInitialStateFolders: (folders: string[]) => StateFolder[]; getSelectedIdentifiers: (metadata: StateMetadata) => StateIdentifier[] | undefined; getFieldErrors: (metadata: StateMetadata) => FieldErrors; - validatePhotogrammetryFields: (fields: PhotogrammetryFields) => boolean; + validateFields: (fields: PhotogrammetryFields | ModelFields, schema: PhotogrammetrySchemaType | ModelSchemaType) => boolean; getCurrentMetadata: (id: FileId) => StateMetadata | undefined; getMetadataInfo: (id: FileId) => MetadataInfo; updateMetadataSteps: () => Promise; updatePhotogrammetryField: (metadataIndex: number, name: string, value: MetadataFieldValue) => void; + updateModelField: (metadataIndex: number, name: string, value: MetadataFieldValue) => void; updateMetadataFolders: () => Promise; updateCameraSettings: (metadatas: StateMetadata[]) => Promise; reset: () => void; @@ -195,7 +216,7 @@ export const useMetadataStore = create((set: SetState { + validateFields: (fields: PhotogrammetryFields | ModelFields, schema: PhotogrammetrySchemaType | ModelSchemaType): boolean => { let hasError: boolean = false; const options: yup.ValidateOptions = { abortEarly: false @@ -204,10 +225,9 @@ export const useMetadataStore = create((set: SetState((set: SetState((set: SetState((set: SetState parseFileId(file.id) === idAssetVersion); if (!file) { @@ -312,6 +344,12 @@ export const useMetadataStore = create((set: SetState((set: SetState((set: SetState((set: SetState { + const { metadatas } = get(); + const updatedMetadatas = lodash.map(metadatas, (metadata: StateMetadata, index: number) => { + if (index === metadataIndex) { + return { + ...metadata, + model: { + ...metadata.model, + [name]: value + } + }; + } + + return metadata; + }); + + set({ metadatas: updatedMetadatas }); + }, getStateFolders: (folders: IngestFolder[]): StateFolder[] => { const stateFolders: StateFolder[] = folders.map(({ name, variantType }, index: number) => ({ id: index, From a70eb2b5c311ec50831710d86af5ec3c18cdbe3b Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Fri, 30 Oct 2020 16:38:29 +0530 Subject: [PATCH 050/239] identifier list fixes --- client/src/components/shared/IdentifierList.tsx | 2 +- client/src/pages/Ingestion/components/SubjectItem/index.tsx | 2 +- client/src/store/metadata.ts | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/client/src/components/shared/IdentifierList.tsx b/client/src/components/shared/IdentifierList.tsx index 933f0db1f..cc6df098b 100644 --- a/client/src/components/shared/IdentifierList.tsx +++ b/client/src/components/shared/IdentifierList.tsx @@ -96,7 +96,7 @@ function IdentifierList(props: IdentifierListProps): React.ReactElement { display='flex' flexDirection='row' alignItems='center' - paddingY={'10px'} + paddingBottom={'10px'} > ((set: SetState Date: Fri, 30 Oct 2020 17:17:15 +0530 Subject: [PATCH 051/239] added folder icons to asset contents --- .../Metadata/Photogrammetry/AssetContents.tsx | 21 ++++++++++++------- .../Metadata/Photogrammetry/index.tsx | 1 + 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/client/src/pages/Ingestion/components/Metadata/Photogrammetry/AssetContents.tsx b/client/src/pages/Ingestion/components/Metadata/Photogrammetry/AssetContents.tsx index 81e5e7d6c..3c56d753f 100644 --- a/client/src/pages/Ingestion/components/Metadata/Photogrammetry/AssetContents.tsx +++ b/client/src/pages/Ingestion/components/Metadata/Photogrammetry/AssetContents.tsx @@ -7,14 +7,16 @@ import { Box, MenuItem, Select, Typography } from '@material-ui/core'; import { fade, makeStyles } from '@material-ui/core/styles'; import React from 'react'; +import { AiFillFolder } from 'react-icons/ai'; import { FieldType } from '../../../../../components'; import { StateFolder, VocabularyOption } from '../../../../../store'; +import { palette } from '../../../../../theme'; -const useStyles = makeStyles(({ palette, typography, breakpoints }) => ({ +const useStyles = makeStyles(({ palette, typography, breakpoints, spacing }) => ({ header: { display: 'flex', flex: 1, - borderBottom: `1px solid ${palette.grey[400]}`, + borderBottom: `1px solid ${palette.primary.contrastText}`, paddingBottom: 10 }, headerTitle: { @@ -30,7 +32,9 @@ const useStyles = makeStyles(({ palette, typography, breakpoints }) => ({ textAlign: 'center' }, contentText: { - color: palette.primary.dark + color: palette.primary.dark, + margin: `0px ${spacing(1)}px`, + wordBreak: 'break-word' }, select: { height: 30, @@ -76,11 +80,14 @@ function AssetContents(props: AssetContentsProps): React.ReactElement { const update = ({ target }) => onUpdate(id, target.value); return ( - - - {name} + + + + + + {name} - + - {options.map(({ idVocabulary, Term }, index) => {Term})} - - - + } + initialEntry={initialEntry} + options={options} + update={update} + /> ); })} @@ -106,4 +89,79 @@ function AssetContents(props: AssetContentsProps): React.ReactElement { ); } +interface EmptyContentProps { + isEmpty: boolean; + label: string; +} + +export function EmptyContent(props: EmptyContentProps): React.ReactElement { + const { isEmpty, label } = props; + const classes = useStyles(); + + if (!isEmpty) { + return ; + } + + return No {label} detected; +} + +interface ContentHeaderProps { + titles: string[]; +} + +export function ContentHeader(props: ContentHeaderProps): React.ReactElement { + const { titles } = props; + const classes = useStyles(); + + return ( + + {titles.map((title: string, index: number) => ( + + {title} + + ))} + + ); +} + +interface ContentProps { + fieldName: string; + value: number | null; + name: string; + icon: React.ReactNode; + initialEntry: number | null; + options: VocabularyOption[]; + update: (event: React.ChangeEvent<{ + name?: string | undefined; + value: unknown; + }>) => void; +} + +export function Content(props: ContentProps): React.ReactElement { + const { fieldName, value, name, icon, initialEntry, update, options } = props; + const classes = useStyles(); + + return ( + + + + {icon} + + {name} + + + + + + ); +} + export default AssetContents; diff --git a/client/src/store/metadata/metadata.defaults.ts b/client/src/store/metadata/metadata.defaults.ts index 71da1161d..fe4980a6d 100644 --- a/client/src/store/metadata/metadata.defaults.ts +++ b/client/src/store/metadata/metadata.defaults.ts @@ -78,9 +78,16 @@ export const photogrammetryFieldsSchema = yup.object().shape({ directory: yup.string().required() }); +const uvMapSchema = yup.object().shape({ + id: yup.number().required(), + name: yup.string().required(), + mapType: yup.number().nullable(true) +}); + export const defaultModelFields: ModelFields = { systemCreated: true, identifiers: [], + uvMaps: [], dateCaptured: new Date(), creationMethod: null, masterModel: false, @@ -110,6 +117,7 @@ export type ModelSchemaType = typeof modelFieldsSchema; export const modelFieldsSchema = yup.object().shape({ systemCreated: yup.boolean().required(), identifiers: yup.array().of(identifierSchema).when('systemCreated', identifiersWhenValidation), + uvMaps: yup.array().of(uvMapSchema), dateCaptured: yup.date().typeError('Date Captured is required'), creationMethod: yup.number().typeError('Creation method is required'), masterModel: yup.boolean().required(), diff --git a/client/src/store/metadata/metadata.types.ts b/client/src/store/metadata/metadata.types.ts index c4822b90d..7f69bcdc4 100644 --- a/client/src/store/metadata/metadata.types.ts +++ b/client/src/store/metadata/metadata.types.ts @@ -41,7 +41,7 @@ export type FieldErrors = { }; }; -export type MetadataFieldValue = string | number | boolean | null | Date | StateIdentifier[] | StateFolder[]; +export type MetadataFieldValue = string | number | boolean | null | Date | StateIdentifier[] | StateFolder[] | StateUVMap[]; export type MetadataUpdate = { valid: boolean; @@ -81,9 +81,16 @@ export type PhotogrammetryFields = { directory: string; }; +export type StateUVMap = { + id: number; + name: string; + mapType: number | null; +}; + export type ModelFields = { systemCreated: boolean; identifiers: StateIdentifier[]; + uvMaps: StateUVMap[]; dateCaptured: Date; creationMethod: number | null; masterModel: boolean; diff --git a/client/src/store/vocabulary.ts b/client/src/store/vocabulary.ts index a4948ead0..7b265c228 100644 --- a/client/src/store/vocabulary.ts +++ b/client/src/store/vocabulary.ts @@ -47,7 +47,8 @@ export const useVocabularyStore = create((set: SetState Date: Tue, 3 Nov 2020 15:42:56 +0530 Subject: [PATCH 066/239] updated ingest --- .../components/Metadata/Model/index.tsx | 8 ++-- .../Ingestion/components/Metadata/index.tsx | 4 +- client/src/store/metadata/index.ts | 4 +- .../src/store/metadata/metadata.defaults.ts | 14 ++++--- client/src/store/metadata/metadata.types.ts | 5 ++- client/src/types/graphql.tsx | 40 ++++++++++++++++++- server/graphql/schema.graphql | 40 ++++++++++++++++++- .../schema/ingestion/mutations.graphql | 40 ++++++++++++++++++- .../mutations/ingestion/ingestData.test.ts | 5 ++- server/types/graphql.ts | 40 ++++++++++++++++++- 10 files changed, 175 insertions(+), 25 deletions(-) diff --git a/client/src/pages/Ingestion/components/Metadata/Model/index.tsx b/client/src/pages/Ingestion/components/Metadata/Model/index.tsx index 7d437b10c..eb9ded35b 100644 --- a/client/src/pages/Ingestion/components/Metadata/Model/index.tsx +++ b/client/src/pages/Ingestion/components/Metadata/Model/index.tsx @@ -129,8 +129,8 @@ function Model(props: ModelProps): React.ReactElement { @@ -138,8 +138,8 @@ function Model(props: ModelProps): React.ReactElement { diff --git a/client/src/pages/Ingestion/components/Metadata/index.tsx b/client/src/pages/Ingestion/components/Metadata/index.tsx index 6f3bb52e1..5e8e47e42 100644 --- a/client/src/pages/Ingestion/components/Metadata/index.tsx +++ b/client/src/pages/Ingestion/components/Metadata/index.tsx @@ -69,7 +69,7 @@ function Metadata(): React.ReactElement { const getSelectedProject = useProjectStore(state => state.getSelectedProject); const getSelectedItem = useItemStore(state => state.getSelectedItem); const [metadatas, getMetadataInfo, validateFields] = useMetadataStore(state => [state.metadatas, state.getMetadataInfo, state.validateFields]); - const { ingestPhotogrammetryData, ingestionComplete } = useIngest(); + const { ingestionStart, ingestionComplete } = useIngest(); const getAssetType = useVocabularyStore(state => state.getAssetType); const metadataLength = metadatas.length; @@ -113,7 +113,7 @@ function Metadata(): React.ReactElement { if (isLast) { setIngestionLoading(true); - const success: boolean = await ingestPhotogrammetryData(); + const success: boolean = await ingestionStart(); setIngestionLoading(false); if (success) { diff --git a/client/src/store/metadata/index.ts b/client/src/store/metadata/index.ts index dbad21942..a0ed851a6 100644 --- a/client/src/store/metadata/index.ts +++ b/client/src/store/metadata/index.ts @@ -47,7 +47,7 @@ type MetadataStore = { metadatas: StateMetadata[]; getStateFolders: (folders: IngestFolder[]) => StateFolder[]; getInitialStateFolders: (folders: string[]) => StateFolder[]; - getSelectedIdentifiers: (metadata: StateMetadata) => StateIdentifier[] | undefined; + getSelectedIdentifiers: (identifiers: StateIdentifier[]) => StateIdentifier[] | undefined; getFieldErrors: (metadata: StateMetadata) => FieldErrors; validateFields: (fields: ValidateFields, schema: ValidateFieldsSchema) => boolean; getCurrentMetadata: (id: FileId) => StateMetadata | undefined; @@ -61,7 +61,7 @@ type MetadataStore = { export const useMetadataStore = create((set: SetState, get: GetState) => ({ metadatas: [], - getSelectedIdentifiers: (metadata: StateMetadata): StateIdentifier[] | undefined => lodash.filter(metadata.photogrammetry.identifiers, { selected: true }), + getSelectedIdentifiers: (identifiers: StateIdentifier[]): StateIdentifier[] | undefined => lodash.filter(identifiers, { selected: true }), getFieldErrors: (metadata: StateMetadata): FieldErrors => { const { getAssetType } = useVocabularyStore.getState(); const errors: FieldErrors = { diff --git a/client/src/store/metadata/metadata.defaults.ts b/client/src/store/metadata/metadata.defaults.ts index fe4980a6d..4f1ff07bd 100644 --- a/client/src/store/metadata/metadata.defaults.ts +++ b/client/src/store/metadata/metadata.defaults.ts @@ -90,8 +90,8 @@ export const defaultModelFields: ModelFields = { uvMaps: [], dateCaptured: new Date(), creationMethod: null, - masterModel: false, - authoritativeModel: false, + master: false, + authoritative: false, modality: null, units: null, purpose: null, @@ -109,7 +109,8 @@ export const defaultModelFields: ModelFields = { boundingBoxP1Z: null, boundingBoxP2X: null, boundingBoxP2Y: null, - boundingBoxP2Z: null + boundingBoxP2Z: null, + directory: '' }; export type ModelSchemaType = typeof modelFieldsSchema; @@ -120,8 +121,8 @@ export const modelFieldsSchema = yup.object().shape({ uvMaps: yup.array().of(uvMapSchema), dateCaptured: yup.date().typeError('Date Captured is required'), creationMethod: yup.number().typeError('Creation method is required'), - masterModel: yup.boolean().required(), - authoritativeModel: yup.boolean().required(), + master: yup.boolean().required(), + authoritative: yup.boolean().required(), modality: yup.number().typeError('Modality is required'), units: yup.number().typeError('Units is required'), purpose: yup.number().typeError('Purpose is required'), @@ -139,7 +140,8 @@ export const modelFieldsSchema = yup.object().shape({ boundingBoxP1Z: yup.number().nullable(true), boundingBoxP2X: yup.number().nullable(true), boundingBoxP2Y: yup.number().nullable(true), - boundingBoxP2Z: yup.number().nullable(true) + boundingBoxP2Z: yup.number().nullable(true), + directory: yup.string().required() }); export const defaultSceneFields: SceneFields = { diff --git a/client/src/store/metadata/metadata.types.ts b/client/src/store/metadata/metadata.types.ts index 7f69bcdc4..815871c83 100644 --- a/client/src/store/metadata/metadata.types.ts +++ b/client/src/store/metadata/metadata.types.ts @@ -93,8 +93,8 @@ export type ModelFields = { uvMaps: StateUVMap[]; dateCaptured: Date; creationMethod: number | null; - masterModel: boolean; - authoritativeModel: boolean; + master: boolean; + authoritative: boolean; modality: number | null; units: number | null; purpose: number | null; @@ -113,6 +113,7 @@ export type ModelFields = { boundingBoxP2X: number | null; boundingBoxP2Y: number | null; boundingBoxP2Z: number | null; + directory: string; }; export type SceneFields = { diff --git a/client/src/types/graphql.tsx b/client/src/types/graphql.tsx index ad9c96251..1943cc2ec 100644 --- a/client/src/types/graphql.tsx +++ b/client/src/types/graphql.tsx @@ -649,16 +649,49 @@ export type IngestPhotogrammetryInput = { identifiers: Array; }; +export type IngestUvMapInput = { + name: Scalars['String']; + mapType: Scalars['Int']; +}; + export type IngestModelInput = { idAssetVersion: Scalars['Int']; + systemCreated: Scalars['Boolean']; + master: Scalars['Boolean']; authoritative: Scalars['Boolean']; - dateCreated: Scalars['String']; creationMethod: Scalars['Int']; modality: Scalars['Int']; purpose: Scalars['Int']; units: Scalars['Int']; - master: Scalars['Boolean']; + dateCaptured: Scalars['String']; + modelFileType: Scalars['Int']; directory: Scalars['String']; + identifiers: Array; + uvMaps: Array; + roughness?: Maybe; + metalness?: Maybe; + pointCount?: Maybe; + faceCount?: Maybe; + isWatertight?: Maybe; + hasNormals?: Maybe; + hasVertexColor?: Maybe; + hasUVSpace?: Maybe; + boundingBoxP1X?: Maybe; + boundingBoxP1Y?: Maybe; + boundingBoxP1Z?: Maybe; + boundingBoxP2X?: Maybe; + boundingBoxP2Y?: Maybe; + boundingBoxP2Z?: Maybe; +}; + +export type IngestSceneInput = { + idAssetVersion: Scalars['Int']; + identifiers: Array; +}; + +export type IngestOtherInput = { + idAssetVersion: Scalars['Int']; + identifiers: Array; }; export type IngestDataInput = { @@ -666,6 +699,9 @@ export type IngestDataInput = { project: IngestProjectInput; item: IngestItemInput; photogrammetry: Array; + model: Array; + scene: Array; + other: Array; }; export type IngestDataResult = { diff --git a/server/graphql/schema.graphql b/server/graphql/schema.graphql index d5328340f..edfa2e637 100644 --- a/server/graphql/schema.graphql +++ b/server/graphql/schema.graphql @@ -395,16 +395,49 @@ input IngestPhotogrammetryInput { identifiers: [IngestIdentifierInput!]! } +input IngestUVMapInput { + name: String! + mapType: Int! +} + input IngestModelInput { idAssetVersion: Int! + systemCreated: Boolean! + master: Boolean! authoritative: Boolean! - dateCreated: String! creationMethod: Int! modality: Int! purpose: Int! units: Int! - master: Boolean! + dateCaptured: String! + modelFileType: Int! directory: String! + identifiers: [IngestIdentifierInput!]! + uvMaps: [IngestUVMapInput!]! + roughness: Int + metalness: Int + pointCount: Int + faceCount: Int + isWatertight: Boolean + hasNormals: Boolean + hasVertexColor: Boolean + hasUVSpace: Boolean + boundingBoxP1X: Int + boundingBoxP1Y: Int + boundingBoxP1Z: Int + boundingBoxP2X: Int + boundingBoxP2Y: Int + boundingBoxP2Z: Int +} + +input IngestSceneInput { + idAssetVersion: Int! + identifiers: [IngestIdentifierInput!]! +} + +input IngestOtherInput { + idAssetVersion: Int! + identifiers: [IngestIdentifierInput!]! } input IngestDataInput { @@ -412,6 +445,9 @@ input IngestDataInput { project: IngestProjectInput! item: IngestItemInput! photogrammetry: [IngestPhotogrammetryInput!]! + model: [IngestModelInput!]! + scene: [IngestSceneInput!]! + other: [IngestOtherInput!]! } type IngestDataResult { diff --git a/server/graphql/schema/ingestion/mutations.graphql b/server/graphql/schema/ingestion/mutations.graphql index 70819cf0e..9db538be4 100644 --- a/server/graphql/schema/ingestion/mutations.graphql +++ b/server/graphql/schema/ingestion/mutations.graphql @@ -51,16 +51,49 @@ input IngestPhotogrammetryInput { identifiers: [IngestIdentifierInput!]! } +input IngestUVMapInput { + name: String! + mapType: Int! +} + input IngestModelInput { idAssetVersion: Int! + systemCreated: Boolean! + master: Boolean! authoritative: Boolean! - dateCreated: String! creationMethod: Int! modality: Int! purpose: Int! units: Int! - master: Boolean! + dateCaptured: String! + modelFileType: Int! directory: String! + identifiers: [IngestIdentifierInput!]! + uvMaps: [IngestUVMapInput!]! + roughness: Int + metalness: Int + pointCount: Int + faceCount: Int + isWatertight: Boolean + hasNormals: Boolean + hasVertexColor: Boolean + hasUVSpace: Boolean + boundingBoxP1X: Int + boundingBoxP1Y: Int + boundingBoxP1Z: Int + boundingBoxP2X: Int + boundingBoxP2Y: Int + boundingBoxP2Z: Int +} + +input IngestSceneInput { + idAssetVersion: Int! + identifiers: [IngestIdentifierInput!]! +} + +input IngestOtherInput { + idAssetVersion: Int! + identifiers: [IngestIdentifierInput!]! } input IngestDataInput { @@ -68,6 +101,9 @@ input IngestDataInput { project: IngestProjectInput! item: IngestItemInput! photogrammetry: [IngestPhotogrammetryInput!]! + model: [IngestModelInput!]! + scene: [IngestSceneInput!]! + other: [IngestOtherInput!]! } type IngestDataResult { diff --git a/server/tests/graphql/mutations/ingestion/ingestData.test.ts b/server/tests/graphql/mutations/ingestion/ingestData.test.ts index 45ea683f4..59a587dcf 100644 --- a/server/tests/graphql/mutations/ingestion/ingestData.test.ts +++ b/server/tests/graphql/mutations/ingestion/ingestData.test.ts @@ -178,7 +178,10 @@ const ingestDataTest = (utils: TestSuiteUtils): void => { subjects: [subject], project, item, - photogrammetry: [photogrammetry] + photogrammetry: [photogrammetry], + model: [], + scene: [], + other: [] }; const result = await graphQLApi.ingestData(ingestDataInput, context); diff --git a/server/types/graphql.ts b/server/types/graphql.ts index 4ac0cbffc..895c4038c 100644 --- a/server/types/graphql.ts +++ b/server/types/graphql.ts @@ -602,16 +602,49 @@ export type IngestPhotogrammetryInput = { identifiers: Array; }; +export type IngestUvMapInput = { + name: Scalars['String']; + mapType: Scalars['Int']; +}; + export type IngestModelInput = { idAssetVersion: Scalars['Int']; + systemCreated: Scalars['Boolean']; + master: Scalars['Boolean']; authoritative: Scalars['Boolean']; - dateCreated: Scalars['String']; creationMethod: Scalars['Int']; modality: Scalars['Int']; purpose: Scalars['Int']; units: Scalars['Int']; - master: Scalars['Boolean']; + dateCaptured: Scalars['String']; + modelFileType: Scalars['Int']; directory: Scalars['String']; + identifiers: Array; + uvMaps: Array; + roughness?: Maybe; + metalness?: Maybe; + pointCount?: Maybe; + faceCount?: Maybe; + isWatertight?: Maybe; + hasNormals?: Maybe; + hasVertexColor?: Maybe; + hasUVSpace?: Maybe; + boundingBoxP1X?: Maybe; + boundingBoxP1Y?: Maybe; + boundingBoxP1Z?: Maybe; + boundingBoxP2X?: Maybe; + boundingBoxP2Y?: Maybe; + boundingBoxP2Z?: Maybe; +}; + +export type IngestSceneInput = { + idAssetVersion: Scalars['Int']; + identifiers: Array; +}; + +export type IngestOtherInput = { + idAssetVersion: Scalars['Int']; + identifiers: Array; }; export type IngestDataInput = { @@ -619,6 +652,9 @@ export type IngestDataInput = { project: IngestProjectInput; item: IngestItemInput; photogrammetry: Array; + model: Array; + scene: Array; + other: Array; }; export type IngestDataResult = { From 4d38c76f4de09841e9ab992453d847e934cf58f2 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Tue, 3 Nov 2020 15:43:10 +0530 Subject: [PATCH 067/239] Update use ingestion hook methods and types --- client/src/pages/Ingestion/hooks/useIngest.ts | 289 +++++++++++++----- client/src/utils/shared.ts | 6 + 2 files changed, 212 insertions(+), 83 deletions(-) diff --git a/client/src/pages/Ingestion/hooks/useIngest.ts b/client/src/pages/Ingestion/hooks/useIngest.ts index 1bd152638..db12ad270 100644 --- a/client/src/pages/Ingestion/hooks/useIngest.ts +++ b/client/src/pages/Ingestion/hooks/useIngest.ts @@ -11,9 +11,13 @@ import { HOME_ROUTES, INGESTION_ROUTES_TYPE, resolveSubRoute } from '../../../co import { apolloClient } from '../../../graphql'; import { defaultItem, + isNewItem, + parseFileId, + StateFolder, StateIdentifier, StateItem, StateProject, + StateUVMap, useItemStore, useMetadataStore, useProjectStore, @@ -21,11 +25,25 @@ import { useUploadStore, useVocabularyStore } from '../../../store'; -import { isNewItem, parseFileId } from '../../../store/utils'; -import { IngestDataDocument, IngestDataMutation, IngestFolderInput, IngestIdentifierInput, IngestPhotogrammetryInput, IngestSubjectInput } from '../../../types/graphql'; +import { + IngestDataDocument, + IngestDataInput, + IngestDataMutation, + IngestFolderInput, + IngestIdentifierInput, + IngestItemInput, + IngestModelInput, + IngestOtherInput, + IngestPhotogrammetryInput, + IngestProjectInput, + IngestSceneInput, + IngestSubjectInput, + IngestUvMapInput +} from '../../../types/graphql'; +import { nonNullValue } from '../../../utils/shared'; interface UseIngest { - ingestPhotogrammetryData: () => Promise; + ingestionStart: () => Promise; ingestionComplete: () => void; ingestionReset: () => void; } @@ -40,51 +58,44 @@ function useIngest(): UseIngest { const history = useHistory(); - const ingestPhotogrammetryData = async (): Promise => { + const ingestionStart = async (): Promise => { try { - let ingestProject = {}; - let ingestItem = {}; - - const ingestPhotogrammetry: IngestPhotogrammetryInput[] = []; - const ingestSubjects: IngestSubjectInput[] = subjects.map(subject => ({ ...subject, id: subject.id || null })); - const project: StateProject | undefined = getSelectedProject(); + const project: StateProject = nonNullValue('project', getSelectedProject()); - if (project) { - const { id, name } = project; - ingestProject = { - id, - name - }; - } + const ingestProject: IngestProjectInput = { + id: project.id, + name: project.name + }; - const item: StateItem | undefined = getSelectedItem(); + const item: StateItem = nonNullValue('item', getSelectedItem()); - if (item) { - const { id, name, entireSubject } = item; + const isDefaultItem = item.id === defaultItem.id; - const isDefaultItem = id === defaultItem.id; + let ingestItemId: number | null = null; - let ingestItemId: number | null = null; + if (!isDefaultItem || isNewItem(item.id)) { + ingestItemId = Number.parseInt(item.id, 10); + } - if (!isDefaultItem || isNewItem(id)) { - ingestItemId = Number.parseInt(id, 10); - } + const ingestItem: IngestItemInput = { + id: ingestItemId, + name: item.name, + entireSubject: item.entireSubject + }; - ingestItem = { - id: ingestItemId, - name, - entireSubject - }; - } + const ingestPhotogrammetry: IngestPhotogrammetryInput[] = []; + const ingestModel: IngestModelInput[] = []; + const ingestScene: IngestSceneInput[] = []; + const ingestOther: IngestOtherInput[] = []; lodash.forEach(metadatas, metadata => { - const { file, photogrammetry } = metadata; - const { photogrammetry: isPhotogrammetry } = getAssetType(file.type); + const { file, photogrammetry, model, scene, other } = metadata; + const { photogrammetry: isPhotogrammetry, model: isModel, scene: isScene, other: isOther } = getAssetType(file.type); if (isPhotogrammetry) { const { @@ -102,51 +113,18 @@ function useIngest(): UseIngest { backgroundRemovalMethod, clusterType, clusterGeometryFieldId, - directory + directory, + identifiers, + folders } = photogrammetry; - const ingestIdentifiers: IngestIdentifierInput[] = []; - const identifiers: StateIdentifier[] | undefined = getSelectedIdentifiers(metadata); - - if (identifiers) { - lodash.forEach(identifiers, data => { - const { identifier, identifierType } = data; - if (!identifierType) { - throw Error('Identifer type is null'); - } - - const identifierData: IngestIdentifierInput = { - identifier, - identifierType - }; - ingestIdentifiers.push(identifierData); - }); - } - - const ingestFolders: IngestFolderInput[] = []; - lodash.forEach(photogrammetry.folders, folder => { - const { name, variantType } = folder; - - if (!variantType) { - throw Error('Folder variantType type is null'); - } - - const folderData = { - name, - variantType - }; - - ingestFolders.push(folderData); - }); - - if (!datasetType) { - throw Error('Dataset Type type is null'); - } - - const photogrammetryData = { + const ingestIdentifiers: IngestIdentifierInput[] = getIngestIdentifiers(identifiers); + const ingestFolders: IngestFolderInput[] = getIngestFolders(folders); + + const photogrammetryData: IngestPhotogrammetryInput = { idAssetVersion: parseFileId(file.id), dateCaptured: dateCaptured.toISOString(), - datasetType, + datasetType: nonNullValue('datasetType', datasetType), systemCreated, description, cameraSettingUniform, @@ -163,22 +141,116 @@ function useIngest(): UseIngest { clusterType, clusterGeometryFieldId }; + ingestPhotogrammetry.push(photogrammetryData); } - }); - const variables = { - input: { - subjects: ingestSubjects, - project: ingestProject, - item: ingestItem, - photogrammetry: ingestPhotogrammetry + if (isModel) { + const { + systemCreated, + identifiers, + uvMaps, + dateCaptured, + creationMethod, + master, + authoritative, + modality, + units, + purpose, + modelFileType, + roughness, + metalness, + pointCount, + faceCount, + isWatertight, + hasNormals, + hasVertexColor, + hasUVSpace, + boundingBoxP1X, + boundingBoxP1Y, + boundingBoxP1Z, + boundingBoxP2X, + boundingBoxP2Y, + boundingBoxP2Z, + directory + } = model; + + const ingestIdentifiers: IngestIdentifierInput[] = getIngestIdentifiers(identifiers); + const ingestUVMaps: IngestUvMapInput[] = getIngestUVMaps(uvMaps); + + const modelData: IngestModelInput = { + idAssetVersion: parseFileId(file.id), + dateCaptured: dateCaptured.toISOString(), + identifiers: ingestIdentifiers, + uvMaps: ingestUVMaps, + systemCreated, + creationMethod: nonNullValue('creationMethod', creationMethod), + master, + authoritative, + modality: nonNullValue('modality', modality), + units: nonNullValue('units', units), + purpose: nonNullValue('purpose', purpose), + modelFileType: nonNullValue('modelFileType', modelFileType), + roughness, + metalness, + pointCount, + faceCount, + isWatertight, + hasNormals, + hasVertexColor, + hasUVSpace, + boundingBoxP1X, + boundingBoxP1Y, + boundingBoxP1Z, + boundingBoxP2X, + boundingBoxP2Y, + boundingBoxP2Z, + directory + }; + + ingestModel.push(modelData); + } + + if (isScene) { + const { identifiers } = scene; + + const ingestIdentifiers: IngestIdentifierInput[] = getIngestIdentifiers(identifiers); + + const sceneData: IngestSceneInput = { + idAssetVersion: parseFileId(file.id), + identifiers: ingestIdentifiers + }; + + ingestScene.push(sceneData); } + + if (isOther) { + const { identifiers } = other; + + const ingestIdentifiers: IngestIdentifierInput[] = getIngestIdentifiers(identifiers); + + const otherData: IngestOtherInput = { + idAssetVersion: parseFileId(file.id), + identifiers: ingestIdentifiers + }; + + ingestOther.push(otherData); + } + }); + + const input: IngestDataInput = { + subjects: ingestSubjects, + project: ingestProject, + item: ingestItem, + photogrammetry: ingestPhotogrammetry, + model: ingestModel, + scene: ingestScene, + other: ingestOther }; const ingestDataMutation: FetchResult = await apolloClient.mutate({ mutation: IngestDataDocument, - variables, + variables: { input }, refetchQueries: ['getUploadedAssetVersion'] }); @@ -216,8 +288,59 @@ function useIngest(): UseIngest { resetIngestionState(); }; + const getIngestIdentifiers = (identifiers: StateIdentifier[]): IngestIdentifierInput[] => { + const ingestIdentifiers: IngestIdentifierInput[] = []; + const selectedIdentifiers: StateIdentifier[] | undefined = getSelectedIdentifiers(identifiers); + + if (selectedIdentifiers) { + lodash.forEach(selectedIdentifiers, (data: StateIdentifier) => { + const { identifier, identifierType } = data; + + const identifierData: IngestIdentifierInput = { + identifier, + identifierType: nonNullValue('identifierType', identifierType) + }; + ingestIdentifiers.push(identifierData); + }); + } + + return ingestIdentifiers; + }; + + const getIngestFolders = (folders: StateFolder[]): IngestFolderInput[] => { + const ingestFolders: IngestFolderInput[] = []; + lodash.forEach(folders, (folder: StateFolder) => { + const { name, variantType } = folder; + + const folderData: IngestFolderInput = { + name, + variantType: nonNullValue('variantType', variantType) + }; + + ingestFolders.push(folderData); + }); + + return ingestFolders; + }; + + const getIngestUVMaps = (uvMaps: StateUVMap[]): IngestUvMapInput[] => { + const ingestUVMaps: IngestUvMapInput[] = []; + lodash.forEach(uvMaps, (uvMap: StateUVMap) => { + const { name, mapType } = uvMap; + + const uvMapData: IngestUvMapInput = { + name, + mapType: nonNullValue('mapType', mapType) + }; + + ingestUVMaps.push(uvMapData); + }); + + return ingestUVMaps; + }; + return { - ingestPhotogrammetryData, + ingestionStart, ingestionComplete, ingestionReset }; diff --git a/client/src/utils/shared.ts b/client/src/utils/shared.ts index 99bda6f7b..ad2b3936a 100644 --- a/client/src/utils/shared.ts +++ b/client/src/utils/shared.ts @@ -17,6 +17,12 @@ export const withDefaultValueNumber = (value: number | null, defaultValue: numbe return defaultValue; }; +export function nonNullValue(name: string, value: T | null | undefined): T { + if (value === null || value === undefined) throw new Error(`Provided ${name} is null`); + + return value; +} + export const actionOnKeyPress = (key: string, actionKey: string, func: () => void): void => { if (key === actionKey) { func(); From 669da7a87bfd23f6fd3ef4f266b330512416f743 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Tue, 3 Nov 2020 16:47:10 +0530 Subject: [PATCH 068/239] metadata update non authenticated user fixes --- .../Ingestion/components/Uploads/index.tsx | 12 +++++++----- client/src/store/metadata/index.ts | 17 ++++++++++++++--- client/src/store/metadata/metadata.defaults.ts | 2 +- client/src/store/metadata/metadata.types.ts | 1 + client/src/store/user.ts | 4 ++++ 5 files changed, 27 insertions(+), 9 deletions(-) diff --git a/client/src/pages/Ingestion/components/Uploads/index.tsx b/client/src/pages/Ingestion/components/Uploads/index.tsx index d1da9adb2..9890155c4 100644 --- a/client/src/pages/Ingestion/components/Uploads/index.tsx +++ b/client/src/pages/Ingestion/components/Uploads/index.tsx @@ -66,9 +66,9 @@ function Uploads(): React.ReactElement { const [loadingVocabulary, setLoadingVocabulary] = useState(true); const [gettingAssetDetails, setGettingAssetDetails] = useState(false); const [discardingFiles, setDiscardingFiles] = useState(false); - const { completed, discardFiles } = useUploadStore(); - const { updateMetadataSteps } = useMetadataStore(); - const { updateVocabularyEntries } = useVocabularyStore(); + const [completed, discardFiles] = useUploadStore(state => [state.completed,state.discardFiles]); + const updateMetadataSteps = useMetadataStore(state => state.updateMetadataSteps); + const updateVocabularyEntries = useVocabularyStore(state => state.updateVocabularyEntries); const fetchVocabularyEntries = async () => { setLoadingVocabulary(true); @@ -80,14 +80,16 @@ function Uploads(): React.ReactElement { fetchVocabularyEntries(); }, []); - const onIngest = async () => { + const onIngest = async (): Promise => { const nextStep = resolveSubRoute(HOME_ROUTES.INGESTION, INGESTION_ROUTE.ROUTES.SUBJECT_ITEM); try { setGettingAssetDetails(true); - const { valid, selectedFiles } = await updateMetadataSteps(); + const { valid, selectedFiles, error } = await updateMetadataSteps(); setGettingAssetDetails(false); + if (error) return; + if (!selectedFiles) { toast.warn('Please select at least 1 file to ingest'); return; diff --git a/client/src/store/metadata/index.ts b/client/src/store/metadata/index.ts index a0ed851a6..a1722b201 100644 --- a/client/src/store/metadata/index.ts +++ b/client/src/store/metadata/index.ts @@ -24,6 +24,7 @@ import { StateItem, useItemStore } from '../item'; import { StateProject, useProjectStore } from '../project'; import { StateSubject, useSubjectStore } from '../subject'; import { FileId, IngestionFile, useUploadStore } from '../upload'; +import { useUserStore } from '../user'; import { parseFileId, parseItemToState, parseProjectToState, parseSubjectUnitIdentifierToState } from '../utils'; import { useVocabularyStore } from '../vocabulary'; import { defaultModelFields, defaultOtherFields, defaultPhotogrammetryFields, defaultSceneFields, ValidateFieldsSchema } from './metadata.defaults'; @@ -139,6 +140,7 @@ export const useMetadataStore = create((set: SetState => { const { getStateFolders } = get(); + const { isAuthenticated } = useUserStore.getState(); const { completed, getSelectedFiles } = useUploadStore.getState(); const { getInitialEntry } = useVocabularyStore.getState(); const { addSubjects } = useSubjectStore.getState(); @@ -150,7 +152,8 @@ export const useMetadataStore = create((set: SetState((set: SetState = await apolloClient.query({ query: GetAssetVersionsDetailsDocument, variables: { @@ -299,7 +307,8 @@ export const useMetadataStore = create((set: SetState((set: SetState, name: string, value: MetadataFieldValue, metadataType: MetadataType) => { @@ -440,3 +450,4 @@ export const useMetadataStore = create((set: SetState Promise; initialize: () => Promise; login: (email: string, password: string) => Promise; logout: () => Promise; @@ -18,6 +19,9 @@ type UserStore = { export const useUserStore = create((set: SetState, get: GetState) => ({ user: null, + isAuthenticated: async (): Promise => { + return !!(await getAuthenticatedUser()); + }, initialize: async () => { const { user } = get(); if (!user) { From 83ec0e70405b9a157f9ee7cb9692bda7b3dbf6ae Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Wed, 4 Nov 2020 20:44:52 +0530 Subject: [PATCH 069/239] Update getAssetVersionDetail types --- client/src/types/graphql.tsx | 90 +++++++++++++++++-- .../queries/asset/getAssetVersionsDetails.ts | 37 +++++++- server/graphql/schema.graphql | 33 ++++++- server/graphql/schema/asset/queries.graphql | 33 ++++++- server/types/graphql.ts | 35 +++++++- server/utils/parser/bulkIngestReader.ts | 12 ++- 6 files changed, 222 insertions(+), 18 deletions(-) diff --git a/client/src/types/graphql.tsx b/client/src/types/graphql.tsx index 1943cc2ec..420f13440 100644 --- a/client/src/types/graphql.tsx +++ b/client/src/types/graphql.tsx @@ -397,17 +397,47 @@ export type IngestPhotogrammetry = { identifiers: Array; }; +export type IngestUvMap = { + __typename?: 'IngestUVMap'; + name: Scalars['String']; + mapType: Scalars['Int']; +}; + export type IngestModel = { __typename?: 'IngestModel'; idAssetVersion: Scalars['Int']; + systemCreated: Scalars['Boolean']; + master: Scalars['Boolean']; authoritative: Scalars['Boolean']; - dateCreated: Scalars['String']; creationMethod: Scalars['Int']; modality: Scalars['Int']; purpose: Scalars['Int']; units: Scalars['Int']; - master: Scalars['Boolean']; + dateCaptured: Scalars['String']; + modelFileType: Scalars['Int']; directory: Scalars['String']; + identifiers: Array; + uvMaps: Array; + roughness?: Maybe; + metalness?: Maybe; + pointCount?: Maybe; + faceCount?: Maybe; + isWatertight?: Maybe; + hasNormals?: Maybe; + hasVertexColor?: Maybe; + hasUVSpace?: Maybe; + boundingBoxP1X?: Maybe; + boundingBoxP1Y?: Maybe; + boundingBoxP1Z?: Maybe; + boundingBoxP2X?: Maybe; + boundingBoxP2Y?: Maybe; + boundingBoxP2Z?: Maybe; +}; + +export type IngestScene = { + __typename?: 'IngestScene'; + idAssetVersion: Scalars['Int']; + identifiers: Array; }; export type GetAssetVersionDetailResult = { @@ -418,6 +448,7 @@ export type GetAssetVersionDetailResult = { Item?: Maybe; CaptureDataPhoto?: Maybe; Model?: Maybe; + Scene?: Maybe; }; export type GetAssetVersionsDetailsResult = { @@ -1825,7 +1856,25 @@ export type GetAssetVersionsDetailsQuery = ( } )>, Model?: Maybe<( { __typename?: 'IngestModel' } - & Pick + & Pick + & { + identifiers: Array<( + { __typename?: 'IngestIdentifier' } + & Pick + )>, uvMaps: Array<( + { __typename?: 'IngestUVMap' } + & Pick + )> + } + )>, Scene?: Maybe<( + { __typename?: 'IngestScene' } + & Pick + & { + identifiers: Array<( + { __typename?: 'IngestIdentifier' } + & Pick + )> + } )> } )> @@ -2973,14 +3022,45 @@ export const GetAssetVersionsDetailsDocument = gql` } Model { idAssetVersion + systemCreated + master authoritative - dateCreated creationMethod modality purpose units - master + dateCaptured + modelFileType directory + identifiers { + identifier + identifierType + } + uvMaps { + name + mapType + } + roughness + metalness + pointCount + faceCount + isWatertight + hasNormals + hasVertexColor + hasUVSpace + boundingBoxP1X + boundingBoxP1Y + boundingBoxP1Z + boundingBoxP2X + boundingBoxP2Y + boundingBoxP2Z + } + Scene { + idAssetVersion + identifiers { + identifier + identifierType + } } } } diff --git a/server/graphql/api/queries/asset/getAssetVersionsDetails.ts b/server/graphql/api/queries/asset/getAssetVersionsDetails.ts index 3e769447e..270207ac4 100644 --- a/server/graphql/api/queries/asset/getAssetVersionsDetails.ts +++ b/server/graphql/api/queries/asset/getAssetVersionsDetails.ts @@ -50,15 +50,46 @@ const getAssetVersionsDetails = gql` } Model { idAssetVersion + systemCreated + master authoritative - dateCreated creationMethod modality purpose units - master + dateCaptured + modelFileType directory - } + identifiers { + identifier + identifierType + } + uvMaps { + name + mapType + } + roughness + metalness + pointCount + faceCount + isWatertight + hasNormals + hasVertexColor + hasUVSpace + boundingBoxP1X + boundingBoxP1Y + boundingBoxP1Z + boundingBoxP2X + boundingBoxP2Y + boundingBoxP2Z + } + Scene { + idAssetVersion + identifiers { + identifier + identifierType + } + } } } } diff --git a/server/graphql/schema.graphql b/server/graphql/schema.graphql index edfa2e637..452417262 100644 --- a/server/graphql/schema.graphql +++ b/server/graphql/schema.graphql @@ -161,16 +161,44 @@ type IngestPhotogrammetry { identifiers: [IngestIdentifier!]! } +type IngestUVMap { + name: String! + mapType: Int! +} + type IngestModel { idAssetVersion: Int! + systemCreated: Boolean! + master: Boolean! authoritative: Boolean! - dateCreated: String! creationMethod: Int! modality: Int! purpose: Int! units: Int! - master: Boolean! + dateCaptured: String! + modelFileType: Int! directory: String! + identifiers: [IngestIdentifier!]! + uvMaps: [IngestUVMap!]! + roughness: Int + metalness: Int + pointCount: Int + faceCount: Int + isWatertight: Boolean + hasNormals: Boolean + hasVertexColor: Boolean + hasUVSpace: Boolean + boundingBoxP1X: Int + boundingBoxP1Y: Int + boundingBoxP1Z: Int + boundingBoxP2X: Int + boundingBoxP2Y: Int + boundingBoxP2Z: Int +} + +type IngestScene { + idAssetVersion: Int! + identifiers: [IngestIdentifier!]! } type GetAssetVersionDetailResult { @@ -180,6 +208,7 @@ type GetAssetVersionDetailResult { Item: Item CaptureDataPhoto: IngestPhotogrammetry Model: IngestModel + Scene: IngestScene } type GetAssetVersionsDetailsResult { diff --git a/server/graphql/schema/asset/queries.graphql b/server/graphql/schema/asset/queries.graphql index 29e04d7cb..6329b140a 100644 --- a/server/graphql/schema/asset/queries.graphql +++ b/server/graphql/schema/asset/queries.graphql @@ -40,16 +40,44 @@ type IngestPhotogrammetry { identifiers: [IngestIdentifier!]! } +type IngestUVMap { + name: String! + mapType: Int! +} + type IngestModel { idAssetVersion: Int! + systemCreated: Boolean! + master: Boolean! authoritative: Boolean! - dateCreated: String! creationMethod: Int! modality: Int! purpose: Int! units: Int! - master: Boolean! + dateCaptured: String! + modelFileType: Int! directory: String! + identifiers: [IngestIdentifier!]! + uvMaps: [IngestUVMap!]! + roughness: Int + metalness: Int + pointCount: Int + faceCount: Int + isWatertight: Boolean + hasNormals: Boolean + hasVertexColor: Boolean + hasUVSpace: Boolean + boundingBoxP1X: Int + boundingBoxP1Y: Int + boundingBoxP1Z: Int + boundingBoxP2X: Int + boundingBoxP2Y: Int + boundingBoxP2Z: Int +} + +type IngestScene { + idAssetVersion: Int! + identifiers: [IngestIdentifier!]! } type GetAssetVersionDetailResult { @@ -59,6 +87,7 @@ type GetAssetVersionDetailResult { Item: Item CaptureDataPhoto: IngestPhotogrammetry Model: IngestModel + Scene: IngestScene } type GetAssetVersionsDetailsResult { diff --git a/server/types/graphql.ts b/server/types/graphql.ts index 895c4038c..66a637b49 100644 --- a/server/types/graphql.ts +++ b/server/types/graphql.ts @@ -350,17 +350,47 @@ export type IngestPhotogrammetry = { identifiers: Array; }; +export type IngestUvMap = { + __typename?: 'IngestUVMap'; + name: Scalars['String']; + mapType: Scalars['Int']; +}; + export type IngestModel = { __typename?: 'IngestModel'; idAssetVersion: Scalars['Int']; + systemCreated: Scalars['Boolean']; + master: Scalars['Boolean']; authoritative: Scalars['Boolean']; - dateCreated: Scalars['String']; creationMethod: Scalars['Int']; modality: Scalars['Int']; purpose: Scalars['Int']; units: Scalars['Int']; - master: Scalars['Boolean']; + dateCaptured: Scalars['String']; + modelFileType: Scalars['Int']; directory: Scalars['String']; + identifiers: Array; + uvMaps: Array; + roughness?: Maybe; + metalness?: Maybe; + pointCount?: Maybe; + faceCount?: Maybe; + isWatertight?: Maybe; + hasNormals?: Maybe; + hasVertexColor?: Maybe; + hasUVSpace?: Maybe; + boundingBoxP1X?: Maybe; + boundingBoxP1Y?: Maybe; + boundingBoxP1Z?: Maybe; + boundingBoxP2X?: Maybe; + boundingBoxP2Y?: Maybe; + boundingBoxP2Z?: Maybe; +}; + +export type IngestScene = { + __typename?: 'IngestScene'; + idAssetVersion: Scalars['Int']; + identifiers: Array; }; export type GetAssetVersionDetailResult = { @@ -371,6 +401,7 @@ export type GetAssetVersionDetailResult = { Item?: Maybe; CaptureDataPhoto?: Maybe; Model?: Maybe; + Scene?: Maybe; }; export type GetAssetVersionsDetailsResult = { diff --git a/server/utils/parser/bulkIngestReader.ts b/server/utils/parser/bulkIngestReader.ts index c966d7339..0f0d237d3 100644 --- a/server/utils/parser/bulkIngestReader.ts +++ b/server/utils/parser/bulkIngestReader.ts @@ -364,17 +364,21 @@ export class BulkIngestReader { { LOG.logger.error(vocabResult.error); return null; } const units: number = vocabResult.idVocabulary; - // directory_path: string; + // TODO: model fields, not sure what is needed here return { - idAssetVersion: 0, // TODO: not sure what is needed here - dateCreated: bagitModel.date_created, + idAssetVersion: 0, + dateCaptured: bagitModel.date_created, creationMethod, master: bagitModel.master != 0, authoritative: bagitModel.authoritative != 0, modality, units, purpose, - directory: bagitModel.directory_path + directory: bagitModel.directory_path, + systemCreated: true, + modelFileType: 0, + identifiers: [], + uvMaps: [] }; } From ed9ae049ee315cd04ca830d9d648496cd930ee8f Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Wed, 4 Nov 2020 20:45:32 +0530 Subject: [PATCH 070/239] Integrate updated types for model and scene --- client/src/store/metadata/index.ts | 67 ++++++++++++++++++------------ client/src/store/utils.ts | 42 +++++++++++++++++-- 2 files changed, 79 insertions(+), 30 deletions(-) diff --git a/client/src/store/metadata/index.ts b/client/src/store/metadata/index.ts index a1722b201..0dab343c1 100644 --- a/client/src/store/metadata/index.ts +++ b/client/src/store/metadata/index.ts @@ -16,7 +16,6 @@ import { GetAssetVersionsDetailsDocument, GetAssetVersionsDetailsQuery, GetContentsForAssetVersionsDocument, - IngestFolder, Project } from '../../types/graphql'; import { eVocabularySetID } from '../../types/server'; @@ -25,7 +24,7 @@ import { StateProject, useProjectStore } from '../project'; import { StateSubject, useSubjectStore } from '../subject'; import { FileId, IngestionFile, useUploadStore } from '../upload'; import { useUserStore } from '../user'; -import { parseFileId, parseItemToState, parseProjectToState, parseSubjectUnitIdentifierToState } from '../utils'; +import { parseFileId, parseFoldersToState, parseIdentifiersToState, parseItemToState, parseProjectToState, parseSubjectUnitIdentifierToState, parseUVMapsToState } from '../utils'; import { useVocabularyStore } from '../vocabulary'; import { defaultModelFields, defaultOtherFields, defaultPhotogrammetryFields, defaultSceneFields, ValidateFieldsSchema } from './metadata.defaults'; import { @@ -46,7 +45,6 @@ import { type MetadataStore = { metadatas: StateMetadata[]; - getStateFolders: (folders: IngestFolder[]) => StateFolder[]; getInitialStateFolders: (folders: string[]) => StateFolder[]; getSelectedIdentifiers: (identifiers: StateIdentifier[]) => StateIdentifier[] | undefined; getFieldErrors: (metadata: StateMetadata) => FieldErrors; @@ -139,7 +137,6 @@ export const useMetadataStore = create((set: SetState => { - const { getStateFolders } = get(); const { isAuthenticated } = useUserStore.getState(); const { completed, getSelectedFiles } = useUploadStore.getState(); const { getInitialEntry } = useVocabularyStore.getState(); @@ -228,7 +225,8 @@ export const useMetadataStore = create((set: SetState((set: SetState ({ - id: index, - identifier, - identifierType, - selected: true - }) - ); - - const stateIdentifiers = parsedIdentifiers.length ? parsedIdentifiers : defaultIdentifierField; + const stateIdentifiers: StateIdentifier[] = parseIdentifiersToState(identifiers, defaultIdentifierField); metadataStep = { ...metadataStep, @@ -281,7 +270,7 @@ export const useMetadataStore = create((set: SetState((set: SetState((set: SetState, name: string, value: MetadataFieldValue, metadataType: MetadataType) => { const { metadatas } = get(); + + if (!(name in metadatas[metadataIndex][metadataType])) { + throw new Error(`Field ${name} doesn't exist on a ${metadataType} asset`); + } + const updatedMetadatas = lodash.map(metadatas, (metadata: StateMetadata, index: number) => { if (index === metadataIndex) { return { @@ -339,15 +362,6 @@ export const useMetadataStore = create((set: SetState { - const stateFolders: StateFolder[] = folders.map(({ name, variantType }, index: number) => ({ - id: index, - name, - variantType - })); - - return stateFolders; - }, getInitialStateFolders: (folders: string[]): StateFolder[] => { const { getInitialEntry } = useVocabularyStore.getState(); const stateFolders: StateFolder[] = folders.map((folder, index: number) => ({ @@ -450,4 +464,3 @@ export const useMetadataStore = create((set: SetState ({ + id: index, + identifier, + identifierType, + selected: true + }) + ); + + const stateIdentifiers = parsedIdentifiers.length ? parsedIdentifiers : defaultIdentifierField; + + return stateIdentifiers; +} + +export function parseFoldersToState(folders: IngestFolder[]): StateFolder[] { + const stateFolders: StateFolder[] = folders.map(({ name, variantType }: IngestFolder, index: number) => ({ + id: index, + name, + variantType + })); + + return stateFolders; +} + +export function parseUVMapsToState(folders: IngestUvMap[]): StateUVMap[] { + const stateFolders: StateUVMap[] = folders.map(({ name, mapType }: IngestUvMap, index: number) => ({ + id: index, + name, + mapType + })); + + return stateFolders; +} From 7f9be55be8addfe5ecf1c1d23247559097c21b07 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Fri, 6 Nov 2020 12:40:51 +0530 Subject: [PATCH 071/239] minor fixes --- .../src/components/shared/IdentifierList.tsx | 51 +++++++++---------- .../components/Uploads/FileListItem.tsx | 2 +- 2 files changed, 25 insertions(+), 28 deletions(-) diff --git a/client/src/components/shared/IdentifierList.tsx b/client/src/components/shared/IdentifierList.tsx index cc6df098b..836f87fdd 100644 --- a/client/src/components/shared/IdentifierList.tsx +++ b/client/src/components/shared/IdentifierList.tsx @@ -11,20 +11,7 @@ import { MdRemoveCircleOutline } from 'react-icons/md'; import FieldType from './FieldType'; import { StateIdentifier, VocabularyOption } from '../../store'; -const useStyles = makeStyles(({ palette, typography, spacing, breakpoints }) => ({ - container: { - marginTop: 20 - }, - assetIdentifier: { - display: 'flex', - alignItems: 'center', - marginBottom: 10, - }, - systemCreatedText: { - marginLeft: spacing(2), - fontStyle: 'italic', - color: palette.primary.contrastText - }, +const useStyles = makeStyles(({ palette, typography, breakpoints }) => ({ identifierInput: { width: '75%', border: 'none', @@ -62,6 +49,14 @@ const useStyles = makeStyles(({ palette, typography, spacing, breakpoints }) => cursor: 'pointer' }, addIdentifier: { + display: 'flex', + alignItems: 'center', + width: 80, + borderRadius: 5, + padding: 10, + backgroundColor: palette.secondary.light + }, + addIdentifierButton: { height: 30, width: 80, fontSize: '0.8em', @@ -83,8 +78,8 @@ function IdentifierList(props: IdentifierListProps): React.ReactElement { return ( - - + {!!identifiers.length && ( + {identifiers.map(({ id, selected, identifier, identifierType }, index) => { const remove = () => onRemove(id); const updateCheckbox = ({ target }) => onUpdate(id, target.name, target.checked); @@ -125,17 +120,19 @@ function IdentifierList(props: IdentifierListProps): React.ReactElement { ); })} - - - + + )} + + + ); } diff --git a/client/src/pages/Ingestion/components/Uploads/FileListItem.tsx b/client/src/pages/Ingestion/components/Uploads/FileListItem.tsx index 58faf497c..8a8f9605e 100644 --- a/client/src/pages/Ingestion/components/Uploads/FileListItem.tsx +++ b/client/src/pages/Ingestion/components/Uploads/FileListItem.tsx @@ -157,7 +157,7 @@ function FileListItem(props: FileListItemProps): React.ReactElement { options = ( {!uploading && !failed && } - {uploading && !failed && } + {uploading && !failed && } {failed && } From 514eb78365d1a2bc772f78211d89c64cb480887f Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Fri, 6 Nov 2020 14:18:14 +0530 Subject: [PATCH 072/239] added source objects model component --- .../Metadata/Model/SourceObjects.tsx | 153 ++++++++++++++++++ 1 file changed, 153 insertions(+) create mode 100644 client/src/pages/Ingestion/components/Metadata/Model/SourceObjects.tsx diff --git a/client/src/pages/Ingestion/components/Metadata/Model/SourceObjects.tsx b/client/src/pages/Ingestion/components/Metadata/Model/SourceObjects.tsx new file mode 100644 index 000000000..b9e3cdba1 --- /dev/null +++ b/client/src/pages/Ingestion/components/Metadata/Model/SourceObjects.tsx @@ -0,0 +1,153 @@ +/** + * SourceObjectsList + * + * This component renders the source object list with add capability + */ +import { Box, Button, Typography } from '@material-ui/core'; +import { makeStyles } from '@material-ui/core/styles'; +import clsx from 'clsx'; +import React from 'react'; +import { MdRemoveCircleOutline } from 'react-icons/md'; +import { eSystemObjectType } from '../../../../../types/server'; +import { getTermForSystemObjectType } from '../../../../../utils/repository'; + +const useStyles = makeStyles(({ palette }) => ({ + container: { + display: 'flex', + width: '52vw', + flexDirection: 'column', + borderRadius: 5, + padding: 10, + backgroundColor: palette.primary.light + }, + list: { + padding: 10, + marginBottom: 10, + borderRadius: 5, + backgroundColor: palette.secondary.light + }, + header: { + fontSize: '0.9em', + color: palette.primary.dark + }, + label: { + fontSize: '0.8em', + color: palette.primary.dark + }, + labelUnderline: { + textDecoration: 'underline', + cursor: 'pointer' + }, + removeIcon: { + marginLeft: 20, + cursor: 'pointer' + }, + addButton: { + height: 30, + width: 80, + marginLeft: 5, + fontSize: '0.8em', + color: palette.background.paper, + } +})); + +export type StateSourceObject = { + id: number; + name: string; + identifier: string; + objectType: eSystemObjectType; +}; + +interface SourceObjectsListProps { + sourceObjects: StateSourceObject[]; + onAdd: () => void; + onRemove: (id: number) => void; +} + +function SourceObjectsList(props: SourceObjectsListProps): React.ReactElement { + const { sourceObjects, onAdd, onRemove } = props; + const classes = useStyles(); + + return ( + +
+ {!!sourceObjects.length && ( + + {sourceObjects.map((sourceObject: StateSourceObject, index: number) => )} + + )} + + + ); +} + +function Header(): React.ReactElement { + const classes = useStyles(); + + return ( + + + Source Object(s) + + + Identifiers + + + Object Type + + + ); +} + +interface ItemProps { + id: number; + name: string; + identifier: string; + objectType: eSystemObjectType; + onRemove: (id: number) => void; +} + +function Item(props: ItemProps): React.ReactElement { + const { id, name, identifier, objectType, onRemove } = props; + const classes = useStyles(); + + const remove = () => onRemove(id); + + return ( + + + {name} + + + {identifier} + + + {getTermForSystemObjectType(objectType)} + + + + ); +} + +export default SourceObjectsList; From 6e1893d137c8afe3fe76372155b9cdf1a5a598b3 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Fri, 6 Nov 2020 14:18:33 +0530 Subject: [PATCH 073/239] added object select modal --- .../Metadata/Model/ObjectSelectModal.tsx | 65 ++++ .../components/Metadata/Model/index.tsx | 304 ++++++++++-------- 2 files changed, 235 insertions(+), 134 deletions(-) create mode 100644 client/src/pages/Ingestion/components/Metadata/Model/ObjectSelectModal.tsx diff --git a/client/src/pages/Ingestion/components/Metadata/Model/ObjectSelectModal.tsx b/client/src/pages/Ingestion/components/Metadata/Model/ObjectSelectModal.tsx new file mode 100644 index 000000000..085acf455 --- /dev/null +++ b/client/src/pages/Ingestion/components/Metadata/Model/ObjectSelectModal.tsx @@ -0,0 +1,65 @@ +/** + * ObjectSelectModal + * + * This component renders the source object select modal which let's user select + * the source objects for a model. + */ +import { AppBar, Button, Dialog, IconButton, Slide, Toolbar, Typography } from '@material-ui/core'; +import { makeStyles } from '@material-ui/core/styles'; +import { TransitionProps } from '@material-ui/core/transitions'; +import CloseIcon from '@material-ui/icons/Close'; +import React from 'react'; +import { StateSourceObject } from './SourceObjects'; + +const useStyles = makeStyles(({ spacing }) => ({ + title: { + marginLeft: spacing(2), + flex: 1, + }, + appBar: { + position: 'relative', + color: 'white' + }, +})); + +interface ObjectSelectModalProps { + open: boolean; + onSelectedObjects: (newSourceObjects: StateSourceObject[]) => void; + onModalClose: () => void; +} + +function ObjectSelectModal(props: ObjectSelectModalProps): React.ReactElement { + const { open, onSelectedObjects, onModalClose } = props; + const classes = useStyles(); + + const onSave = () => { + onSelectedObjects([]); + }; + + return ( + + + + + + + + Select Source Objects + + + + + + ); +} + +const Transition = React.forwardRef(function Transition( + props: TransitionProps & { children?: React.ReactElement }, + ref: React.Ref, +) { + return ; +}); + +export default ObjectSelectModal; diff --git a/client/src/pages/Ingestion/components/Metadata/Model/index.tsx b/client/src/pages/Ingestion/components/Metadata/Model/index.tsx index eb9ded35b..38dc21f0b 100644 --- a/client/src/pages/Ingestion/components/Metadata/Model/index.tsx +++ b/client/src/pages/Ingestion/components/Metadata/Model/index.tsx @@ -5,13 +5,15 @@ */ import { Box, Checkbox } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; -import React from 'react'; +import React, { useState } from 'react'; import { AssetIdentifiers, DateInputField, FieldType, IdInputField, SelectField } from '../../../../../components'; import { StateIdentifier, useMetadataStore, useVocabularyStore } from '../../../../../store'; import { MetadataType } from '../../../../../store/metadata'; import { eVocabularySetID } from '../../../../../types/server'; import { withDefaultValueBoolean, withDefaultValueNumber } from '../../../../../utils/shared'; import BoundingBoxInput from './BoundingBoxInput'; +import ObjectSelectModal from './ObjectSelectModal'; +import SourceObjects, { StateSourceObject } from './SourceObjects'; import UVContents from './UVContents'; const useStyles = makeStyles(({ palette, typography }) => ({ @@ -35,6 +37,15 @@ const useStyles = makeStyles(({ palette, typography }) => ({ } })); +const mockSourceObjects: StateSourceObject[] = [ + { + id: 0, + name: 'PhotoSet1.zip', + identifier: 'kq4f7fdb94c-e9ea-4a34-b616-0806e8576da4', + objectType: 4 + } +]; + interface ModelProps { readonly metadataIndex: number; } @@ -47,6 +58,9 @@ function Model(props: ModelProps): React.ReactElement { const [updateMetadataField, getFieldErrors] = useMetadataStore(state => [state.updateMetadataField, state.getFieldErrors]); const [getEntries, getInitialEntry] = useVocabularyStore(state => [state.getEntries, state.getInitialEntry]); + const [modalOpen, setModalOpen] = useState(false); + const [sourceObjects, setSourceObjects] = useState(mockSourceObjects); + const errors = getFieldErrors(metadata); const onIdentifersChange = (identifiers: StateIdentifier[]): void => { @@ -91,154 +105,176 @@ function Model(props: ModelProps): React.ReactElement { updateMetadataField(metadataIndex, 'uvMaps', updatedUVMaps, MetadataType.model); }; + const openSourceObjectModal = () => { + setModalOpen(true); + }; + + const onRemoveSourceObject = (id: number) => { + const updatedSourceObjects = sourceObjects.filter(sourceObject => sourceObject.id !== id); + setSourceObjects(updatedSourceObjects); + }; + + const onModalClose = () => { + setModalOpen(false); + }; + + const onSelectedObjects = (newSourceObjects: StateSourceObject[]) => { + setSourceObjects([...sourceObjects, ...newSourceObjects]); + setModalOpen(false); + }; + const noteLabelProps = { style: { fontStyle: 'italic' } }; const noteFieldProps = { alignItems: 'center', style: { paddingBottom: 0 } }; const rowFieldProps = { alignItems: 'center', justifyContent: 'space-between' }; return ( - - - - - - setDateField('dateCaptured', value)} /> - - - - - - - - - - + + + + + + + setDateField('dateCaptured', value)} /> + + + - - - - - - - - - - - - - - - - - - - - + + + + + + + + - - - - - + - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + - + + - + + ); } From a55cc9f36acafae8743f398acd619bd881578d4f Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Fri, 6 Nov 2020 14:18:44 +0530 Subject: [PATCH 074/239] getVocabularyTerm method --- client/src/store/vocabulary.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/client/src/store/vocabulary.ts b/client/src/store/vocabulary.ts index 7b265c228..4fa9a3974 100644 --- a/client/src/store/vocabulary.ts +++ b/client/src/store/vocabulary.ts @@ -25,6 +25,7 @@ type VocabularyStore = { updateVocabularyEntries: () => Promise; getEntries: (eVocabularySetID: eVocabularySetID) => VocabularyOption[]; getInitialEntry: (eVocabularySetID: eVocabularySetID) => number | null; + getVocabularyTerm: (eVocabularySetID: eVocabularySetID) => string | null; getAssetType: (idVocabulary: number) => AssetType; }; @@ -92,6 +93,16 @@ export const useVocabularyStore = create((set: SetState { + const { vocabularies } = get(); + const vocabularyEntry = vocabularies.get(eVocabularySetID); + + if (vocabularyEntry && vocabularyEntry.length) { + return vocabularyEntry[0].Term; + } + + return null; + }, getAssetType: (idVocabulary: number): AssetType => { const { vocabularies } = get(); const vocabularyEntry = vocabularies.get(eVocabularySetID.eAssetAssetType); From 22f2c96b0272edf2a4dfd844e814e58e2876c02e Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Fri, 6 Nov 2020 16:39:57 +0530 Subject: [PATCH 075/239] Refactor repository tree for reusability with object selection modal --- .../Metadata/Model/ObjectSelectModal.tsx | 39 +++++++++-- .../Metadata/Model/SourceObjects.tsx | 27 +++----- .../components/Metadata/Model/index.tsx | 21 ++---- .../Metadata/Photogrammetry/AssetContents.tsx | 2 +- .../RepositoryTreeHeader.tsx | 5 +- .../RepositoryTreeView/TreeLabel.tsx | 16 ++++- .../components/RepositoryTreeView/index.tsx | 67 ++++++++++++++++--- client/src/store/metadata/metadata.types.ts | 8 +++ client/src/utils/repository.tsx | 21 +++++- 9 files changed, 154 insertions(+), 52 deletions(-) diff --git a/client/src/pages/Ingestion/components/Metadata/Model/ObjectSelectModal.tsx b/client/src/pages/Ingestion/components/Metadata/Model/ObjectSelectModal.tsx index 085acf455..01ed0bd24 100644 --- a/client/src/pages/Ingestion/components/Metadata/Model/ObjectSelectModal.tsx +++ b/client/src/pages/Ingestion/components/Metadata/Model/ObjectSelectModal.tsx @@ -4,36 +4,59 @@ * This component renders the source object select modal which let's user select * the source objects for a model. */ -import { AppBar, Button, Dialog, IconButton, Slide, Toolbar, Typography } from '@material-ui/core'; +import { AppBar, Box, Button, Dialog, IconButton, Slide, Toolbar, Typography } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; import { TransitionProps } from '@material-ui/core/transitions'; import CloseIcon from '@material-ui/icons/Close'; import React from 'react'; -import { StateSourceObject } from './SourceObjects'; +import { StateSourceObject } from '../../../../../store'; +import RepositoryFilterView from '../../../../Repository/components/RepositoryFilterView'; +import RepositoryTreeView from '../../../../Repository/components/RepositoryTreeView'; -const useStyles = makeStyles(({ spacing }) => ({ +const useStyles = makeStyles(({ palette, spacing, breakpoints }) => ({ title: { marginLeft: spacing(2), flex: 1, }, appBar: { position: 'relative', - color: 'white' + color: palette.background.paper }, + repositoryContainer: { + display: 'flex', + flexDirection: 'column', + padding: 20, + paddingBottom: 0, + [breakpoints.down('lg')]: { + padding: 10 + } + } })); interface ObjectSelectModalProps { open: boolean; + selectedObjects: StateSourceObject[]; onSelectedObjects: (newSourceObjects: StateSourceObject[]) => void; onModalClose: () => void; } function ObjectSelectModal(props: ObjectSelectModalProps): React.ReactElement { - const { open, onSelectedObjects, onModalClose } = props; + const { open, onSelectedObjects, selectedObjects, onModalClose } = props; const classes = useStyles(); + const [selected, setSelected] = React.useState(selectedObjects); const onSave = () => { - onSelectedObjects([]); + onSelectedObjects(selected); + setSelected([]); + }; + + const onSelect = (sourceObject: StateSourceObject) => { + setSelected([...selected, sourceObject]); + }; + + const onUnSelect = (idObject: number) => { + const updatedSelected = selected.filter(sourceObject => sourceObject.idObject !== idObject); + setSelected(updatedSelected); }; return ( @@ -51,6 +74,10 @@ function ObjectSelectModal(props: ObjectSelectModalProps): React.ReactElement { + + + + ); } diff --git a/client/src/pages/Ingestion/components/Metadata/Model/SourceObjects.tsx b/client/src/pages/Ingestion/components/Metadata/Model/SourceObjects.tsx index b9e3cdba1..c5a3196e8 100644 --- a/client/src/pages/Ingestion/components/Metadata/Model/SourceObjects.tsx +++ b/client/src/pages/Ingestion/components/Metadata/Model/SourceObjects.tsx @@ -8,6 +8,7 @@ import { makeStyles } from '@material-ui/core/styles'; import clsx from 'clsx'; import React from 'react'; import { MdRemoveCircleOutline } from 'react-icons/md'; +import { StateSourceObject } from '../../../../../store'; import { eSystemObjectType } from '../../../../../types/server'; import { getTermForSystemObjectType } from '../../../../../utils/repository'; @@ -22,6 +23,7 @@ const useStyles = makeStyles(({ palette }) => ({ }, list: { padding: 10, + paddingBottom: 0, marginBottom: 10, borderRadius: 5, backgroundColor: palette.secondary.light @@ -51,13 +53,6 @@ const useStyles = makeStyles(({ palette }) => ({ } })); -export type StateSourceObject = { - id: number; - name: string; - identifier: string; - objectType: eSystemObjectType; -}; - interface SourceObjectsListProps { sourceObjects: StateSourceObject[]; onAdd: () => void; @@ -101,10 +96,10 @@ function Header(): React.ReactElement { marginBottom={1} width='90%' > - + Source Object(s) - + Identifiers @@ -115,7 +110,7 @@ function Header(): React.ReactElement { } interface ItemProps { - id: number; + idObject: number; name: string; identifier: string; objectType: eSystemObjectType; @@ -123,10 +118,10 @@ interface ItemProps { } function Item(props: ItemProps): React.ReactElement { - const { id, name, identifier, objectType, onRemove } = props; + const { idObject, name, identifier, objectType, onRemove } = props; const classes = useStyles(); - const remove = () => onRemove(id); + const remove = () => onRemove(idObject); return ( - + {name} - + {identifier} {getTermForSystemObjectType(objectType)} - + ); } diff --git a/client/src/pages/Ingestion/components/Metadata/Model/index.tsx b/client/src/pages/Ingestion/components/Metadata/Model/index.tsx index 38dc21f0b..39c13ba2c 100644 --- a/client/src/pages/Ingestion/components/Metadata/Model/index.tsx +++ b/client/src/pages/Ingestion/components/Metadata/Model/index.tsx @@ -7,13 +7,13 @@ import { Box, Checkbox } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; import React, { useState } from 'react'; import { AssetIdentifiers, DateInputField, FieldType, IdInputField, SelectField } from '../../../../../components'; -import { StateIdentifier, useMetadataStore, useVocabularyStore } from '../../../../../store'; +import { StateIdentifier, StateSourceObject, useMetadataStore, useVocabularyStore } from '../../../../../store'; import { MetadataType } from '../../../../../store/metadata'; import { eVocabularySetID } from '../../../../../types/server'; import { withDefaultValueBoolean, withDefaultValueNumber } from '../../../../../utils/shared'; import BoundingBoxInput from './BoundingBoxInput'; import ObjectSelectModal from './ObjectSelectModal'; -import SourceObjects, { StateSourceObject } from './SourceObjects'; +import SourceObjects from './SourceObjects'; import UVContents from './UVContents'; const useStyles = makeStyles(({ palette, typography }) => ({ @@ -37,15 +37,6 @@ const useStyles = makeStyles(({ palette, typography }) => ({ } })); -const mockSourceObjects: StateSourceObject[] = [ - { - id: 0, - name: 'PhotoSet1.zip', - identifier: 'kq4f7fdb94c-e9ea-4a34-b616-0806e8576da4', - objectType: 4 - } -]; - interface ModelProps { readonly metadataIndex: number; } @@ -59,7 +50,7 @@ function Model(props: ModelProps): React.ReactElement { const [getEntries, getInitialEntry] = useVocabularyStore(state => [state.getEntries, state.getInitialEntry]); const [modalOpen, setModalOpen] = useState(false); - const [sourceObjects, setSourceObjects] = useState(mockSourceObjects); + const [sourceObjects, setSourceObjects] = useState([]); const errors = getFieldErrors(metadata); @@ -109,8 +100,8 @@ function Model(props: ModelProps): React.ReactElement { setModalOpen(true); }; - const onRemoveSourceObject = (id: number) => { - const updatedSourceObjects = sourceObjects.filter(sourceObject => sourceObject.id !== id); + const onRemoveSourceObject = (idObject: number) => { + const updatedSourceObjects = sourceObjects.filter(sourceObject => sourceObject.idObject !== idObject); setSourceObjects(updatedSourceObjects); }; @@ -273,7 +264,7 @@ function Model(props: ModelProps): React.ReactElement { - + ); } diff --git a/client/src/pages/Ingestion/components/Metadata/Photogrammetry/AssetContents.tsx b/client/src/pages/Ingestion/components/Metadata/Photogrammetry/AssetContents.tsx index 140d97de5..f5016fcc6 100644 --- a/client/src/pages/Ingestion/components/Metadata/Photogrammetry/AssetContents.tsx +++ b/client/src/pages/Ingestion/components/Metadata/Photogrammetry/AssetContents.tsx @@ -27,7 +27,7 @@ const useStyles = makeStyles(({ palette, typography, breakpoints, spacing }) => color: palette.primary.dark }, emptyFolders: { - marginTop: 10, + margin: '10px 0px', color: palette.grey[600], textAlign: 'center' }, diff --git a/client/src/pages/Repository/components/RepositoryTreeView/RepositoryTreeHeader.tsx b/client/src/pages/Repository/components/RepositoryTreeView/RepositoryTreeHeader.tsx index fdc602a92..ab90514d7 100644 --- a/client/src/pages/Repository/components/RepositoryTreeView/RepositoryTreeHeader.tsx +++ b/client/src/pages/Repository/components/RepositoryTreeView/RepositoryTreeHeader.tsx @@ -52,16 +52,17 @@ const useStyles = makeStyles(({ palette, typography, breakpoints }) => ({ })); interface RepositoryTreeHeaderProps { + fullWidth?: boolean; metadataColumns: eMetadata[]; } function RepositoryTreeHeader(props: RepositoryTreeHeaderProps): React.ReactElement { - const { metadataColumns } = props; + const { fullWidth = false, metadataColumns } = props; const classes = useStyles(); const sideBarExpanded = useControlStore(state => state.sideBarExpanded); const treeColumns = getTreeViewColumns(metadataColumns, true); - const width = getTreeWidth(treeColumns.length, sideBarExpanded); + const width = getTreeWidth(treeColumns.length, sideBarExpanded, fullWidth); return ( diff --git a/client/src/pages/Repository/components/RepositoryTreeView/TreeLabel.tsx b/client/src/pages/Repository/components/RepositoryTreeView/TreeLabel.tsx index 16ff387e3..a6f77f3de 100644 --- a/client/src/pages/Repository/components/RepositoryTreeView/TreeLabel.tsx +++ b/client/src/pages/Repository/components/RepositoryTreeView/TreeLabel.tsx @@ -3,11 +3,15 @@ * * This component renders a tree label for StyledTreeItem. */ +import { Box } from '@material-ui/core'; +import { grey } from '@material-ui/core/colors'; import { makeStyles } from '@material-ui/core/styles'; import clsx from 'clsx'; import lodash from 'lodash'; import React, { useMemo } from 'react'; +import { FaCheckCircle, FaRegCircle } from 'react-icons/fa'; import { Progress } from '../../../../components'; +import { palette } from '../../../../theme'; import { getTermForSystemObjectType } from '../../../../utils/repository'; import MetadataView, { TreeViewColumn } from './MetadataView'; @@ -41,16 +45,26 @@ interface TreeLabelProps { objectType: number; color: string; treeColumns: TreeViewColumn[]; + renderSelected?: boolean; + selected?: boolean; + onSelect?: (event: React.MouseEvent) => void; + onUnSelect?: (event: React.MouseEvent) => void; } function TreeLabel(props: TreeLabelProps): React.ReactElement { - const { label, treeColumns, objectType } = props; + const { label, treeColumns, renderSelected = false, selected = false, onSelect, onUnSelect, objectType } = props; const classes = useStyles(props); const objectTitle = useMemo(() => `${getTermForSystemObjectType(objectType)} ${label}`, [objectType, label]); return (
+ {renderSelected && ( + + {!selected && } + {selected && } + + )}
{label}
diff --git a/client/src/pages/Repository/components/RepositoryTreeView/index.tsx b/client/src/pages/Repository/components/RepositoryTreeView/index.tsx index 4d145e21f..14e85465e 100644 --- a/client/src/pages/Repository/components/RepositoryTreeView/index.tsx +++ b/client/src/pages/Repository/components/RepositoryTreeView/index.tsx @@ -10,9 +10,9 @@ import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; import { TreeView } from '@material-ui/lab'; import React, { useCallback, useEffect } from 'react'; import { Loader } from '../../../../components'; -import { treeRootKey, useControlStore, useRepositoryStore } from '../../../../store'; +import { StateSourceObject, treeRootKey, useControlStore, useRepositoryStore } from '../../../../store'; import { NavigationResultEntry } from '../../../../types/graphql'; -import { getObjectInterfaceDetails, getRepositoryTreeNodeId, getTreeColorVariant, getTreeViewColumns, getTreeWidth } from '../../../../utils/repository'; +import { getObjectInterfaceDetails, getRepositoryTreeNodeId, getTreeColorVariant, getTreeViewColumns, getTreeWidth, isRepositoryItemSelected } from '../../../../utils/repository'; import RepositoryTreeHeader from './RepositoryTreeHeader'; import StyledTreeItem from './StyledTreeItem'; import TreeLabel, { TreeLabelEmpty, TreeLabelLoading } from './TreeLabel'; @@ -34,7 +34,10 @@ const useStyles = makeStyles(({ breakpoints }) => ({ tree: { display: 'flex', flexDirection: 'column', - flex: 1, + flex: 1 + }, + fullWidth: { + maxWidth: '95.5vw' } })); @@ -43,7 +46,16 @@ type StyleProps = { isExpanded: boolean; }; -function RepositoryTreeView(): React.ReactElement { +interface RepositoryTreeViewProps { + isModal?: boolean; + selectedItems?: StateSourceObject[]; + onSelect?: (item: StateSourceObject) => void; + onUnSelect?: (id: number) => void; +} + +function RepositoryTreeView(props: RepositoryTreeViewProps): React.ReactElement { + const { isModal = false, selectedItems, onSelect, onUnSelect } = props; + const [loading, isExpanded] = useRepositoryStore(useCallback(state => [state.loading, state.isExpanded], [])); const sideBarExpanded = useControlStore(state => state.sideBarExpanded); @@ -66,7 +78,7 @@ function RepositoryTreeView(): React.ReactElement { } }, [tree, getChildren]); - const renderTree = useCallback((children: NavigationResultEntry[] | undefined) => { + const renderTree = (children: NavigationResultEntry[] | undefined) => { if (!children) return null; return children.map((child: NavigationResultEntry, index: number) => { const { idSystemObject, objectType, idObject, name, metadata } = child; @@ -86,8 +98,41 @@ function RepositoryTreeView(): React.ReactElement { const variant = getTreeColorVariant(index); const { icon, color } = getObjectInterfaceDetails(objectType, variant); const treeColumns = getTreeViewColumns(metadataColumns, false, metadata); + const isSelected = isRepositoryItemSelected(nodeId, selectedItems || []); + + const select = (event: React.MouseEvent) => { + if (onSelect) { + event.stopPropagation(); + const repositoryItem = { + idObject, + name, + objectType, + identifier: 'TODO: get identifier' + }; + onSelect(repositoryItem); + } + }; - const label: React.ReactNode = ; + const unSelect = (event: React.MouseEvent) => { + // TODO: implement unselect + if (onUnSelect) { + event.stopPropagation(); + onUnSelect(idObject); + } + }; + + const label: React.ReactNode = ( + + ); return ( ); }); - }, [tree, metadataColumns]); + }; let content: React.ReactNode = ; if (!loading) { const treeColumns = getTreeViewColumns(metadataColumns, false); - const width = getTreeWidth(treeColumns.length, sideBarExpanded); + const width = getTreeWidth(treeColumns.length, sideBarExpanded, isModal); const children = tree.get(treeRootKey); content = ( @@ -118,14 +163,16 @@ function RepositoryTreeView(): React.ReactElement { onNodeToggle={onNodeToggle} style={{ width }} > - + {renderTree(children)} ); } + const fullWidthStyles = isModal ? { maxWidth: '98vw' } : {}; + return ( -
+
{content}
); diff --git a/client/src/store/metadata/metadata.types.ts b/client/src/store/metadata/metadata.types.ts index 91ee7dbfd..1852ef63d 100644 --- a/client/src/store/metadata/metadata.types.ts +++ b/client/src/store/metadata/metadata.types.ts @@ -1,3 +1,4 @@ +import { eSystemObjectType } from '../../types/server'; /** * Metadata Store Types * @@ -127,4 +128,11 @@ export type OtherFields = { identifiers: StateIdentifier[]; }; +export type StateSourceObject = { + idObject: number; + name: string; + identifier: string; + objectType: eSystemObjectType; +}; + export type ValidateFields = PhotogrammetryFields | ModelFields | SceneFields | OtherFields; \ No newline at end of file diff --git a/client/src/utils/repository.tsx b/client/src/utils/repository.tsx index ca7cc22b3..69b23edb3 100644 --- a/client/src/utils/repository.tsx +++ b/client/src/utils/repository.tsx @@ -12,6 +12,7 @@ import { RepositoryIcon } from '../components'; import { HOME_ROUTES } from '../constants'; import { RepositoryFilter } from '../pages/Repository'; import { TreeViewColumn } from '../pages/Repository/components/RepositoryTreeView/MetadataView'; +import { StateSourceObject } from '../store'; import Colors, { RepositoryColorVariant } from '../theme/colors'; import { NavigationResultEntry } from '../types/graphql'; import { eMetadata, eSystemObjectType } from '../types/server'; @@ -117,10 +118,14 @@ export function generateRepositoryUrl(filter: RepositoryFilter): string { return `${HOME_ROUTES.REPOSITORY}?${qs.stringify(queryResult)}`; } -export function getTreeWidth(columnSize: number, sideBarExpanded: boolean): string { +export function getTreeWidth(columnSize: number, sideBarExpanded: boolean, fullWidth: boolean): string { const computedWidth = 50 + columnSize * 10; const isXLScreen = window.innerWidth >= 1600; + if (fullWidth) { + return '98vw'; + } + if (computedWidth <= 80) { if (isXLScreen) { if (sideBarExpanded) return '85vw'; @@ -210,4 +215,18 @@ export function getObjectInterfaceDetails(objectType: eSystemObjectType, variant export function sortEntriesAlphabetically(entries: NavigationResultEntry[]): NavigationResultEntry[] { return lodash.orderBy(entries, [entry => entry.name.toLowerCase().trim()], ['asc']); +} + +export function isRepositoryItemSelected(nodeId: string, sourceObjects: StateSourceObject[]): boolean { + const { idObject } = parseRepositoryTreeNodeId(nodeId); + + for (let i = 0; i < sourceObjects.length; i++) { + const sourceObject = sourceObjects[i]; + + if (sourceObject.idObject === idObject) { + return true; + } + } + + return false; } \ No newline at end of file From 82ad13204674080de6cd83ca6c6b816aa808411c Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Fri, 6 Nov 2020 16:49:19 +0530 Subject: [PATCH 076/239] update search header --- client/src/components/shared/Header.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/components/shared/Header.tsx b/client/src/components/shared/Header.tsx index 0e29529e0..dff9b87e3 100644 --- a/client/src/components/shared/Header.tsx +++ b/client/src/components/shared/Header.tsx @@ -34,11 +34,11 @@ const useStyles = makeStyles(({ palette, spacing, typography, breakpoints }) => display: 'flex', alignItems: 'center', marginLeft: 50, - padding: 5, + padding: '5px 10px', width: '40vw', minWidth: '30vw', borderRadius: 5, - border: `0.25px solid ${fade(Colors.defaults.white, 0.65)}`, + backgroundColor: fade(Colors.defaults.white, 0.1), [breakpoints.down('lg')]: { marginLeft: 30, }, From 52722cb160eba13a9cb319d249e62213b4b56da1 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Mon, 9 Nov 2020 13:11:57 +0530 Subject: [PATCH 077/239] use idSO for source objects --- .../components/Metadata/Model/ObjectSelectModal.tsx | 6 +++--- .../Ingestion/components/Metadata/Model/SourceObjects.tsx | 6 +++--- .../pages/Ingestion/components/Metadata/Model/index.tsx | 4 ++-- .../src/pages/Ingestion/components/Uploads/UploadList.tsx | 5 +---- .../Repository/components/RepositoryTreeView/index.tsx | 7 +++---- client/src/store/metadata/metadata.types.ts | 2 +- client/src/utils/repository.tsx | 4 ++-- 7 files changed, 15 insertions(+), 19 deletions(-) diff --git a/client/src/pages/Ingestion/components/Metadata/Model/ObjectSelectModal.tsx b/client/src/pages/Ingestion/components/Metadata/Model/ObjectSelectModal.tsx index 01ed0bd24..3f5def135 100644 --- a/client/src/pages/Ingestion/components/Metadata/Model/ObjectSelectModal.tsx +++ b/client/src/pages/Ingestion/components/Metadata/Model/ObjectSelectModal.tsx @@ -50,12 +50,12 @@ function ObjectSelectModal(props: ObjectSelectModalProps): React.ReactElement { setSelected([]); }; - const onSelect = (sourceObject: StateSourceObject) => { + const onSelect = (sourceObject: StateSourceObject): void => { setSelected([...selected, sourceObject]); }; - const onUnSelect = (idObject: number) => { - const updatedSelected = selected.filter(sourceObject => sourceObject.idObject !== idObject); + const onUnSelect = (idSystemObject: number): void => { + const updatedSelected = selected.filter(sourceObject => sourceObject.idSystemObject !== idSystemObject); setSelected(updatedSelected); }; diff --git a/client/src/pages/Ingestion/components/Metadata/Model/SourceObjects.tsx b/client/src/pages/Ingestion/components/Metadata/Model/SourceObjects.tsx index c5a3196e8..f9c5842d3 100644 --- a/client/src/pages/Ingestion/components/Metadata/Model/SourceObjects.tsx +++ b/client/src/pages/Ingestion/components/Metadata/Model/SourceObjects.tsx @@ -110,7 +110,7 @@ function Header(): React.ReactElement { } interface ItemProps { - idObject: number; + idSystemObject: number; name: string; identifier: string; objectType: eSystemObjectType; @@ -118,10 +118,10 @@ interface ItemProps { } function Item(props: ItemProps): React.ReactElement { - const { idObject, name, identifier, objectType, onRemove } = props; + const { idSystemObject, name, identifier, objectType, onRemove } = props; const classes = useStyles(); - const remove = () => onRemove(idObject); + const remove = () => onRemove(idSystemObject); return ( { - const updatedSourceObjects = sourceObjects.filter(sourceObject => sourceObject.idObject !== idObject); + const onRemoveSourceObject = (idSystemObject: number): void => { + const updatedSourceObjects = sourceObjects.filter(sourceObject => sourceObject.idSystemObject !== idSystemObject); setSourceObjects(updatedSourceObjects); }; diff --git a/client/src/pages/Ingestion/components/Uploads/UploadList.tsx b/client/src/pages/Ingestion/components/Uploads/UploadList.tsx index aa565fb80..048a32520 100644 --- a/client/src/pages/Ingestion/components/Uploads/UploadList.tsx +++ b/client/src/pages/Ingestion/components/Uploads/UploadList.tsx @@ -40,10 +40,7 @@ export const useUploadListStyles = makeStyles(({ palette, breakpoints }) => ({ textAlign: 'center', color: palette.grey[500], fontStyle: 'italic', - marginTop: '8%', - [breakpoints.down('lg')]: { - marginTop: '10%', - } + marginTop: '8%' }, })); diff --git a/client/src/pages/Repository/components/RepositoryTreeView/index.tsx b/client/src/pages/Repository/components/RepositoryTreeView/index.tsx index 14e85465e..43a3b7732 100644 --- a/client/src/pages/Repository/components/RepositoryTreeView/index.tsx +++ b/client/src/pages/Repository/components/RepositoryTreeView/index.tsx @@ -103,8 +103,8 @@ function RepositoryTreeView(props: RepositoryTreeViewProps): React.ReactElement const select = (event: React.MouseEvent) => { if (onSelect) { event.stopPropagation(); - const repositoryItem = { - idObject, + const repositoryItem: StateSourceObject = { + idSystemObject, name, objectType, identifier: 'TODO: get identifier' @@ -114,10 +114,9 @@ function RepositoryTreeView(props: RepositoryTreeViewProps): React.ReactElement }; const unSelect = (event: React.MouseEvent) => { - // TODO: implement unselect if (onUnSelect) { event.stopPropagation(); - onUnSelect(idObject); + onUnSelect(idSystemObject); } }; diff --git a/client/src/store/metadata/metadata.types.ts b/client/src/store/metadata/metadata.types.ts index 1852ef63d..112e6723e 100644 --- a/client/src/store/metadata/metadata.types.ts +++ b/client/src/store/metadata/metadata.types.ts @@ -129,7 +129,7 @@ export type OtherFields = { }; export type StateSourceObject = { - idObject: number; + idSystemObject: number; name: string; identifier: string; objectType: eSystemObjectType; diff --git a/client/src/utils/repository.tsx b/client/src/utils/repository.tsx index 69b23edb3..7c69c467c 100644 --- a/client/src/utils/repository.tsx +++ b/client/src/utils/repository.tsx @@ -218,12 +218,12 @@ export function sortEntriesAlphabetically(entries: NavigationResultEntry[]): Nav } export function isRepositoryItemSelected(nodeId: string, sourceObjects: StateSourceObject[]): boolean { - const { idObject } = parseRepositoryTreeNodeId(nodeId); + const { idSystemObject } = parseRepositoryTreeNodeId(nodeId); for (let i = 0; i < sourceObjects.length; i++) { const sourceObject = sourceObjects[i]; - if (sourceObject.idObject === idObject) { + if (sourceObject.idSystemObject === idSystemObject) { return true; } } From 9c3c469c7d538a1fb1b7beab38093a230ab62b68 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Mon, 9 Nov 2020 14:11:37 +0530 Subject: [PATCH 078/239] added reference model component --- .../Metadata/Model/SourceObjects.tsx | 25 +-- .../Metadata/Scene/ReferenceModels.tsx | 163 ++++++++++++++++++ .../components/Metadata/Scene/index.tsx | 2 + client/src/store/metadata/metadata.types.ts | 13 ++ 4 files changed, 192 insertions(+), 11 deletions(-) create mode 100644 client/src/pages/Ingestion/components/Metadata/Scene/ReferenceModels.tsx diff --git a/client/src/pages/Ingestion/components/Metadata/Model/SourceObjects.tsx b/client/src/pages/Ingestion/components/Metadata/Model/SourceObjects.tsx index f9c5842d3..5c725a897 100644 --- a/client/src/pages/Ingestion/components/Metadata/Model/SourceObjects.tsx +++ b/client/src/pages/Ingestion/components/Metadata/Model/SourceObjects.tsx @@ -1,7 +1,7 @@ /** * SourceObjectsList * - * This component renders the source object list with add capability + * This component renders the source object list with add capability. */ import { Box, Button, Typography } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; @@ -9,7 +9,6 @@ import clsx from 'clsx'; import React from 'react'; import { MdRemoveCircleOutline } from 'react-icons/md'; import { StateSourceObject } from '../../../../../store'; -import { eSystemObjectType } from '../../../../../types/server'; import { getTermForSystemObjectType } from '../../../../../utils/repository'; const useStyles = makeStyles(({ palette }) => ({ @@ -29,7 +28,7 @@ const useStyles = makeStyles(({ palette }) => ({ backgroundColor: palette.secondary.light }, header: { - fontSize: '0.9em', + fontSize: '0.8em', color: palette.primary.dark }, label: { @@ -63,12 +62,14 @@ function SourceObjectsList(props: SourceObjectsListProps): React.ReactElement { const { sourceObjects, onAdd, onRemove } = props; const classes = useStyles(); + const hasSourceObjects = !!sourceObjects.length; + return (
- {!!sourceObjects.length && ( + {hasSourceObjects && ( - {sourceObjects.map((sourceObject: StateSourceObject, index: number) => )} + {sourceObjects.map((sourceObject: StateSourceObject, index: number) => )} )} + + ); + } + + return ( + + {content} + + ); +} + +export default ObjectNotFoundView; \ No newline at end of file diff --git a/client/src/pages/Repository/components/DetailsView/index.tsx b/client/src/pages/Repository/components/DetailsView/index.tsx index 3df2eaa3b..bf8137908 100644 --- a/client/src/pages/Repository/components/DetailsView/index.tsx +++ b/client/src/pages/Repository/components/DetailsView/index.tsx @@ -6,26 +6,81 @@ import { Box } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; import React from 'react'; +import { useParams } from 'react-router'; +import { eSystemObjectType } from '../../../../types/server'; +import DetailsHeader from './DetailsHeader'; +import ObjectNotFoundView from './ObjectNotFoundView'; -const useStyles = makeStyles(({ palette }) => ({ +const useStyles = makeStyles(({ palette, breakpoints }) => ({ container: { display: 'flex', flex: 1, flexDirection: 'column', - padding: 10, + padding: 20, marginBottom: 20, borderRadius: 10, - backgroundColor: palette.primary.light + backgroundColor: palette.primary.light, + [breakpoints.down('lg')]: { + padding: 10 + } } })); +const mockData = { + 1: { + name: 'PhotoSet1.zip', + retired: true, + objectType: eSystemObjectType.eCaptureData, + path: [ + { + idSystemObject: 0, + name: 'USNM', + objectType: eSystemObjectType.eUnit, + }, { + idSystemObject: 1, + name: 'Armstrong Suit', + objectType: eSystemObjectType.eProject, + }, { + idSystemObject: 0, + name: 'Armstrong Glove', + objectType: eSystemObjectType.eSubject, + }, { + idSystemObject: 0, + name: 'Armstrong Glove Full', + objectType: eSystemObjectType.eItem, + }, { + idSystemObject: 0, + name: 'PhotoSet1.zip', + objectType: eSystemObjectType.eCaptureData, + } + ] + } +}; + +type DetailsParams = { + idSystemObject: string; +}; function DetailsView(): React.ReactElement { const classes = useStyles(); + const params = useParams(); + const data = mockData[params.idSystemObject]; + + if (!data) { + return ; + } + + const { name, objectType, path, retired } = data; return ( - + ); } From d6f067af036ba48030a184723be4b2a926da98d6 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Thu, 12 Nov 2020 13:04:06 +0530 Subject: [PATCH 082/239] make reusable link component and connect with details page --- client/src/components/index.ts | 4 ++- .../src/components/shared/BreadcrumbsView.tsx | 11 +++--- client/src/components/shared/Header.tsx | 1 - client/src/components/shared/NewTabLink.tsx | 35 +++++++++++++++++++ .../Metadata/Model/SourceObjects.tsx | 11 +++--- .../Metadata/Scene/ReferenceModels.tsx | 16 +++++---- client/src/store/metadata/metadata.types.ts | 3 +- 7 files changed, 58 insertions(+), 23 deletions(-) create mode 100644 client/src/components/shared/NewTabLink.tsx diff --git a/client/src/components/index.ts b/client/src/components/index.ts index c28465d94..7b747a72a 100644 --- a/client/src/components/index.ts +++ b/client/src/components/index.ts @@ -17,6 +17,7 @@ import SelectField from './controls/SelectField'; import IdInputField from './controls/IdInputField'; import DateInputField from './controls/DateInputField'; import BreadcrumbsView from './shared/BreadcrumbsView'; +import NewTabLink from './shared/NewTabLink'; export { Header, @@ -33,5 +34,6 @@ export { SelectField, IdInputField, DateInputField, - BreadcrumbsView + BreadcrumbsView, + NewTabLink }; diff --git a/client/src/components/shared/BreadcrumbsView.tsx b/client/src/components/shared/BreadcrumbsView.tsx index ab158745a..a9e57f77b 100644 --- a/client/src/components/shared/BreadcrumbsView.tsx +++ b/client/src/components/shared/BreadcrumbsView.tsx @@ -8,9 +8,10 @@ import { fade, makeStyles } from '@material-ui/core/styles'; import clsx from 'clsx'; import React from 'react'; import { MdNavigateNext } from 'react-icons/md'; -import { Link } from 'react-router-dom'; import { RepositoryPath } from '../../store'; +import { Colors } from '../../theme'; import { getDetailsUrlForObject, getTermForSystemObjectType } from '../../utils/repository'; +import NewTabLink from './NewTabLink'; const useStyles = makeStyles(({ palette, breakpoints }) => ({ container: { @@ -23,10 +24,6 @@ const useStyles = makeStyles(({ palette, breakpoints }) => ({ padding: '5px 10px', borderRadius: 5, }, - link: { - color: palette.background.paper, - textDecoration: 'none' - }, label: { [breakpoints.down('lg')]: { fontSize: '0.8em' @@ -44,9 +41,9 @@ function BreadcrumbsView(props: BreadcrumbsViewProps): React.ReactElement { const classes = useStyles(); const renderBreadcrumbs = ({ idSystemObject, name, objectType }: RepositoryPath, index: number) => ( - + {getTermForSystemObjectType(objectType)} {name} - + ); return ( diff --git a/client/src/components/shared/Header.tsx b/client/src/components/shared/Header.tsx index fc5a3f037..455c9d825 100644 --- a/client/src/components/shared/Header.tsx +++ b/client/src/components/shared/Header.tsx @@ -27,7 +27,6 @@ const useStyles = makeStyles(({ palette, spacing, typography, breakpoints }) => color: Colors.defaults.white }, logo: { - cursor: 'pointer', paddingRight: spacing(2), }, searchBox: { diff --git a/client/src/components/shared/NewTabLink.tsx b/client/src/components/shared/NewTabLink.tsx new file mode 100644 index 000000000..ab674c8e6 --- /dev/null +++ b/client/src/components/shared/NewTabLink.tsx @@ -0,0 +1,35 @@ +/** + * NewTabLink + * + * This is a reusable link component. + */ +import { makeStyles } from '@material-ui/core/styles'; +import React from 'react'; +import { Link, LinkProps } from 'react-router-dom'; + +const useStyles = makeStyles(({ palette }) => ({ + link: { + color: ({ color }: NewTabLinkProps) => color || palette.primary.dark, + textDecoration: 'none' + } +})); + +interface NewTabLinkProps { + color?: string; +} + +function NewTabLink(props: NewTabLinkProps & LinkProps): React.ReactElement { + const classes = useStyles(props); + + if (props.children) { + return ( + + {props.children} + + ); + } + + return ; +} + +export default NewTabLink; \ No newline at end of file diff --git a/client/src/pages/Ingestion/components/Metadata/Model/SourceObjects.tsx b/client/src/pages/Ingestion/components/Metadata/Model/SourceObjects.tsx index 5c725a897..17956f3af 100644 --- a/client/src/pages/Ingestion/components/Metadata/Model/SourceObjects.tsx +++ b/client/src/pages/Ingestion/components/Metadata/Model/SourceObjects.tsx @@ -8,8 +8,9 @@ import { makeStyles } from '@material-ui/core/styles'; import clsx from 'clsx'; import React from 'react'; import { MdRemoveCircleOutline } from 'react-icons/md'; +import { NewTabLink } from '../../../../../components'; import { StateSourceObject } from '../../../../../store'; -import { getTermForSystemObjectType } from '../../../../../utils/repository'; +import { getDetailsUrlForObject, getTermForSystemObjectType } from '../../../../../utils/repository'; const useStyles = makeStyles(({ palette }) => ({ container: { @@ -122,10 +123,6 @@ function Item(props: ItemProps): React.ReactElement { const remove = () => onRemove(idSystemObject); - const onModelDetail = () => { - alert('TODO: Handle source object click'); - }; - return ( - {name} + + {name} + {identifier} diff --git a/client/src/pages/Ingestion/components/Metadata/Scene/ReferenceModels.tsx b/client/src/pages/Ingestion/components/Metadata/Scene/ReferenceModels.tsx index 3cad7dcab..fbb9885fe 100644 --- a/client/src/pages/Ingestion/components/Metadata/Scene/ReferenceModels.tsx +++ b/client/src/pages/Ingestion/components/Metadata/Scene/ReferenceModels.tsx @@ -7,7 +7,9 @@ import { Box, Typography } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; import clsx from 'clsx'; import React from 'react'; +import { NewTabLink } from '../../../../../components'; import { ReferenceModelAction, StateReferenceModel } from '../../../../../store'; +import { getDetailsUrlForObject } from '../../../../../utils/repository'; import { formatBytes } from '../../../../../utils/upload'; const useStyles = makeStyles(({ palette }) => ({ @@ -48,6 +50,7 @@ const useStyles = makeStyles(({ palette }) => ({ const mockReferenceModels: StateReferenceModel[] = [ { + idSystemObject: 1, model: 'Armstrong1.obj', fileSize: 1.27e+7, uvResolution: 2000, @@ -55,6 +58,7 @@ const mockReferenceModels: StateReferenceModel[] = [ action: ReferenceModelAction.Update }, { + idSystemObject: 1, model: 'Armstrong2.obj', fileSize: 8.7e+6, uvResolution: 500, @@ -116,15 +120,11 @@ interface ItemProps { function Item(props: ItemProps): React.ReactElement { const { referenceModel } = props; - const { model, fileSize, uvResolution, boundingBox, action } = referenceModel; + const { idSystemObject, model, fileSize, uvResolution, boundingBox, action } = referenceModel; const classes = useStyles(); - const onModelDetail = () => { - alert('TODO: Handle model click'); - }; - const onAction = () => { - alert(`TODO: Handle ${action.toString()} action`); + alert(`TODO: KARAN: Handle ${action.toString()} action`); }; return ( @@ -136,7 +136,9 @@ function Item(props: ItemProps): React.ReactElement { marginBottom={1} > - {model} + + {model} + {formatBytes(fileSize)} diff --git a/client/src/store/metadata/metadata.types.ts b/client/src/store/metadata/metadata.types.ts index b32e72fab..65dfe8be3 100644 --- a/client/src/store/metadata/metadata.types.ts +++ b/client/src/store/metadata/metadata.types.ts @@ -141,10 +141,11 @@ export enum ReferenceModelAction { } export type StateReferenceModel = { + idSystemObject: number; model: string; fileSize: number; uvResolution: number; - boundingBox: string; // TODO: convert into individual points + boundingBox: string; // TODO: KARAN: convert into individual points action: ReferenceModelAction; }; From baf9b52bd4d2b70b7f77409740c350f214c76788 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Thu, 12 Nov 2020 15:11:59 +0530 Subject: [PATCH 083/239] fetch vocabulary while initializing --- client/src/index.tsx | 6 ++- .../Ingestion/components/Uploads/index.tsx | 40 +++++-------------- 2 files changed, 14 insertions(+), 32 deletions(-) diff --git a/client/src/index.tsx b/client/src/index.tsx index c56bde406..cd19bcbec 100644 --- a/client/src/index.tsx +++ b/client/src/index.tsx @@ -18,17 +18,19 @@ import './global/root.css'; import { apolloClient } from './graphql'; import { Home, Login } from './pages'; import * as serviceWorker from './serviceWorker'; -import { useUserStore } from './store'; +import { useUserStore, useVocabularyStore } from './store'; import theme from './theme'; function AppRouter(): React.ReactElement { const [loading, setLoading] = useState(true); const initialize = useUserStore(state => state.initialize); + const updateVocabularyEntries = useVocabularyStore(state => state.updateVocabularyEntries); const initializeUser = useCallback(async () => { await initialize(); + await updateVocabularyEntries(); setLoading(false); - }, [initialize]); + }, [initialize, updateVocabularyEntries]); useEffect(() => { initializeUser(); diff --git a/client/src/pages/Ingestion/components/Uploads/index.tsx b/client/src/pages/Ingestion/components/Uploads/index.tsx index 9890155c4..31caead4c 100644 --- a/client/src/pages/Ingestion/components/Uploads/index.tsx +++ b/client/src/pages/Ingestion/components/Uploads/index.tsx @@ -6,13 +6,13 @@ */ import { Box } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; -import React, { useEffect, useState } from 'react'; +import React, { useState } from 'react'; import KeepAlive from 'react-activation'; import { useHistory } from 'react-router'; import { toast } from 'react-toastify'; -import { Loader, SidebarBottomNavigator } from '../../../../components'; +import { SidebarBottomNavigator } from '../../../../components'; import { HOME_ROUTES, INGESTION_ROUTE, resolveSubRoute } from '../../../../constants'; -import { useMetadataStore, useUploadStore, useVocabularyStore } from '../../../../store'; +import { useMetadataStore, useUploadStore } from '../../../../store'; import { Colors } from '../../../../theme'; import UploadCompleteList from './UploadCompleteList'; import UploadFilesPicker from './UploadFilesPicker'; @@ -63,22 +63,10 @@ const useStyles = makeStyles(({ palette, typography, spacing }) => ({ function Uploads(): React.ReactElement { const classes = useStyles(); const history = useHistory(); - const [loadingVocabulary, setLoadingVocabulary] = useState(true); const [gettingAssetDetails, setGettingAssetDetails] = useState(false); const [discardingFiles, setDiscardingFiles] = useState(false); const [completed, discardFiles] = useUploadStore(state => [state.completed,state.discardFiles]); const updateMetadataSteps = useMetadataStore(state => state.updateMetadataSteps); - const updateVocabularyEntries = useVocabularyStore(state => state.updateVocabularyEntries); - - const fetchVocabularyEntries = async () => { - setLoadingVocabulary(true); - await updateVocabularyEntries(); - setLoadingVocabulary(false); - }; - - useEffect(() => { - fetchVocabularyEntries(); - }, []); const onIngest = async (): Promise => { const nextStep = resolveSubRoute(HOME_ROUTES.INGESTION, INGESTION_ROUTE.ROUTES.SUBJECT_ITEM); @@ -118,24 +106,16 @@ function Uploads(): React.ReactElement { } }; - let content: React.ReactNode = ; - - if (!loadingVocabulary) { - content = ( - - - - - - - - ); - } - return ( - {content} + + + + + + + Date: Thu, 12 Nov 2020 15:12:14 +0530 Subject: [PATCH 084/239] added default thumbnail asset --- client/src/assets/images/default-thumbnail.png | Bin 0 -> 8076 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 client/src/assets/images/default-thumbnail.png diff --git a/client/src/assets/images/default-thumbnail.png b/client/src/assets/images/default-thumbnail.png new file mode 100644 index 0000000000000000000000000000000000000000..8b1064ac228dae5ed40226ed61c6c091b8df7846 GIT binary patch literal 8076 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 Date: Thu, 12 Nov 2020 15:31:16 +0530 Subject: [PATCH 085/239] updated details component to support view mode --- .../src/components/shared/IdentifierList.tsx | 45 ++++++++----------- .../Metadata/Model/ObjectSelectModal.tsx | 2 +- .../components/Metadata/Model/index.tsx | 4 +- .../components/DetailsView/DetailsHeader.tsx | 8 ++-- client/src/utils/shared.ts | 8 ++++ 5 files changed, 33 insertions(+), 34 deletions(-) diff --git a/client/src/components/shared/IdentifierList.tsx b/client/src/components/shared/IdentifierList.tsx index 836f87fdd..cc53cdbdd 100644 --- a/client/src/components/shared/IdentifierList.tsx +++ b/client/src/components/shared/IdentifierList.tsx @@ -8,8 +8,9 @@ import { fade, makeStyles } from '@material-ui/core/styles'; import React from 'react'; import { DebounceInput } from 'react-debounce-input'; import { MdRemoveCircleOutline } from 'react-icons/md'; -import FieldType from './FieldType'; import { StateIdentifier, VocabularyOption } from '../../store'; +import { sharedButtonProps } from '../../utils/shared'; +import FieldType from './FieldType'; const useStyles = makeStyles(({ palette, typography, breakpoints }) => ({ identifierInput: { @@ -48,20 +49,7 @@ const useStyles = makeStyles(({ palette, typography, breakpoints }) => ({ marginLeft: 20, cursor: 'pointer' }, - addIdentifier: { - display: 'flex', - alignItems: 'center', - width: 80, - borderRadius: 5, - padding: 10, - backgroundColor: palette.secondary.light - }, - addIdentifierButton: { - height: 30, - width: 80, - fontSize: '0.8em', - color: palette.background.paper, - } + addIdentifierButton: sharedButtonProps })); interface IdentifierListProps { @@ -70,10 +58,11 @@ interface IdentifierListProps { onUpdate: (id: number, fieldName: string, fieldValue: number | string | boolean) => void; onRemove: (id: number) => void; identifierTypes: VocabularyOption[]; + disabled?: boolean; } function IdentifierList(props: IdentifierListProps): React.ReactElement { - const { identifiers, onAdd, onUpdate, identifierTypes, onRemove } = props; + const { identifiers, onAdd, onUpdate, identifierTypes, onRemove, disabled = false } = props; const classes = useStyles(); return ( @@ -98,6 +87,7 @@ function IdentifierList(props: IdentifierListProps): React.ReactElement { name='selected' color='primary' onChange={updateCheckbox} + disabled={disabled} /> @@ -120,19 +112,18 @@ function IdentifierList(props: IdentifierListProps): React.ReactElement { ); })} + )} - - - ); } diff --git a/client/src/pages/Ingestion/components/Metadata/Model/ObjectSelectModal.tsx b/client/src/pages/Ingestion/components/Metadata/Model/ObjectSelectModal.tsx index 3f5def135..f389bcd60 100644 --- a/client/src/pages/Ingestion/components/Metadata/Model/ObjectSelectModal.tsx +++ b/client/src/pages/Ingestion/components/Metadata/Model/ObjectSelectModal.tsx @@ -70,7 +70,7 @@ function ObjectSelectModal(props: ObjectSelectModalProps): React.ReactElement { Select Source Objects diff --git a/client/src/pages/Ingestion/components/Metadata/Model/index.tsx b/client/src/pages/Ingestion/components/Metadata/Model/index.tsx index 87314c7a7..a04bf27cd 100644 --- a/client/src/pages/Ingestion/components/Metadata/Model/index.tsx +++ b/client/src/pages/Ingestion/components/Metadata/Model/index.tsx @@ -13,7 +13,7 @@ import { eVocabularySetID } from '../../../../../types/server'; import { withDefaultValueBoolean, withDefaultValueNumber } from '../../../../../utils/shared'; import BoundingBoxInput from './BoundingBoxInput'; import ObjectSelectModal from './ObjectSelectModal'; -import SourceObjects from './SourceObjects'; +import SourceObjectsList from './SourceObjectsList'; import UVContents from './UVContents'; const useStyles = makeStyles(({ palette, typography }) => ({ @@ -129,7 +129,7 @@ function Model(props: ModelProps): React.ReactElement { onUpdateIdentifer={onIdentifersChange} onRemoveIdentifer={onIdentifersChange} /> - + + {getTermForSystemObjectType(objectType)} @@ -61,7 +61,7 @@ function DetailsHeader(props: DetailsHeaderProps): React.ReactElement { className={classes.checkboxLabel} labelPlacement='start' label='Retired' - control={} + control={} /> diff --git a/client/src/utils/shared.ts b/client/src/utils/shared.ts index ad2b3936a..18dfad5de 100644 --- a/client/src/utils/shared.ts +++ b/client/src/utils/shared.ts @@ -4,6 +4,7 @@ * Shared utilities for components, functionality. */ import { CSSProperties } from '@material-ui/core/styles/withStyles'; +import { Colors } from '../theme'; export const withDefaultValueBoolean = (value: boolean | null, defaultValue: boolean): boolean => value || defaultValue; @@ -47,3 +48,10 @@ export const scrollBarProperties = (vertical: boolean, horizontal: boolean, back backgroundColor } }); + +export const sharedButtonProps: CSSProperties = ({ + height: 30, + width: 80, + fontSize: '0.8em', + color: Colors.defaults.white +}); From 22f7e5bf7c16f028d303609a78406c883e33ab06 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Thu, 12 Nov 2020 15:48:34 +0530 Subject: [PATCH 086/239] added header for identifier list --- .../src/components/shared/IdentifierList.tsx | 132 ++++++++++-------- client/src/utils/shared.ts | 11 +- 2 files changed, 83 insertions(+), 60 deletions(-) diff --git a/client/src/components/shared/IdentifierList.tsx b/client/src/components/shared/IdentifierList.tsx index cc53cdbdd..b0ec5c0b4 100644 --- a/client/src/components/shared/IdentifierList.tsx +++ b/client/src/components/shared/IdentifierList.tsx @@ -3,13 +3,13 @@ * * This component renders identifier list used in photogrammetry metadata component. */ -import { Box, Button, Checkbox, MenuItem, Select } from '@material-ui/core'; +import { Box, Button, Checkbox, MenuItem, Select, Typography } from '@material-ui/core'; import { fade, makeStyles } from '@material-ui/core/styles'; import React from 'react'; import { DebounceInput } from 'react-debounce-input'; import { MdRemoveCircleOutline } from 'react-icons/md'; import { StateIdentifier, VocabularyOption } from '../../store'; -import { sharedButtonProps } from '../../utils/shared'; +import { sharedButtonProps, sharedLabelProps } from '../../utils/shared'; import FieldType from './FieldType'; const useStyles = makeStyles(({ palette, typography, breakpoints }) => ({ @@ -49,6 +49,9 @@ const useStyles = makeStyles(({ palette, typography, breakpoints }) => ({ marginLeft: 20, cursor: 'pointer' }, + header: { + ...sharedLabelProps, fontSize: '0.9em' + }, addIdentifierButton: sharedButtonProps })); @@ -67,63 +70,78 @@ function IdentifierList(props: IdentifierListProps): React.ReactElement { return ( - {!!identifiers.length && ( - - {identifiers.map(({ id, selected, identifier, identifierType }, index) => { - const remove = () => onRemove(id); - const updateCheckbox = ({ target }) => onUpdate(id, target.name, target.checked); - const update = ({ target }) => onUpdate(id, target.name, target.value); + + {!!identifiers.length &&
} + {identifiers.map(({ id, selected, identifier, identifierType }, index) => { + const remove = () => onRemove(id); + const updateCheckbox = ({ target }) => onUpdate(id, target.name, target.checked); + const update = ({ target }) => onUpdate(id, target.name, target.value); - return ( - + + + - {identifierTypes.map(({ idVocabulary, Term }, index) => {Term})} - - - - ); - })} - - - )} + {identifierTypes.map(({ idVocabulary, Term }, index) => {Term})} + + + + ); + })} + + + + ); +} + +function Header(): React.ReactElement { + const classes = useStyles(); + + return ( + + + + Identifer + + + Identifer Type + ); } diff --git a/client/src/utils/shared.ts b/client/src/utils/shared.ts index 18dfad5de..7b7416c3f 100644 --- a/client/src/utils/shared.ts +++ b/client/src/utils/shared.ts @@ -4,7 +4,7 @@ * Shared utilities for components, functionality. */ import { CSSProperties } from '@material-ui/core/styles/withStyles'; -import { Colors } from '../theme'; +import { Colors, palette } from '../theme'; export const withDefaultValueBoolean = (value: boolean | null, defaultValue: boolean): boolean => value || defaultValue; @@ -49,9 +49,14 @@ export const scrollBarProperties = (vertical: boolean, horizontal: boolean, back } }); -export const sharedButtonProps: CSSProperties = ({ +export const sharedButtonProps: CSSProperties = { height: 30, width: 80, fontSize: '0.8em', color: Colors.defaults.white -}); +}; + +export const sharedLabelProps: CSSProperties = { + fontSize: '0.8em', + color: palette.primary.dark +}; From 8885155bb23a9bc5dfe9d2f850c7ea8b09055c31 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Thu, 12 Nov 2020 15:59:19 +0530 Subject: [PATCH 087/239] updated source object list to support viewmode --- ...ourceObjects.tsx => SourceObjectsList.tsx} | 85 +++++++++++-------- 1 file changed, 50 insertions(+), 35 deletions(-) rename client/src/pages/Ingestion/components/Metadata/Model/{SourceObjects.tsx => SourceObjectsList.tsx} (58%) diff --git a/client/src/pages/Ingestion/components/Metadata/Model/SourceObjects.tsx b/client/src/pages/Ingestion/components/Metadata/Model/SourceObjectsList.tsx similarity index 58% rename from client/src/pages/Ingestion/components/Metadata/Model/SourceObjects.tsx rename to client/src/pages/Ingestion/components/Metadata/Model/SourceObjectsList.tsx index 17956f3af..8ef8d9c71 100644 --- a/client/src/pages/Ingestion/components/Metadata/Model/SourceObjects.tsx +++ b/client/src/pages/Ingestion/components/Metadata/Model/SourceObjectsList.tsx @@ -11,31 +11,28 @@ import { MdRemoveCircleOutline } from 'react-icons/md'; import { NewTabLink } from '../../../../../components'; import { StateSourceObject } from '../../../../../store'; import { getDetailsUrlForObject, getTermForSystemObjectType } from '../../../../../utils/repository'; +import { sharedButtonProps, sharedLabelProps } from '../../../../../utils/shared'; -const useStyles = makeStyles(({ palette }) => ({ +export const useStyles = makeStyles(({ palette }) => ({ container: { display: 'flex', width: '52vw', flexDirection: 'column', borderRadius: 5, padding: 10, - backgroundColor: palette.primary.light + marginTop: (viewMode: boolean) => viewMode ? 10 : 0, + backgroundColor: (viewMode: boolean) => viewMode ? palette.secondary.light : palette.primary.light }, list: { - padding: 10, - paddingBottom: 0, - marginBottom: 10, + paddingTop: 10, + paddingLeft: (viewMode: boolean) => viewMode ? 0: 10, borderRadius: 5, - backgroundColor: palette.secondary.light + backgroundColor: palette.secondary.light, }, header: { - fontSize: '0.8em', - color: palette.primary.dark - }, - label: { - fontSize: '0.8em', - color: palette.primary.dark + ...sharedLabelProps }, + label: sharedLabelProps, labelUnderline: { textDecoration: 'underline', cursor: 'pointer' @@ -45,32 +42,41 @@ const useStyles = makeStyles(({ palette }) => ({ cursor: 'pointer' }, addButton: { - height: 30, - width: 80, - marginLeft: 5, - fontSize: '0.8em', - color: palette.background.paper, + ...sharedButtonProps, + marginTop: 5 } })); interface SourceObjectsListProps { sourceObjects: StateSourceObject[]; onAdd: () => void; - onRemove: (id: number) => void; + onRemove?: (id: number) => void; + viewMode?: boolean; + disabled?: boolean; } function SourceObjectsList(props: SourceObjectsListProps): React.ReactElement { - const { sourceObjects, onAdd, onRemove } = props; - const classes = useStyles(); + const { sourceObjects, onAdd, onRemove, viewMode = false, disabled = false } = props; + const classes = useStyles(viewMode); + const titles = ['Source Object(s)', 'Identifiers', 'Object Type']; const hasSourceObjects = !!sourceObjects.length; + const buttonLabel: string = viewMode ? 'Connect' : 'Add'; + return ( -
+
{hasSourceObjects && ( - {sourceObjects.map((sourceObject: StateSourceObject, index: number) => )} + {sourceObjects.map((sourceObject: StateSourceObject, index: number) => ( + + ))} )} ); } -function Header(): React.ReactElement { - const classes = useStyles(); +interface ObjectHeader { + titles: string[]; +} + +export function Header(props: ObjectHeader): React.ReactElement { + const { titles } = props; + const classes = useStyles(false); + const [title1, title2, title3] = titles; return ( - Source Object(s) + {title1} - Identifiers + {title2} - Object Type + {title3} ); @@ -113,15 +125,16 @@ function Header(): React.ReactElement { interface ItemProps { sourceObject: StateSourceObject; - onRemove: (id: number) => void; + onRemove?: (id: number) => void; + viewMode?: boolean; } function Item(props: ItemProps): React.ReactElement { - const { sourceObject, onRemove } = props; + const { sourceObject, onRemove, viewMode = false } = props; const { idSystemObject, name, identifier, objectType } = sourceObject; - const classes = useStyles(); + const classes = useStyles(viewMode); - const remove = () => onRemove(idSystemObject); + const remove = () => onRemove?.(idSystemObject); return ( {getTermForSystemObjectType(objectType)} - + + {!viewMode && } + ); } From 551263195cfc7b401ccb777f75aedf115f3a3a5d Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Thu, 12 Nov 2020 15:59:42 +0530 Subject: [PATCH 088/239] added derived object list component --- .../src/components/shared/IdentifierList.tsx | 4 +- .../Metadata/Model/DerivedObjectsList.tsx | 103 +++++++++++++ .../DetailsView/DetailsThumbnail.tsx | 35 +++++ .../components/DetailsView/index.tsx | 139 +++++++++++++++++- client/src/store/metadata/metadata.types.ts | 7 + 5 files changed, 277 insertions(+), 11 deletions(-) create mode 100644 client/src/pages/Ingestion/components/Metadata/Model/DerivedObjectsList.tsx create mode 100644 client/src/pages/Repository/components/DetailsView/DetailsThumbnail.tsx diff --git a/client/src/components/shared/IdentifierList.tsx b/client/src/components/shared/IdentifierList.tsx index b0ec5c0b4..52e3a50b4 100644 --- a/client/src/components/shared/IdentifierList.tsx +++ b/client/src/components/shared/IdentifierList.tsx @@ -49,9 +49,7 @@ const useStyles = makeStyles(({ palette, typography, breakpoints }) => ({ marginLeft: 20, cursor: 'pointer' }, - header: { - ...sharedLabelProps, fontSize: '0.9em' - }, + header: sharedLabelProps, addIdentifierButton: sharedButtonProps })); diff --git a/client/src/pages/Ingestion/components/Metadata/Model/DerivedObjectsList.tsx b/client/src/pages/Ingestion/components/Metadata/Model/DerivedObjectsList.tsx new file mode 100644 index 000000000..174ca8d76 --- /dev/null +++ b/client/src/pages/Ingestion/components/Metadata/Model/DerivedObjectsList.tsx @@ -0,0 +1,103 @@ +/** + * DerivedObjectsList + * + * This component renders the derived object list with add capability. + */ +import { Box, Button, Typography } from '@material-ui/core'; +import clsx from 'clsx'; +import React from 'react'; +import { MdRemoveCircleOutline } from 'react-icons/md'; +import { NewTabLink } from '../../../../../components'; +import { StateDerivedObject, useVocabularyStore } from '../../../../../store'; +import { eVocabularySetID } from '../../../../../types/server'; +import { getDetailsUrlForObject, getTermForSystemObjectType } from '../../../../../utils/repository'; +import { Header, useStyles } from './SourceObjectsList'; + +interface DerivedObjectsListProps { + derivedObjects: StateDerivedObject[]; + onAdd: () => void; + onRemove?: (id: number) => void; + viewMode?: boolean; + disabled?: boolean; +} + +function DerivedObjectsList(props: DerivedObjectsListProps): React.ReactElement { + const { derivedObjects, onAdd, onRemove, viewMode = false, disabled = false } = props; + const classes = useStyles(viewMode); + + const titles = ['Source Object(s)', 'Variant Type', 'Object Type']; + const hasDerivedObjects = !!derivedObjects.length; + + return ( + +
+ {hasDerivedObjects && ( + + {derivedObjects.map((derivedObject: StateDerivedObject, index: number) => ( + + ))} + + )} + + + ); +} + +interface ItemProps { + derivedObject: StateDerivedObject; + onRemove?: (id: number) => void; + viewMode?: boolean; +} + +function Item(props: ItemProps): React.ReactElement { + const { derivedObject, onRemove, viewMode = false } = props; + const { idSystemObject, name, variantType, objectType } = derivedObject; + const classes = useStyles(viewMode); + const getEntries = useVocabularyStore(state => state.getEntries); + + const entries = getEntries(eVocabularySetID.eCaptureDataFileVariantType); + const variant = entries.find(entry => entry.idVocabulary === variantType); + + const remove = () => onRemove?.(idSystemObject); + + return ( + + + + {name} + + + + {variant?.Term} + + + {getTermForSystemObjectType(objectType)} + + + {!viewMode && } + + + ); +} + +export default DerivedObjectsList; diff --git a/client/src/pages/Repository/components/DetailsView/DetailsThumbnail.tsx b/client/src/pages/Repository/components/DetailsView/DetailsThumbnail.tsx new file mode 100644 index 000000000..ce7fbccb2 --- /dev/null +++ b/client/src/pages/Repository/components/DetailsView/DetailsThumbnail.tsx @@ -0,0 +1,35 @@ +/** + * DetailsThumbnail + * + * This component renders details thumbnail for the Repository Details UI. + */ +import { Box } from '@material-ui/core'; +import React from 'react'; +import { makeStyles } from '@material-ui/core/styles'; +import DefaultThumbnail from '../../../../assets/images/default-thumbnail.png'; + +const useStyles = makeStyles(() => ({ + thumbnail: { + height: 200, + width: 200, + marginTop: 50, + borderRadius: 10 + } +})); + +interface DetailsThumbnailProps { + thumbnail?: string; +} + +function DetailsThumbnail(props: DetailsThumbnailProps): React.ReactElement { + const { thumbnail = DefaultThumbnail } = props; + const classes = useStyles(); + + return ( + + + + ); +} + +export default DetailsThumbnail; \ No newline at end of file diff --git a/client/src/pages/Repository/components/DetailsView/index.tsx b/client/src/pages/Repository/components/DetailsView/index.tsx index bf8137908..a1910df0e 100644 --- a/client/src/pages/Repository/components/DetailsView/index.tsx +++ b/client/src/pages/Repository/components/DetailsView/index.tsx @@ -5,10 +5,16 @@ */ import { Box } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; -import React from 'react'; +import React, { useState } from 'react'; import { useParams } from 'react-router'; -import { eSystemObjectType } from '../../../../types/server'; +import IdentifierList from '../../../../components/shared/IdentifierList'; +import { useVocabularyStore } from '../../../../store'; +import { eSystemObjectType, eVocabularySetID } from '../../../../types/server'; +import DerivedObjectsList from '../../../Ingestion/components/Metadata/Model/DerivedObjectsList'; +import ObjectSelectModal from '../../../Ingestion/components/Metadata/Model/ObjectSelectModal'; +import SourceObjectsList from '../../../Ingestion/components/Metadata/Model/SourceObjectsList'; import DetailsHeader from './DetailsHeader'; +import DetailsThumbnail from './DetailsThumbnail'; import ObjectNotFoundView from './ObjectNotFoundView'; const useStyles = makeStyles(({ palette, breakpoints }) => ({ @@ -16,11 +22,14 @@ const useStyles = makeStyles(({ palette, breakpoints }) => ({ display: 'flex', flex: 1, flexDirection: 'column', + maxHeight: 'calc(100vh - 160px)', padding: 20, marginBottom: 20, borderRadius: 10, + overflowY: 'scroll', backgroundColor: palette.primary.light, [breakpoints.down('lg')]: { + maxHeight: 'calc(100vh - 120px)', padding: 10 } } @@ -41,19 +50,67 @@ const mockData = { name: 'Armstrong Suit', objectType: eSystemObjectType.eProject, }, { - idSystemObject: 0, + idSystemObject: 2, name: 'Armstrong Glove', objectType: eSystemObjectType.eSubject, }, { - idSystemObject: 0, + idSystemObject: 3, name: 'Armstrong Glove Full', objectType: eSystemObjectType.eItem, }, { - idSystemObject: 0, + idSystemObject: 4, name: 'PhotoSet1.zip', objectType: eSystemObjectType.eCaptureData, } - ] + ], + identifiers: [ + { + id: 0, + identifier: '31958de82-ab13-4049-c979-746e2fbe229e', + identifierType: 75, + selected: true + }, + ], + sourceObjects: [ + { + idSystemObject: 0, + name: 'PhotoSetAlpha1.zip', + identifier: 'a5cf8642-7466-4896-a0a2-d698f2009cd3', + objectType: eSystemObjectType.eModel + }, + { + idSystemObject: 0, + name: 'PhotoSetAlpha2.zip', + identifier: 'a5cf8642-7466-4896-a0a2-d698f2009cd3', + objectType: eSystemObjectType.eCaptureData + } + ], + derivedObjects: [ + { + idSystemObject: 0, + name: 'Photo1.zip', + variantType: 28, + objectType: eSystemObjectType.eAsset + }, + { + idSystemObject: 1, + name: 'Photo2.zip', + variantType: 28, + objectType: eSystemObjectType.eAsset + }, + { + idSystemObject: 2, + name: 'Photo3.zip', + variantType: 28, + objectType: eSystemObjectType.eAsset + }, + { + idSystemObject: 3, + name: 'Photo4.zip', + variantType: 28, + objectType: eSystemObjectType.eAsset + }, + ], } }; @@ -64,13 +121,45 @@ type DetailsParams = { function DetailsView(): React.ReactElement { const classes = useStyles(); const params = useParams(); + const [modalOpen, setModalOpen] = useState(false); + const data = mockData[params.idSystemObject]; + const getEntries = useVocabularyStore(state => state.getEntries); if (!data) { return ; } - const { name, objectType, path, retired } = data; + const { name, objectType, path, retired, identifiers, sourceObjects, derivedObjects, thumbnail } = data; + const disabled: boolean = false; + + const addIdentifer = () => { + alert('TODO: KARAN: add identifier'); + }; + + const removeIdentifier = () => { + alert('TODO: KARAN: remove identifier'); + }; + + const updateIdentifierFields = () => { + alert('TODO: KARAN: update identifier'); + }; + + const onSelectedObjects = () => { + onModalClose(); + }; + + const onModalClose = () => { + setModalOpen(false); + }; + + const onAddSourceObject = () => { + setModalOpen(true); + }; + + const onAddDerivedObject = () => { + alert('TODO: KARAN: on add derived object'); + }; return ( @@ -79,7 +168,41 @@ function DetailsView(): React.ReactElement { objectType={objectType} path={path} retired={retired} - editable + disabled={disabled} + /> + + + + + + + + + + + + ); diff --git a/client/src/store/metadata/metadata.types.ts b/client/src/store/metadata/metadata.types.ts index 65dfe8be3..6367252b9 100644 --- a/client/src/store/metadata/metadata.types.ts +++ b/client/src/store/metadata/metadata.types.ts @@ -135,6 +135,13 @@ export type StateSourceObject = { objectType: eSystemObjectType; }; +export type StateDerivedObject = { + idSystemObject: number; + name: string; + variantType: number; + objectType: eSystemObjectType; +}; + export enum ReferenceModelAction { Update = 'Update', Ingest = 'Ingest' From 5cb139bc64ede5ce57cb947630a0e304ffc9dc3b Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Thu, 12 Nov 2020 22:15:10 +0530 Subject: [PATCH 089/239] minor fixes --- client/src/index.tsx | 12 ++++++++---- .../components/DetailsView/DetailsThumbnail.tsx | 2 +- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/client/src/index.tsx b/client/src/index.tsx index cd19bcbec..c88e953b8 100644 --- a/client/src/index.tsx +++ b/client/src/index.tsx @@ -10,7 +10,7 @@ import React, { useCallback, useEffect, useState } from 'react'; import { AliveScope } from 'react-activation'; import ReactDOM from 'react-dom'; import { BrowserRouter as Router, Switch } from 'react-router-dom'; -import { Slide, ToastContainer } from 'react-toastify'; +import { Slide, toast, ToastContainer } from 'react-toastify'; import 'react-toastify/dist/ReactToastify.css'; import { ErrorBoundary, Loader, PrivateRoute, PublicRoute } from './components'; import { ROUTES } from './constants'; @@ -27,9 +27,13 @@ function AppRouter(): React.ReactElement { const updateVocabularyEntries = useVocabularyStore(state => state.updateVocabularyEntries); const initializeUser = useCallback(async () => { - await initialize(); - await updateVocabularyEntries(); - setLoading(false); + try { + await initialize(); + await updateVocabularyEntries(); + setLoading(false); + } catch { + toast.error('Error occurred while initializing'); + } }, [initialize, updateVocabularyEntries]); useEffect(() => { diff --git a/client/src/pages/Repository/components/DetailsView/DetailsThumbnail.tsx b/client/src/pages/Repository/components/DetailsView/DetailsThumbnail.tsx index ce7fbccb2..6a3a0d293 100644 --- a/client/src/pages/Repository/components/DetailsView/DetailsThumbnail.tsx +++ b/client/src/pages/Repository/components/DetailsView/DetailsThumbnail.tsx @@ -27,7 +27,7 @@ function DetailsThumbnail(props: DetailsThumbnailProps): React.ReactElement { return ( - + asset thumbnail ); } From 1a9d1bcd0ee877b99b393e1f7c5403cc1705f4d5 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Fri, 13 Nov 2020 01:14:15 +0530 Subject: [PATCH 090/239] minor updates --- client/src/components/shared/Header.tsx | 2 +- .../components/Metadata/Model/DerivedObjectsList.tsx | 2 +- .../src/pages/Repository/components/DetailsView/index.tsx | 8 +------- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/client/src/components/shared/Header.tsx b/client/src/components/shared/Header.tsx index 455c9d825..4ab3bde2c 100644 --- a/client/src/components/shared/Header.tsx +++ b/client/src/components/shared/Header.tsx @@ -32,7 +32,7 @@ const useStyles = makeStyles(({ palette, spacing, typography, breakpoints }) => searchBox: { display: 'flex', alignItems: 'center', - marginLeft: 50, + marginLeft: 35, padding: '5px 10px', width: '40vw', minWidth: '30vw', diff --git a/client/src/pages/Ingestion/components/Metadata/Model/DerivedObjectsList.tsx b/client/src/pages/Ingestion/components/Metadata/Model/DerivedObjectsList.tsx index 174ca8d76..f0f89e773 100644 --- a/client/src/pages/Ingestion/components/Metadata/Model/DerivedObjectsList.tsx +++ b/client/src/pages/Ingestion/components/Metadata/Model/DerivedObjectsList.tsx @@ -25,7 +25,7 @@ function DerivedObjectsList(props: DerivedObjectsListProps): React.ReactElement const { derivedObjects, onAdd, onRemove, viewMode = false, disabled = false } = props; const classes = useStyles(viewMode); - const titles = ['Source Object(s)', 'Variant Type', 'Object Type']; + const titles = ['Derived Object(s)', 'Variant Type', 'Object Type']; const hasDerivedObjects = !!derivedObjects.length; return ( diff --git a/client/src/pages/Repository/components/DetailsView/index.tsx b/client/src/pages/Repository/components/DetailsView/index.tsx index a1910df0e..74df20544 100644 --- a/client/src/pages/Repository/components/DetailsView/index.tsx +++ b/client/src/pages/Repository/components/DetailsView/index.tsx @@ -22,7 +22,7 @@ const useStyles = makeStyles(({ palette, breakpoints }) => ({ display: 'flex', flex: 1, flexDirection: 'column', - maxHeight: 'calc(100vh - 160px)', + maxHeight: 'calc(100vh - 140px)', padding: 20, marginBottom: 20, borderRadius: 10, @@ -77,12 +77,6 @@ const mockData = { name: 'PhotoSetAlpha1.zip', identifier: 'a5cf8642-7466-4896-a0a2-d698f2009cd3', objectType: eSystemObjectType.eModel - }, - { - idSystemObject: 0, - name: 'PhotoSetAlpha2.zip', - identifier: 'a5cf8642-7466-4896-a0a2-d698f2009cd3', - objectType: eSystemObjectType.eCaptureData } ], derivedObjects: [ From 8d14ae5496f8560be627d563516e32aa3a4ec109 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Fri, 13 Nov 2020 13:38:46 +0530 Subject: [PATCH 091/239] updates ingestion, state types --- .../components/Metadata/Model/index.tsx | 13 +-- .../Metadata/Scene/ReferenceModels.tsx | 35 +++++--- client/src/pages/Ingestion/hooks/useIngest.ts | 13 ++- .../components/DetailsView/index.tsx | 13 ++- client/src/store/metadata/index.ts | 5 +- .../src/store/metadata/metadata.defaults.ts | 29 +++++- client/src/store/metadata/metadata.types.ts | 36 ++------ client/src/types/graphql.tsx | 88 ++++++++++++++++--- server/graphql/schema.graphql | 85 +++++++++++++++--- server/graphql/schema/asset/queries.graphql | 48 ++++++++-- .../schema/ingestion/mutations.graphql | 37 ++++++-- server/types/graphql.ts | 88 ++++++++++++++++--- server/utils/parser/bulkIngestReader.ts | 3 +- 13 files changed, 388 insertions(+), 105 deletions(-) diff --git a/client/src/pages/Ingestion/components/Metadata/Model/index.tsx b/client/src/pages/Ingestion/components/Metadata/Model/index.tsx index a04bf27cd..e08c0ecdb 100644 --- a/client/src/pages/Ingestion/components/Metadata/Model/index.tsx +++ b/client/src/pages/Ingestion/components/Metadata/Model/index.tsx @@ -50,7 +50,6 @@ function Model(props: ModelProps): React.ReactElement { const [getEntries, getInitialEntry] = useVocabularyStore(state => [state.getEntries, state.getInitialEntry]); const [modalOpen, setModalOpen] = useState(false); - const [sourceObjects, setSourceObjects] = useState([]); const errors = getFieldErrors(metadata); @@ -101,8 +100,9 @@ function Model(props: ModelProps): React.ReactElement { }; const onRemoveSourceObject = (idSystemObject: number): void => { + const { sourceObjects } = model; const updatedSourceObjects = sourceObjects.filter(sourceObject => sourceObject.idSystemObject !== idSystemObject); - setSourceObjects(updatedSourceObjects); + updateMetadataField(metadataIndex, 'sourceObjects', updatedSourceObjects, MetadataType.model); }; const onModalClose = () => { @@ -110,8 +110,9 @@ function Model(props: ModelProps): React.ReactElement { }; const onSelectedObjects = (newSourceObjects: StateSourceObject[]) => { - setSourceObjects([...sourceObjects, ...newSourceObjects]); - setModalOpen(false); + const { sourceObjects } = model; + const updatedSourceObjects = [...sourceObjects, ...newSourceObjects]; + updateMetadataField(metadataIndex, 'sourceObjects', updatedSourceObjects, MetadataType.model); }; const noteLabelProps = { style: { fontStyle: 'italic' } }; @@ -129,7 +130,7 @@ function Model(props: ModelProps): React.ReactElement { onUpdateIdentifer={onIdentifersChange} onRemoveIdentifer={onIdentifersChange} /> - + - + ); } diff --git a/client/src/pages/Ingestion/components/Metadata/Scene/ReferenceModels.tsx b/client/src/pages/Ingestion/components/Metadata/Scene/ReferenceModels.tsx index fbb9885fe..f57c5955b 100644 --- a/client/src/pages/Ingestion/components/Metadata/Scene/ReferenceModels.tsx +++ b/client/src/pages/Ingestion/components/Metadata/Scene/ReferenceModels.tsx @@ -51,19 +51,29 @@ const useStyles = makeStyles(({ palette }) => ({ const mockReferenceModels: StateReferenceModel[] = [ { idSystemObject: 1, - model: 'Armstrong1.obj', + name: 'Armstrong1.obj', fileSize: 1.27e+7, - uvResolution: 2000, - boundingBox: '(1.0, 1.0, 1.0) - (10.0, 10.0, 10.0)', + resolution: 2000, + boundingBoxP1X: 1.0, + boundingBoxP1Y: 1.0, + boundingBoxP1Z: 1.0, + boundingBoxP2X: 10.0, + boundingBoxP2Y: 10.0, + boundingBoxP2Z: 10.0, action: ReferenceModelAction.Update }, { idSystemObject: 1, - model: 'Armstrong2.obj', - fileSize: 8.7e+6, - uvResolution: 500, - boundingBox: '(1.0, 1.0, 1.0) - (10.0, 10.0, 10.0)', - action: ReferenceModelAction.Ingest + name: 'Armstrong2.obj', + fileSize: 1.27e+7, + resolution: 2000, + boundingBoxP1X: 1.0, + boundingBoxP1Y: 1.0, + boundingBoxP1Z: 1.0, + boundingBoxP2X: 10.0, + boundingBoxP2Y: 10.0, + boundingBoxP2Z: 10.0, + action: ReferenceModelAction.Update } ]; @@ -120,13 +130,16 @@ interface ItemProps { function Item(props: ItemProps): React.ReactElement { const { referenceModel } = props; - const { idSystemObject, model, fileSize, uvResolution, boundingBox, action } = referenceModel; + const { idSystemObject, name, fileSize, resolution, action } = referenceModel; + const { boundingBoxP1X, boundingBoxP1Y, boundingBoxP1Z, boundingBoxP2X, boundingBoxP2Y, boundingBoxP2Z } = referenceModel; const classes = useStyles(); const onAction = () => { alert(`TODO: KARAN: Handle ${action.toString()} action`); }; + const boundingBox: string = `(${boundingBoxP1X}, ${boundingBoxP1Y}, ${boundingBoxP1Z}) - (${boundingBoxP2X}, ${boundingBoxP2Y}, ${boundingBoxP2Z})`; + return ( - {model} + {name} {formatBytes(fileSize)} - {uvResolution} + {resolution} {boundingBox} diff --git a/client/src/pages/Ingestion/hooks/useIngest.ts b/client/src/pages/Ingestion/hooks/useIngest.ts index db12ad270..64dabe88f 100644 --- a/client/src/pages/Ingestion/hooks/useIngest.ts +++ b/client/src/pages/Ingestion/hooks/useIngest.ts @@ -150,6 +150,7 @@ function useIngest(): UseIngest { systemCreated, identifiers, uvMaps, + sourceObjects, dateCaptured, creationMethod, master, @@ -191,6 +192,7 @@ function useIngest(): UseIngest { units: nonNullValue('units', units), purpose: nonNullValue('purpose', purpose), modelFileType: nonNullValue('modelFileType', modelFileType), + sourceObjects, roughness, metalness, pointCount, @@ -212,26 +214,29 @@ function useIngest(): UseIngest { } if (isScene) { - const { identifiers } = scene; + const { identifiers, systemCreated, referenceModels } = scene; const ingestIdentifiers: IngestIdentifierInput[] = getIngestIdentifiers(identifiers); const sceneData: IngestSceneInput = { idAssetVersion: parseFileId(file.id), - identifiers: ingestIdentifiers + identifiers: ingestIdentifiers, + systemCreated, + referenceModels }; ingestScene.push(sceneData); } if (isOther) { - const { identifiers } = other; + const { identifiers, systemCreated } = other; const ingestIdentifiers: IngestIdentifierInput[] = getIngestIdentifiers(identifiers); const otherData: IngestOtherInput = { idAssetVersion: parseFileId(file.id), - identifiers: ingestIdentifiers + identifiers: ingestIdentifiers, + systemCreated }; ingestOther.push(otherData); diff --git a/client/src/pages/Repository/components/DetailsView/index.tsx b/client/src/pages/Repository/components/DetailsView/index.tsx index 74df20544..63b27cc67 100644 --- a/client/src/pages/Repository/components/DetailsView/index.tsx +++ b/client/src/pages/Repository/components/DetailsView/index.tsx @@ -41,23 +41,28 @@ const mockData = { retired: true, objectType: eSystemObjectType.eCaptureData, path: [ + // TODO: KARAN: 2 Dimensional array { idSystemObject: 0, name: 'USNM', objectType: eSystemObjectType.eUnit, - }, { + }, + { idSystemObject: 1, name: 'Armstrong Suit', objectType: eSystemObjectType.eProject, - }, { + }, + { idSystemObject: 2, name: 'Armstrong Glove', objectType: eSystemObjectType.eSubject, - }, { + }, + { idSystemObject: 3, name: 'Armstrong Glove Full', objectType: eSystemObjectType.eItem, - }, { + }, + { idSystemObject: 4, name: 'PhotoSet1.zip', objectType: eSystemObjectType.eCaptureData, diff --git a/client/src/store/metadata/index.ts b/client/src/store/metadata/index.ts index 0dab343c1..2813b4fec 100644 --- a/client/src/store/metadata/index.ts +++ b/client/src/store/metadata/index.ts @@ -12,7 +12,7 @@ import { apolloClient } from '../../graphql'; import { AreCameraSettingsUniformDocument, AssetVersionContent, - GetAssetVersionDetailResult, + GetAssetVersionsDetailsDocument, GetAssetVersionsDetailsQuery, GetContentsForAssetVersionsDocument, @@ -227,7 +227,7 @@ export const useMetadataStore = create((set: SetState((set: SetState; uvMaps: Array; + sourceObjects: Array; roughness?: Maybe; metalness?: Maybe; pointCount?: Maybe; @@ -426,18 +443,40 @@ export type IngestModel = { hasNormals?: Maybe; hasVertexColor?: Maybe; hasUVSpace?: Maybe; - boundingBoxP1X?: Maybe; - boundingBoxP1Y?: Maybe; - boundingBoxP1Z?: Maybe; - boundingBoxP2X?: Maybe; - boundingBoxP2Y?: Maybe; - boundingBoxP2Z?: Maybe; + boundingBoxP1X?: Maybe; + boundingBoxP1Y?: Maybe; + boundingBoxP1Z?: Maybe; + boundingBoxP2X?: Maybe; + boundingBoxP2Y?: Maybe; + boundingBoxP2Z?: Maybe; +}; + +export enum ReferenceModelAction { + Update = 'Update', + Ingest = 'Ingest' +} + +export type ReferenceModel = { + __typename?: 'ReferenceModel'; + idSystemObject: Scalars['Int']; + name: Scalars['String']; + fileSize: Scalars['Int']; + resolution?: Maybe; + boundingBoxP1X?: Maybe; + boundingBoxP1Y?: Maybe; + boundingBoxP1Z?: Maybe; + boundingBoxP2X?: Maybe; + boundingBoxP2Y?: Maybe; + boundingBoxP2Z?: Maybe; + action: ReferenceModelAction; }; export type IngestScene = { __typename?: 'IngestScene'; idAssetVersion: Scalars['Int']; + systemCreated: Scalars['Boolean']; identifiers: Array; + referenceModels: Array; }; export type GetAssetVersionDetailResult = { @@ -685,6 +724,13 @@ export type IngestUvMapInput = { mapType: Scalars['Int']; }; +export type SourceObjectInput = { + idSystemObject: Scalars['Int']; + name: Scalars['String']; + identifier: Scalars['String']; + objectType: Scalars['Int']; +}; + export type IngestModelInput = { idAssetVersion: Scalars['Int']; systemCreated: Scalars['Boolean']; @@ -699,6 +745,7 @@ export type IngestModelInput = { directory: Scalars['String']; identifiers: Array; uvMaps: Array; + sourceObjects: Array; roughness?: Maybe; metalness?: Maybe; pointCount?: Maybe; @@ -707,21 +754,38 @@ export type IngestModelInput = { hasNormals?: Maybe; hasVertexColor?: Maybe; hasUVSpace?: Maybe; - boundingBoxP1X?: Maybe; - boundingBoxP1Y?: Maybe; - boundingBoxP1Z?: Maybe; - boundingBoxP2X?: Maybe; - boundingBoxP2Y?: Maybe; - boundingBoxP2Z?: Maybe; + boundingBoxP1X?: Maybe; + boundingBoxP1Y?: Maybe; + boundingBoxP1Z?: Maybe; + boundingBoxP2X?: Maybe; + boundingBoxP2Y?: Maybe; + boundingBoxP2Z?: Maybe; +}; + +export type ReferenceModelInput = { + idSystemObject: Scalars['Int']; + name: Scalars['String']; + fileSize: Scalars['Int']; + resolution?: Maybe; + boundingBoxP1X?: Maybe; + boundingBoxP1Y?: Maybe; + boundingBoxP1Z?: Maybe; + boundingBoxP2X?: Maybe; + boundingBoxP2Y?: Maybe; + boundingBoxP2Z?: Maybe; + action: ReferenceModelAction; }; export type IngestSceneInput = { idAssetVersion: Scalars['Int']; + systemCreated: Scalars['Boolean']; identifiers: Array; + referenceModels: Array; }; export type IngestOtherInput = { idAssetVersion: Scalars['Int']; + systemCreated: Scalars['Boolean']; identifiers: Array; }; diff --git a/server/graphql/schema.graphql b/server/graphql/schema.graphql index 452417262..f77de715f 100644 --- a/server/graphql/schema.graphql +++ b/server/graphql/schema.graphql @@ -166,6 +166,20 @@ type IngestUVMap { mapType: Int! } +type SourceObject { + idSystemObject: Int! + name: String! + identifier: String! + objectType: Int! +} + +type DerivedObject { + idSystemObject: Int! + name: String! + variantType: Int! + objectType: Int! +} + type IngestModel { idAssetVersion: Int! systemCreated: Boolean! @@ -180,6 +194,7 @@ type IngestModel { directory: String! identifiers: [IngestIdentifier!]! uvMaps: [IngestUVMap!]! + sourceObjects: [SourceObject!]! roughness: Int metalness: Int pointCount: Int @@ -188,17 +203,38 @@ type IngestModel { hasNormals: Boolean hasVertexColor: Boolean hasUVSpace: Boolean - boundingBoxP1X: Int - boundingBoxP1Y: Int - boundingBoxP1Z: Int - boundingBoxP2X: Int - boundingBoxP2Y: Int - boundingBoxP2Z: Int + boundingBoxP1X: Float + boundingBoxP1Y: Float + boundingBoxP1Z: Float + boundingBoxP2X: Float + boundingBoxP2Y: Float + boundingBoxP2Z: Float +} + +enum ReferenceModelAction { + Update + Ingest +} + +type ReferenceModel { + idSystemObject: Int! + name: String! + fileSize: Int! + resolution: Int + boundingBoxP1X: Float + boundingBoxP1Y: Float + boundingBoxP1Z: Float + boundingBoxP2X: Float + boundingBoxP2Y: Float + boundingBoxP2Z: Float + action: ReferenceModelAction! } type IngestScene { idAssetVersion: Int! + systemCreated: Boolean! identifiers: [IngestIdentifier!]! + referenceModels: [ReferenceModel!]! } type GetAssetVersionDetailResult { @@ -429,6 +465,13 @@ input IngestUVMapInput { mapType: Int! } +input SourceObjectInput { + idSystemObject: Int! + name: String! + identifier: String! + objectType: Int! +} + input IngestModelInput { idAssetVersion: Int! systemCreated: Boolean! @@ -443,6 +486,7 @@ input IngestModelInput { directory: String! identifiers: [IngestIdentifierInput!]! uvMaps: [IngestUVMapInput!]! + sourceObjects: [SourceObjectInput!]! roughness: Int metalness: Int pointCount: Int @@ -451,21 +495,38 @@ input IngestModelInput { hasNormals: Boolean hasVertexColor: Boolean hasUVSpace: Boolean - boundingBoxP1X: Int - boundingBoxP1Y: Int - boundingBoxP1Z: Int - boundingBoxP2X: Int - boundingBoxP2Y: Int - boundingBoxP2Z: Int + boundingBoxP1X: Float + boundingBoxP1Y: Float + boundingBoxP1Z: Float + boundingBoxP2X: Float + boundingBoxP2Y: Float + boundingBoxP2Z: Float +} + +input ReferenceModelInput { + idSystemObject: Int! + name: String! + fileSize: Int! + resolution: Int + boundingBoxP1X: Float + boundingBoxP1Y: Float + boundingBoxP1Z: Float + boundingBoxP2X: Float + boundingBoxP2Y: Float + boundingBoxP2Z: Float + action: ReferenceModelAction! } input IngestSceneInput { idAssetVersion: Int! + systemCreated: Boolean! identifiers: [IngestIdentifierInput!]! + referenceModels: [ReferenceModelInput!]! } input IngestOtherInput { idAssetVersion: Int! + systemCreated: Boolean! identifiers: [IngestIdentifierInput!]! } diff --git a/server/graphql/schema/asset/queries.graphql b/server/graphql/schema/asset/queries.graphql index 6329b140a..e1358436a 100644 --- a/server/graphql/schema/asset/queries.graphql +++ b/server/graphql/schema/asset/queries.graphql @@ -45,6 +45,20 @@ type IngestUVMap { mapType: Int! } +type SourceObject { + idSystemObject: Int! + name: String! + identifier: String! + objectType: Int! +} + +type DerivedObject { + idSystemObject: Int! + name: String! + variantType: Int! + objectType: Int! +} + type IngestModel { idAssetVersion: Int! systemCreated: Boolean! @@ -59,6 +73,7 @@ type IngestModel { directory: String! identifiers: [IngestIdentifier!]! uvMaps: [IngestUVMap!]! + sourceObjects: [SourceObject!]! roughness: Int metalness: Int pointCount: Int @@ -67,17 +82,38 @@ type IngestModel { hasNormals: Boolean hasVertexColor: Boolean hasUVSpace: Boolean - boundingBoxP1X: Int - boundingBoxP1Y: Int - boundingBoxP1Z: Int - boundingBoxP2X: Int - boundingBoxP2Y: Int - boundingBoxP2Z: Int + boundingBoxP1X: Float + boundingBoxP1Y: Float + boundingBoxP1Z: Float + boundingBoxP2X: Float + boundingBoxP2Y: Float + boundingBoxP2Z: Float +} + +enum ReferenceModelAction { + Update + Ingest +} + +type ReferenceModel { + idSystemObject: Int! + name: String! + fileSize: Int! + resolution: Int + boundingBoxP1X: Float + boundingBoxP1Y: Float + boundingBoxP1Z: Float + boundingBoxP2X: Float + boundingBoxP2Y: Float + boundingBoxP2Z: Float + action: ReferenceModelAction! } type IngestScene { idAssetVersion: Int! + systemCreated: Boolean! identifiers: [IngestIdentifier!]! + referenceModels: [ReferenceModel!]! } type GetAssetVersionDetailResult { diff --git a/server/graphql/schema/ingestion/mutations.graphql b/server/graphql/schema/ingestion/mutations.graphql index 9db538be4..903e85864 100644 --- a/server/graphql/schema/ingestion/mutations.graphql +++ b/server/graphql/schema/ingestion/mutations.graphql @@ -56,6 +56,13 @@ input IngestUVMapInput { mapType: Int! } +input SourceObjectInput { + idSystemObject: Int! + name: String! + identifier: String! + objectType: Int! +} + input IngestModelInput { idAssetVersion: Int! systemCreated: Boolean! @@ -70,6 +77,7 @@ input IngestModelInput { directory: String! identifiers: [IngestIdentifierInput!]! uvMaps: [IngestUVMapInput!]! + sourceObjects: [SourceObjectInput!]! roughness: Int metalness: Int pointCount: Int @@ -78,21 +86,38 @@ input IngestModelInput { hasNormals: Boolean hasVertexColor: Boolean hasUVSpace: Boolean - boundingBoxP1X: Int - boundingBoxP1Y: Int - boundingBoxP1Z: Int - boundingBoxP2X: Int - boundingBoxP2Y: Int - boundingBoxP2Z: Int + boundingBoxP1X: Float + boundingBoxP1Y: Float + boundingBoxP1Z: Float + boundingBoxP2X: Float + boundingBoxP2Y: Float + boundingBoxP2Z: Float +} + +input ReferenceModelInput { + idSystemObject: Int! + name: String! + fileSize: Int! + resolution: Int + boundingBoxP1X: Float + boundingBoxP1Y: Float + boundingBoxP1Z: Float + boundingBoxP2X: Float + boundingBoxP2Y: Float + boundingBoxP2Z: Float + action: ReferenceModelAction! } input IngestSceneInput { idAssetVersion: Int! + systemCreated: Boolean! identifiers: [IngestIdentifierInput!]! + referenceModels: [ReferenceModelInput!]! } input IngestOtherInput { idAssetVersion: Int! + systemCreated: Boolean! identifiers: [IngestIdentifierInput!]! } diff --git a/server/types/graphql.ts b/server/types/graphql.ts index 66a637b49..fb4bb6416 100644 --- a/server/types/graphql.ts +++ b/server/types/graphql.ts @@ -356,6 +356,22 @@ export type IngestUvMap = { mapType: Scalars['Int']; }; +export type SourceObject = { + __typename?: 'SourceObject'; + idSystemObject: Scalars['Int']; + name: Scalars['String']; + identifier: Scalars['String']; + objectType: Scalars['Int']; +}; + +export type DerivedObject = { + __typename?: 'DerivedObject'; + idSystemObject: Scalars['Int']; + name: Scalars['String']; + variantType: Scalars['Int']; + objectType: Scalars['Int']; +}; + export type IngestModel = { __typename?: 'IngestModel'; idAssetVersion: Scalars['Int']; @@ -371,6 +387,7 @@ export type IngestModel = { directory: Scalars['String']; identifiers: Array; uvMaps: Array; + sourceObjects: Array; roughness?: Maybe; metalness?: Maybe; pointCount?: Maybe; @@ -379,18 +396,40 @@ export type IngestModel = { hasNormals?: Maybe; hasVertexColor?: Maybe; hasUVSpace?: Maybe; - boundingBoxP1X?: Maybe; - boundingBoxP1Y?: Maybe; - boundingBoxP1Z?: Maybe; - boundingBoxP2X?: Maybe; - boundingBoxP2Y?: Maybe; - boundingBoxP2Z?: Maybe; + boundingBoxP1X?: Maybe; + boundingBoxP1Y?: Maybe; + boundingBoxP1Z?: Maybe; + boundingBoxP2X?: Maybe; + boundingBoxP2Y?: Maybe; + boundingBoxP2Z?: Maybe; +}; + +export enum ReferenceModelAction { + Update = 'Update', + Ingest = 'Ingest' +} + +export type ReferenceModel = { + __typename?: 'ReferenceModel'; + idSystemObject: Scalars['Int']; + name: Scalars['String']; + fileSize: Scalars['Int']; + resolution?: Maybe; + boundingBoxP1X?: Maybe; + boundingBoxP1Y?: Maybe; + boundingBoxP1Z?: Maybe; + boundingBoxP2X?: Maybe; + boundingBoxP2Y?: Maybe; + boundingBoxP2Z?: Maybe; + action: ReferenceModelAction; }; export type IngestScene = { __typename?: 'IngestScene'; idAssetVersion: Scalars['Int']; + systemCreated: Scalars['Boolean']; identifiers: Array; + referenceModels: Array; }; export type GetAssetVersionDetailResult = { @@ -638,6 +677,13 @@ export type IngestUvMapInput = { mapType: Scalars['Int']; }; +export type SourceObjectInput = { + idSystemObject: Scalars['Int']; + name: Scalars['String']; + identifier: Scalars['String']; + objectType: Scalars['Int']; +}; + export type IngestModelInput = { idAssetVersion: Scalars['Int']; systemCreated: Scalars['Boolean']; @@ -652,6 +698,7 @@ export type IngestModelInput = { directory: Scalars['String']; identifiers: Array; uvMaps: Array; + sourceObjects: Array; roughness?: Maybe; metalness?: Maybe; pointCount?: Maybe; @@ -660,21 +707,38 @@ export type IngestModelInput = { hasNormals?: Maybe; hasVertexColor?: Maybe; hasUVSpace?: Maybe; - boundingBoxP1X?: Maybe; - boundingBoxP1Y?: Maybe; - boundingBoxP1Z?: Maybe; - boundingBoxP2X?: Maybe; - boundingBoxP2Y?: Maybe; - boundingBoxP2Z?: Maybe; + boundingBoxP1X?: Maybe; + boundingBoxP1Y?: Maybe; + boundingBoxP1Z?: Maybe; + boundingBoxP2X?: Maybe; + boundingBoxP2Y?: Maybe; + boundingBoxP2Z?: Maybe; +}; + +export type ReferenceModelInput = { + idSystemObject: Scalars['Int']; + name: Scalars['String']; + fileSize: Scalars['Int']; + resolution?: Maybe; + boundingBoxP1X?: Maybe; + boundingBoxP1Y?: Maybe; + boundingBoxP1Z?: Maybe; + boundingBoxP2X?: Maybe; + boundingBoxP2Y?: Maybe; + boundingBoxP2Z?: Maybe; + action: ReferenceModelAction; }; export type IngestSceneInput = { idAssetVersion: Scalars['Int']; + systemCreated: Scalars['Boolean']; identifiers: Array; + referenceModels: Array; }; export type IngestOtherInput = { idAssetVersion: Scalars['Int']; + systemCreated: Scalars['Boolean']; identifiers: Array; }; diff --git a/server/utils/parser/bulkIngestReader.ts b/server/utils/parser/bulkIngestReader.ts index 0f0d237d3..e65b92cea 100644 --- a/server/utils/parser/bulkIngestReader.ts +++ b/server/utils/parser/bulkIngestReader.ts @@ -378,7 +378,8 @@ export class BulkIngestReader { systemCreated: true, modelFileType: 0, identifiers: [], - uvMaps: [] + uvMaps: [], + sourceObjects: [] }; } From f97ce8b11561b0e8606accf835067ea18fa28355 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Fri, 13 Nov 2020 14:59:06 +0530 Subject: [PATCH 092/239] added and integrate getSourceObjectIdentifer for source object identifier fetching --- .../Metadata/Model/ObjectSelectModal.tsx | 49 +++++++++-- .../components/Metadata/Model/index.tsx | 1 + .../components/RepositoryTreeView/index.tsx | 6 +- client/src/types/graphql.tsx | 81 ++++++++++++++++++- client/src/utils/repository.tsx | 11 +-- server/graphql/api/index.ts | 18 ++++- .../systemobject/getSourceObjectIdentifer.ts | 14 ++++ server/graphql/schema.graphql | 18 ++++- server/graphql/schema/asset/queries.graphql | 2 +- .../schema/ingestion/mutations.graphql | 2 +- .../schema/systemobject/queries.graphql | 16 ++++ .../schema/systemobject/resolvers/index.ts | 4 + .../queries/getSourceObjectIdentifer.ts | 22 +++++ server/types/graphql.ts | 24 +++++- 14 files changed, 239 insertions(+), 29 deletions(-) create mode 100644 server/graphql/api/queries/systemobject/getSourceObjectIdentifer.ts create mode 100644 server/graphql/schema/systemobject/queries.graphql create mode 100644 server/graphql/schema/systemobject/resolvers/queries/getSourceObjectIdentifer.ts diff --git a/client/src/pages/Ingestion/components/Metadata/Model/ObjectSelectModal.tsx b/client/src/pages/Ingestion/components/Metadata/Model/ObjectSelectModal.tsx index f389bcd60..1da8a1bb2 100644 --- a/client/src/pages/Ingestion/components/Metadata/Model/ObjectSelectModal.tsx +++ b/client/src/pages/Ingestion/components/Metadata/Model/ObjectSelectModal.tsx @@ -4,12 +4,16 @@ * This component renders the source object select modal which let's user select * the source objects for a model. */ +import { ApolloQueryResult } from '@apollo/client'; import { AppBar, Box, Button, Dialog, IconButton, Slide, Toolbar, Typography } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; import { TransitionProps } from '@material-ui/core/transitions'; import CloseIcon from '@material-ui/icons/Close'; -import React from 'react'; +import React, { useState } from 'react'; +import { toast } from 'react-toastify'; +import { apolloClient } from '../../../../../graphql'; import { StateSourceObject } from '../../../../../store'; +import { GetSourceObjectIdentiferDocument, GetSourceObjectIdentiferInput, GetSourceObjectIdentiferQuery } from '../../../../../types/graphql'; import RepositoryFilterView from '../../../../Repository/components/RepositoryFilterView'; import RepositoryTreeView from '../../../../Repository/components/RepositoryTreeView'; @@ -30,6 +34,9 @@ const useStyles = makeStyles(({ palette, spacing, breakpoints }) => ({ [breakpoints.down('lg')]: { padding: 10 } + }, + loader: { + color: palette.background.paper } })); @@ -43,11 +50,39 @@ interface ObjectSelectModalProps { function ObjectSelectModal(props: ObjectSelectModalProps): React.ReactElement { const { open, onSelectedObjects, selectedObjects, onModalClose } = props; const classes = useStyles(); - const [selected, setSelected] = React.useState(selectedObjects); + const [selected, setSelected] = useState(selectedObjects); + const [isSaving, setIsSaving] = useState(false); + + const onSave = async (): Promise => { + try { + if (isSaving) return; + setIsSaving(true); + const idSystemObjects: number[] = selected.map(({ idSystemObject }) => idSystemObject); + const input: GetSourceObjectIdentiferInput = { + idSystemObjects + }; - const onSave = () => { - onSelectedObjects(selected); - setSelected([]); + const { data }: ApolloQueryResult = await apolloClient.query({ + query: GetSourceObjectIdentiferDocument, + variables: { + input + } + }); + + if (data) { + const { getSourceObjectIdentifer } = data; + const { sourceObjectIdentifiers } = getSourceObjectIdentifer; + + const selectedSourceObjects: StateSourceObject[] = selected.map((selected: StateSourceObject, index: number) => ({ + ...selected, + identifier: sourceObjectIdentifiers[index]?.identifier + })); + onSelectedObjects(selectedSourceObjects); + } + } catch (error) { + toast.error('Error occurred while fetching identifiers'); + } + setIsSaving(false); }; const onSelect = (sourceObject: StateSourceObject): void => { @@ -70,7 +105,7 @@ function ObjectSelectModal(props: ObjectSelectModalProps): React.ReactElement { Select Source Objects @@ -78,7 +113,7 @@ function ObjectSelectModal(props: ObjectSelectModalProps): React.ReactElement { - + ); } diff --git a/client/src/pages/Ingestion/components/Metadata/Model/index.tsx b/client/src/pages/Ingestion/components/Metadata/Model/index.tsx index e08c0ecdb..cffdde2bd 100644 --- a/client/src/pages/Ingestion/components/Metadata/Model/index.tsx +++ b/client/src/pages/Ingestion/components/Metadata/Model/index.tsx @@ -113,6 +113,7 @@ function Model(props: ModelProps): React.ReactElement { const { sourceObjects } = model; const updatedSourceObjects = [...sourceObjects, ...newSourceObjects]; updateMetadataField(metadataIndex, 'sourceObjects', updatedSourceObjects, MetadataType.model); + onModalClose(); }; const noteLabelProps = { style: { fontStyle: 'italic' } }; diff --git a/client/src/pages/Repository/components/RepositoryTreeView/index.tsx b/client/src/pages/Repository/components/RepositoryTreeView/index.tsx index 43a3b7732..92f1deeea 100644 --- a/client/src/pages/Repository/components/RepositoryTreeView/index.tsx +++ b/client/src/pages/Repository/components/RepositoryTreeView/index.tsx @@ -54,7 +54,7 @@ interface RepositoryTreeViewProps { } function RepositoryTreeView(props: RepositoryTreeViewProps): React.ReactElement { - const { isModal = false, selectedItems, onSelect, onUnSelect } = props; + const { isModal = false, selectedItems = [], onSelect, onUnSelect } = props; const [loading, isExpanded] = useRepositoryStore(useCallback(state => [state.loading, state.isExpanded], [])); const sideBarExpanded = useControlStore(state => state.sideBarExpanded); @@ -98,7 +98,7 @@ function RepositoryTreeView(props: RepositoryTreeViewProps): React.ReactElement const variant = getTreeColorVariant(index); const { icon, color } = getObjectInterfaceDetails(objectType, variant); const treeColumns = getTreeViewColumns(metadataColumns, false, metadata); - const isSelected = isRepositoryItemSelected(nodeId, selectedItems || []); + const isSelected = isRepositoryItemSelected(nodeId, selectedItems); const select = (event: React.MouseEvent) => { if (onSelect) { @@ -107,7 +107,7 @@ function RepositoryTreeView(props: RepositoryTreeViewProps): React.ReactElement idSystemObject, name, objectType, - identifier: 'TODO: get identifier' + identifier: '' }; onSelect(repositoryItem); } diff --git a/client/src/types/graphql.tsx b/client/src/types/graphql.tsx index 9245ddccc..1e3bbde47 100644 --- a/client/src/types/graphql.tsx +++ b/client/src/types/graphql.tsx @@ -38,6 +38,7 @@ export type Query = { getProject: GetProjectResult; getProjectDocumentation: GetProjectDocumentationResult; getScene: GetSceneResult; + getSourceObjectIdentifer: GetSourceObjectIdentiferResult; getSubject: GetSubjectResult; getSubjectsForUnit: GetSubjectsForUnitResult; getUnit: GetUnitResult; @@ -145,6 +146,11 @@ export type QueryGetSceneArgs = { }; +export type QueryGetSourceObjectIdentiferArgs = { + input: GetSourceObjectIdentiferInput; +}; + + export type QueryGetSubjectArgs = { input: GetSubjectInput; }; @@ -407,7 +413,7 @@ export type SourceObject = { __typename?: 'SourceObject'; idSystemObject: Scalars['Int']; name: Scalars['String']; - identifier: Scalars['String']; + identifier?: Maybe; objectType: Scalars['Int']; }; @@ -727,7 +733,7 @@ export type IngestUvMapInput = { export type SourceObjectInput = { idSystemObject: Scalars['Int']; name: Scalars['String']; - identifier: Scalars['String']; + identifier?: Maybe; objectType: Scalars['Int']; }; @@ -1067,6 +1073,21 @@ export type IntermediaryFile = { SystemObject?: Maybe; }; +export type GetSourceObjectIdentiferInput = { + idSystemObjects: Array; +}; + +export type SourceObjectIdentifier = { + __typename?: 'SourceObjectIdentifier'; + idSystemObject: Scalars['Int']; + identifier?: Maybe; +}; + +export type GetSourceObjectIdentiferResult = { + __typename?: 'GetSourceObjectIdentiferResult'; + sourceObjectIdentifiers: Array; +}; + export type SystemObject = { __typename?: 'SystemObject'; idSystemObject: Scalars['Int']; @@ -2153,6 +2174,26 @@ export type GetSceneQuery = ( } ); +export type GetSourceObjectIdentiferQueryVariables = Exact<{ + input: GetSourceObjectIdentiferInput; +}>; + + +export type GetSourceObjectIdentiferQuery = ( + { __typename?: 'Query' } + & { + getSourceObjectIdentifer: ( + { __typename?: 'GetSourceObjectIdentiferResult' } + & { + sourceObjectIdentifiers: Array<( + { __typename?: 'SourceObjectIdentifier' } + & Pick + )> + } + ) + } +); + export type GetIngestionItemsForSubjectsQueryVariables = Exact<{ input: GetIngestionItemsForSubjectsInput; }>; @@ -3522,6 +3563,42 @@ export function useGetSceneLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions; export type GetSceneLazyQueryHookResult = ReturnType; export type GetSceneQueryResult = Apollo.QueryResult; +export const GetSourceObjectIdentiferDocument = gql` + query getSourceObjectIdentifer($input: GetSourceObjectIdentiferInput!) { + getSourceObjectIdentifer(input: $input) { + sourceObjectIdentifiers { + idSystemObject + identifier + } + } +} + `; + +/** + * __useGetSourceObjectIdentiferQuery__ + * + * To run a query within a React component, call `useGetSourceObjectIdentiferQuery` and pass it any options that fit your needs. + * When your component renders, `useGetSourceObjectIdentiferQuery` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. + * + * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useGetSourceObjectIdentiferQuery({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ +export function useGetSourceObjectIdentiferQuery(baseOptions?: Apollo.QueryHookOptions) { + return Apollo.useQuery(GetSourceObjectIdentiferDocument, baseOptions); +} +export function useGetSourceObjectIdentiferLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { + return Apollo.useLazyQuery(GetSourceObjectIdentiferDocument, baseOptions); +} +export type GetSourceObjectIdentiferQueryHookResult = ReturnType; +export type GetSourceObjectIdentiferLazyQueryHookResult = ReturnType; +export type GetSourceObjectIdentiferQueryResult = Apollo.QueryResult; export const GetIngestionItemsForSubjectsDocument = gql` query getIngestionItemsForSubjects($input: GetIngestionItemsForSubjectsInput!) { getIngestionItemsForSubjects(input: $input) { diff --git a/client/src/utils/repository.tsx b/client/src/utils/repository.tsx index 6f4b72468..ef62868af 100644 --- a/client/src/utils/repository.tsx +++ b/client/src/utils/repository.tsx @@ -218,16 +218,9 @@ export function sortEntriesAlphabetically(entries: NavigationResultEntry[]): Nav export function isRepositoryItemSelected(nodeId: string, sourceObjects: StateSourceObject[]): boolean { const { idSystemObject } = parseRepositoryTreeNodeId(nodeId); + const idSystemObjects: number[] = sourceObjects.map(({ idSystemObject }) => idSystemObject); - for (let i = 0; i < sourceObjects.length; i++) { - const sourceObject = sourceObjects[i]; - - if (sourceObject.idSystemObject === idSystemObject) { - return true; - } - } - - return false; + return idSystemObjects.includes(idSystemObject); } export function getDetailsUrlForObject(idSystemObject: number): string { diff --git a/server/graphql/api/index.ts b/server/graphql/api/index.ts index 920b4e03f..3dff5bad6 100644 --- a/server/graphql/api/index.ts +++ b/server/graphql/api/index.ts @@ -90,7 +90,9 @@ import { DiscardUploadedAssetVersionsInput, DiscardUploadedAssetVersionsResult, GetObjectChildrenInput, - GetObjectChildrenResult + GetObjectChildrenResult, + GetSourceObjectIdentiferInput, + GetSourceObjectIdentiferResult } from '../../types/graphql'; // Queries @@ -123,6 +125,7 @@ import getObjectsForItem from './queries/unit/getObjectsForItem'; import getProjectDocumentation from './queries/unit/getProjectDocumentation'; import getIntermediaryFile from './queries/scene/getIntermediaryFile'; import getObjectChildren from './queries/repository/getObjectChildren'; +import getSourceObjectIdentifer from './queries/systemobject/getSourceObjectIdentifer'; // Mutations import createUser from './mutations/user/createUser'; @@ -185,7 +188,8 @@ const allQueries = { getProjectDocumentation, getIntermediaryFile, discardUploadedAssetVersions, - getObjectChildren + getObjectChildren, + getSourceObjectIdentifer }; type GraphQLRequest = { @@ -516,6 +520,16 @@ class GraphQLApi { }); } + async getSourceObjectIdentifer(input: GetSourceObjectIdentiferInput, context?: Context): Promise { + const operationName = 'getSourceObjectIdentifer'; + const variables = { input }; + return this.graphqlRequest({ + operationName, + variables, + context + }); + } + async createUnit(input: CreateUnitInput, context?: Context): Promise { const operationName = 'createUnit'; const variables = { input }; diff --git a/server/graphql/api/queries/systemobject/getSourceObjectIdentifer.ts b/server/graphql/api/queries/systemobject/getSourceObjectIdentifer.ts new file mode 100644 index 000000000..e2f5ddd5e --- /dev/null +++ b/server/graphql/api/queries/systemobject/getSourceObjectIdentifer.ts @@ -0,0 +1,14 @@ +import { gql } from 'apollo-server-express'; + +const getSourceObjectIdentifer = gql` + query getSourceObjectIdentifer($input: GetSourceObjectIdentiferInput!) { + getSourceObjectIdentifer(input: $input) { + sourceObjectIdentifiers { + idSystemObject + identifier + } + } + } +`; + +export default getSourceObjectIdentifer; diff --git a/server/graphql/schema.graphql b/server/graphql/schema.graphql index f77de715f..7b25628f9 100644 --- a/server/graphql/schema.graphql +++ b/server/graphql/schema.graphql @@ -19,6 +19,7 @@ type Query { getProject(input: GetProjectInput!): GetProjectResult! getProjectDocumentation(input: GetProjectDocumentationInput!): GetProjectDocumentationResult! getScene(input: GetSceneInput!): GetSceneResult! + getSourceObjectIdentifer(input: GetSourceObjectIdentiferInput!): GetSourceObjectIdentiferResult! getSubject(input: GetSubjectInput!): GetSubjectResult! getSubjectsForUnit(input: GetSubjectsForUnitInput!): GetSubjectsForUnitResult! getUnit(input: GetUnitInput!): GetUnitResult! @@ -169,7 +170,7 @@ type IngestUVMap { type SourceObject { idSystemObject: Int! name: String! - identifier: String! + identifier: String objectType: Int! } @@ -468,7 +469,7 @@ input IngestUVMapInput { input SourceObjectInput { idSystemObject: Int! name: String! - identifier: String! + identifier: String objectType: Int! } @@ -786,6 +787,19 @@ type IntermediaryFile { SystemObject: SystemObject } +input GetSourceObjectIdentiferInput { + idSystemObjects: [Int!]! +} + +type SourceObjectIdentifier { + idSystemObject: Int! + identifier: String +} + +type GetSourceObjectIdentiferResult { + sourceObjectIdentifiers: [SourceObjectIdentifier!]! +} + type SystemObject { idSystemObject: Int! Retired: Boolean! diff --git a/server/graphql/schema/asset/queries.graphql b/server/graphql/schema/asset/queries.graphql index e1358436a..6a527279c 100644 --- a/server/graphql/schema/asset/queries.graphql +++ b/server/graphql/schema/asset/queries.graphql @@ -48,7 +48,7 @@ type IngestUVMap { type SourceObject { idSystemObject: Int! name: String! - identifier: String! + identifier: String objectType: Int! } diff --git a/server/graphql/schema/ingestion/mutations.graphql b/server/graphql/schema/ingestion/mutations.graphql index 903e85864..6225d89f4 100644 --- a/server/graphql/schema/ingestion/mutations.graphql +++ b/server/graphql/schema/ingestion/mutations.graphql @@ -59,7 +59,7 @@ input IngestUVMapInput { input SourceObjectInput { idSystemObject: Int! name: String! - identifier: String! + identifier: String objectType: Int! } diff --git a/server/graphql/schema/systemobject/queries.graphql b/server/graphql/schema/systemobject/queries.graphql new file mode 100644 index 000000000..b04fb1c46 --- /dev/null +++ b/server/graphql/schema/systemobject/queries.graphql @@ -0,0 +1,16 @@ +type Query { + getSourceObjectIdentifer(input: GetSourceObjectIdentiferInput!): GetSourceObjectIdentiferResult! +} + +input GetSourceObjectIdentiferInput { + idSystemObjects: [Int!]! +} + +type SourceObjectIdentifier { + idSystemObject: Int! + identifier: String +} + +type GetSourceObjectIdentiferResult { + sourceObjectIdentifiers: [SourceObjectIdentifier!]! +} diff --git a/server/graphql/schema/systemobject/resolvers/index.ts b/server/graphql/schema/systemobject/resolvers/index.ts index ae5023938..faa1f637a 100644 --- a/server/graphql/schema/systemobject/resolvers/index.ts +++ b/server/graphql/schema/systemobject/resolvers/index.ts @@ -2,8 +2,12 @@ import SystemObject from './types/SystemObject'; import SystemObjectVersion from './types/SystemObjectVersion'; import Identifier from './types/Identifier'; import Metadata from './types/Metadata'; +import getSourceObjectIdentifer from './queries/getSourceObjectIdentifer'; const resolvers = { + Query: { + getSourceObjectIdentifer + }, SystemObject, SystemObjectVersion, Identifier, diff --git a/server/graphql/schema/systemobject/resolvers/queries/getSourceObjectIdentifer.ts b/server/graphql/schema/systemobject/resolvers/queries/getSourceObjectIdentifer.ts new file mode 100644 index 000000000..f05b3a3fa --- /dev/null +++ b/server/graphql/schema/systemobject/resolvers/queries/getSourceObjectIdentifer.ts @@ -0,0 +1,22 @@ +import { GetSourceObjectIdentiferResult, QueryGetSourceObjectIdentiferArgs, SourceObjectIdentifier } from '../../../../../types/graphql'; +import { Parent } from '../../../../../types/resolvers'; +import * as DBAPI from '../../../../../db'; + +export default async function getSourceObjectIdentifer(_: Parent, args: QueryGetSourceObjectIdentiferArgs): Promise { + const { input } = args; + const { idSystemObjects } = input; + const sourceObjectIdentifiers: SourceObjectIdentifier[] = []; + + for (let i = 0; i < idSystemObjects.length; i++) { + const idSystemObject = idSystemObjects[i]; + const identifier: DBAPI.Identifier[] | null = await DBAPI.Identifier.fetchFromSystemObject(idSystemObject); + + const sourceObjectIdentifier: SourceObjectIdentifier = { + idSystemObject, + identifier: identifier?.[0]?.IdentifierValue ?? null + }; + sourceObjectIdentifiers.push(sourceObjectIdentifier); + } + + return { sourceObjectIdentifiers }; +} diff --git a/server/types/graphql.ts b/server/types/graphql.ts index fb4bb6416..6541a379c 100644 --- a/server/types/graphql.ts +++ b/server/types/graphql.ts @@ -34,6 +34,7 @@ export type Query = { getProject: GetProjectResult; getProjectDocumentation: GetProjectDocumentationResult; getScene: GetSceneResult; + getSourceObjectIdentifer: GetSourceObjectIdentiferResult; getSubject: GetSubjectResult; getSubjectsForUnit: GetSubjectsForUnitResult; getUnit: GetUnitResult; @@ -121,6 +122,10 @@ export type QueryGetSceneArgs = { input: GetSceneInput; }; +export type QueryGetSourceObjectIdentiferArgs = { + input: GetSourceObjectIdentiferInput; +}; + export type QueryGetSubjectArgs = { input: GetSubjectInput; }; @@ -360,7 +365,7 @@ export type SourceObject = { __typename?: 'SourceObject'; idSystemObject: Scalars['Int']; name: Scalars['String']; - identifier: Scalars['String']; + identifier?: Maybe; objectType: Scalars['Int']; }; @@ -680,7 +685,7 @@ export type IngestUvMapInput = { export type SourceObjectInput = { idSystemObject: Scalars['Int']; name: Scalars['String']; - identifier: Scalars['String']; + identifier?: Maybe; objectType: Scalars['Int']; }; @@ -1020,6 +1025,21 @@ export type IntermediaryFile = { SystemObject?: Maybe; }; +export type GetSourceObjectIdentiferInput = { + idSystemObjects: Array; +}; + +export type SourceObjectIdentifier = { + __typename?: 'SourceObjectIdentifier'; + idSystemObject: Scalars['Int']; + identifier?: Maybe; +}; + +export type GetSourceObjectIdentiferResult = { + __typename?: 'GetSourceObjectIdentiferResult'; + sourceObjectIdentifiers: Array; +}; + export type SystemObject = { __typename?: 'SystemObject'; idSystemObject: Scalars['Int']; From 5f9a4b0a8fe82e16ebe89eaa1fc48ed1877b5062 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Fri, 13 Nov 2020 15:44:05 +0530 Subject: [PATCH 093/239] make source object select modal intuitive --- .../Metadata/Model/ObjectSelectModal.tsx | 28 ++++++++----------- .../components/RepositoryTreeView/index.tsx | 13 +++++---- client/src/utils/repository.tsx | 20 +++++++++++++ 3 files changed, 39 insertions(+), 22 deletions(-) diff --git a/client/src/pages/Ingestion/components/Metadata/Model/ObjectSelectModal.tsx b/client/src/pages/Ingestion/components/Metadata/Model/ObjectSelectModal.tsx index 1da8a1bb2..b026709eb 100644 --- a/client/src/pages/Ingestion/components/Metadata/Model/ObjectSelectModal.tsx +++ b/client/src/pages/Ingestion/components/Metadata/Model/ObjectSelectModal.tsx @@ -5,11 +5,9 @@ * the source objects for a model. */ import { ApolloQueryResult } from '@apollo/client'; -import { AppBar, Box, Button, Dialog, IconButton, Slide, Toolbar, Typography } from '@material-ui/core'; +import { AppBar, Box, Button, Dialog, Toolbar, Typography } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; -import { TransitionProps } from '@material-ui/core/transitions'; -import CloseIcon from '@material-ui/icons/Close'; -import React, { useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { toast } from 'react-toastify'; import { apolloClient } from '../../../../../graphql'; import { StateSourceObject } from '../../../../../store'; @@ -20,6 +18,7 @@ import RepositoryTreeView from '../../../../Repository/components/RepositoryTree const useStyles = makeStyles(({ palette, spacing, breakpoints }) => ({ title: { marginLeft: spacing(2), + textAlign: 'center', flex: 1, }, appBar: { @@ -50,9 +49,13 @@ interface ObjectSelectModalProps { function ObjectSelectModal(props: ObjectSelectModalProps): React.ReactElement { const { open, onSelectedObjects, selectedObjects, onModalClose } = props; const classes = useStyles(); - const [selected, setSelected] = useState(selectedObjects); + const [selected, setSelected] = useState([]); const [isSaving, setIsSaving] = useState(false); + useEffect(() => { + setSelected(selectedObjects); + }, [selectedObjects]); + const onSave = async (): Promise => { try { if (isSaving) return; @@ -95,12 +98,12 @@ function ObjectSelectModal(props: ObjectSelectModalProps): React.ReactElement { }; return ( - + - - - + Select Source Objects @@ -117,11 +120,4 @@ function ObjectSelectModal(props: ObjectSelectModalProps): React.ReactElement { ); } -const Transition = React.forwardRef(function Transition( - props: TransitionProps & { children?: React.ReactElement }, - ref: React.Ref, -) { - return ; -}); - export default ObjectSelectModal; diff --git a/client/src/pages/Repository/components/RepositoryTreeView/index.tsx b/client/src/pages/Repository/components/RepositoryTreeView/index.tsx index 92f1deeea..03dc7eb3b 100644 --- a/client/src/pages/Repository/components/RepositoryTreeView/index.tsx +++ b/client/src/pages/Repository/components/RepositoryTreeView/index.tsx @@ -12,7 +12,7 @@ import React, { useCallback, useEffect } from 'react'; import { Loader } from '../../../../components'; import { StateSourceObject, treeRootKey, useControlStore, useRepositoryStore } from '../../../../store'; import { NavigationResultEntry } from '../../../../types/graphql'; -import { getObjectInterfaceDetails, getRepositoryTreeNodeId, getTreeColorVariant, getTreeViewColumns, getTreeWidth, isRepositoryItemSelected } from '../../../../utils/repository'; +import { getObjectInterfaceDetails, getRepositoryTreeNodeId, getTreeColorVariant, getTreeViewColumns, getTreeViewStyleHeight, getTreeViewStyleWidth, getTreeWidth, isRepositoryItemSelected } from '../../../../utils/repository'; import RepositoryTreeHeader from './RepositoryTreeHeader'; import StyledTreeItem from './StyledTreeItem'; import TreeLabel, { TreeLabelEmpty, TreeLabelLoading } from './TreeLabel'; @@ -21,14 +21,14 @@ const useStyles = makeStyles(({ breakpoints }) => ({ container: { display: 'flex', flex: 5, - maxHeight: ({ isExpanded }: StyleProps) => isExpanded ? '62vh' : '82vh', - maxWidth: ({ sideBarExpanded }: StyleProps) => sideBarExpanded ? '85vw' : '93vw', + maxHeight: ({ isExpanded, isModal }: StyleProps) => getTreeViewStyleHeight(isExpanded, isModal, 'xl'), + maxWidth: ({ sideBarExpanded }: StyleProps) => getTreeViewStyleWidth(sideBarExpanded, 'xl'), flexDirection: 'column', overflow: 'auto', transition: '250ms height, width ease', [breakpoints.down('lg')]: { - maxHeight: ({ isExpanded }: StyleProps) => isExpanded ? '54vh' : '79vh', - maxWidth: ({ sideBarExpanded }: StyleProps) => sideBarExpanded ? '81.5vw' : '92vw' + maxHeight: ({ isExpanded, isModal }: StyleProps) => getTreeViewStyleHeight(isExpanded, isModal, 'lg'), + maxWidth: ({ sideBarExpanded }: StyleProps) => getTreeViewStyleWidth(sideBarExpanded, 'lg') } }, tree: { @@ -44,6 +44,7 @@ const useStyles = makeStyles(({ breakpoints }) => ({ type StyleProps = { sideBarExpanded: boolean; isExpanded: boolean; + isModal: boolean; }; interface RepositoryTreeViewProps { @@ -59,7 +60,7 @@ function RepositoryTreeView(props: RepositoryTreeViewProps): React.ReactElement const [loading, isExpanded] = useRepositoryStore(useCallback(state => [state.loading, state.isExpanded], [])); const sideBarExpanded = useControlStore(state => state.sideBarExpanded); - const classes = useStyles({ isExpanded, sideBarExpanded }); + const classes = useStyles({ isExpanded, sideBarExpanded, isModal }); const [tree, initializeTree, getChildren] = useRepositoryStore(state => [state.tree, state.initializeTree, state.getChildren]); const metadataColumns = useRepositoryStore(state => state.metadataToDisplay); diff --git a/client/src/utils/repository.tsx b/client/src/utils/repository.tsx index ef62868af..23b4023a0 100644 --- a/client/src/utils/repository.tsx +++ b/client/src/utils/repository.tsx @@ -4,6 +4,7 @@ * * Utilities for components associated with Repository UI. */ +import { Breakpoint } from '@material-ui/core/styles/createBreakpoints'; import lodash from 'lodash'; import * as qs from 'query-string'; import React from 'react'; @@ -225,4 +226,23 @@ export function isRepositoryItemSelected(nodeId: string, sourceObjects: StateSou export function getDetailsUrlForObject(idSystemObject: number): string { return `/repository/details/${idSystemObject}`; +} + +export function getTreeViewStyleHeight(isExpanded: boolean, isModal: boolean, breakpoint: Breakpoint): string { + const isSmallScreen: boolean = breakpoint === 'lg'; + + if (isExpanded) { + if (isModal) return isSmallScreen ? '45vh' : '55vh'; + else return isSmallScreen ? '54vh' : '62vh'; + } else { + if (isModal) return isSmallScreen ? '70vh' : '75vh'; + else return isSmallScreen ? '79vh' : '82vh'; + } +} + +export function getTreeViewStyleWidth(sideBarExpanded: boolean, breakpoint: Breakpoint): string { + const isSmallScreen: boolean = breakpoint === 'lg'; + + if (sideBarExpanded) return isSmallScreen ? '81.5vw' : '85vw'; + else return isSmallScreen ? '92vw' : '93vw'; } \ No newline at end of file From 2af4f623acf0a3be6f4877d6fc4c8609384a4b9a Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Fri, 13 Nov 2020 20:43:21 +0530 Subject: [PATCH 094/239] fix duplicate source objects issue --- .../src/pages/Ingestion/components/Metadata/Model/index.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/client/src/pages/Ingestion/components/Metadata/Model/index.tsx b/client/src/pages/Ingestion/components/Metadata/Model/index.tsx index cffdde2bd..656eecd0d 100644 --- a/client/src/pages/Ingestion/components/Metadata/Model/index.tsx +++ b/client/src/pages/Ingestion/components/Metadata/Model/index.tsx @@ -110,9 +110,7 @@ function Model(props: ModelProps): React.ReactElement { }; const onSelectedObjects = (newSourceObjects: StateSourceObject[]) => { - const { sourceObjects } = model; - const updatedSourceObjects = [...sourceObjects, ...newSourceObjects]; - updateMetadataField(metadataIndex, 'sourceObjects', updatedSourceObjects, MetadataType.model); + updateMetadataField(metadataIndex, 'sourceObjects', newSourceObjects, MetadataType.model); onModalClose(); }; From 521c67e7cab2e3c8657179b4444302eb2e4c4148 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Fri, 13 Nov 2020 21:19:09 +0530 Subject: [PATCH 095/239] minor lint fix --- .../Ingestion/components/Metadata/Model/BoundingBoxInput.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/pages/Ingestion/components/Metadata/Model/BoundingBoxInput.tsx b/client/src/pages/Ingestion/components/Metadata/Model/BoundingBoxInput.tsx index 7bf403249..09bc51e46 100644 --- a/client/src/pages/Ingestion/components/Metadata/Model/BoundingBoxInput.tsx +++ b/client/src/pages/Ingestion/components/Metadata/Model/BoundingBoxInput.tsx @@ -1,7 +1,7 @@ /** * BoundingBoxInput - * - * This is the component used in Model metadata component for + * + * This is the component used in Model metadata component for * bounding box input. */ import { Box } from '@material-ui/core'; From 0dbcca7967d6c7dc2b406a4e527619cac4e21ef1 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Mon, 16 Nov 2020 17:29:59 +0530 Subject: [PATCH 096/239] added getSystemObjectDetails query --- client/src/store/repository.ts | 6 - client/src/types/graphql.tsx | 118 ++++++++++++++++++ server/graphql/api/index.ts | 18 ++- .../systemobject/getSystemObjectDetails.ts | 36 ++++++ server/graphql/schema.graphql | 23 ++++ .../schema/systemobject/queries.graphql | 23 ++++ .../schema/systemobject/resolvers/index.ts | 2 + server/types/graphql.ts | 29 +++++ 8 files changed, 247 insertions(+), 8 deletions(-) create mode 100644 server/graphql/api/queries/systemobject/getSystemObjectDetails.ts diff --git a/client/src/store/repository.ts b/client/src/store/repository.ts index 39cf20ad3..69ed6f1c8 100644 --- a/client/src/store/repository.ts +++ b/client/src/store/repository.ts @@ -9,12 +9,6 @@ import { NavigationResultEntry } from '../types/graphql'; import { eMetadata, eSystemObjectType } from '../types/server'; import { parseRepositoryTreeNodeId, sortEntriesAlphabetically } from '../utils/repository'; -export type RepositoryPath = { - idSystemObject: number; - name: string; - objectType: eSystemObjectType; -}; - type RepositoryStore = { isExpanded: boolean; search: string; diff --git a/client/src/types/graphql.tsx b/client/src/types/graphql.tsx index 1e3bbde47..05b7864e7 100644 --- a/client/src/types/graphql.tsx +++ b/client/src/types/graphql.tsx @@ -41,6 +41,7 @@ export type Query = { getSourceObjectIdentifer: GetSourceObjectIdentiferResult; getSubject: GetSubjectResult; getSubjectsForUnit: GetSubjectsForUnitResult; + getSystemObjectDetails: GetSystemObjectDetailsResult; getUnit: GetUnitResult; getUploadedAssetVersion: GetUploadedAssetVersionResult; getUser: GetUserResult; @@ -161,6 +162,11 @@ export type QueryGetSubjectsForUnitArgs = { }; +export type QueryGetSystemObjectDetailsArgs = { + input: GetSystemObjectDetailsInput; +}; + + export type QueryGetUnitArgs = { input: GetUnitInput; }; @@ -1073,6 +1079,30 @@ export type IntermediaryFile = { SystemObject?: Maybe; }; +export type GetSystemObjectDetailsInput = { + idSystemObject: Scalars['Int']; +}; + +export type RepositoryPath = { + __typename?: 'RepositoryPath'; + idSystemObject: Scalars['Int']; + name: Scalars['String']; + objectType: Scalars['Int']; +}; + +export type GetSystemObjectDetailsResult = { + __typename?: 'GetSystemObjectDetailsResult'; + name: Scalars['String']; + retired: Scalars['Boolean']; + objectType: Scalars['Int']; + allowed: Scalars['Boolean']; + thumbnail?: Maybe; + identifiers: Array; + objectAncestors: Array>; + sourceObjects: Array; + derivedObjects: Array; +}; + export type GetSourceObjectIdentiferInput = { idSystemObjects: Array; }; @@ -2194,6 +2224,36 @@ export type GetSourceObjectIdentiferQuery = ( } ); +export type GetSystemObjectDetailsQueryVariables = Exact<{ + input: GetSystemObjectDetailsInput; +}>; + + +export type GetSystemObjectDetailsQuery = ( + { __typename?: 'Query' } + & { + getSystemObjectDetails: ( + { __typename?: 'GetSystemObjectDetailsResult' } + & Pick + & { + identifiers: Array<( + { __typename?: 'IngestIdentifier' } + & Pick + )>, objectAncestors: Array + )>>, sourceObjects: Array<( + { __typename?: 'SourceObject' } + & Pick + )>, derivedObjects: Array<( + { __typename?: 'DerivedObject' } + & Pick + )> + } + ) + } +); + export type GetIngestionItemsForSubjectsQueryVariables = Exact<{ input: GetIngestionItemsForSubjectsInput; }>; @@ -3599,6 +3659,64 @@ export function useGetSourceObjectIdentiferLazyQuery(baseOptions?: Apollo.LazyQu export type GetSourceObjectIdentiferQueryHookResult = ReturnType; export type GetSourceObjectIdentiferLazyQueryHookResult = ReturnType; export type GetSourceObjectIdentiferQueryResult = Apollo.QueryResult; +export const GetSystemObjectDetailsDocument = gql` + query getSystemObjectDetails($input: GetSystemObjectDetailsInput!) { + getSystemObjectDetails(input: $input) { + name + retired + objectType + allowed + thumbnail + identifiers { + identifier + identifierType + } + objectAncestors { + idSystemObject + name + objectType + } + sourceObjects { + idSystemObject + name + identifier + objectType + } + derivedObjects { + idSystemObject + name + variantType + objectType + } + } +} + `; + +/** + * __useGetSystemObjectDetailsQuery__ + * + * To run a query within a React component, call `useGetSystemObjectDetailsQuery` and pass it any options that fit your needs. + * When your component renders, `useGetSystemObjectDetailsQuery` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. + * + * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useGetSystemObjectDetailsQuery({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ +export function useGetSystemObjectDetailsQuery(baseOptions?: Apollo.QueryHookOptions) { + return Apollo.useQuery(GetSystemObjectDetailsDocument, baseOptions); +} +export function useGetSystemObjectDetailsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { + return Apollo.useLazyQuery(GetSystemObjectDetailsDocument, baseOptions); +} +export type GetSystemObjectDetailsQueryHookResult = ReturnType; +export type GetSystemObjectDetailsLazyQueryHookResult = ReturnType; +export type GetSystemObjectDetailsQueryResult = Apollo.QueryResult; export const GetIngestionItemsForSubjectsDocument = gql` query getIngestionItemsForSubjects($input: GetIngestionItemsForSubjectsInput!) { getIngestionItemsForSubjects(input: $input) { diff --git a/server/graphql/api/index.ts b/server/graphql/api/index.ts index 3dff5bad6..93107f188 100644 --- a/server/graphql/api/index.ts +++ b/server/graphql/api/index.ts @@ -92,7 +92,9 @@ import { GetObjectChildrenInput, GetObjectChildrenResult, GetSourceObjectIdentiferInput, - GetSourceObjectIdentiferResult + GetSourceObjectIdentiferResult, + GetSystemObjectDetailsInput, + GetSystemObjectDetailsResult } from '../../types/graphql'; // Queries @@ -126,6 +128,7 @@ import getProjectDocumentation from './queries/unit/getProjectDocumentation'; import getIntermediaryFile from './queries/scene/getIntermediaryFile'; import getObjectChildren from './queries/repository/getObjectChildren'; import getSourceObjectIdentifer from './queries/systemobject/getSourceObjectIdentifer'; +import getSystemObjectDetails from './queries/systemobject/getSystemObjectDetails'; // Mutations import createUser from './mutations/user/createUser'; @@ -189,7 +192,8 @@ const allQueries = { getIntermediaryFile, discardUploadedAssetVersions, getObjectChildren, - getSourceObjectIdentifer + getSourceObjectIdentifer, + getSystemObjectDetails }; type GraphQLRequest = { @@ -530,6 +534,16 @@ class GraphQLApi { }); } + async getSystemObjectDetails(input: GetSystemObjectDetailsInput, context?: Context): Promise { + const operationName = 'getSystemObjectDetails'; + const variables = { input }; + return this.graphqlRequest({ + operationName, + variables, + context + }); + } + async createUnit(input: CreateUnitInput, context?: Context): Promise { const operationName = 'createUnit'; const variables = { input }; diff --git a/server/graphql/api/queries/systemobject/getSystemObjectDetails.ts b/server/graphql/api/queries/systemobject/getSystemObjectDetails.ts new file mode 100644 index 000000000..2dcf44905 --- /dev/null +++ b/server/graphql/api/queries/systemobject/getSystemObjectDetails.ts @@ -0,0 +1,36 @@ +import { gql } from 'apollo-server-express'; + +const getSystemObjectDetails = gql` + query getSystemObjectDetails($input: GetSystemObjectDetailsInput!) { + getSystemObjectDetails(input: $input) { + name + retired + objectType + allowed + thumbnail + identifiers { + identifier + identifierType + } + objectAncestors { + idSystemObject + name + objectType + } + sourceObjects { + idSystemObject + name + identifier + objectType + } + derivedObjects { + idSystemObject + name + variantType + objectType + } + } + } +`; + +export default getSystemObjectDetails; diff --git a/server/graphql/schema.graphql b/server/graphql/schema.graphql index 7b25628f9..efc04c8c3 100644 --- a/server/graphql/schema.graphql +++ b/server/graphql/schema.graphql @@ -22,6 +22,7 @@ type Query { getSourceObjectIdentifer(input: GetSourceObjectIdentiferInput!): GetSourceObjectIdentiferResult! getSubject(input: GetSubjectInput!): GetSubjectResult! getSubjectsForUnit(input: GetSubjectsForUnitInput!): GetSubjectsForUnitResult! + getSystemObjectDetails(input: GetSystemObjectDetailsInput!): GetSystemObjectDetailsResult! getUnit(input: GetUnitInput!): GetUnitResult! getUploadedAssetVersion: GetUploadedAssetVersionResult! getUser(input: GetUserInput!): GetUserResult! @@ -787,6 +788,28 @@ type IntermediaryFile { SystemObject: SystemObject } +input GetSystemObjectDetailsInput { + idSystemObject: Int! +} + +type RepositoryPath { + idSystemObject: Int! + name: String! + objectType: Int! +} + +type GetSystemObjectDetailsResult { + name: String! + retired: Boolean! + objectType: Int! + allowed: Boolean! + thumbnail: String + identifiers: [IngestIdentifier!]! + objectAncestors: [[RepositoryPath!]!]! + sourceObjects: [SourceObject!]! + derivedObjects: [DerivedObject!]! +} + input GetSourceObjectIdentiferInput { idSystemObjects: [Int!]! } diff --git a/server/graphql/schema/systemobject/queries.graphql b/server/graphql/schema/systemobject/queries.graphql index b04fb1c46..f239d1fa2 100644 --- a/server/graphql/schema/systemobject/queries.graphql +++ b/server/graphql/schema/systemobject/queries.graphql @@ -1,7 +1,30 @@ type Query { + getSystemObjectDetails(input: GetSystemObjectDetailsInput!): GetSystemObjectDetailsResult! getSourceObjectIdentifer(input: GetSourceObjectIdentiferInput!): GetSourceObjectIdentiferResult! } +input GetSystemObjectDetailsInput { + idSystemObject: Int! +} + +type RepositoryPath { + idSystemObject: Int! + name: String! + objectType: Int! +} + +type GetSystemObjectDetailsResult { + name: String! + retired: Boolean! + objectType: Int! + allowed: Boolean! + thumbnail: String + identifiers: [IngestIdentifier!]! + objectAncestors: [[RepositoryPath!]!]! + sourceObjects: [SourceObject!]! + derivedObjects: [DerivedObject!]! +} + input GetSourceObjectIdentiferInput { idSystemObjects: [Int!]! } diff --git a/server/graphql/schema/systemobject/resolvers/index.ts b/server/graphql/schema/systemobject/resolvers/index.ts index faa1f637a..be31a4b0d 100644 --- a/server/graphql/schema/systemobject/resolvers/index.ts +++ b/server/graphql/schema/systemobject/resolvers/index.ts @@ -2,10 +2,12 @@ import SystemObject from './types/SystemObject'; import SystemObjectVersion from './types/SystemObjectVersion'; import Identifier from './types/Identifier'; import Metadata from './types/Metadata'; +import getSystemObjectDetails from './queries/getSystemObjectDetails'; import getSourceObjectIdentifer from './queries/getSourceObjectIdentifer'; const resolvers = { Query: { + getSystemObjectDetails, getSourceObjectIdentifer }, SystemObject, diff --git a/server/types/graphql.ts b/server/types/graphql.ts index 6541a379c..9dcad7231 100644 --- a/server/types/graphql.ts +++ b/server/types/graphql.ts @@ -37,6 +37,7 @@ export type Query = { getSourceObjectIdentifer: GetSourceObjectIdentiferResult; getSubject: GetSubjectResult; getSubjectsForUnit: GetSubjectsForUnitResult; + getSystemObjectDetails: GetSystemObjectDetailsResult; getUnit: GetUnitResult; getUploadedAssetVersion: GetUploadedAssetVersionResult; getUser: GetUserResult; @@ -134,6 +135,10 @@ export type QueryGetSubjectsForUnitArgs = { input: GetSubjectsForUnitInput; }; +export type QueryGetSystemObjectDetailsArgs = { + input: GetSystemObjectDetailsInput; +}; + export type QueryGetUnitArgs = { input: GetUnitInput; }; @@ -1025,6 +1030,30 @@ export type IntermediaryFile = { SystemObject?: Maybe; }; +export type GetSystemObjectDetailsInput = { + idSystemObject: Scalars['Int']; +}; + +export type RepositoryPath = { + __typename?: 'RepositoryPath'; + idSystemObject: Scalars['Int']; + name: Scalars['String']; + objectType: Scalars['Int']; +}; + +export type GetSystemObjectDetailsResult = { + __typename?: 'GetSystemObjectDetailsResult'; + name: Scalars['String']; + retired: Scalars['Boolean']; + objectType: Scalars['Int']; + allowed: Scalars['Boolean']; + thumbnail?: Maybe; + identifiers: Array; + objectAncestors: Array>; + sourceObjects: Array; + derivedObjects: Array; +}; + export type GetSourceObjectIdentiferInput = { idSystemObjects: Array; }; From e0668e75762ea7337e43a8ef6c6dbd7dea824729 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Mon, 16 Nov 2020 18:15:37 +0530 Subject: [PATCH 097/239] Handle multi bread view case and added custom use details view hook --- .../src/components/shared/BreadcrumbsView.tsx | 82 ++++++++++++--- .../components/DetailsView/DetailsHeader.tsx | 6 +- .../DetailsView/DetailsThumbnail.tsx | 8 +- .../components/DetailsView/index.tsx | 99 +++---------------- .../pages/Repository/hooks/useDetailsView.ts | 12 +++ 5 files changed, 102 insertions(+), 105 deletions(-) create mode 100644 client/src/pages/Repository/hooks/useDetailsView.ts diff --git a/client/src/components/shared/BreadcrumbsView.tsx b/client/src/components/shared/BreadcrumbsView.tsx index a9e57f77b..1f3ddda31 100644 --- a/client/src/components/shared/BreadcrumbsView.tsx +++ b/client/src/components/shared/BreadcrumbsView.tsx @@ -3,13 +3,14 @@ * * This is a reusable component which renders breadcrumbs. */ -import { Breadcrumbs, Typography } from '@material-ui/core'; -import { fade, makeStyles } from '@material-ui/core/styles'; +import { Breadcrumbs, MenuItem, Select, Typography } from '@material-ui/core'; +import { fade, makeStyles, withStyles } from '@material-ui/core/styles'; import clsx from 'clsx'; import React from 'react'; import { MdNavigateNext } from 'react-icons/md'; -import { RepositoryPath } from '../../store'; -import { Colors } from '../../theme'; +import { Colors, palette } from '../../theme'; +import { RepositoryPath } from '../../types/graphql'; +import { eSystemObjectType } from '../../types/server'; import { getDetailsUrlForObject, getTermForSystemObjectType } from '../../utils/repository'; import NewTabLink from './NewTabLink'; @@ -21,18 +22,27 @@ const useStyles = makeStyles(({ palette, breakpoints }) => ({ highlighted: { backgroundColor: fade(palette.primary.main, 0.80), color: palette.background.paper, - padding: '5px 10px', + padding: '2.5px 10px', borderRadius: 5, }, label: { [breakpoints.down('lg')]: { fontSize: '0.8em' } + }, + selectIcon: { + color: Colors.defaults.white, + [breakpoints.down('lg')]: { + fontSize: '0.8em' + } + }, + menuItem: { + fontSize: '0.8em' } })); interface BreadcrumbsViewProps { - items: RepositoryPath[]; + items: RepositoryPath[][]; highlighted?: boolean; } @@ -40,11 +50,7 @@ function BreadcrumbsView(props: BreadcrumbsViewProps): React.ReactElement { const { items, highlighted = false } = props; const classes = useStyles(); - const renderBreadcrumbs = ({ idSystemObject, name, objectType }: RepositoryPath, index: number) => ( - - {getTermForSystemObjectType(objectType)} {name} - - ); + const renderBreadcrumbs = (paths: RepositoryPath[], index: number): JSX.Element => ; return ( }> @@ -53,4 +59,58 @@ function BreadcrumbsView(props: BreadcrumbsViewProps): React.ReactElement { ); } +const BreadcrumbSelect = withStyles(() => ({ + root: { + color: Colors.defaults.white, + fontSize: '0.8em' + }, + select: { + paddingRight: '0px !important', + } +}))(Select); + +interface BreadcrumbItemProps { + paths: RepositoryPath[]; +} + +function BreadcrumbItem(props: BreadcrumbItemProps): React.ReactElement { + const { paths } = props; + const classes = useStyles(); + + const getLabel = (objectType: eSystemObjectType, name: string) => `${getTermForSystemObjectType(objectType)} ${name}`; + + if (paths.length === 1) { + return ( + + {paths.map(({ idSystemObject, name, objectType }, index: number) => ( + + {getLabel(objectType, name)} + + ))} + + ); + } + + const [{ idSystemObject, objectType, name }] = paths; + + const renderValue = () => {getLabel(objectType, name)} (multiple); + + return ( + null} + disableUnderline + > + {paths.map(({ idSystemObject, name, objectType }, index: number) => ( + + + {getLabel(objectType, name)} + + + ))} + + ); +} + export default BreadcrumbsView; \ No newline at end of file diff --git a/client/src/pages/Repository/components/DetailsView/DetailsHeader.tsx b/client/src/pages/Repository/components/DetailsView/DetailsHeader.tsx index f24c0c3b7..b41746287 100644 --- a/client/src/pages/Repository/components/DetailsView/DetailsHeader.tsx +++ b/client/src/pages/Repository/components/DetailsView/DetailsHeader.tsx @@ -7,9 +7,9 @@ import { Box, Checkbox, FormControlLabel, Typography } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; import React from 'react'; import { BreadcrumbsView } from '../../../../components'; -import { RepositoryPath } from '../../../../store/repository'; import Colors from '../../../../theme/colors'; import { eSystemObjectType } from '../../../../types/server'; +import { RepositoryPath } from '../../../../types/graphql'; import { getTermForSystemObjectType } from '../../../../utils/repository'; const useStyles = makeStyles(({ palette }) => ({ @@ -33,7 +33,7 @@ const useStyles = makeStyles(({ palette }) => ({ interface DetailsHeaderProps { objectType: eSystemObjectType; - path: RepositoryPath[]; + path: RepositoryPath[][]; name: string; retired: boolean; disabled: boolean; @@ -50,7 +50,7 @@ function DetailsHeader(props: DetailsHeaderProps): React.ReactElement { {getTermForSystemObjectType(objectType)} - + {!!path.length && } diff --git a/client/src/pages/Repository/components/DetailsView/DetailsThumbnail.tsx b/client/src/pages/Repository/components/DetailsView/DetailsThumbnail.tsx index 6a3a0d293..db8d08c0c 100644 --- a/client/src/pages/Repository/components/DetailsView/DetailsThumbnail.tsx +++ b/client/src/pages/Repository/components/DetailsView/DetailsThumbnail.tsx @@ -4,8 +4,8 @@ * This component renders details thumbnail for the Repository Details UI. */ import { Box } from '@material-ui/core'; -import React from 'react'; import { makeStyles } from '@material-ui/core/styles'; +import React from 'react'; import DefaultThumbnail from '../../../../assets/images/default-thumbnail.png'; const useStyles = makeStyles(() => ({ @@ -18,16 +18,16 @@ const useStyles = makeStyles(() => ({ })); interface DetailsThumbnailProps { - thumbnail?: string; + thumbnail?: string | null; } function DetailsThumbnail(props: DetailsThumbnailProps): React.ReactElement { - const { thumbnail = DefaultThumbnail } = props; + const { thumbnail } = props; const classes = useStyles(); return ( - asset thumbnail + asset thumbnail ); } diff --git a/client/src/pages/Repository/components/DetailsView/index.tsx b/client/src/pages/Repository/components/DetailsView/index.tsx index 63b27cc67..dbfaa6e0b 100644 --- a/client/src/pages/Repository/components/DetailsView/index.tsx +++ b/client/src/pages/Repository/components/DetailsView/index.tsx @@ -8,11 +8,12 @@ import { makeStyles } from '@material-ui/core/styles'; import React, { useState } from 'react'; import { useParams } from 'react-router'; import IdentifierList from '../../../../components/shared/IdentifierList'; -import { useVocabularyStore } from '../../../../store'; -import { eSystemObjectType, eVocabularySetID } from '../../../../types/server'; +import { parseIdentifiersToState, useVocabularyStore } from '../../../../store'; +import { eVocabularySetID } from '../../../../types/server'; import DerivedObjectsList from '../../../Ingestion/components/Metadata/Model/DerivedObjectsList'; import ObjectSelectModal from '../../../Ingestion/components/Metadata/Model/ObjectSelectModal'; import SourceObjectsList from '../../../Ingestion/components/Metadata/Model/SourceObjectsList'; +import { useObjectDetails } from '../../hooks/useDetailsView'; import DetailsHeader from './DetailsHeader'; import DetailsThumbnail from './DetailsThumbnail'; import ObjectNotFoundView from './ObjectNotFoundView'; @@ -35,84 +36,6 @@ const useStyles = makeStyles(({ palette, breakpoints }) => ({ } })); -const mockData = { - 1: { - name: 'PhotoSet1.zip', - retired: true, - objectType: eSystemObjectType.eCaptureData, - path: [ - // TODO: KARAN: 2 Dimensional array - { - idSystemObject: 0, - name: 'USNM', - objectType: eSystemObjectType.eUnit, - }, - { - idSystemObject: 1, - name: 'Armstrong Suit', - objectType: eSystemObjectType.eProject, - }, - { - idSystemObject: 2, - name: 'Armstrong Glove', - objectType: eSystemObjectType.eSubject, - }, - { - idSystemObject: 3, - name: 'Armstrong Glove Full', - objectType: eSystemObjectType.eItem, - }, - { - idSystemObject: 4, - name: 'PhotoSet1.zip', - objectType: eSystemObjectType.eCaptureData, - } - ], - identifiers: [ - { - id: 0, - identifier: '31958de82-ab13-4049-c979-746e2fbe229e', - identifierType: 75, - selected: true - }, - ], - sourceObjects: [ - { - idSystemObject: 0, - name: 'PhotoSetAlpha1.zip', - identifier: 'a5cf8642-7466-4896-a0a2-d698f2009cd3', - objectType: eSystemObjectType.eModel - } - ], - derivedObjects: [ - { - idSystemObject: 0, - name: 'Photo1.zip', - variantType: 28, - objectType: eSystemObjectType.eAsset - }, - { - idSystemObject: 1, - name: 'Photo2.zip', - variantType: 28, - objectType: eSystemObjectType.eAsset - }, - { - idSystemObject: 2, - name: 'Photo3.zip', - variantType: 28, - objectType: eSystemObjectType.eAsset - }, - { - idSystemObject: 3, - name: 'Photo4.zip', - variantType: 28, - objectType: eSystemObjectType.eAsset - }, - ], - } -}; - type DetailsParams = { idSystemObject: string; }; @@ -122,15 +45,17 @@ function DetailsView(): React.ReactElement { const params = useParams(); const [modalOpen, setModalOpen] = useState(false); - const data = mockData[params.idSystemObject]; + const { data, loading } = useObjectDetails(Number.parseInt(params.idSystemObject, 10)); + const getEntries = useVocabularyStore(state => state.getEntries); - if (!data) { - return ; + if (!data || !params.idSystemObject) { + return ; } - const { name, objectType, path, retired, identifiers, sourceObjects, derivedObjects, thumbnail } = data; - const disabled: boolean = false; + const { name, objectType, retired, identifiers, allowed, thumbnail, objectAncestors, sourceObjects, derivedObjects } = data.getSystemObjectDetails; + + const disabled: boolean = !allowed; const addIdentifer = () => { alert('TODO: KARAN: add identifier'); @@ -165,7 +90,7 @@ function DetailsView(): React.ReactElement { @@ -173,7 +98,7 @@ function DetailsView(): React.ReactElement { Date: Mon, 16 Nov 2020 18:15:48 +0530 Subject: [PATCH 098/239] added getSystemObjectDetails resolver --- .../queries/getSystemObjectDetails.ts | 301 ++++++++++++++++++ 1 file changed, 301 insertions(+) create mode 100644 server/graphql/schema/systemobject/resolvers/queries/getSystemObjectDetails.ts diff --git a/server/graphql/schema/systemobject/resolvers/queries/getSystemObjectDetails.ts b/server/graphql/schema/systemobject/resolvers/queries/getSystemObjectDetails.ts new file mode 100644 index 000000000..f0f870b54 --- /dev/null +++ b/server/graphql/schema/systemobject/resolvers/queries/getSystemObjectDetails.ts @@ -0,0 +1,301 @@ +import * as CACHE from '../../../../../cache'; +import * as DBAPI from '../../../../../db'; +import { eObjectGraphMode, eSystemObjectType } from '../../../../../db'; +import { GetSystemObjectDetailsResult, QueryGetSystemObjectDetailsArgs, RepositoryPath } from '../../../../../types/graphql'; +import { Parent } from '../../../../../types/resolvers'; +import * as LOGGER from '../../../../../utils/logger'; + +export default async function getSystemObjectDetails(_: Parent, args: QueryGetSystemObjectDetailsArgs): Promise { + const { input } = args; + const { idSystemObject } = input; + + const oID: CACHE.ObjectIDAndType | undefined = await CACHE.SystemObjectCache.getObjectFromSystem(idSystemObject); + const objectAncestors: RepositoryPath[][] = await getRepositoryPathFromObjectGraph(idSystemObject); + + const systemObject = await DBAPI.SystemObject.fetch(idSystemObject); + + if (!oID) { + const message: string = `No system object for ID: ${idSystemObject}`; + LOGGER.logger.error(message); + throw new Error(message); + } + + if (!systemObject) { + const message: string = `No system object for ID: ${idSystemObject}`; + LOGGER.logger.error(message); + throw new Error(message); + } + + return { + name: 'PhotoSet1.zip', + retired: systemObject.Retired, + objectType: oID.eObjectType, + allowed: true, + thumbnail: null, + objectAncestors, + identifiers: [ + { + identifier: '31958de82-ab13-4049-c979-746e2fbe229e', + identifierType: 75 + } + ], + sourceObjects: [ + { + idSystemObject: 0, + name: 'PhotoSetAlpha1.zip', + identifier: 'a5cf8642-7466-4896-a0a2-d698f2009cd3', + objectType: eSystemObjectType.eModel + } + ], + derivedObjects: [ + { + idSystemObject: 0, + name: 'Photo1.zip', + variantType: 28, + objectType: eSystemObjectType.eAsset + }, + { + idSystemObject: 1, + name: 'Photo2.zip', + variantType: 28, + objectType: eSystemObjectType.eAsset + }, + { + idSystemObject: 2, + name: 'Photo3.zip', + variantType: 28, + objectType: eSystemObjectType.eAsset + }, + { + idSystemObject: 3, + name: 'Photo4.zip', + variantType: 28, + objectType: eSystemObjectType.eAsset + } + ] + }; +} + +async function getRepositoryPathFromObjectGraph(idSystemObject: number): Promise { + const objectGraph = new DBAPI.ObjectGraph(idSystemObject, eObjectGraphMode.eAncestors); + + if (!(await objectGraph.fetch())) { + return []; + } + + const { unit, project, subject, item, captureData, model, scene, intermediaryFile, projectDocumentation, asset, assetVersion, actor, stakeholder } = objectGraph; + const objectAncestors: RepositoryPath[][] = []; + + if (unit) objectAncestors.push(await unitsToRepositoryPath(unit)); + if (project) objectAncestors.push(await projectsToRepositoryPath(project)); + if (subject) objectAncestors.push(await subjectsToRepositoryPath(subject)); + if (item) objectAncestors.push(await itemsToRepositoryPath(item)); + if (captureData) objectAncestors.push(await captureDatasToRepositoryPath(captureData)); + if (model) objectAncestors.push(await modelsToRepositoryPath(model)); + if (scene) objectAncestors.push(await scenesToRepositoryPath(scene)); + if (intermediaryFile) objectAncestors.push(await intermediaryFilesToRepositoryPath(intermediaryFile)); + if (projectDocumentation) objectAncestors.push(await projectDocumentationsToRepositoryPath(projectDocumentation)); + if (asset) objectAncestors.push(await assetsToRepositoryPath(asset)); + if (assetVersion) objectAncestors.push(await assetVersionsToRepositoryPath(assetVersion)); + if (actor) objectAncestors.push(await actorsToRepositoryPath(actor)); + if (stakeholder) objectAncestors.push(await stakeholdersToRepositoryPath(stakeholder)); + + return objectAncestors; +} + +const defaultName: string = ''; + +async function unitsToRepositoryPath(units: DBAPI.Unit[]): Promise { + const paths: RepositoryPath[] = []; + for (const { idUnit, Abbreviation } of units) { + const idSystemObject: number = (await DBAPI.SystemObject.fetchFromUnitID(idUnit))?.idSystemObject ?? 0; + const path: RepositoryPath = { + idSystemObject, + name: Abbreviation || defaultName, + objectType: eSystemObjectType.eUnit + }; + paths.push(path); + } + + return paths; +} + +async function projectsToRepositoryPath(projects: DBAPI.Project[]): Promise { + const paths: RepositoryPath[] = []; + for (const { idProject, Name } of projects) { + const idSystemObject: number = (await DBAPI.SystemObject.fetchFromProjectID(idProject))?.idSystemObject ?? 0; + const path: RepositoryPath = { + idSystemObject, + name: Name, + objectType: eSystemObjectType.eProject + }; + paths.push(path); + } + + return paths; +} + +async function subjectsToRepositoryPath(subjects: DBAPI.Subject[]): Promise { + const paths: RepositoryPath[] = []; + for (const { idSubject, Name } of subjects) { + const idSystemObject: number = (await DBAPI.SystemObject.fetchFromSubjectID(idSubject))?.idSystemObject ?? 0; + const path: RepositoryPath = { + idSystemObject, + name: Name, + objectType: eSystemObjectType.eSubject + }; + paths.push(path); + } + + return paths; +} + +async function itemsToRepositoryPath(items: DBAPI.Item[]): Promise { + const paths: RepositoryPath[] = []; + for (const { idItem, Name } of items) { + const idSystemObject: number = (await DBAPI.SystemObject.fetchFromSubjectID(idItem))?.idSystemObject ?? 0; + const path: RepositoryPath = { + idSystemObject, + name: Name, + objectType: eSystemObjectType.eItem + }; + paths.push(path); + } + + return paths; +} + +async function captureDatasToRepositoryPath(captureDatas: DBAPI.CaptureData[]): Promise { + const paths: RepositoryPath[] = []; + for (const { idCaptureData } of captureDatas) { + const idSystemObject: number = (await DBAPI.SystemObject.fetchFromCaptureDataID(idCaptureData))?.idSystemObject ?? 0; + const path: RepositoryPath = { + idSystemObject, + name: 'CD', // TODO: KARAN: get name for CD + objectType: eSystemObjectType.eCaptureData + }; + paths.push(path); + } + + return paths; +} + +async function modelsToRepositoryPath(models: DBAPI.Model[]): Promise { + const paths: RepositoryPath[] = []; + for (const { idModel } of models) { + const idSystemObject: number = (await DBAPI.SystemObject.fetchFromModelID(idModel))?.idSystemObject ?? 0; + const path: RepositoryPath = { + idSystemObject, + name: 'Model', // TODO: KARAN: get name for model + objectType: eSystemObjectType.eModel + }; + paths.push(path); + } + + return paths; +} + +async function scenesToRepositoryPath(scenes: DBAPI.Scene[]): Promise { + const paths: RepositoryPath[] = []; + for (const { idScene, Name } of scenes) { + const idSystemObject: number = (await DBAPI.SystemObject.fetchFromSceneID(idScene))?.idSystemObject ?? 0; + const path: RepositoryPath = { + idSystemObject, + name: Name, + objectType: eSystemObjectType.eScene + }; + paths.push(path); + } + + return paths; +} + +async function intermediaryFilesToRepositoryPath(intermediaryFiles: DBAPI.IntermediaryFile[]): Promise { + const paths: RepositoryPath[] = []; + for (const { idIntermediaryFile } of intermediaryFiles) { + const idSystemObject: number = (await DBAPI.SystemObject.fetchFromIntermediaryFileID(idIntermediaryFile))?.idSystemObject ?? 0; + const path: RepositoryPath = { + idSystemObject, + name: 'IF', // TODO: KARAN: get name for IF + objectType: eSystemObjectType.eIntermediaryFile + }; + paths.push(path); + } + + return paths; +} + +async function projectDocumentationsToRepositoryPath(projectDocumentations: DBAPI.ProjectDocumentation[]): Promise { + const paths: RepositoryPath[] = []; + for (const { idProjectDocumentation, Name } of projectDocumentations) { + const idSystemObject: number = (await DBAPI.SystemObject.fetchFromProjectDocumentationID(idProjectDocumentation))?.idSystemObject ?? 0; + const path: RepositoryPath = { + idSystemObject, + name: Name, + objectType: eSystemObjectType.eProjectDocumentation + }; + paths.push(path); + } + + return paths; +} + +async function assetsToRepositoryPath(assets: DBAPI.Asset[]): Promise { + const paths: RepositoryPath[] = []; + for (const { idAsset, FileName } of assets) { + const idSystemObject: number = (await DBAPI.SystemObject.fetchFromAssetID(idAsset))?.idSystemObject ?? 0; + const path: RepositoryPath = { + idSystemObject, + name: FileName, + objectType: eSystemObjectType.eAsset + }; + paths.push(path); + } + + return paths; +} + +async function assetVersionsToRepositoryPath(assetVersions: DBAPI.AssetVersion[]): Promise { + const paths: RepositoryPath[] = []; + for (const { idAssetVersion, FileName } of assetVersions) { + const idSystemObject: number = (await DBAPI.SystemObject.fetchFromAssetVersionID(idAssetVersion))?.idSystemObject ?? 0; + const path: RepositoryPath = { + idSystemObject, + name: FileName, + objectType: eSystemObjectType.eAssetVersion + }; + paths.push(path); + } + + return paths; +} + +async function actorsToRepositoryPath(actors: DBAPI.Actor[]): Promise { + const paths: RepositoryPath[] = []; + for (const { idActor } of actors) { + const idSystemObject: number = (await DBAPI.SystemObject.fetchFromActorID(idActor))?.idSystemObject ?? 0; + const path: RepositoryPath = { + idSystemObject, + name: 'Actor', // TODO: KARAN: get name for actor + objectType: eSystemObjectType.eActor + }; + paths.push(path); + } + + return paths; +} + +async function stakeholdersToRepositoryPath(stakeholders: DBAPI.Stakeholder[]): Promise { + const paths: RepositoryPath[] = []; + for (const { idStakeholder, IndividualName } of stakeholders) { + const idSystemObject: number = (await DBAPI.SystemObject.fetchFromStakeholderID(idStakeholder))?.idSystemObject ?? 0; + const path: RepositoryPath = { + idSystemObject, + name: IndividualName, + objectType: eSystemObjectType.eStakeholder + }; + paths.push(path); + } + + return paths; +} From e98aea712887e616bfa35996fc0a46482fecdbff Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Wed, 18 Nov 2020 13:31:23 +0530 Subject: [PATCH 099/239] added source, derived objects and identifiers to getSystemObjectDetails query --- .../queries/getSystemObjectDetails.ts | 249 ++++++++++++++---- 1 file changed, 203 insertions(+), 46 deletions(-) diff --git a/server/graphql/schema/systemobject/resolvers/queries/getSystemObjectDetails.ts b/server/graphql/schema/systemobject/resolvers/queries/getSystemObjectDetails.ts index f0f870b54..58721226a 100644 --- a/server/graphql/schema/systemobject/resolvers/queries/getSystemObjectDetails.ts +++ b/server/graphql/schema/systemobject/resolvers/queries/getSystemObjectDetails.ts @@ -1,7 +1,15 @@ import * as CACHE from '../../../../../cache'; import * as DBAPI from '../../../../../db'; import { eObjectGraphMode, eSystemObjectType } from '../../../../../db'; -import { GetSystemObjectDetailsResult, QueryGetSystemObjectDetailsArgs, RepositoryPath } from '../../../../../types/graphql'; +import { + DerivedObject, + GetSystemObjectDetailsResult, + IngestIdentifier, + QueryGetSystemObjectDetailsArgs, + RepositoryPath, + SourceObject, + SystemObject +} from '../../../../../types/graphql'; import { Parent } from '../../../../../types/resolvers'; import * as LOGGER from '../../../../../utils/logger'; @@ -12,70 +20,107 @@ export default async function getSystemObjectDetails(_: Parent, args: QueryGetSy const oID: CACHE.ObjectIDAndType | undefined = await CACHE.SystemObjectCache.getObjectFromSystem(idSystemObject); const objectAncestors: RepositoryPath[][] = await getRepositoryPathFromObjectGraph(idSystemObject); - const systemObject = await DBAPI.SystemObject.fetch(idSystemObject); + const systemObject: SystemObject | null = await DBAPI.SystemObject.fetch(idSystemObject); + const sourceObjects: SourceObject[] = await getSourceObjects(idSystemObject); + const derivedObjects: DerivedObject[] = await getDerivedObjects(idSystemObject); + + const identifiers = await getIngestIdentifiers(idSystemObject); if (!oID) { - const message: string = `No system object for ID: ${idSystemObject}`; + const message: string = `No object ID found for ID: ${idSystemObject}`; LOGGER.logger.error(message); throw new Error(message); } if (!systemObject) { - const message: string = `No system object for ID: ${idSystemObject}`; + const message: string = `No system object found for ID: ${idSystemObject}`; LOGGER.logger.error(message); throw new Error(message); } + const name: string = await resolveNameForObjectType(systemObject, oID.eObjectType); + return { - name: 'PhotoSet1.zip', + name, retired: systemObject.Retired, objectType: oID.eObjectType, - allowed: true, + allowed: true, // TODO: KARAN: tell the client if user has access to edit thumbnail: null, objectAncestors, - identifiers: [ - { - identifier: '31958de82-ab13-4049-c979-746e2fbe229e', - identifierType: 75 - } - ], - sourceObjects: [ - { - idSystemObject: 0, - name: 'PhotoSetAlpha1.zip', - identifier: 'a5cf8642-7466-4896-a0a2-d698f2009cd3', - objectType: eSystemObjectType.eModel - } - ], - derivedObjects: [ - { - idSystemObject: 0, - name: 'Photo1.zip', - variantType: 28, - objectType: eSystemObjectType.eAsset - }, - { - idSystemObject: 1, - name: 'Photo2.zip', - variantType: 28, - objectType: eSystemObjectType.eAsset - }, - { - idSystemObject: 2, - name: 'Photo3.zip', - variantType: 28, - objectType: eSystemObjectType.eAsset - }, - { - idSystemObject: 3, - name: 'Photo4.zip', - variantType: 28, - objectType: eSystemObjectType.eAsset - } - ] + identifiers, + sourceObjects, + derivedObjects }; } +async function getSourceObjects(idSystemObject: number): Promise { + const masterObjects = await DBAPI.SystemObject.fetchMasterFromXref(idSystemObject); + if (!masterObjects) return []; + + const sourceObjects: SourceObject[] = []; + + for (const masterObject of masterObjects) { + const identifier: DBAPI.Identifier[] | null = await DBAPI.Identifier.fetchFromSystemObject(idSystemObject); + const oID: CACHE.ObjectIDAndType | undefined = await CACHE.SystemObjectCache.getObjectFromSystem(masterObject.idSystemObject); + + if (!oID) { + const message: string = `No object ID found for ID: ${idSystemObject}`; + LOGGER.logger.error(message); + throw new Error(message); + } + + const sourceObject: SourceObject = { + idSystemObject: masterObject.idSystemObject, + name: await resolveNameForObjectType(masterObject, oID.eObjectType), + identifier: identifier?.[0]?.IdentifierValue ?? null, + objectType: oID.eObjectType + }; + + sourceObjects.push(sourceObject); + } + + return sourceObjects; +} + +async function getDerivedObjects(idSystemObject: number): Promise { + const derivedSystemObjects = await DBAPI.SystemObject.fetchDerivedFromXref(idSystemObject); + if (!derivedSystemObjects) return []; + + const derivedObjects: DerivedObject[] = []; + + for (const derivedSystemObject of derivedSystemObjects) { + const oID: CACHE.ObjectIDAndType | undefined = await CACHE.SystemObjectCache.getObjectFromSystem(derivedSystemObject.idSystemObject); + + if (!oID) { + const message: string = `No object ID found for ID: ${idSystemObject}`; + LOGGER.logger.error(message); + throw new Error(message); + } + + const derivedObject: DerivedObject = { + idSystemObject: derivedSystemObject.idSystemObject, + name: await resolveNameForObjectType(derivedSystemObject, oID.eObjectType), + variantType: 28, // TODO: KARAN: how to compute variant? + objectType: oID.eObjectType + }; + + derivedObjects.push(derivedObject); + } + + return derivedObjects; +} + +async function getIngestIdentifiers(idSystemObject: number): Promise { + const identifier: DBAPI.Identifier[] | null = await DBAPI.Identifier.fetchFromSystemObject(idSystemObject); + + if (!identifier) return []; + + return identifier.map(({ IdentifierValue, idVIdentifierType }) => ({ + identifier: IdentifierValue, + identifierType: idVIdentifierType + })); +} + async function getRepositoryPathFromObjectGraph(idSystemObject: number): Promise { const objectGraph = new DBAPI.ObjectGraph(idSystemObject, eObjectGraphMode.eAncestors); @@ -299,3 +344,115 @@ async function stakeholdersToRepositoryPath(stakeholders: DBAPI.Stakeholder[]): return paths; } + + +async function resolveNameForObjectType(systemObject: SystemObject, objectType: eSystemObjectType): Promise { + const unknownName: string = ''; + + switch (objectType) { + case eSystemObjectType.eUnit: + if (systemObject.idUnit) { + const Unit = await DBAPI.Unit.fetch(systemObject.idUnit); + if (Unit) { + return Unit.Name; + } + } + + return unknownName; + + case eSystemObjectType.eProject: + if (systemObject.idProject) { + const Project = await DBAPI.Project.fetch(systemObject.idProject); + if (Project) { + return Project.Name; + } + } + + return unknownName; + + case eSystemObjectType.eSubject: + if (systemObject.idSubject) { + const Subject = await DBAPI.Subject.fetch(systemObject.idSubject); + if (Subject) { + return Subject.Name; + } + } + + return unknownName; + + case eSystemObjectType.eItem: + if (systemObject.idItem) { + const Item = await DBAPI.Item.fetch(systemObject.idItem); + if (Item) { + return Item.Name; + } + } + + return unknownName; + + case eSystemObjectType.eCaptureData: + return unknownName; + + case eSystemObjectType.eModel: + return unknownName; + + case eSystemObjectType.eScene: + if (systemObject.idScene) { + const Scene = await DBAPI.Scene.fetch(systemObject.idScene); + if (Scene) { + return Scene.Name; + } + } + + return unknownName; + + case eSystemObjectType.eIntermediaryFile: + return unknownName; + + case eSystemObjectType.eProjectDocumentation: + if (systemObject.idProjectDocumentation) { + const ProjectDocumentation = await DBAPI.ProjectDocumentation.fetch(systemObject.idProjectDocumentation); + if (ProjectDocumentation) { + return ProjectDocumentation.Name; + } + } + + return unknownName; + + case eSystemObjectType.eAsset: + if (systemObject.idAsset) { + const Asset = await DBAPI.Asset.fetch(systemObject.idAsset); + if (Asset) { + return Asset.FileName; + } + } + + return unknownName; + + case eSystemObjectType.eAssetVersion: + if (systemObject.idAssetVersion) { + const AssetVersion = await DBAPI.AssetVersion.fetch(systemObject.idAssetVersion); + if (AssetVersion) { + return AssetVersion.FileName; + } + } + + return unknownName; + + case eSystemObjectType.eActor: + return unknownName; + + case eSystemObjectType.eStakeholder: + if (systemObject.idStakeholder) { + const Stakeholder = await DBAPI.Stakeholder.fetch(systemObject.idStakeholder); + if (Stakeholder) { + return Stakeholder.IndividualName; + } + } + + return unknownName; + + default: + return unknownName; + } +} \ No newline at end of file From a5be4140bb368fb12f43d63e1d0a5c5c7022624b Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Wed, 18 Nov 2020 13:32:18 +0530 Subject: [PATCH 100/239] minor fixes to debounce fields --- .../src/components/controls/IdInputField.tsx | 1 + client/src/components/shared/Header.tsx | 1 + .../src/components/shared/IdentifierList.tsx | 20 ++++++++++++++++--- .../Metadata/Model/BoundingBoxInput.tsx | 1 + .../Metadata/Photogrammetry/Description.tsx | 1 + .../components/SubjectItem/ItemList.tsx | 1 + .../components/DetailsView/DetailsHeader.tsx | 2 +- .../components/DetailsView/index.tsx | 1 + 8 files changed, 24 insertions(+), 4 deletions(-) diff --git a/client/src/components/controls/IdInputField.tsx b/client/src/components/controls/IdInputField.tsx index 689f08734..545121a8d 100644 --- a/client/src/components/controls/IdInputField.tsx +++ b/client/src/components/controls/IdInputField.tsx @@ -16,6 +16,7 @@ const useStyles = makeStyles(({ palette, typography, breakpoints }) => ({ border: `1px solid ${fade(palette.primary.contrastText, 0.4)}`, padding: 8, borderRadius: 5, + fontWeight: typography.fontWeightRegular, fontFamily: typography.fontFamily, [breakpoints.down('lg')]: { fontSize: '0.8em', diff --git a/client/src/components/shared/Header.tsx b/client/src/components/shared/Header.tsx index 4ab3bde2c..b04c06b9f 100644 --- a/client/src/components/shared/Header.tsx +++ b/client/src/components/shared/Header.tsx @@ -51,6 +51,7 @@ const useStyles = makeStyles(({ palette, spacing, typography, breakpoints }) => border: 'none', color: fade(Colors.defaults.white, 0.65), background: 'transparent', + fontWeight: typography.fontWeightRegular, fontFamily: typography.fontFamily, [breakpoints.down('lg')]: { height: 20, diff --git a/client/src/components/shared/IdentifierList.tsx b/client/src/components/shared/IdentifierList.tsx index 52e3a50b4..0c795c57f 100644 --- a/client/src/components/shared/IdentifierList.tsx +++ b/client/src/components/shared/IdentifierList.tsx @@ -20,7 +20,8 @@ const useStyles = makeStyles(({ palette, typography, breakpoints }) => ({ padding: '0px 2px', paddingBottom: 5, backgroundColor: 'transparent', - fontSize: '0.9em', + fontSize: '0.8em', + fontWeight: typography.fontWeightRegular, fontFamily: typography.fontFamily, borderBottom: `1px solid ${palette.grey[300]}`, '&:focus': { @@ -49,6 +50,11 @@ const useStyles = makeStyles(({ palette, typography, breakpoints }) => ({ marginLeft: 20, cursor: 'pointer' }, + empty: { + fontSize: '0.8em', + color: palette.primary.dark, + fontStyle: 'italic' + }, header: sharedLabelProps, addIdentifierButton: sharedButtonProps })); @@ -60,16 +66,24 @@ interface IdentifierListProps { onRemove: (id: number) => void; identifierTypes: VocabularyOption[]; disabled?: boolean; + viewMode?: boolean; } function IdentifierList(props: IdentifierListProps): React.ReactElement { - const { identifiers, onAdd, onUpdate, identifierTypes, onRemove, disabled = false } = props; + const { identifiers, onAdd, onUpdate, identifierTypes, onRemove, viewMode = false, disabled = false } = props; const classes = useStyles(); + const hasIdentifiers: boolean = !!identifiers.length; + return ( - {!!identifiers.length &&
} + {hasIdentifiers &&
} + {!hasIdentifiers && viewMode && ( + + No Identifiers + + )} {identifiers.map(({ id, selected, identifier, identifierType }, index) => { const remove = () => onRemove(id); const updateCheckbox = ({ target }) => onUpdate(id, target.name, target.checked); diff --git a/client/src/pages/Ingestion/components/Metadata/Model/BoundingBoxInput.tsx b/client/src/pages/Ingestion/components/Metadata/Model/BoundingBoxInput.tsx index 09bc51e46..af759014f 100644 --- a/client/src/pages/Ingestion/components/Metadata/Model/BoundingBoxInput.tsx +++ b/client/src/pages/Ingestion/components/Metadata/Model/BoundingBoxInput.tsx @@ -47,6 +47,7 @@ const useStyles = makeStyles(({ palette, typography, breakpoints }) => ({ padding: 8, borderRadius: 5, marginLeft: 5, + fontWeight: typography.fontWeightRegular, fontFamily: typography.fontFamily, [breakpoints.down('lg')]: { fontSize: '0.8em', diff --git a/client/src/pages/Ingestion/components/Metadata/Photogrammetry/Description.tsx b/client/src/pages/Ingestion/components/Metadata/Photogrammetry/Description.tsx index 86e267508..261977b06 100644 --- a/client/src/pages/Ingestion/components/Metadata/Photogrammetry/Description.tsx +++ b/client/src/pages/Ingestion/components/Metadata/Photogrammetry/Description.tsx @@ -17,6 +17,7 @@ const useStyles = makeStyles(({ palette, typography }) => ({ overflow: 'scroll', border: `1px solid ${fade(palette.primary.contrastText, 0.4)}`, borderRadius: 5, + fontWeight: typography.fontWeightRegular, fontFamily: typography.fontFamily } })); diff --git a/client/src/pages/Ingestion/components/SubjectItem/ItemList.tsx b/client/src/pages/Ingestion/components/SubjectItem/ItemList.tsx index 7a3a172ba..ddb46e2ca 100644 --- a/client/src/pages/Ingestion/components/SubjectItem/ItemList.tsx +++ b/client/src/pages/Ingestion/components/SubjectItem/ItemList.tsx @@ -48,6 +48,7 @@ const useStyles = makeStyles(({ palette, spacing, typography, breakpoints }) => outline: 'none', padding: '0px 2px', fontSize: '1em', + fontWeight: typography.fontWeightRegular, fontFamily: typography.fontFamily, '&:focus': { outline: 'none', diff --git a/client/src/pages/Repository/components/DetailsView/DetailsHeader.tsx b/client/src/pages/Repository/components/DetailsView/DetailsHeader.tsx index b41746287..e802fae5a 100644 --- a/client/src/pages/Repository/components/DetailsView/DetailsHeader.tsx +++ b/client/src/pages/Repository/components/DetailsView/DetailsHeader.tsx @@ -17,7 +17,7 @@ const useStyles = makeStyles(({ palette }) => ({ color: palette.primary.dark }, name: { - width: 180, + minWidth: 180, padding: '5px 8px', borderRadius: 5, marginRight: 20, diff --git a/client/src/pages/Repository/components/DetailsView/index.tsx b/client/src/pages/Repository/components/DetailsView/index.tsx index dbfaa6e0b..18eefef75 100644 --- a/client/src/pages/Repository/components/DetailsView/index.tsx +++ b/client/src/pages/Repository/components/DetailsView/index.tsx @@ -97,6 +97,7 @@ function DetailsView(): React.ReactElement { Date: Wed, 18 Nov 2020 13:32:46 +0530 Subject: [PATCH 101/239] minor fixes to reference object modal --- .../Ingestion/components/Metadata/Model/ObjectSelectModal.tsx | 2 +- .../pages/Repository/components/RepositoryTreeView/index.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/pages/Ingestion/components/Metadata/Model/ObjectSelectModal.tsx b/client/src/pages/Ingestion/components/Metadata/Model/ObjectSelectModal.tsx index b026709eb..dcbe3c4b2 100644 --- a/client/src/pages/Ingestion/components/Metadata/Model/ObjectSelectModal.tsx +++ b/client/src/pages/Ingestion/components/Metadata/Model/ObjectSelectModal.tsx @@ -98,7 +98,7 @@ function ObjectSelectModal(props: ObjectSelectModalProps): React.ReactElement { }; return ( - + - - ); -} - -interface ItemProps { - derivedObject: StateDerivedObject; - onRemove?: (id: number) => void; - viewMode?: boolean; -} - -function Item(props: ItemProps): React.ReactElement { - const { derivedObject, onRemove, viewMode = false } = props; - const { idSystemObject, name, variantType, objectType } = derivedObject; - const classes = useStyles(viewMode); - const getEntries = useVocabularyStore(state => state.getEntries); - - const entries = getEntries(eVocabularySetID.eCaptureDataFileVariantType); - const variant = entries.find(entry => entry.idVocabulary === variantType); - - const remove = () => onRemove?.(idSystemObject); - - return ( - - - - {name} - - - - {variant?.Term} - - - {getTermForSystemObjectType(objectType)} - - - {!viewMode && } - - - ); -} - -export default DerivedObjectsList; diff --git a/client/src/pages/Ingestion/components/Metadata/Model/ObjectSelectModal.tsx b/client/src/pages/Ingestion/components/Metadata/Model/ObjectSelectModal.tsx index dcbe3c4b2..8f13dd557 100644 --- a/client/src/pages/Ingestion/components/Metadata/Model/ObjectSelectModal.tsx +++ b/client/src/pages/Ingestion/components/Metadata/Model/ObjectSelectModal.tsx @@ -10,7 +10,7 @@ import { makeStyles } from '@material-ui/core/styles'; import React, { useEffect, useState } from 'react'; import { toast } from 'react-toastify'; import { apolloClient } from '../../../../../graphql'; -import { StateSourceObject } from '../../../../../store'; +import { StateRelatedObject } from '../../../../../store'; import { GetSourceObjectIdentiferDocument, GetSourceObjectIdentiferInput, GetSourceObjectIdentiferQuery } from '../../../../../types/graphql'; import RepositoryFilterView from '../../../../Repository/components/RepositoryFilterView'; import RepositoryTreeView from '../../../../Repository/components/RepositoryTreeView'; @@ -41,15 +41,15 @@ const useStyles = makeStyles(({ palette, spacing, breakpoints }) => ({ interface ObjectSelectModalProps { open: boolean; - selectedObjects: StateSourceObject[]; - onSelectedObjects: (newSourceObjects: StateSourceObject[]) => void; + selectedObjects: StateRelatedObject[]; + onSelectedObjects: (newSourceObjects: StateRelatedObject[]) => void; onModalClose: () => void; } function ObjectSelectModal(props: ObjectSelectModalProps): React.ReactElement { const { open, onSelectedObjects, selectedObjects, onModalClose } = props; const classes = useStyles(); - const [selected, setSelected] = useState([]); + const [selected, setSelected] = useState([]); const [isSaving, setIsSaving] = useState(false); useEffect(() => { @@ -76,7 +76,7 @@ function ObjectSelectModal(props: ObjectSelectModalProps): React.ReactElement { const { getSourceObjectIdentifer } = data; const { sourceObjectIdentifiers } = getSourceObjectIdentifer; - const selectedSourceObjects: StateSourceObject[] = selected.map((selected: StateSourceObject, index: number) => ({ + const selectedSourceObjects: StateRelatedObject[] = selected.map((selected: StateRelatedObject, index: number) => ({ ...selected, identifier: sourceObjectIdentifiers[index]?.identifier })); @@ -88,7 +88,7 @@ function ObjectSelectModal(props: ObjectSelectModalProps): React.ReactElement { setIsSaving(false); }; - const onSelect = (sourceObject: StateSourceObject): void => { + const onSelect = (sourceObject: StateRelatedObject): void => { setSelected([...selected, sourceObject]); }; diff --git a/client/src/pages/Ingestion/components/Metadata/Model/SourceObjectsList.tsx b/client/src/pages/Ingestion/components/Metadata/Model/RelatedObjectsList.tsx similarity index 85% rename from client/src/pages/Ingestion/components/Metadata/Model/SourceObjectsList.tsx rename to client/src/pages/Ingestion/components/Metadata/Model/RelatedObjectsList.tsx index 8ef8d9c71..c2437cdb7 100644 --- a/client/src/pages/Ingestion/components/Metadata/Model/SourceObjectsList.tsx +++ b/client/src/pages/Ingestion/components/Metadata/Model/RelatedObjectsList.tsx @@ -9,11 +9,12 @@ import clsx from 'clsx'; import React from 'react'; import { MdRemoveCircleOutline } from 'react-icons/md'; import { NewTabLink } from '../../../../../components'; -import { StateSourceObject } from '../../../../../store'; +import { StateRelatedObject } from '../../../../../store'; +import { RelatedObjectType } from '../../../../../types/graphql'; import { getDetailsUrlForObject, getTermForSystemObjectType } from '../../../../../utils/repository'; import { sharedButtonProps, sharedLabelProps } from '../../../../../utils/shared'; -export const useStyles = makeStyles(({ palette }) => ({ +const useStyles = makeStyles(({ palette }) => ({ container: { display: 'flex', width: '52vw', @@ -47,29 +48,30 @@ export const useStyles = makeStyles(({ palette }) => ({ } })); -interface SourceObjectsListProps { - sourceObjects: StateSourceObject[]; +interface RelatedObjectsListProps { + relatedObjects: StateRelatedObject[]; + type: RelatedObjectType; onAdd: () => void; onRemove?: (id: number) => void; viewMode?: boolean; disabled?: boolean; } -function SourceObjectsList(props: SourceObjectsListProps): React.ReactElement { - const { sourceObjects, onAdd, onRemove, viewMode = false, disabled = false } = props; +function RelatedObjectsList(props: RelatedObjectsListProps): React.ReactElement { + const { relatedObjects, type, onAdd, onRemove, viewMode = false, disabled = false } = props; const classes = useStyles(viewMode); - const titles = ['Source Object(s)', 'Identifiers', 'Object Type']; - const hasSourceObjects = !!sourceObjects.length; + const titles = [`${type.toString()} Object(s)`, 'Identifier', 'Object Type']; + const hasRelatedObjects = !!relatedObjects.length; const buttonLabel: string = viewMode ? 'Connect' : 'Add'; return (
- {hasSourceObjects && ( + {hasRelatedObjects && ( - {sourceObjects.map((sourceObject: StateSourceObject, index: number) => ( + {relatedObjects.map((sourceObject: StateRelatedObject, index: number) => ( void; viewMode?: boolean; } @@ -162,4 +164,4 @@ function Item(props: ItemProps): React.ReactElement { ); } -export default SourceObjectsList; +export default RelatedObjectsList; diff --git a/client/src/pages/Ingestion/components/Metadata/Model/index.tsx b/client/src/pages/Ingestion/components/Metadata/Model/index.tsx index 656eecd0d..175eadb2c 100644 --- a/client/src/pages/Ingestion/components/Metadata/Model/index.tsx +++ b/client/src/pages/Ingestion/components/Metadata/Model/index.tsx @@ -7,13 +7,14 @@ import { Box, Checkbox } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; import React, { useState } from 'react'; import { AssetIdentifiers, DateInputField, FieldType, IdInputField, SelectField } from '../../../../../components'; -import { StateIdentifier, StateSourceObject, useMetadataStore, useVocabularyStore } from '../../../../../store'; +import { StateIdentifier, StateRelatedObject, useMetadataStore, useVocabularyStore } from '../../../../../store'; import { MetadataType } from '../../../../../store/metadata'; +import { RelatedObjectType } from '../../../../../types/graphql'; import { eVocabularySetID } from '../../../../../types/server'; import { withDefaultValueBoolean, withDefaultValueNumber } from '../../../../../utils/shared'; import BoundingBoxInput from './BoundingBoxInput'; import ObjectSelectModal from './ObjectSelectModal'; -import SourceObjectsList from './SourceObjectsList'; +import RelatedObjectsList from './RelatedObjectsList'; import UVContents from './UVContents'; const useStyles = makeStyles(({ palette, typography }) => ({ @@ -109,7 +110,7 @@ function Model(props: ModelProps): React.ReactElement { setModalOpen(false); }; - const onSelectedObjects = (newSourceObjects: StateSourceObject[]) => { + const onSelectedObjects = (newSourceObjects: StateRelatedObject[]) => { updateMetadataField(metadataIndex, 'sourceObjects', newSourceObjects, MetadataType.model); onModalClose(); }; @@ -129,7 +130,7 @@ function Model(props: ModelProps): React.ReactElement { onUpdateIdentifer={onIdentifersChange} onRemoveIdentifer={onIdentifersChange} /> - + ({ const mockReferenceModels: StateReferenceModel[] = [ { idSystemObject: 1, - name: 'Armstrong1.obj', + name: 'Armstrong1.obj (mock)', fileSize: 1.27e+7, resolution: 2000, boundingBoxP1X: 1.0, @@ -64,7 +64,7 @@ const mockReferenceModels: StateReferenceModel[] = [ }, { idSystemObject: 1, - name: 'Armstrong2.obj', + name: 'Armstrong2.obj (mock)', fileSize: 1.27e+7, resolution: 2000, boundingBoxP1X: 1.0, diff --git a/client/src/pages/Repository/components/DetailsView/index.tsx b/client/src/pages/Repository/components/DetailsView/index.tsx index 18eefef75..7b7b90e33 100644 --- a/client/src/pages/Repository/components/DetailsView/index.tsx +++ b/client/src/pages/Repository/components/DetailsView/index.tsx @@ -9,10 +9,10 @@ import React, { useState } from 'react'; import { useParams } from 'react-router'; import IdentifierList from '../../../../components/shared/IdentifierList'; import { parseIdentifiersToState, useVocabularyStore } from '../../../../store'; +import { RelatedObjectType } from '../../../../types/graphql'; import { eVocabularySetID } from '../../../../types/server'; -import DerivedObjectsList from '../../../Ingestion/components/Metadata/Model/DerivedObjectsList'; import ObjectSelectModal from '../../../Ingestion/components/Metadata/Model/ObjectSelectModal'; -import SourceObjectsList from '../../../Ingestion/components/Metadata/Model/SourceObjectsList'; +import RelatedObjectsList from '../../../Ingestion/components/Metadata/Model/RelatedObjectsList'; import { useObjectDetails } from '../../hooks/useDetailsView'; import DetailsHeader from './DetailsHeader'; import DetailsThumbnail from './DetailsThumbnail'; @@ -105,16 +105,18 @@ function DetailsView(): React.ReactElement { onRemove={removeIdentifier} onUpdate={updateIdentifierFields} /> - - diff --git a/client/src/pages/Repository/components/RepositoryTreeView/index.tsx b/client/src/pages/Repository/components/RepositoryTreeView/index.tsx index 345e0eb08..051c97b75 100644 --- a/client/src/pages/Repository/components/RepositoryTreeView/index.tsx +++ b/client/src/pages/Repository/components/RepositoryTreeView/index.tsx @@ -10,7 +10,7 @@ import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; import { TreeView } from '@material-ui/lab'; import React, { useCallback, useEffect } from 'react'; import { Loader } from '../../../../components'; -import { StateSourceObject, treeRootKey, useControlStore, useRepositoryStore } from '../../../../store'; +import { StateRelatedObject, treeRootKey, useControlStore, useRepositoryStore } from '../../../../store'; import { NavigationResultEntry } from '../../../../types/graphql'; import { getObjectInterfaceDetails, getRepositoryTreeNodeId, getTreeColorVariant, getTreeViewColumns, getTreeViewStyleHeight, getTreeViewStyleWidth, getTreeWidth, isRepositoryItemSelected } from '../../../../utils/repository'; import RepositoryTreeHeader from './RepositoryTreeHeader'; @@ -49,8 +49,8 @@ type StyleProps = { interface RepositoryTreeViewProps { isModal?: boolean; - selectedItems?: StateSourceObject[]; - onSelect?: (item: StateSourceObject) => void; + selectedItems?: StateRelatedObject[]; + onSelect?: (item: StateRelatedObject) => void; onUnSelect?: (id: number) => void; } @@ -104,7 +104,7 @@ function RepositoryTreeView(props: RepositoryTreeViewProps): React.ReactElement const select = (event: React.MouseEvent) => { if (onSelect) { event.stopPropagation(); - const repositoryItem: StateSourceObject = { + const repositoryItem: StateRelatedObject = { idSystemObject, name, objectType, diff --git a/client/src/store/metadata/metadata.types.ts b/client/src/store/metadata/metadata.types.ts index 5a1844d5b..d3ee23407 100644 --- a/client/src/store/metadata/metadata.types.ts +++ b/client/src/store/metadata/metadata.types.ts @@ -3,7 +3,7 @@ * * Type definitions for the metadata store. */ -import { DerivedObject, ReferenceModel, ReferenceModelAction, SourceObject } from '../../types/graphql'; +import { RelatedObject, ReferenceModel, ReferenceModelAction } from '../../types/graphql'; import { IngestionFile } from '../upload'; export type StateMetadata = { @@ -42,7 +42,7 @@ export type FieldErrors = { }; }; -export type MetadataFieldValue = string | number | boolean | null | Date | StateIdentifier[] | StateFolder[] | StateUVMap[] | SourceObject[]; +export type MetadataFieldValue = string | number | boolean | null | Date | StateIdentifier[] | StateFolder[] | StateUVMap[] | StateRelatedObject[]; export type MetadataUpdate = { valid: boolean; @@ -93,7 +93,7 @@ export type ModelFields = { systemCreated: boolean; identifiers: StateIdentifier[]; uvMaps: StateUVMap[]; - sourceObjects: StateSourceObject[]; + sourceObjects: StateRelatedObject[]; dateCaptured: Date; creationMethod: number | null; master: boolean; @@ -130,9 +130,7 @@ export type OtherFields = { identifiers: StateIdentifier[]; }; -export type StateSourceObject = SourceObject; - -export type StateDerivedObject = DerivedObject; +export type StateRelatedObject = RelatedObject; export type StateReferenceModel = ReferenceModel; diff --git a/client/src/types/graphql.tsx b/client/src/types/graphql.tsx index 05b7864e7..da06a1f8f 100644 --- a/client/src/types/graphql.tsx +++ b/client/src/types/graphql.tsx @@ -415,19 +415,16 @@ export type IngestUvMap = { mapType: Scalars['Int']; }; -export type SourceObject = { - __typename?: 'SourceObject'; - idSystemObject: Scalars['Int']; - name: Scalars['String']; - identifier?: Maybe; - objectType: Scalars['Int']; -}; +export enum RelatedObjectType { + Source = 'Source', + Derived = 'Derived' +} -export type DerivedObject = { - __typename?: 'DerivedObject'; +export type RelatedObject = { + __typename?: 'RelatedObject'; idSystemObject: Scalars['Int']; name: Scalars['String']; - variantType: Scalars['Int']; + identifier?: Maybe; objectType: Scalars['Int']; }; @@ -446,7 +443,7 @@ export type IngestModel = { directory: Scalars['String']; identifiers: Array; uvMaps: Array; - sourceObjects: Array; + sourceObjects: Array; roughness?: Maybe; metalness?: Maybe; pointCount?: Maybe; @@ -736,7 +733,7 @@ export type IngestUvMapInput = { mapType: Scalars['Int']; }; -export type SourceObjectInput = { +export type RelatedObjectInput = { idSystemObject: Scalars['Int']; name: Scalars['String']; identifier?: Maybe; @@ -757,7 +754,7 @@ export type IngestModelInput = { directory: Scalars['String']; identifiers: Array; uvMaps: Array; - sourceObjects: Array; + sourceObjects: Array; roughness?: Maybe; metalness?: Maybe; pointCount?: Maybe; @@ -1099,8 +1096,8 @@ export type GetSystemObjectDetailsResult = { thumbnail?: Maybe; identifiers: Array; objectAncestors: Array>; - sourceObjects: Array; - derivedObjects: Array; + sourceObjects: Array; + derivedObjects: Array; }; export type GetSourceObjectIdentiferInput = { @@ -2243,11 +2240,11 @@ export type GetSystemObjectDetailsQuery = ( { __typename?: 'RepositoryPath' } & Pick )>>, sourceObjects: Array<( - { __typename?: 'SourceObject' } - & Pick + { __typename?: 'RelatedObject' } + & Pick )>, derivedObjects: Array<( - { __typename?: 'DerivedObject' } - & Pick + { __typename?: 'RelatedObject' } + & Pick )> } ) @@ -3685,7 +3682,7 @@ export const GetSystemObjectDetailsDocument = gql` derivedObjects { idSystemObject name - variantType + identifier objectType } } diff --git a/client/src/utils/repository.tsx b/client/src/utils/repository.tsx index 23b4023a0..4a6d0dc52 100644 --- a/client/src/utils/repository.tsx +++ b/client/src/utils/repository.tsx @@ -12,7 +12,7 @@ import { AiOutlineFileText } from 'react-icons/ai'; import { RepositoryIcon } from '../components'; import { RepositoryFilter } from '../pages/Repository'; import { TreeViewColumn } from '../pages/Repository/components/RepositoryTreeView/MetadataView'; -import { StateSourceObject } from '../store'; +import { StateRelatedObject } from '../store'; import Colors, { RepositoryColorVariant } from '../theme/colors'; import { NavigationResultEntry } from '../types/graphql'; import { eMetadata, eSystemObjectType } from '../types/server'; @@ -217,7 +217,7 @@ export function sortEntriesAlphabetically(entries: NavigationResultEntry[]): Nav return lodash.orderBy(entries, [entry => entry.name.toLowerCase().trim()], ['asc']); } -export function isRepositoryItemSelected(nodeId: string, sourceObjects: StateSourceObject[]): boolean { +export function isRepositoryItemSelected(nodeId: string, sourceObjects: StateRelatedObject[]): boolean { const { idSystemObject } = parseRepositoryTreeNodeId(nodeId); const idSystemObjects: number[] = sourceObjects.map(({ idSystemObject }) => idSystemObject); From c09dd05cf558313636e74c8d327a4640c0350992 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Thu, 19 Nov 2020 13:04:48 +0530 Subject: [PATCH 104/239] Refactor objects name resolver methods --- .../queries/getSystemObjectDetails.ts | 341 +++++------------- 1 file changed, 96 insertions(+), 245 deletions(-) diff --git a/server/graphql/schema/systemobject/resolvers/queries/getSystemObjectDetails.ts b/server/graphql/schema/systemobject/resolvers/queries/getSystemObjectDetails.ts index 58721226a..aab187684 100644 --- a/server/graphql/schema/systemobject/resolvers/queries/getSystemObjectDetails.ts +++ b/server/graphql/schema/systemobject/resolvers/queries/getSystemObjectDetails.ts @@ -2,12 +2,12 @@ import * as CACHE from '../../../../../cache'; import * as DBAPI from '../../../../../db'; import { eObjectGraphMode, eSystemObjectType } from '../../../../../db'; import { - DerivedObject, GetSystemObjectDetailsResult, IngestIdentifier, QueryGetSystemObjectDetailsArgs, + RelatedObject, + RelatedObjectType, RepositoryPath, - SourceObject, SystemObject } from '../../../../../types/graphql'; import { Parent } from '../../../../../types/resolvers'; @@ -21,8 +21,8 @@ export default async function getSystemObjectDetails(_: Parent, args: QueryGetSy const objectAncestors: RepositoryPath[][] = await getRepositoryPathFromObjectGraph(idSystemObject); const systemObject: SystemObject | null = await DBAPI.SystemObject.fetch(idSystemObject); - const sourceObjects: SourceObject[] = await getSourceObjects(idSystemObject); - const derivedObjects: DerivedObject[] = await getDerivedObjects(idSystemObject); + const sourceObjects: RelatedObject[] = await getRelatedObjects(idSystemObject, RelatedObjectType.Source); + const derivedObjects: RelatedObject[] = await getRelatedObjects(idSystemObject, RelatedObjectType.Derived); const identifiers = await getIngestIdentifiers(idSystemObject); @@ -44,7 +44,7 @@ export default async function getSystemObjectDetails(_: Parent, args: QueryGetSy name, retired: systemObject.Retired, objectType: oID.eObjectType, - allowed: true, // TODO: KARAN: tell the client if user has access to edit + allowed: true, // TODO: True until Access control is implemented (Post MVP) thumbnail: null, objectAncestors, identifiers, @@ -53,43 +53,22 @@ export default async function getSystemObjectDetails(_: Parent, args: QueryGetSy }; } -async function getSourceObjects(idSystemObject: number): Promise { - const masterObjects = await DBAPI.SystemObject.fetchMasterFromXref(idSystemObject); - if (!masterObjects) return []; +async function getRelatedObjects(idSystemObject: number, type: RelatedObjectType): Promise { + let relatedSystemObjects: SystemObject[] | null = []; - const sourceObjects: SourceObject[] = []; - - for (const masterObject of masterObjects) { - const identifier: DBAPI.Identifier[] | null = await DBAPI.Identifier.fetchFromSystemObject(idSystemObject); - const oID: CACHE.ObjectIDAndType | undefined = await CACHE.SystemObjectCache.getObjectFromSystem(masterObject.idSystemObject); - - if (!oID) { - const message: string = `No object ID found for ID: ${idSystemObject}`; - LOGGER.logger.error(message); - throw new Error(message); - } - - const sourceObject: SourceObject = { - idSystemObject: masterObject.idSystemObject, - name: await resolveNameForObjectType(masterObject, oID.eObjectType), - identifier: identifier?.[0]?.IdentifierValue ?? null, - objectType: oID.eObjectType - }; - - sourceObjects.push(sourceObject); + if (type === RelatedObjectType.Source) { + relatedSystemObjects = await DBAPI.SystemObject.fetchMasterFromXref(idSystemObject); + } else if (type === RelatedObjectType.Derived) { + relatedSystemObjects = await DBAPI.SystemObject.fetchDerivedFromXref(idSystemObject); } - return sourceObjects; -} - -async function getDerivedObjects(idSystemObject: number): Promise { - const derivedSystemObjects = await DBAPI.SystemObject.fetchDerivedFromXref(idSystemObject); - if (!derivedSystemObjects) return []; + if (!relatedSystemObjects) return []; - const derivedObjects: DerivedObject[] = []; + const relatedObjects: RelatedObject[] = []; - for (const derivedSystemObject of derivedSystemObjects) { - const oID: CACHE.ObjectIDAndType | undefined = await CACHE.SystemObjectCache.getObjectFromSystem(derivedSystemObject.idSystemObject); + for (const relatedSystemObject of relatedSystemObjects) { + const identifier: DBAPI.Identifier[] | null = await DBAPI.Identifier.fetchFromSystemObject(relatedSystemObject.idSystemObject); + const oID: CACHE.ObjectIDAndType | undefined = await CACHE.SystemObjectCache.getObjectFromSystem(relatedSystemObject.idSystemObject); if (!oID) { const message: string = `No object ID found for ID: ${idSystemObject}`; @@ -97,17 +76,17 @@ async function getDerivedObjects(idSystemObject: number): Promise { @@ -131,168 +110,50 @@ async function getRepositoryPathFromObjectGraph(idSystemObject: number): Promise const { unit, project, subject, item, captureData, model, scene, intermediaryFile, projectDocumentation, asset, assetVersion, actor, stakeholder } = objectGraph; const objectAncestors: RepositoryPath[][] = []; - if (unit) objectAncestors.push(await unitsToRepositoryPath(unit)); - if (project) objectAncestors.push(await projectsToRepositoryPath(project)); - if (subject) objectAncestors.push(await subjectsToRepositoryPath(subject)); - if (item) objectAncestors.push(await itemsToRepositoryPath(item)); - if (captureData) objectAncestors.push(await captureDatasToRepositoryPath(captureData)); - if (model) objectAncestors.push(await modelsToRepositoryPath(model)); - if (scene) objectAncestors.push(await scenesToRepositoryPath(scene)); - if (intermediaryFile) objectAncestors.push(await intermediaryFilesToRepositoryPath(intermediaryFile)); - if (projectDocumentation) objectAncestors.push(await projectDocumentationsToRepositoryPath(projectDocumentation)); - if (asset) objectAncestors.push(await assetsToRepositoryPath(asset)); - if (assetVersion) objectAncestors.push(await assetVersionsToRepositoryPath(assetVersion)); - if (actor) objectAncestors.push(await actorsToRepositoryPath(actor)); - if (stakeholder) objectAncestors.push(await stakeholdersToRepositoryPath(stakeholder)); + if (unit) objectAncestors.push(await objectToRepositoryPath(unit, eSystemObjectType.eUnit)); + if (project) objectAncestors.push(await objectToRepositoryPath(project, eSystemObjectType.eProject)); + if (subject) objectAncestors.push(await objectToRepositoryPath(subject, eSystemObjectType.eSubject)); + if (item) objectAncestors.push(await objectToRepositoryPath(item, eSystemObjectType.eItem)); + if (captureData) objectAncestors.push(await objectToRepositoryPath(captureData, eSystemObjectType.eCaptureData)); + if (model) objectAncestors.push(await objectToRepositoryPath(model, eSystemObjectType.eModel)); + if (scene) objectAncestors.push(await objectToRepositoryPath(scene, eSystemObjectType.eScene)); + if (intermediaryFile) objectAncestors.push(await objectToRepositoryPath(intermediaryFile, eSystemObjectType.eIntermediaryFile)); + if (projectDocumentation) objectAncestors.push(await objectToRepositoryPath(projectDocumentation, eSystemObjectType.eProjectDocumentation)); + if (asset) objectAncestors.push(await objectToRepositoryPath(asset, eSystemObjectType.eAsset)); + if (assetVersion) objectAncestors.push(await objectToRepositoryPath(assetVersion, eSystemObjectType.eAssetVersion)); + if (actor) objectAncestors.push(await objectToRepositoryPath(actor, eSystemObjectType.eActor)); + if (stakeholder) objectAncestors.push(await objectToRepositoryPath(stakeholder, eSystemObjectType.eStakeholder)); return objectAncestors; } -const defaultName: string = ''; - -async function unitsToRepositoryPath(units: DBAPI.Unit[]): Promise { - const paths: RepositoryPath[] = []; - for (const { idUnit, Abbreviation } of units) { - const idSystemObject: number = (await DBAPI.SystemObject.fetchFromUnitID(idUnit))?.idSystemObject ?? 0; - const path: RepositoryPath = { - idSystemObject, - name: Abbreviation || defaultName, - objectType: eSystemObjectType.eUnit - }; - paths.push(path); - } - - return paths; -} - -async function projectsToRepositoryPath(projects: DBAPI.Project[]): Promise { - const paths: RepositoryPath[] = []; - for (const { idProject, Name } of projects) { - const idSystemObject: number = (await DBAPI.SystemObject.fetchFromProjectID(idProject))?.idSystemObject ?? 0; - const path: RepositoryPath = { - idSystemObject, - name: Name, - objectType: eSystemObjectType.eProject - }; - paths.push(path); - } - - return paths; -} - -async function subjectsToRepositoryPath(subjects: DBAPI.Subject[]): Promise { - const paths: RepositoryPath[] = []; - for (const { idSubject, Name } of subjects) { - const idSystemObject: number = (await DBAPI.SystemObject.fetchFromSubjectID(idSubject))?.idSystemObject ?? 0; - const path: RepositoryPath = { - idSystemObject, - name: Name, - objectType: eSystemObjectType.eSubject - }; - paths.push(path); - } - - return paths; -} - -async function itemsToRepositoryPath(items: DBAPI.Item[]): Promise { - const paths: RepositoryPath[] = []; - for (const { idItem, Name } of items) { - const idSystemObject: number = (await DBAPI.SystemObject.fetchFromSubjectID(idItem))?.idSystemObject ?? 0; - const path: RepositoryPath = { - idSystemObject, - name: Name, - objectType: eSystemObjectType.eItem - }; - paths.push(path); - } - - return paths; -} - -async function captureDatasToRepositoryPath(captureDatas: DBAPI.CaptureData[]): Promise { - const paths: RepositoryPath[] = []; - for (const { idCaptureData } of captureDatas) { - const idSystemObject: number = (await DBAPI.SystemObject.fetchFromCaptureDataID(idCaptureData))?.idSystemObject ?? 0; - const path: RepositoryPath = { - idSystemObject, - name: 'CD', // TODO: KARAN: get name for CD - objectType: eSystemObjectType.eCaptureData - }; - paths.push(path); - } - - return paths; -} - -async function modelsToRepositoryPath(models: DBAPI.Model[]): Promise { - const paths: RepositoryPath[] = []; - for (const { idModel } of models) { - const idSystemObject: number = (await DBAPI.SystemObject.fetchFromModelID(idModel))?.idSystemObject ?? 0; - const path: RepositoryPath = { - idSystemObject, - name: 'Model', // TODO: KARAN: get name for model - objectType: eSystemObjectType.eModel - }; - paths.push(path); - } - - return paths; -} - -async function scenesToRepositoryPath(scenes: DBAPI.Scene[]): Promise { - const paths: RepositoryPath[] = []; - for (const { idScene, Name } of scenes) { - const idSystemObject: number = (await DBAPI.SystemObject.fetchFromSceneID(idScene))?.idSystemObject ?? 0; - const path: RepositoryPath = { - idSystemObject, - name: Name, - objectType: eSystemObjectType.eScene - }; - paths.push(path); - } - - return paths; -} - -async function intermediaryFilesToRepositoryPath(intermediaryFiles: DBAPI.IntermediaryFile[]): Promise { - const paths: RepositoryPath[] = []; - for (const { idIntermediaryFile } of intermediaryFiles) { - const idSystemObject: number = (await DBAPI.SystemObject.fetchFromIntermediaryFileID(idIntermediaryFile))?.idSystemObject ?? 0; - const path: RepositoryPath = { - idSystemObject, - name: 'IF', // TODO: KARAN: get name for IF - objectType: eSystemObjectType.eIntermediaryFile - }; - paths.push(path); - } +const unknownName: string = ''; - return paths; -} +type Objects = DBAPI.Unit[] | DBAPI.Project[] | DBAPI.Subject[] | DBAPI.Item[] | DBAPI.CaptureData[] | DBAPI.Model[] | DBAPI.Scene[] | DBAPI.IntermediaryFile[] | DBAPI.ProjectDocumentation[] | DBAPI.Asset[] | DBAPI.AssetVersion[] | DBAPI.Actor[] | DBAPI.Stakeholder[]; -async function projectDocumentationsToRepositoryPath(projectDocumentations: DBAPI.ProjectDocumentation[]): Promise { +async function objectToRepositoryPath(objects: Objects, objectType: eSystemObjectType): Promise { const paths: RepositoryPath[] = []; - for (const { idProjectDocumentation, Name } of projectDocumentations) { - const idSystemObject: number = (await DBAPI.SystemObject.fetchFromProjectDocumentationID(idProjectDocumentation))?.idSystemObject ?? 0; - const path: RepositoryPath = { - idSystemObject, - name: Name, - objectType: eSystemObjectType.eProjectDocumentation - }; - paths.push(path); - } - - return paths; -} + for (const object of objects) { + let SystemObject: SystemObject | null = null; + + if (object instanceof DBAPI.Unit && objectType === eSystemObjectType.eUnit) SystemObject = await DBAPI.SystemObject.fetchFromUnitID(object.idUnit); + if (object instanceof DBAPI.Project && objectType === eSystemObjectType.eProject) SystemObject = await DBAPI.SystemObject.fetchFromProjectID(object.idProject); + if (object instanceof DBAPI.Subject && objectType === eSystemObjectType.eSubject) SystemObject = await DBAPI.SystemObject.fetchFromSubjectID(object.idSubject); + if (object instanceof DBAPI.Item && objectType === eSystemObjectType.eItem) SystemObject = await DBAPI.SystemObject.fetchFromItemID(object.idItem); + if (object instanceof DBAPI.CaptureData && objectType === eSystemObjectType.eCaptureData) SystemObject = await DBAPI.SystemObject.fetchFromCaptureDataID(object.idCaptureData); + if (object instanceof DBAPI.Model && objectType === eSystemObjectType.eModel) SystemObject = await DBAPI.SystemObject.fetchFromModelID(object.idModel); + if (object instanceof DBAPI.Scene && objectType === eSystemObjectType.eScene) SystemObject = await DBAPI.SystemObject.fetchFromSceneID(object.idScene); + if (object instanceof DBAPI.IntermediaryFile && objectType === eSystemObjectType.eIntermediaryFile) SystemObject = await DBAPI.SystemObject.fetchFromIntermediaryFileID(object.idIntermediaryFile); + if (object instanceof DBAPI.ProjectDocumentation && objectType === eSystemObjectType.eProjectDocumentation) SystemObject = await DBAPI.SystemObject.fetchFromProjectDocumentationID(object.idProjectDocumentation); + if (object instanceof DBAPI.Asset && objectType === eSystemObjectType.eAsset) SystemObject = await DBAPI.SystemObject.fetchFromAssetID(object.idAsset); + if (object instanceof DBAPI.AssetVersion && objectType === eSystemObjectType.eAssetVersion) SystemObject = await DBAPI.SystemObject.fetchFromAssetVersionID(object.idAssetVersion); + if (object instanceof DBAPI.Actor && objectType === eSystemObjectType.eActor) SystemObject = await DBAPI.SystemObject.fetchFromActorID(object.idActor); + if (object instanceof DBAPI.Stakeholder && objectType === eSystemObjectType.eStakeholder) SystemObject = await DBAPI.SystemObject.fetchFromStakeholderID(object.idStakeholder); -async function assetsToRepositoryPath(assets: DBAPI.Asset[]): Promise { - const paths: RepositoryPath[] = []; - for (const { idAsset, FileName } of assets) { - const idSystemObject: number = (await DBAPI.SystemObject.fetchFromAssetID(idAsset))?.idSystemObject ?? 0; const path: RepositoryPath = { - idSystemObject, - name: FileName, - objectType: eSystemObjectType.eAsset + idSystemObject: SystemObject?.idSystemObject ?? 0, + name: await resolveNameForObjectType(SystemObject, objectType), + objectType }; paths.push(path); } @@ -300,54 +161,8 @@ async function assetsToRepositoryPath(assets: DBAPI.Asset[]): Promise { - const paths: RepositoryPath[] = []; - for (const { idAssetVersion, FileName } of assetVersions) { - const idSystemObject: number = (await DBAPI.SystemObject.fetchFromAssetVersionID(idAssetVersion))?.idSystemObject ?? 0; - const path: RepositoryPath = { - idSystemObject, - name: FileName, - objectType: eSystemObjectType.eAssetVersion - }; - paths.push(path); - } - - return paths; -} - -async function actorsToRepositoryPath(actors: DBAPI.Actor[]): Promise { - const paths: RepositoryPath[] = []; - for (const { idActor } of actors) { - const idSystemObject: number = (await DBAPI.SystemObject.fetchFromActorID(idActor))?.idSystemObject ?? 0; - const path: RepositoryPath = { - idSystemObject, - name: 'Actor', // TODO: KARAN: get name for actor - objectType: eSystemObjectType.eActor - }; - paths.push(path); - } - - return paths; -} - -async function stakeholdersToRepositoryPath(stakeholders: DBAPI.Stakeholder[]): Promise { - const paths: RepositoryPath[] = []; - for (const { idStakeholder, IndividualName } of stakeholders) { - const idSystemObject: number = (await DBAPI.SystemObject.fetchFromStakeholderID(idStakeholder))?.idSystemObject ?? 0; - const path: RepositoryPath = { - idSystemObject, - name: IndividualName, - objectType: eSystemObjectType.eStakeholder - }; - paths.push(path); - } - - return paths; -} - - -async function resolveNameForObjectType(systemObject: SystemObject, objectType: eSystemObjectType): Promise { - const unknownName: string = ''; +async function resolveNameForObjectType(systemObject: SystemObject | null, objectType: eSystemObjectType): Promise { + if (!systemObject) return unknownName; switch (objectType) { case eSystemObjectType.eUnit: @@ -391,9 +206,28 @@ async function resolveNameForObjectType(systemObject: SystemObject, objectType: return unknownName; case eSystemObjectType.eCaptureData: + if (systemObject.idCaptureData) { + const CaptureData = await DBAPI.CaptureData.fetch(systemObject.idCaptureData); + if (CaptureData) { + const Vocabulary = await DBAPI.Vocabulary.fetch(CaptureData.idVCaptureMethod); + if (Vocabulary) { + return Vocabulary.Term; + } + } + } + return unknownName; case eSystemObjectType.eModel: + if (systemObject.idModel) { + const ModelGeometryFile = await DBAPI.ModelGeometryFile.fetchFromModel(systemObject.idModel); + if (ModelGeometryFile && ModelGeometryFile[0]) { + const Asset = await DBAPI.Asset.fetch(ModelGeometryFile[0].idAsset); + if (Asset) { + return Asset.FileName; + } + } + } return unknownName; case eSystemObjectType.eScene: @@ -407,6 +241,16 @@ async function resolveNameForObjectType(systemObject: SystemObject, objectType: return unknownName; case eSystemObjectType.eIntermediaryFile: + if (systemObject.idIntermediaryFile) { + const IntermediaryFile = await DBAPI.IntermediaryFile.fetch(systemObject.idIntermediaryFile); + if (IntermediaryFile) { + const Asset = await DBAPI.Asset.fetch(IntermediaryFile.idAsset); + if (Asset) { + return Asset.FileName; + } + } + } + return unknownName; case eSystemObjectType.eProjectDocumentation: @@ -440,6 +284,13 @@ async function resolveNameForObjectType(systemObject: SystemObject, objectType: return unknownName; case eSystemObjectType.eActor: + if (systemObject.idActor) { + const Actor = await DBAPI.Actor.fetch(systemObject.idActor); + if (Actor) { + return Actor?.IndividualName ?? unknownName; + } + } + return unknownName; case eSystemObjectType.eStakeholder: @@ -455,4 +306,4 @@ async function resolveNameForObjectType(systemObject: SystemObject, objectType: default: return unknownName; } -} \ No newline at end of file +} From af22743a5870d69e2ee0acb11e6ebf768db228b2 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Fri, 20 Nov 2020 15:46:17 +0530 Subject: [PATCH 105/239] added react helmet --- client/package.json | 1 + yarn.lock | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/client/package.json b/client/package.json index 25e5c27c2..cb93e5a6e 100644 --- a/client/package.json +++ b/client/package.json @@ -58,6 +58,7 @@ "react-debounce-input": "3.2.2", "react-dom": "16.13.1", "react-dropzone": "11.1.0", + "react-helmet": "6.1.0", "react-icons": "3.11.0", "react-router": "5.2.0", "react-router-dom": "5.2.0", diff --git a/yarn.lock b/yarn.lock index 4c5ec820b..7a049bfbb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13938,6 +13938,21 @@ react-fast-compare@^2.0.1: resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-2.0.4.tgz#e84b4d455b0fec113e0402c329352715196f81f9" integrity sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw== +react-fast-compare@^3.1.1: + version "3.2.0" + resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.0.tgz#641a9da81b6a6320f270e89724fb45a0b39e43bb" + integrity sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA== + +react-helmet@6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/react-helmet/-/react-helmet-6.1.0.tgz#a750d5165cb13cf213e44747502652e794468726" + integrity sha512-4uMzEY9nlDlgxr61NL3XbKRy1hEkXmKNXhjbAIOVw5vcFrsdYbH2FEwcNyWvWinl103nXgzYNlns9ca+8kFiWw== + dependencies: + object-assign "^4.1.1" + prop-types "^15.7.2" + react-fast-compare "^3.1.1" + react-side-effect "^2.1.0" + react-icons@3.11.0: version "3.11.0" resolved "https://registry.yarnpkg.com/react-icons/-/react-icons-3.11.0.tgz#2ca2903dfab8268ca18ebd8cc2e879921ec3b254" @@ -14053,6 +14068,11 @@ react-scripts@3.4.0: optionalDependencies: fsevents "2.1.2" +react-side-effect@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/react-side-effect/-/react-side-effect-2.1.1.tgz#66c5701c3e7560ab4822a4ee2742dee215d72eb3" + integrity sha512-2FoTQzRNTncBVtnzxFOk2mCpcfxQpenBMbk5kSVBg5UcPqV9fRbgY2zhb7GTWWOlpFmAxhClBDlIq8Rsubz1yQ== + react-spring@8.0.27: version "8.0.27" resolved "https://registry.yarnpkg.com/react-spring/-/react-spring-8.0.27.tgz#97d4dee677f41e0b2adcb696f3839680a3aa356a" From 5ec437dc6249dab14ff8696b4d33cc4bce84a012 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Fri, 20 Nov 2020 15:47:59 +0530 Subject: [PATCH 106/239] update tabl title for better reference on details screen --- client/src/index.tsx | 4 ++++ .../Repository/components/DetailsView/DetailsHeader.tsx | 8 +++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/client/src/index.tsx b/client/src/index.tsx index c88e953b8..86bce95c1 100644 --- a/client/src/index.tsx +++ b/client/src/index.tsx @@ -9,6 +9,7 @@ import { ThemeProvider } from '@material-ui/core'; import React, { useCallback, useEffect, useState } from 'react'; import { AliveScope } from 'react-activation'; import ReactDOM from 'react-dom'; +import { Helmet } from 'react-helmet'; import { BrowserRouter as Router, Switch } from 'react-router-dom'; import { Slide, toast, ToastContainer } from 'react-toastify'; import 'react-toastify/dist/ReactToastify.css'; @@ -64,6 +65,9 @@ function App(): React.ReactElement { return ( + + Packrat + ({ @@ -43,8 +44,13 @@ function DetailsHeader(props: DetailsHeaderProps): React.ReactElement { const { objectType, path, name, retired, disabled } = props; const classes = useStyles(); + const title = `${name} ${getTermForSystemObjectType(objectType)} | Packrat`; + return ( + + {title} + {getTermForSystemObjectType(objectType)} From 3a82089e685eb255e4974414b21b56d24f06beb6 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Fri, 20 Nov 2020 15:59:18 +0530 Subject: [PATCH 107/239] refactor header utils --- client/src/index.tsx | 3 ++- .../Repository/components/DetailsView/DetailsHeader.tsx | 3 ++- client/src/utils/shared.ts | 5 +++++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/client/src/index.tsx b/client/src/index.tsx index 86bce95c1..f7978e731 100644 --- a/client/src/index.tsx +++ b/client/src/index.tsx @@ -21,6 +21,7 @@ import { Home, Login } from './pages'; import * as serviceWorker from './serviceWorker'; import { useUserStore, useVocabularyStore } from './store'; import theme from './theme'; +import { getHeaderTitle } from './utils/shared'; function AppRouter(): React.ReactElement { const [loading, setLoading] = useState(true); @@ -66,7 +67,7 @@ function App(): React.ReactElement { - Packrat + {getHeaderTitle()} ({ header: { @@ -44,7 +45,7 @@ function DetailsHeader(props: DetailsHeaderProps): React.ReactElement { const { objectType, path, name, retired, disabled } = props; const classes = useStyles(); - const title = `${name} ${getTermForSystemObjectType(objectType)} | Packrat`; + const title = getHeaderTitle(`${name} ${getTermForSystemObjectType(objectType)}`); return ( diff --git a/client/src/utils/shared.ts b/client/src/utils/shared.ts index 7b7416c3f..2001a17f6 100644 --- a/client/src/utils/shared.ts +++ b/client/src/utils/shared.ts @@ -60,3 +60,8 @@ export const sharedLabelProps: CSSProperties = { fontSize: '0.8em', color: palette.primary.dark }; + +export function getHeaderTitle(title?: string): string { + if (title) return `${title} - Packrat`; + return 'Packrat'; +} \ No newline at end of file From eff72e8f9b969950d8d3f3504de899c78c611387 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Fri, 20 Nov 2020 20:31:45 +0530 Subject: [PATCH 108/239] added test for graphql api getSourceObjectIdentiferTest --- .../getSourceObjectIdentifer.test.ts | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 server/tests/graphql/queries/systemobject/getSourceObjectIdentifer.test.ts diff --git a/server/tests/graphql/queries/systemobject/getSourceObjectIdentifer.test.ts b/server/tests/graphql/queries/systemobject/getSourceObjectIdentifer.test.ts new file mode 100644 index 000000000..776d36f69 --- /dev/null +++ b/server/tests/graphql/queries/systemobject/getSourceObjectIdentifer.test.ts @@ -0,0 +1,81 @@ +import * as DBAPI from '../../../../db'; +import GraphQLApi from '../../../../graphql'; +import { + CreateSubjectInput, + CreateUnitInput, + CreateVocabularyInput, + CreateVocabularySetInput, + GetSourceObjectIdentiferInput, + GetSourceObjectIdentiferResult +} from '../../../../types/graphql'; +import * as UTIL from '../../../db/api'; +import TestSuiteUtils from '../../utils'; + +const getSourceObjectIdentiferTest = (utils: TestSuiteUtils): void => { + let graphQLApi: GraphQLApi; + let createUnitInput: () => CreateUnitInput; + let createSubjectInput: (idUnit: number, idIdentifierPreferred?: number) => CreateSubjectInput; + let createVocabularyInput: (idVocabularySet: number) => CreateVocabularyInput; + let createVocabularySetInput: () => CreateVocabularySetInput; + + beforeAll(() => { + graphQLApi = utils.graphQLApi; + createUnitInput = utils.createUnitInput; + createSubjectInput = utils.createSubjectInput; + createVocabularyInput = utils.createVocabularyInput; + createVocabularySetInput = utils.createVocabularySetInput; + }); + + describe('Query: getSourceObjectIdentifer', () => { + test('should work with valid input', async () => { + const unitArgs: CreateUnitInput = createUnitInput(); + const { Unit } = await graphQLApi.createUnit(unitArgs); + expect(Unit).toBeTruthy(); + + const vocabularySetArgs: CreateVocabularySetInput = createVocabularySetInput(); + const { VocabularySet } = await graphQLApi.createVocabularySet(vocabularySetArgs); + expect(VocabularySet).toBeTruthy(); + + if (VocabularySet && Unit) { + const vocabularyArgs: CreateVocabularyInput = createVocabularyInput(VocabularySet.idVocabularySet); + const { Vocabulary } = await graphQLApi.createVocabulary(vocabularyArgs); + expect(Vocabulary).toBeTruthy(); + + if (Vocabulary) { + const IdentifierValue: string = 'Test Identifier Null 2'; + const Identifier = await UTIL.createIdentifierTest({ + IdentifierValue, + idVIdentifierType: Vocabulary.idVocabulary, + idSystemObject: null, + idIdentifier: 0 + }); + + if (Identifier) { + const subjectArgs: CreateSubjectInput = createSubjectInput(Unit.idUnit, Identifier.idIdentifier); + const { Subject } = await graphQLApi.createSubject(subjectArgs); + expect(Subject).toBeTruthy(); + + if (Subject) { + const SO = await DBAPI.SystemObject.fetchFromSubjectID(Subject.idSubject); + + if (SO) { + Identifier.idSystemObject = SO.idSystemObject; + await Identifier.update(); + + const input: GetSourceObjectIdentiferInput = { + idSystemObjects: [SO.idSystemObject] + }; + const result: GetSourceObjectIdentiferResult = await graphQLApi.getSourceObjectIdentifer(input); + const [{ idSystemObject, identifier }] = result.sourceObjectIdentifiers; + expect(idSystemObject).toBe(SO.idSystemObject); + expect(identifier).toBe(IdentifierValue); + } + } + } + } + } + }); + }); +}; + +export default getSourceObjectIdentiferTest; From 575497dc4b670c881f4c34ef115a19d3149046a7 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Fri, 20 Nov 2020 20:32:05 +0530 Subject: [PATCH 109/239] added test for graphql api getSystemObjectDetailsTest --- server/tests/graphql/graphql.test.ts | 4 + .../getSystemObjectDetails.test.ts | 94 +++++++++++++++++++ server/tests/graphql/utils/index.ts | 7 +- 3 files changed, 102 insertions(+), 3 deletions(-) create mode 100644 server/tests/graphql/queries/systemobject/getSystemObjectDetails.test.ts diff --git a/server/tests/graphql/graphql.test.ts b/server/tests/graphql/graphql.test.ts index 7c3dd8ba0..44b80e653 100644 --- a/server/tests/graphql/graphql.test.ts +++ b/server/tests/graphql/graphql.test.ts @@ -25,6 +25,8 @@ import getItemsForSubjectTest from './queries/unit/getItemsForSubject.test'; import getAssetVersionsDetailsTest from './queries/asset/getAssetVersionsDetails.test'; import getProjectDocumentationTest from './queries/unit/getProjectDocumentation.test'; import getIntermediaryFileTest from './queries/scene/getIntermediaryFile.test'; +import getSourceObjectIdentiferTest from './queries/systemobject/getSourceObjectIdentifer.test'; +import getSystemObjectDetailsTest from './queries/systemobject/getSystemObjectDetails.test'; import createCaptureDataTest from './mutations/capturedata/createCaptureData.test'; import createModelTest from './mutations/model/createModel.test'; @@ -68,6 +70,8 @@ describe('GraphQL Test Suite', () => { getAssetVersionsDetailsTest(utils); getProjectDocumentationTest(utils); getIntermediaryFileTest(utils); + getSourceObjectIdentiferTest(utils); + getSystemObjectDetailsTest(utils); // Mutations createCaptureDataTest(utils); diff --git a/server/tests/graphql/queries/systemobject/getSystemObjectDetails.test.ts b/server/tests/graphql/queries/systemobject/getSystemObjectDetails.test.ts new file mode 100644 index 000000000..c1a41db7c --- /dev/null +++ b/server/tests/graphql/queries/systemobject/getSystemObjectDetails.test.ts @@ -0,0 +1,94 @@ +import * as DBAPI from '../../../../db'; +import GraphQLApi from '../../../../graphql'; +import { + CreateSubjectInput, + CreateUnitInput, + CreateVocabularyInput, + CreateVocabularySetInput, + GetSystemObjectDetailsInput, + GetSystemObjectDetailsResult +} from '../../../../types/graphql'; +import * as UTIL from '../../../db/api'; +import TestSuiteUtils from '../../utils'; + +const getSystemObjectDetailsTest = (utils: TestSuiteUtils): void => { + let graphQLApi: GraphQLApi; + let createUnitInput: () => CreateUnitInput; + let createSubjectInput: (idUnit: number, idIdentifierPreferred?: number) => CreateSubjectInput; + let createVocabularyInput: (idVocabularySet: number) => CreateVocabularyInput; + let createVocabularySetInput: () => CreateVocabularySetInput; + + beforeAll(() => { + graphQLApi = utils.graphQLApi; + createUnitInput = utils.createUnitInput; + createSubjectInput = utils.createSubjectInput; + createVocabularyInput = utils.createVocabularyInput; + createVocabularySetInput = utils.createVocabularySetInput; + }); + + describe('Query: getSystemObjectDetails', () => { + test('should work with valid input', async () => { + const unitArgs: CreateUnitInput = createUnitInput(); + const { Unit } = await graphQLApi.createUnit(unitArgs); + expect(Unit).toBeTruthy(); + + const vocabularySetArgs: CreateVocabularySetInput = createVocabularySetInput(); + const { VocabularySet } = await graphQLApi.createVocabularySet(vocabularySetArgs); + expect(VocabularySet).toBeTruthy(); + + if (VocabularySet && Unit) { + const vocabularyArgs: CreateVocabularyInput = createVocabularyInput(VocabularySet.idVocabularySet); + const { Vocabulary } = await graphQLApi.createVocabulary(vocabularyArgs); + expect(Vocabulary).toBeTruthy(); + + if (Vocabulary) { + const IdentifierValue: string = 'Test Identifier Null 2'; + const Identifier = await UTIL.createIdentifierTest({ + IdentifierValue, + idVIdentifierType: Vocabulary.idVocabulary, + idSystemObject: null, + idIdentifier: 0 + }); + + if (Identifier) { + const subjectArgs: CreateSubjectInput = createSubjectInput(Unit.idUnit, Identifier.idIdentifier); + const { Subject } = await graphQLApi.createSubject(subjectArgs); + expect(Subject).toBeTruthy(); + + if (Subject) { + const SO = await DBAPI.SystemObject.fetchFromSubjectID(Subject.idSubject); + + if (SO) { + Identifier.idSystemObject = SO.idSystemObject; + await Identifier.update(); + + const input: GetSystemObjectDetailsInput = { + idSystemObject: SO.idSystemObject + }; + + const { name, identifiers, objectAncestors, sourceObjects, derivedObjects }: GetSystemObjectDetailsResult = await graphQLApi.getSystemObjectDetails(input); + const [{ identifier }] = identifiers; + + expect(name).toBe(subjectArgs.Name); + expect(identifier).toBe(IdentifierValue); + expect(objectAncestors).toBeTruthy(); + expect(sourceObjects).toBeTruthy(); + expect(derivedObjects).toBeTruthy(); + } + } + } + } + } + }); + + test('should fail with invalid input', async () => { + const input: GetSystemObjectDetailsInput = { + idSystemObject: 0 + }; + + await expect(graphQLApi.getSystemObjectDetails(input)).rejects.toThrow(); + }); + }); +}; + +export default getSystemObjectDetailsTest; diff --git a/server/tests/graphql/utils/index.ts b/server/tests/graphql/utils/index.ts index dd010204f..63b55c887 100644 --- a/server/tests/graphql/utils/index.ts +++ b/server/tests/graphql/utils/index.ts @@ -68,16 +68,17 @@ class TestSuiteUtils { createUnitInput = (): CreateUnitInput => { return { - Name: 'Test Name', + Name: 'Test Unit Name', Abbreviation: 'Test Abbreviation', ARKPrefix: 'Test ARKPrefix' }; }; - createSubjectInput = (idUnit: number): CreateSubjectInput => { + createSubjectInput = (idUnit: number, idIdentifierPreferred?: number): CreateSubjectInput => { return { idUnit, - Name: 'Test Subject' + Name: 'Test Subject', + idIdentifierPreferred: idIdentifierPreferred || null }; }; From 96510a77362c5aacdff5f4e4891413b51a66a73a Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Thu, 3 Dec 2020 18:46:31 +0530 Subject: [PATCH 110/239] added fields to getSystemObjectDetails query --- client/src/types/graphql.tsx | 36 ++++++ .../systemobject/getSystemObjectDetails.ts | 20 +++ server/graphql/schema.graphql | 4 + .../schema/systemobject/queries.graphql | 4 + .../queries/getSystemObjectDetails.ts | 115 ++++++++++++++---- server/types/graphql.ts | 4 + 6 files changed, 158 insertions(+), 25 deletions(-) diff --git a/client/src/types/graphql.tsx b/client/src/types/graphql.tsx index da06a1f8f..9134d3db8 100644 --- a/client/src/types/graphql.tsx +++ b/client/src/types/graphql.tsx @@ -1098,6 +1098,10 @@ export type GetSystemObjectDetailsResult = { objectAncestors: Array>; sourceObjects: Array; derivedObjects: Array; + unit?: Maybe; + project?: Maybe; + subject?: Maybe; + item?: Maybe; }; export type GetSourceObjectIdentiferInput = { @@ -2236,6 +2240,18 @@ export type GetSystemObjectDetailsQuery = ( identifiers: Array<( { __typename?: 'IngestIdentifier' } & Pick + )>, unit?: Maybe<( + { __typename?: 'RepositoryPath' } + & Pick + )>, project?: Maybe<( + { __typename?: 'RepositoryPath' } + & Pick + )>, subject?: Maybe<( + { __typename?: 'RepositoryPath' } + & Pick + )>, item?: Maybe<( + { __typename?: 'RepositoryPath' } + & Pick )>, objectAncestors: Array @@ -3668,6 +3684,26 @@ export const GetSystemObjectDetailsDocument = gql` identifier identifierType } + unit { + idSystemObject + name + objectType + } + project { + idSystemObject + name + objectType + } + subject { + idSystemObject + name + objectType + } + item { + idSystemObject + name + objectType + } objectAncestors { idSystemObject name diff --git a/server/graphql/api/queries/systemobject/getSystemObjectDetails.ts b/server/graphql/api/queries/systemobject/getSystemObjectDetails.ts index 2ff2aa63f..1693d77a5 100644 --- a/server/graphql/api/queries/systemobject/getSystemObjectDetails.ts +++ b/server/graphql/api/queries/systemobject/getSystemObjectDetails.ts @@ -12,6 +12,26 @@ const getSystemObjectDetails = gql` identifier identifierType } + unit { + idSystemObject + name + objectType + } + project { + idSystemObject + name + objectType + } + subject { + idSystemObject + name + objectType + } + item { + idSystemObject + name + objectType + } objectAncestors { idSystemObject name diff --git a/server/graphql/schema.graphql b/server/graphql/schema.graphql index 5148b3829..9dfb22b29 100644 --- a/server/graphql/schema.graphql +++ b/server/graphql/schema.graphql @@ -806,6 +806,10 @@ type GetSystemObjectDetailsResult { objectAncestors: [[RepositoryPath!]!]! sourceObjects: [RelatedObject!]! derivedObjects: [RelatedObject!]! + unit: RepositoryPath + project: RepositoryPath + subject: RepositoryPath + item: RepositoryPath } input GetSourceObjectIdentiferInput { diff --git a/server/graphql/schema/systemobject/queries.graphql b/server/graphql/schema/systemobject/queries.graphql index a4ebcaea3..8fd6fb945 100644 --- a/server/graphql/schema/systemobject/queries.graphql +++ b/server/graphql/schema/systemobject/queries.graphql @@ -23,6 +23,10 @@ type GetSystemObjectDetailsResult { objectAncestors: [[RepositoryPath!]!]! sourceObjects: [RelatedObject!]! derivedObjects: [RelatedObject!]! + unit: RepositoryPath + project: RepositoryPath + subject: RepositoryPath + item: RepositoryPath } input GetSourceObjectIdentiferInput { diff --git a/server/graphql/schema/systemobject/resolvers/queries/getSystemObjectDetails.ts b/server/graphql/schema/systemobject/resolvers/queries/getSystemObjectDetails.ts index aab187684..f5004326a 100644 --- a/server/graphql/schema/systemobject/resolvers/queries/getSystemObjectDetails.ts +++ b/server/graphql/schema/systemobject/resolvers/queries/getSystemObjectDetails.ts @@ -18,7 +18,7 @@ export default async function getSystemObjectDetails(_: Parent, args: QueryGetSy const { idSystemObject } = input; const oID: CACHE.ObjectIDAndType | undefined = await CACHE.SystemObjectCache.getObjectFromSystem(idSystemObject); - const objectAncestors: RepositoryPath[][] = await getRepositoryPathFromObjectGraph(idSystemObject); + const { unit, project, subject, item, objectAncestors } = await getObjectAncestors(idSystemObject); const systemObject: SystemObject | null = await DBAPI.SystemObject.fetch(idSystemObject); const sourceObjects: RelatedObject[] = await getRelatedObjects(idSystemObject, RelatedObjectType.Source); @@ -46,6 +46,10 @@ export default async function getSystemObjectDetails(_: Parent, args: QueryGetSy objectType: oID.eObjectType, allowed: true, // TODO: True until Access control is implemented (Post MVP) thumbnail: null, + unit, + project, + subject, + item, objectAncestors, identifiers, sourceObjects, @@ -100,36 +104,92 @@ async function getIngestIdentifiers(idSystemObject: number): Promise { +type GetObjectAncestorsResult = { + unit: RepositoryPath | null; + project: RepositoryPath | null; + subject: RepositoryPath | null; + item: RepositoryPath | null; + objectAncestors: RepositoryPath[][]; +}; + +async function getObjectAncestors(idSystemObject: number): Promise { const objectGraph = new DBAPI.ObjectGraph(idSystemObject, eObjectGraphMode.eAncestors); + let unit: RepositoryPath | null = null; + let project: RepositoryPath | null = null; + let subject: RepositoryPath | null = null; + let item: RepositoryPath | null = null; if (!(await objectGraph.fetch())) { - return []; + return { + unit, + project, + subject, + item, + objectAncestors: [] + }; } - const { unit, project, subject, item, captureData, model, scene, intermediaryFile, projectDocumentation, asset, assetVersion, actor, stakeholder } = objectGraph; const objectAncestors: RepositoryPath[][] = []; - if (unit) objectAncestors.push(await objectToRepositoryPath(unit, eSystemObjectType.eUnit)); - if (project) objectAncestors.push(await objectToRepositoryPath(project, eSystemObjectType.eProject)); - if (subject) objectAncestors.push(await objectToRepositoryPath(subject, eSystemObjectType.eSubject)); - if (item) objectAncestors.push(await objectToRepositoryPath(item, eSystemObjectType.eItem)); - if (captureData) objectAncestors.push(await objectToRepositoryPath(captureData, eSystemObjectType.eCaptureData)); - if (model) objectAncestors.push(await objectToRepositoryPath(model, eSystemObjectType.eModel)); - if (scene) objectAncestors.push(await objectToRepositoryPath(scene, eSystemObjectType.eScene)); - if (intermediaryFile) objectAncestors.push(await objectToRepositoryPath(intermediaryFile, eSystemObjectType.eIntermediaryFile)); - if (projectDocumentation) objectAncestors.push(await objectToRepositoryPath(projectDocumentation, eSystemObjectType.eProjectDocumentation)); - if (asset) objectAncestors.push(await objectToRepositoryPath(asset, eSystemObjectType.eAsset)); - if (assetVersion) objectAncestors.push(await objectToRepositoryPath(assetVersion, eSystemObjectType.eAssetVersion)); - if (actor) objectAncestors.push(await objectToRepositoryPath(actor, eSystemObjectType.eActor)); - if (stakeholder) objectAncestors.push(await objectToRepositoryPath(stakeholder, eSystemObjectType.eStakeholder)); - - return objectAncestors; + if (objectGraph.unit) { + const objectAncestor: RepositoryPath[] = await objectToRepositoryPath(objectGraph.unit, eSystemObjectType.eUnit); + unit = objectAncestor[0]; + objectAncestors.push(objectAncestor); + } + + if (objectGraph.project) { + const objectAncestor: RepositoryPath[] = await objectToRepositoryPath(objectGraph.project, eSystemObjectType.eProject); + project = objectAncestor[0]; + objectAncestors.push(objectAncestor); + } + + if (objectGraph.subject) { + const objectAncestor: RepositoryPath[] = await objectToRepositoryPath(objectGraph.subject, eSystemObjectType.eSubject); + subject = objectAncestor[0]; + objectAncestors.push(objectAncestor); + } + + if (objectGraph.item) { + const objectAncestor: RepositoryPath[] = await objectToRepositoryPath(objectGraph.item, eSystemObjectType.eItem); + item = objectAncestor[0]; + objectAncestors.push(objectAncestor); + } + + if (objectGraph.captureData) objectAncestors.push(await objectToRepositoryPath(objectGraph.captureData, eSystemObjectType.eCaptureData)); + if (objectGraph.model) objectAncestors.push(await objectToRepositoryPath(objectGraph.model, eSystemObjectType.eModel)); + if (objectGraph.scene) objectAncestors.push(await objectToRepositoryPath(objectGraph.scene, eSystemObjectType.eScene)); + if (objectGraph.intermediaryFile) objectAncestors.push(await objectToRepositoryPath(objectGraph.intermediaryFile, eSystemObjectType.eIntermediaryFile)); + if (objectGraph.projectDocumentation) objectAncestors.push(await objectToRepositoryPath(objectGraph.projectDocumentation, eSystemObjectType.eProjectDocumentation)); + if (objectGraph.asset) objectAncestors.push(await objectToRepositoryPath(objectGraph.asset, eSystemObjectType.eAsset)); + if (objectGraph.assetVersion) objectAncestors.push(await objectToRepositoryPath(objectGraph.assetVersion, eSystemObjectType.eAssetVersion)); + if (objectGraph.actor) objectAncestors.push(await objectToRepositoryPath(objectGraph.actor, eSystemObjectType.eActor)); + if (objectGraph.stakeholder) objectAncestors.push(await objectToRepositoryPath(objectGraph.stakeholder, eSystemObjectType.eStakeholder)); + + return { + unit, + project, + subject, + item, + objectAncestors + }; } const unknownName: string = ''; -type Objects = DBAPI.Unit[] | DBAPI.Project[] | DBAPI.Subject[] | DBAPI.Item[] | DBAPI.CaptureData[] | DBAPI.Model[] | DBAPI.Scene[] | DBAPI.IntermediaryFile[] | DBAPI.ProjectDocumentation[] | DBAPI.Asset[] | DBAPI.AssetVersion[] | DBAPI.Actor[] | DBAPI.Stakeholder[]; +type Objects = + | DBAPI.Unit[] + | DBAPI.Project[] + | DBAPI.Subject[] + | DBAPI.Item[] + | DBAPI.CaptureData[] + | DBAPI.Model[] + | DBAPI.Scene[] + | DBAPI.IntermediaryFile[] + | DBAPI.ProjectDocumentation[] + | DBAPI.Asset[] + | DBAPI.AssetVersion[] + | DBAPI.Actor[] + | DBAPI.Stakeholder[]; async function objectToRepositoryPath(objects: Objects, objectType: eSystemObjectType): Promise { const paths: RepositoryPath[] = []; @@ -140,15 +200,20 @@ async function objectToRepositoryPath(objects: Objects, objectType: eSystemObjec if (object instanceof DBAPI.Project && objectType === eSystemObjectType.eProject) SystemObject = await DBAPI.SystemObject.fetchFromProjectID(object.idProject); if (object instanceof DBAPI.Subject && objectType === eSystemObjectType.eSubject) SystemObject = await DBAPI.SystemObject.fetchFromSubjectID(object.idSubject); if (object instanceof DBAPI.Item && objectType === eSystemObjectType.eItem) SystemObject = await DBAPI.SystemObject.fetchFromItemID(object.idItem); - if (object instanceof DBAPI.CaptureData && objectType === eSystemObjectType.eCaptureData) SystemObject = await DBAPI.SystemObject.fetchFromCaptureDataID(object.idCaptureData); + if (object instanceof DBAPI.CaptureData && objectType === eSystemObjectType.eCaptureData) + SystemObject = await DBAPI.SystemObject.fetchFromCaptureDataID(object.idCaptureData); if (object instanceof DBAPI.Model && objectType === eSystemObjectType.eModel) SystemObject = await DBAPI.SystemObject.fetchFromModelID(object.idModel); if (object instanceof DBAPI.Scene && objectType === eSystemObjectType.eScene) SystemObject = await DBAPI.SystemObject.fetchFromSceneID(object.idScene); - if (object instanceof DBAPI.IntermediaryFile && objectType === eSystemObjectType.eIntermediaryFile) SystemObject = await DBAPI.SystemObject.fetchFromIntermediaryFileID(object.idIntermediaryFile); - if (object instanceof DBAPI.ProjectDocumentation && objectType === eSystemObjectType.eProjectDocumentation) SystemObject = await DBAPI.SystemObject.fetchFromProjectDocumentationID(object.idProjectDocumentation); + if (object instanceof DBAPI.IntermediaryFile && objectType === eSystemObjectType.eIntermediaryFile) + SystemObject = await DBAPI.SystemObject.fetchFromIntermediaryFileID(object.idIntermediaryFile); + if (object instanceof DBAPI.ProjectDocumentation && objectType === eSystemObjectType.eProjectDocumentation) + SystemObject = await DBAPI.SystemObject.fetchFromProjectDocumentationID(object.idProjectDocumentation); if (object instanceof DBAPI.Asset && objectType === eSystemObjectType.eAsset) SystemObject = await DBAPI.SystemObject.fetchFromAssetID(object.idAsset); - if (object instanceof DBAPI.AssetVersion && objectType === eSystemObjectType.eAssetVersion) SystemObject = await DBAPI.SystemObject.fetchFromAssetVersionID(object.idAssetVersion); + if (object instanceof DBAPI.AssetVersion && objectType === eSystemObjectType.eAssetVersion) + SystemObject = await DBAPI.SystemObject.fetchFromAssetVersionID(object.idAssetVersion); if (object instanceof DBAPI.Actor && objectType === eSystemObjectType.eActor) SystemObject = await DBAPI.SystemObject.fetchFromActorID(object.idActor); - if (object instanceof DBAPI.Stakeholder && objectType === eSystemObjectType.eStakeholder) SystemObject = await DBAPI.SystemObject.fetchFromStakeholderID(object.idStakeholder); + if (object instanceof DBAPI.Stakeholder && objectType === eSystemObjectType.eStakeholder) + SystemObject = await DBAPI.SystemObject.fetchFromStakeholderID(object.idStakeholder); const path: RepositoryPath = { idSystemObject: SystemObject?.idSystemObject ?? 0, diff --git a/server/types/graphql.ts b/server/types/graphql.ts index a35c90a32..8dc6da4ac 100644 --- a/server/types/graphql.ts +++ b/server/types/graphql.ts @@ -1049,6 +1049,10 @@ export type GetSystemObjectDetailsResult = { objectAncestors: Array>; sourceObjects: Array; derivedObjects: Array; + unit?: Maybe; + project?: Maybe; + subject?: Maybe; + item?: Maybe; }; export type GetSourceObjectIdentiferInput = { From ab2b6fa035766a7bfead4140a8aa65656e6ac844 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Thu, 3 Dec 2020 18:47:03 +0530 Subject: [PATCH 111/239] updated details header component --- .../components/DetailsView/DetailsHeader.tsx | 33 +++++-------------- 1 file changed, 8 insertions(+), 25 deletions(-) diff --git a/client/src/pages/Repository/components/DetailsView/DetailsHeader.tsx b/client/src/pages/Repository/components/DetailsView/DetailsHeader.tsx index 2802adbae..a9f74294e 100644 --- a/client/src/pages/Repository/components/DetailsView/DetailsHeader.tsx +++ b/client/src/pages/Repository/components/DetailsView/DetailsHeader.tsx @@ -3,7 +3,7 @@ * * This component renders repository details header for the DetailsView component. */ -import { Box, Checkbox, FormControlLabel, Typography } from '@material-ui/core'; +import { Box, Typography } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; import React from 'react'; import { Helmet } from 'react-helmet'; @@ -20,6 +20,7 @@ const useStyles = makeStyles(({ palette }) => ({ }, name: { minWidth: 180, + height: 20, padding: '5px 8px', borderRadius: 5, marginRight: 20, @@ -27,9 +28,6 @@ const useStyles = makeStyles(({ palette }) => ({ backgroundColor: Colors.defaults.white, border: `0.5px solid ${palette.primary.contrastText}`, fontSize: '0.8em' - }, - checkboxLabel: { - color: palette.primary.dark } })); @@ -37,39 +35,24 @@ interface DetailsHeaderProps { objectType: eSystemObjectType; path: RepositoryPath[][]; name: string; - retired: boolean; - disabled: boolean; } function DetailsHeader(props: DetailsHeaderProps): React.ReactElement { - const { objectType, path, name, retired, disabled } = props; + const { objectType, path, name } = props; const classes = useStyles(); const title = getHeaderTitle(`${name} ${getTermForSystemObjectType(objectType)}`); return ( - + {title} - - - {getTermForSystemObjectType(objectType)} - - - {!!path.length && } - + + {getTermForSystemObjectType(objectType)} - - - {name} - - } - /> + + {!!path.length && } ); From f1786015098202fee28f33f225cfaf9f88c8c9c0 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Thu, 3 Dec 2020 18:47:18 +0530 Subject: [PATCH 112/239] added ObjectDetails componenet --- .../components/DetailsView/ObjectDetails.tsx | 104 ++++++++++++++++++ .../components/DetailsView/index.tsx | 26 ++++- 2 files changed, 125 insertions(+), 5 deletions(-) create mode 100644 client/src/pages/Repository/components/DetailsView/ObjectDetails.tsx diff --git a/client/src/pages/Repository/components/DetailsView/ObjectDetails.tsx b/client/src/pages/Repository/components/DetailsView/ObjectDetails.tsx new file mode 100644 index 000000000..6d5f7103f --- /dev/null +++ b/client/src/pages/Repository/components/DetailsView/ObjectDetails.tsx @@ -0,0 +1,104 @@ +/** + * Object Details + * + * This component renders object details for the Repository Details UI. + */ +import { Box, Typography } from '@material-ui/core'; +import { makeStyles } from '@material-ui/core/styles'; +import React from 'react'; +import { MdCheckBox, MdCheckBoxOutlineBlank } from 'react-icons/md'; +import { NewTabLink } from '../../../../components'; +import { palette } from '../../../../theme'; +import { RepositoryPath } from '../../../../types/graphql'; +import { getDetailsUrlForObject } from '../../../../utils/repository'; + +const useStyles = makeStyles(({ palette, typography }) => ({ + detail: { + display: 'flex', + height: 20, + width: '100%', + marginBottom: 8 + }, + label: { + fontWeight: typography.fontWeightMedium + }, + value: { + color: ({ clickable = true }: DetailProps) => clickable ? palette.primary.main : palette.primary.dark, + textDecoration: ({ clickable = true, value }: DetailProps) => clickable && value ? 'underline' : undefined + }, +})); + +interface ObjectDetailsProps { + name: string, + unit?: RepositoryPath | null; + project?: RepositoryPath | null; + subject?: RepositoryPath | null; + item?: RepositoryPath | null; + disabled: boolean; + published: boolean; + retired: boolean; +} + +function ObjectDetails(props: ObjectDetailsProps): React.ReactElement { + const { name, unit, project, subject, item, published, retired, disabled } = props; + + const publishedLabel = published ? 'Published' : 'Unpublished'; + + const updateRetired = () => { + if (disabled) return; + }; + + const retiredValueComponent = ( + + {retired ? : } + + ); + + return ( + + + + + + + + + + ); +} + +interface DetailProps { + idSystemObject?: number; + label: string; + value?: string; + valueComponent?: React.ReactNode; + clickable?: boolean; +} + +function Detail(props: DetailProps): React.ReactElement { + const { idSystemObject, label, value, valueComponent, clickable = true } = props; + const classes = useStyles(props); + + let content: React.ReactNode = {value || '-'}; + + if (clickable && idSystemObject) { + content = ( + + {content} + + ); + } + + return ( + + + {label} + + + {valueComponent || content} + + + ); +} + +export default ObjectDetails; \ No newline at end of file diff --git a/client/src/pages/Repository/components/DetailsView/index.tsx b/client/src/pages/Repository/components/DetailsView/index.tsx index 7b7b90e33..a6ba06bbc 100644 --- a/client/src/pages/Repository/components/DetailsView/index.tsx +++ b/client/src/pages/Repository/components/DetailsView/index.tsx @@ -17,6 +17,7 @@ import { useObjectDetails } from '../../hooks/useDetailsView'; import DetailsHeader from './DetailsHeader'; import DetailsThumbnail from './DetailsThumbnail'; import ObjectNotFoundView from './ObjectNotFoundView'; +import ObjectDetails from './ObjectDetails'; const useStyles = makeStyles(({ palette, breakpoints }) => ({ container: { @@ -53,7 +54,7 @@ function DetailsView(): React.ReactElement { return ; } - const { name, objectType, retired, identifiers, allowed, thumbnail, objectAncestors, sourceObjects, derivedObjects } = data.getSystemObjectDetails; + const { name, objectType, identifiers, retired, allowed, thumbnail, unit, project, subject, item, objectAncestors, sourceObjects, derivedObjects } = data.getSystemObjectDetails; const disabled: boolean = !allowed; @@ -91,11 +92,20 @@ function DetailsView(): React.ReactElement { name={name} objectType={objectType} path={objectAncestors} - retired={retired} - disabled={disabled} /> - - + + + + + + + + + + Date: Fri, 4 Dec 2020 11:08:48 +0530 Subject: [PATCH 113/239] minor fixes --- .../pages/Repository/components/DetailsView/ObjectDetails.tsx | 4 ++-- client/src/pages/Repository/components/DetailsView/index.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/client/src/pages/Repository/components/DetailsView/ObjectDetails.tsx b/client/src/pages/Repository/components/DetailsView/ObjectDetails.tsx index 6d5f7103f..e0c255d05 100644 --- a/client/src/pages/Repository/components/DetailsView/ObjectDetails.tsx +++ b/client/src/pages/Repository/components/DetailsView/ObjectDetails.tsx @@ -15,7 +15,7 @@ import { getDetailsUrlForObject } from '../../../../utils/repository'; const useStyles = makeStyles(({ palette, typography }) => ({ detail: { display: 'flex', - height: 20, + minHeight: 20, width: '100%', marginBottom: 8 }, @@ -55,7 +55,7 @@ function ObjectDetails(props: ObjectDetailsProps): React.ReactElement { ); return ( - + diff --git a/client/src/pages/Repository/components/DetailsView/index.tsx b/client/src/pages/Repository/components/DetailsView/index.tsx index a6ba06bbc..77f6b61b8 100644 --- a/client/src/pages/Repository/components/DetailsView/index.tsx +++ b/client/src/pages/Repository/components/DetailsView/index.tsx @@ -105,7 +105,7 @@ function DetailsView(): React.ReactElement { retired={retired} disabled={disabled} /> - + Date: Fri, 4 Dec 2020 18:00:24 +0530 Subject: [PATCH 114/239] updated node docker image version to 14 --- docker/dev.Dockerfile | 2 +- docker/prod.Dockerfile | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docker/dev.Dockerfile b/docker/dev.Dockerfile index cef2b38c3..dfd8da6ed 100644 --- a/docker/dev.Dockerfile +++ b/docker/dev.Dockerfile @@ -1,4 +1,4 @@ -FROM node:12-alpine AS base +FROM node:14-alpine AS base # Add a work directory WORKDIR /app # Copy package.json for caching diff --git a/docker/prod.Dockerfile b/docker/prod.Dockerfile index 422c56230..294fe6e4a 100644 --- a/docker/prod.Dockerfile +++ b/docker/prod.Dockerfile @@ -1,4 +1,4 @@ -FROM node:12-alpine AS base +FROM node:14-alpine AS base # Add a work directory WORKDIR /app # Copy package.json for caching @@ -19,7 +19,7 @@ RUN rm -rf client RUN yarn install --frozen-lockfile && yarn build # Client's production image -FROM node:12-alpine AS client +FROM node:14-alpine AS client # Add a work directory WORKDIR /app # Copy from client-builder @@ -32,7 +32,7 @@ RUN npm i -g serve CMD serve -s . -l 3000 # Server's production image -FROM node:12-alpine AS server +FROM node:14-alpine AS server # Add a work directory WORKDIR /app # Copy from server-builder From be9bffcbef16c6568f7e3472b8a0b542fdbadf9c Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Fri, 4 Dec 2020 18:07:53 +0530 Subject: [PATCH 115/239] added publishedState to getObjectDetails query --- .../components/DetailsView/ObjectDetails.tsx | 8 ++-- .../components/DetailsView/index.tsx | 4 +- client/src/types/graphql.tsx | 4 +- .../systemobject/getSystemObjectDetails.ts | 1 + server/graphql/schema.graphql | 1 + .../schema/systemobject/queries.graphql | 1 + .../queries/getSystemObjectDetails.ts | 18 ++++++++ server/types/graphql.ts | 46 +++++++++++++++++++ 8 files changed, 75 insertions(+), 8 deletions(-) diff --git a/client/src/pages/Repository/components/DetailsView/ObjectDetails.tsx b/client/src/pages/Repository/components/DetailsView/ObjectDetails.tsx index e0c255d05..48dbb69bf 100644 --- a/client/src/pages/Repository/components/DetailsView/ObjectDetails.tsx +++ b/client/src/pages/Repository/components/DetailsView/ObjectDetails.tsx @@ -35,14 +35,12 @@ interface ObjectDetailsProps { subject?: RepositoryPath | null; item?: RepositoryPath | null; disabled: boolean; - published: boolean; + publishedState: string; retired: boolean; } function ObjectDetails(props: ObjectDetailsProps): React.ReactElement { - const { name, unit, project, subject, item, published, retired, disabled } = props; - - const publishedLabel = published ? 'Published' : 'Unpublished'; + const { name, unit, project, subject, item, publishedState, retired, disabled } = props; const updateRetired = () => { if (disabled) return; @@ -61,7 +59,7 @@ function ObjectDetails(props: ObjectDetailsProps): React.ReactElement { - + ); diff --git a/client/src/pages/Repository/components/DetailsView/index.tsx b/client/src/pages/Repository/components/DetailsView/index.tsx index 77f6b61b8..4ff04d7e2 100644 --- a/client/src/pages/Repository/components/DetailsView/index.tsx +++ b/client/src/pages/Repository/components/DetailsView/index.tsx @@ -54,7 +54,7 @@ function DetailsView(): React.ReactElement { return ; } - const { name, objectType, identifiers, retired, allowed, thumbnail, unit, project, subject, item, objectAncestors, sourceObjects, derivedObjects } = data.getSystemObjectDetails; + const { name, objectType, identifiers, retired, allowed, publishedState, thumbnail, unit, project, subject, item, objectAncestors, sourceObjects, derivedObjects } = data.getSystemObjectDetails; const disabled: boolean = !allowed; @@ -101,7 +101,7 @@ function DetailsView(): React.ReactElement { project={project} subject={subject} item={item} - published // TODO: KARAN: update the resolver + publishedState={publishedState} retired={retired} disabled={disabled} /> diff --git a/client/src/types/graphql.tsx b/client/src/types/graphql.tsx index 9134d3db8..2a33efc9b 100644 --- a/client/src/types/graphql.tsx +++ b/client/src/types/graphql.tsx @@ -1093,6 +1093,7 @@ export type GetSystemObjectDetailsResult = { retired: Scalars['Boolean']; objectType: Scalars['Int']; allowed: Scalars['Boolean']; + publishedState: Scalars['String']; thumbnail?: Maybe; identifiers: Array; objectAncestors: Array>; @@ -2235,7 +2236,7 @@ export type GetSystemObjectDetailsQuery = ( & { getSystemObjectDetails: ( { __typename?: 'GetSystemObjectDetailsResult' } - & Pick + & Pick & { identifiers: Array<( { __typename?: 'IngestIdentifier' } @@ -3679,6 +3680,7 @@ export const GetSystemObjectDetailsDocument = gql` retired objectType allowed + publishedState thumbnail identifiers { identifier diff --git a/server/graphql/api/queries/systemobject/getSystemObjectDetails.ts b/server/graphql/api/queries/systemobject/getSystemObjectDetails.ts index 1693d77a5..42c2ff5bf 100644 --- a/server/graphql/api/queries/systemobject/getSystemObjectDetails.ts +++ b/server/graphql/api/queries/systemobject/getSystemObjectDetails.ts @@ -7,6 +7,7 @@ const getSystemObjectDetails = gql` retired objectType allowed + publishedState thumbnail identifiers { identifier diff --git a/server/graphql/schema.graphql b/server/graphql/schema.graphql index 9dfb22b29..2cb1225f8 100644 --- a/server/graphql/schema.graphql +++ b/server/graphql/schema.graphql @@ -801,6 +801,7 @@ type GetSystemObjectDetailsResult { retired: Boolean! objectType: Int! allowed: Boolean! + publishedState: String! thumbnail: String identifiers: [IngestIdentifier!]! objectAncestors: [[RepositoryPath!]!]! diff --git a/server/graphql/schema/systemobject/queries.graphql b/server/graphql/schema/systemobject/queries.graphql index 8fd6fb945..eb6af3708 100644 --- a/server/graphql/schema/systemobject/queries.graphql +++ b/server/graphql/schema/systemobject/queries.graphql @@ -18,6 +18,7 @@ type GetSystemObjectDetailsResult { retired: Boolean! objectType: Int! allowed: Boolean! + publishedState: String! thumbnail: String identifiers: [IngestIdentifier!]! objectAncestors: [[RepositoryPath!]!]! diff --git a/server/graphql/schema/systemobject/resolvers/queries/getSystemObjectDetails.ts b/server/graphql/schema/systemobject/resolvers/queries/getSystemObjectDetails.ts index f5004326a..e1dd53df1 100644 --- a/server/graphql/schema/systemobject/resolvers/queries/getSystemObjectDetails.ts +++ b/server/graphql/schema/systemobject/resolvers/queries/getSystemObjectDetails.ts @@ -23,6 +23,7 @@ export default async function getSystemObjectDetails(_: Parent, args: QueryGetSy const systemObject: SystemObject | null = await DBAPI.SystemObject.fetch(idSystemObject); const sourceObjects: RelatedObject[] = await getRelatedObjects(idSystemObject, RelatedObjectType.Source); const derivedObjects: RelatedObject[] = await getRelatedObjects(idSystemObject, RelatedObjectType.Derived); + const publishedState: string = await getPublishedState(idSystemObject); const identifiers = await getIngestIdentifiers(idSystemObject); @@ -45,6 +46,7 @@ export default async function getSystemObjectDetails(_: Parent, args: QueryGetSy retired: systemObject.Retired, objectType: oID.eObjectType, allowed: true, // TODO: True until Access control is implemented (Post MVP) + publishedState, thumbnail: null, unit, project, @@ -57,6 +59,22 @@ export default async function getSystemObjectDetails(_: Parent, args: QueryGetSy }; } +enum ePublishedState { + eNotPublished = 'Not Published', + eViewOnly = 'View Only', + eViewDownloadRestriction = 'View and Download with usage restrictions', + eViewDownloadCC0 = 'View and Download CC0' +} + +// TODO: define system object version cache +async function getPublishedState(idSystemObject: number): Promise { + const systemObjectVersions: DBAPI.SystemObjectVersion[] | null = await DBAPI.SystemObjectVersion.fetchFromSystemObject(idSystemObject); + + if (!systemObjectVersions || !systemObjectVersions?.length) return ePublishedState.eNotPublished; + + return ePublishedState.eViewOnly; +} + async function getRelatedObjects(idSystemObject: number, type: RelatedObjectType): Promise { let relatedSystemObjects: SystemObject[] | null = []; diff --git a/server/types/graphql.ts b/server/types/graphql.ts index 8dc6da4ac..70cb270ec 100644 --- a/server/types/graphql.ts +++ b/server/types/graphql.ts @@ -47,118 +47,147 @@ export type Query = { searchIngestionSubjects: SearchIngestionSubjectsResult; }; + export type QueryAreCameraSettingsUniformArgs = { input: AreCameraSettingsUniformInput; }; + export type QueryGetAccessPolicyArgs = { input: GetAccessPolicyInput; }; + export type QueryGetAssetArgs = { input: GetAssetInput; }; + export type QueryGetAssetVersionsDetailsArgs = { input: GetAssetVersionsDetailsInput; }; + export type QueryGetCaptureDataArgs = { input: GetCaptureDataInput; }; + export type QueryGetCaptureDataPhotoArgs = { input: GetCaptureDataPhotoInput; }; + export type QueryGetContentsForAssetVersionsArgs = { input: GetContentsForAssetVersionsInput; }; + export type QueryGetIngestionItemsForSubjectsArgs = { input: GetIngestionItemsForSubjectsInput; }; + export type QueryGetIngestionProjectsForSubjectsArgs = { input: GetIngestionProjectsForSubjectsInput; }; + export type QueryGetIntermediaryFileArgs = { input: GetIntermediaryFileInput; }; + export type QueryGetItemArgs = { input: GetItemInput; }; + export type QueryGetItemsForSubjectArgs = { input: GetItemsForSubjectInput; }; + export type QueryGetLicenseArgs = { input: GetLicenseInput; }; + export type QueryGetModelArgs = { input: GetModelInput; }; + export type QueryGetObjectChildrenArgs = { input: GetObjectChildrenInput; }; + export type QueryGetObjectsForItemArgs = { input: GetObjectsForItemInput; }; + export type QueryGetProjectArgs = { input: GetProjectInput; }; + export type QueryGetProjectDocumentationArgs = { input: GetProjectDocumentationInput; }; + export type QueryGetSceneArgs = { input: GetSceneInput; }; + export type QueryGetSourceObjectIdentiferArgs = { input: GetSourceObjectIdentiferInput; }; + export type QueryGetSubjectArgs = { input: GetSubjectInput; }; + export type QueryGetSubjectsForUnitArgs = { input: GetSubjectsForUnitInput; }; + export type QueryGetSystemObjectDetailsArgs = { input: GetSystemObjectDetailsInput; }; + export type QueryGetUnitArgs = { input: GetUnitInput; }; + export type QueryGetUserArgs = { input: GetUserInput; }; + export type QueryGetVocabularyArgs = { input: GetVocabularyInput; }; + export type QueryGetVocabularyEntriesArgs = { input: GetVocabularyEntriesInput; }; + export type QueryGetWorkflowArgs = { input: GetWorkflowInput; }; + export type QuerySearchIngestionSubjectsArgs = { input: SearchIngestionSubjectsInput; }; @@ -172,6 +201,7 @@ export type GetAccessPolicyResult = { AccessPolicy?: Maybe; }; + export type AccessAction = { __typename?: 'AccessAction'; idAccessAction: Scalars['Int']; @@ -220,6 +250,7 @@ export type AccessRole = { AccessAction?: Maybe>>; }; + export type Mutation = { __typename?: 'Mutation'; createCaptureData: CreateCaptureDataResult; @@ -238,58 +269,72 @@ export type Mutation = { uploadAsset: UploadAssetResult; }; + export type MutationCreateCaptureDataArgs = { input: CreateCaptureDataInput; }; + export type MutationCreateCaptureDataPhotoArgs = { input: CreateCaptureDataPhotoInput; }; + export type MutationCreateItemArgs = { input: CreateItemInput; }; + export type MutationCreateModelArgs = { input: CreateModelInput; }; + export type MutationCreateProjectArgs = { input: CreateProjectInput; }; + export type MutationCreateSceneArgs = { input: CreateSceneInput; }; + export type MutationCreateSubjectArgs = { input: CreateSubjectInput; }; + export type MutationCreateUnitArgs = { input: CreateUnitInput; }; + export type MutationCreateUserArgs = { input: CreateUserInput; }; + export type MutationCreateVocabularyArgs = { input: CreateVocabularyInput; }; + export type MutationCreateVocabularySetArgs = { input: CreateVocabularySetInput; }; + export type MutationDiscardUploadedAssetVersionsArgs = { input: DiscardUploadedAssetVersionsInput; }; + export type MutationIngestDataArgs = { input: IngestDataInput; }; + export type MutationUploadAssetArgs = { file: Scalars['Upload']; type: Scalars['Int']; @@ -1044,6 +1089,7 @@ export type GetSystemObjectDetailsResult = { retired: Scalars['Boolean']; objectType: Scalars['Int']; allowed: Scalars['Boolean']; + publishedState: Scalars['String']; thumbnail?: Maybe; identifiers: Array; objectAncestors: Array>; From b29957137dd6c360ef8b7bab93c811b51a674f3f Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Sat, 5 Dec 2020 00:22:08 +0530 Subject: [PATCH 116/239] connect repository browser with details page --- client/src/components/shared/NewTabLink.tsx | 8 ++++++-- .../components/RepositoryTreeView/TreeLabel.tsx | 17 ++++++++++++++--- .../components/RepositoryTreeView/index.tsx | 1 + 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/client/src/components/shared/NewTabLink.tsx b/client/src/components/shared/NewTabLink.tsx index ab674c8e6..666a5072b 100644 --- a/client/src/components/shared/NewTabLink.tsx +++ b/client/src/components/shared/NewTabLink.tsx @@ -21,15 +21,19 @@ interface NewTabLinkProps { function NewTabLink(props: NewTabLinkProps & LinkProps): React.ReactElement { const classes = useStyles(props); + const customProps = { + onClick: event => event.stopPropagation() + }; + if (props.children) { return ( - + {props.children} ); } - return ; + return ; } export default NewTabLink; \ No newline at end of file diff --git a/client/src/pages/Repository/components/RepositoryTreeView/TreeLabel.tsx b/client/src/pages/Repository/components/RepositoryTreeView/TreeLabel.tsx index a6f77f3de..91a3c63f5 100644 --- a/client/src/pages/Repository/components/RepositoryTreeView/TreeLabel.tsx +++ b/client/src/pages/Repository/components/RepositoryTreeView/TreeLabel.tsx @@ -10,10 +10,11 @@ import clsx from 'clsx'; import lodash from 'lodash'; import React, { useMemo } from 'react'; import { FaCheckCircle, FaRegCircle } from 'react-icons/fa'; -import { Progress } from '../../../../components'; +import { NewTabLink, Progress } from '../../../../components'; import { palette } from '../../../../theme'; -import { getTermForSystemObjectType } from '../../../../utils/repository'; +import { getDetailsUrlForObject, getTermForSystemObjectType } from '../../../../utils/repository'; import MetadataView, { TreeViewColumn } from './MetadataView'; +import { RiExternalLinkFill } from 'react-icons/ri'; const useStyles = makeStyles(({ breakpoints }) => ({ container: { @@ -30,6 +31,9 @@ const useStyles = makeStyles(({ breakpoints }) => ({ } }, labelText: { + display: 'flex', + alignItems: 'center', + justifyContent: 'space-between', width: '60%', fontSize: '0.8em', background: ({ color }: TreeLabelProps) => color, @@ -37,10 +41,14 @@ const useStyles = makeStyles(({ breakpoints }) => ({ [breakpoints.down('lg')]: { fontSize: '0.9em', } + }, + options: { + display: 'flex' } })); interface TreeLabelProps { + idSystemObject: number; label?: React.ReactNode; objectType: number; color: string; @@ -52,7 +60,7 @@ interface TreeLabelProps { } function TreeLabel(props: TreeLabelProps): React.ReactElement { - const { label, treeColumns, renderSelected = false, selected = false, onSelect, onUnSelect, objectType } = props; + const { idSystemObject, label, treeColumns, renderSelected = false, selected = false, onSelect, onUnSelect, objectType } = props; const classes = useStyles(props); const objectTitle = useMemo(() => `${getTermForSystemObjectType(objectType)} ${label}`, [objectType, label]); @@ -67,6 +75,9 @@ function TreeLabel(props: TreeLabelProps): React.ReactElement { )}
{label} + + +
diff --git a/client/src/pages/Repository/components/RepositoryTreeView/index.tsx b/client/src/pages/Repository/components/RepositoryTreeView/index.tsx index 051c97b75..7d8a5269e 100644 --- a/client/src/pages/Repository/components/RepositoryTreeView/index.tsx +++ b/client/src/pages/Repository/components/RepositoryTreeView/index.tsx @@ -123,6 +123,7 @@ function RepositoryTreeView(props: RepositoryTreeViewProps): React.ReactElement const label: React.ReactNode = ( Date: Sat, 5 Dec 2020 16:08:10 +0530 Subject: [PATCH 117/239] fix node version issue with docker --- docker/dev.Dockerfile | 2 +- docker/prod.Dockerfile | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docker/dev.Dockerfile b/docker/dev.Dockerfile index dfd8da6ed..3c5ade6ec 100644 --- a/docker/dev.Dockerfile +++ b/docker/dev.Dockerfile @@ -1,4 +1,4 @@ -FROM node:14-alpine AS base +FROM node:12.18.4-alpine AS base # Add a work directory WORKDIR /app # Copy package.json for caching diff --git a/docker/prod.Dockerfile b/docker/prod.Dockerfile index 294fe6e4a..058215fba 100644 --- a/docker/prod.Dockerfile +++ b/docker/prod.Dockerfile @@ -1,4 +1,4 @@ -FROM node:14-alpine AS base +FROM node:12.18.4-alpine AS base # Add a work directory WORKDIR /app # Copy package.json for caching @@ -19,7 +19,7 @@ RUN rm -rf client RUN yarn install --frozen-lockfile && yarn build # Client's production image -FROM node:14-alpine AS client +FROM node:12.18.4-alpine AS client # Add a work directory WORKDIR /app # Copy from client-builder @@ -32,7 +32,7 @@ RUN npm i -g serve CMD serve -s . -l 3000 # Server's production image -FROM node:14-alpine AS server +FROM node:12.18.4-alpine AS server # Add a work directory WORKDIR /app # Copy from server-builder From 5877f52f7c66e689b74b4cbd996c219fdd259e15 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Sat, 5 Dec 2020 17:00:33 +0530 Subject: [PATCH 118/239] added asset details field to getSystemObjectDetails --- client/src/types/graphql.tsx | 24 ++++++++++++++- .../systemobject/getSystemObjectDetails.ts | 8 +++++ server/graphql/schema.graphql | 10 +++++++ .../schema/systemobject/queries.graphql | 12 ++++++++ .../queries/getSystemObjectDetails.ts | 30 ++++++++++++++++++- server/types/graphql.ts | 11 +++++++ 6 files changed, 93 insertions(+), 2 deletions(-) diff --git a/client/src/types/graphql.tsx b/client/src/types/graphql.tsx index 2a33efc9b..ea75d8261 100644 --- a/client/src/types/graphql.tsx +++ b/client/src/types/graphql.tsx @@ -1087,6 +1087,16 @@ export type RepositoryPath = { objectType: Scalars['Int']; }; +export type AssetDetail = { + __typename?: 'AssetDetail'; + name: Scalars['String']; + path: Scalars['String']; + assetType: Scalars['Int']; + version: Scalars['Int']; + dateCreated: Scalars['DateTime']; + size: Scalars['Int']; +}; + export type GetSystemObjectDetailsResult = { __typename?: 'GetSystemObjectDetailsResult'; name: Scalars['String']; @@ -1095,6 +1105,7 @@ export type GetSystemObjectDetailsResult = { allowed: Scalars['Boolean']; publishedState: Scalars['String']; thumbnail?: Maybe; + assetDetails: Array; identifiers: Array; objectAncestors: Array>; sourceObjects: Array; @@ -2238,7 +2249,10 @@ export type GetSystemObjectDetailsQuery = ( { __typename?: 'GetSystemObjectDetailsResult' } & Pick & { - identifiers: Array<( + assetDetails: Array<( + { __typename?: 'AssetDetail' } + & Pick + )>, identifiers: Array<( { __typename?: 'IngestIdentifier' } & Pick )>, unit?: Maybe<( @@ -3682,6 +3696,14 @@ export const GetSystemObjectDetailsDocument = gql` allowed publishedState thumbnail + assetDetails { + name + path + assetType + version + dateCreated + size + } identifiers { identifier identifierType diff --git a/server/graphql/api/queries/systemobject/getSystemObjectDetails.ts b/server/graphql/api/queries/systemobject/getSystemObjectDetails.ts index 42c2ff5bf..0d9ce62cf 100644 --- a/server/graphql/api/queries/systemobject/getSystemObjectDetails.ts +++ b/server/graphql/api/queries/systemobject/getSystemObjectDetails.ts @@ -9,6 +9,14 @@ const getSystemObjectDetails = gql` allowed publishedState thumbnail + assetDetails { + name + path + assetType + version + dateCreated + size + } identifiers { identifier identifierType diff --git a/server/graphql/schema.graphql b/server/graphql/schema.graphql index 2cb1225f8..a5ee0298b 100644 --- a/server/graphql/schema.graphql +++ b/server/graphql/schema.graphql @@ -796,6 +796,15 @@ type RepositoryPath { objectType: Int! } +type AssetDetail { + name: String! + path: String! + assetType: Int! + version: Int! + dateCreated: DateTime! + size: Int! +} + type GetSystemObjectDetailsResult { name: String! retired: Boolean! @@ -803,6 +812,7 @@ type GetSystemObjectDetailsResult { allowed: Boolean! publishedState: String! thumbnail: String + assetDetails: [AssetDetail!]! identifiers: [IngestIdentifier!]! objectAncestors: [[RepositoryPath!]!]! sourceObjects: [RelatedObject!]! diff --git a/server/graphql/schema/systemobject/queries.graphql b/server/graphql/schema/systemobject/queries.graphql index eb6af3708..6dc61c360 100644 --- a/server/graphql/schema/systemobject/queries.graphql +++ b/server/graphql/schema/systemobject/queries.graphql @@ -1,3 +1,5 @@ +scalar DateTime + type Query { getSystemObjectDetails(input: GetSystemObjectDetailsInput!): GetSystemObjectDetailsResult! getSourceObjectIdentifer(input: GetSourceObjectIdentiferInput!): GetSourceObjectIdentiferResult! @@ -13,6 +15,15 @@ type RepositoryPath { objectType: Int! } +type AssetDetail { + name: String! + path: String! + assetType: Int! + version: Int! + dateCreated: DateTime! + size: Int! +} + type GetSystemObjectDetailsResult { name: String! retired: Boolean! @@ -20,6 +31,7 @@ type GetSystemObjectDetailsResult { allowed: Boolean! publishedState: String! thumbnail: String + assetDetails: [AssetDetail!]! identifiers: [IngestIdentifier!]! objectAncestors: [[RepositoryPath!]!]! sourceObjects: [RelatedObject!]! diff --git a/server/graphql/schema/systemobject/resolvers/queries/getSystemObjectDetails.ts b/server/graphql/schema/systemobject/resolvers/queries/getSystemObjectDetails.ts index e1dd53df1..b3a481a79 100644 --- a/server/graphql/schema/systemobject/resolvers/queries/getSystemObjectDetails.ts +++ b/server/graphql/schema/systemobject/resolvers/queries/getSystemObjectDetails.ts @@ -2,6 +2,7 @@ import * as CACHE from '../../../../../cache'; import * as DBAPI from '../../../../../db'; import { eObjectGraphMode, eSystemObjectType } from '../../../../../db'; import { + AssetDetail, GetSystemObjectDetailsResult, IngestIdentifier, QueryGetSystemObjectDetailsArgs, @@ -24,7 +25,7 @@ export default async function getSystemObjectDetails(_: Parent, args: QueryGetSy const sourceObjects: RelatedObject[] = await getRelatedObjects(idSystemObject, RelatedObjectType.Source); const derivedObjects: RelatedObject[] = await getRelatedObjects(idSystemObject, RelatedObjectType.Derived); const publishedState: string = await getPublishedState(idSystemObject); - + const assetDetails: AssetDetail[] = await getAssetDetails(idSystemObject); const identifiers = await getIngestIdentifiers(idSystemObject); if (!oID) { @@ -47,6 +48,7 @@ export default async function getSystemObjectDetails(_: Parent, args: QueryGetSy objectType: oID.eObjectType, allowed: true, // TODO: True until Access control is implemented (Post MVP) publishedState, + assetDetails, thumbnail: null, unit, project, @@ -59,6 +61,32 @@ export default async function getSystemObjectDetails(_: Parent, args: QueryGetSy }; } +async function getAssetDetails(idSystemObject: number): Promise { + const assetDetails: AssetDetail[] = []; + const assets: DBAPI.Asset[] | null = await DBAPI.Asset.fetchFromSystemObject(idSystemObject); + if (assets) { + for (const asset of assets) { + const assetVersions: DBAPI.AssetVersion[] | null = await DBAPI.AssetVersion.fetchFromAsset(asset.idAsset); + if (assetVersions) { + for (const assetVersion of assetVersions) { + const assetDetail: AssetDetail = { + name: assetVersion.FileName, + path: asset.FilePath, + assetType: asset.idVAssetType, + version: assetVersion.Version, + dateCreated: assetVersion.DateCreated, + size: assetVersion.StorageSize + }; + + assetDetails.push(assetDetail); + } + } + } + } + + return assetDetails; +} + enum ePublishedState { eNotPublished = 'Not Published', eViewOnly = 'View Only', diff --git a/server/types/graphql.ts b/server/types/graphql.ts index 70cb270ec..85310a6f9 100644 --- a/server/types/graphql.ts +++ b/server/types/graphql.ts @@ -1083,6 +1083,16 @@ export type RepositoryPath = { objectType: Scalars['Int']; }; +export type AssetDetail = { + __typename?: 'AssetDetail'; + name: Scalars['String']; + path: Scalars['String']; + assetType: Scalars['Int']; + version: Scalars['Int']; + dateCreated: Scalars['DateTime']; + size: Scalars['Int']; +}; + export type GetSystemObjectDetailsResult = { __typename?: 'GetSystemObjectDetailsResult'; name: Scalars['String']; @@ -1091,6 +1101,7 @@ export type GetSystemObjectDetailsResult = { allowed: Scalars['Boolean']; publishedState: Scalars['String']; thumbnail?: Maybe; + assetDetails: Array; identifiers: Array; objectAncestors: Array>; sourceObjects: Array; From 2056a42749b666b30df8327ce8155ab162b785d0 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Mon, 7 Dec 2020 18:02:05 +0530 Subject: [PATCH 119/239] Added details tab view related objects --- .../Ingestion/components/Metadata/Model/RelatedObjectsList.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/pages/Ingestion/components/Metadata/Model/RelatedObjectsList.tsx b/client/src/pages/Ingestion/components/Metadata/Model/RelatedObjectsList.tsx index c2437cdb7..8a0b5b434 100644 --- a/client/src/pages/Ingestion/components/Metadata/Model/RelatedObjectsList.tsx +++ b/client/src/pages/Ingestion/components/Metadata/Model/RelatedObjectsList.tsx @@ -17,7 +17,7 @@ import { sharedButtonProps, sharedLabelProps } from '../../../../../utils/shared const useStyles = makeStyles(({ palette }) => ({ container: { display: 'flex', - width: '52vw', + width: (viewMode: boolean) => viewMode ? undefined : '52vw', flexDirection: 'column', borderRadius: 5, padding: 10, From 2392c0540f1b18adccdb82a1910b0f21f11d4cc2 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Mon, 7 Dec 2020 18:02:35 +0530 Subject: [PATCH 120/239] Added table view for assets --- .../DetailsView/AssetDetailsTable.tsx | 90 +++++++++++++++ .../components/DetailsView/DetailsTab.tsx | 109 ++++++++++++++++++ 2 files changed, 199 insertions(+) create mode 100644 client/src/pages/Repository/components/DetailsView/AssetDetailsTable.tsx create mode 100644 client/src/pages/Repository/components/DetailsView/DetailsTab.tsx diff --git a/client/src/pages/Repository/components/DetailsView/AssetDetailsTable.tsx b/client/src/pages/Repository/components/DetailsView/AssetDetailsTable.tsx new file mode 100644 index 000000000..71b73043f --- /dev/null +++ b/client/src/pages/Repository/components/DetailsView/AssetDetailsTable.tsx @@ -0,0 +1,90 @@ +/** + * AssetTable + * + * This component renders asset table tab for the DetailsTab component. + */ +import { Box, Typography } from '@material-ui/core'; +import { makeStyles } from '@material-ui/core/styles'; +import React from 'react'; +import { StateAssetDetail, useVocabularyStore } from '../../../../store'; +import { eVocabularySetID } from '../../../../types/server'; +import { formatBytes } from '../../../../utils/upload'; + +const useStyles = makeStyles(({ palette }) => ({ + table: { + width: '100%', + background: palette.secondary.light, + padding: 5, + borderRadius: 5 + }, + header: { + fontSize: '0.9em', + color: palette.primary.dark + }, + value: { + fontSize: '0.8em', + color: palette.primary.dark + }, +})); + +interface AssetDetailsTableProps { + assetDetails: StateAssetDetail[]; +} + +function AssetDetailsTable(props: AssetDetailsTableProps): React.ReactElement { + const classes = useStyles(); + const { assetDetails } = props; + const getVocabularyTerm = useVocabularyStore(state => state.getVocabularyTerm); + + const headers: string[] = [ + 'Name', + 'Path', + 'Asset Type', + 'Version', + 'Date Created', + 'Size', + ]; + + return ( +
+ + {headers.map((header, index: number) => ( + + ))} + + {assetDetails.map((assetDetail: StateAssetDetail, index: number) => ( + + + + + + + + + ))} + +
+ {header} +
+ {assetDetail.name} + + {assetDetail.path} + + {getVocabularyTerm(eVocabularySetID.eAssetAssetType, assetDetail.assetType)} + + {assetDetail.version} + + {assetDetail.dateCreated} + + {formatBytes(assetDetail.size)} +
+ {!assetDetails.length && ( + + No assets found + + )} +
+ ); +} + +export default AssetDetailsTable; \ No newline at end of file diff --git a/client/src/pages/Repository/components/DetailsView/DetailsTab.tsx b/client/src/pages/Repository/components/DetailsView/DetailsTab.tsx new file mode 100644 index 000000000..b17a470ba --- /dev/null +++ b/client/src/pages/Repository/components/DetailsView/DetailsTab.tsx @@ -0,0 +1,109 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/** + * DetailsTab + * + * This component renders details tab for the DetailsView component. + */ +import { Box, Tab, TabProps, Tabs } from '@material-ui/core'; +import { fade, makeStyles, withStyles } from '@material-ui/core/styles'; +import React, { useState } from 'react'; +import { StateAssetDetail, StateRelatedObject } from '../../../../store'; +import { RelatedObjectType } from '../../../../types/graphql'; +import RelatedObjectsList from '../../../Ingestion/components/Metadata/Model/RelatedObjectsList'; +import AssetDetailsTable from './AssetDetailsTable'; + +const useStyles = makeStyles(({ palette }) => ({ + tab: { + backgroundColor: fade(palette.primary.main, 0.25) + }, + tabpanel: { + backgroundColor: fade(palette.primary.main, 0.25) + } +})); + +type DetailsTabParams = { + disabled: boolean; + assetDetails: StateAssetDetail[]; + sourceObjects: StateRelatedObject[]; + derivedObjects: StateRelatedObject[]; + onAddSourceObject: () => void; + onAddDerivedObject: () => void; +}; + +function DetailsTab(props: DetailsTabParams): React.ReactElement { + const { disabled, assetDetails, sourceObjects, derivedObjects, onAddSourceObject, onAddDerivedObject } = props; + const [tab, setTab] = useState(0); + const classes = useStyles(); + const handleTabChange = (_, nextTab: number) => { + setTab(nextTab); + }; + + return ( + + + + + + + + + + + Details + + + + + + + ); +} + +function TabPanel(props: any): React.ReactElement { + const { children, value, index, ...rest } = props; + const classes = useStyles(); + + return ( + + ); +} + +const StyledTab = withStyles(({ palette }) => ({ + root: { + color: palette.background.paper, + '&:focus': { + opacity: 1 + }, + }, +}))((props: TabProps) => ); + +export default DetailsTab; \ No newline at end of file From d7fb75d4bb294fc1adecc118dc011753eb8d7ab0 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Mon, 7 Dec 2020 18:04:14 +0530 Subject: [PATCH 121/239] Updated metadata store types and methods --- .../components/DetailsView/index.tsx | 32 +++++++------------ client/src/store/metadata/metadata.types.ts | 4 ++- client/src/store/vocabulary.ts | 9 ++++-- 3 files changed, 20 insertions(+), 25 deletions(-) diff --git a/client/src/pages/Repository/components/DetailsView/index.tsx b/client/src/pages/Repository/components/DetailsView/index.tsx index 4ff04d7e2..0bba7b3c4 100644 --- a/client/src/pages/Repository/components/DetailsView/index.tsx +++ b/client/src/pages/Repository/components/DetailsView/index.tsx @@ -9,15 +9,14 @@ import React, { useState } from 'react'; import { useParams } from 'react-router'; import IdentifierList from '../../../../components/shared/IdentifierList'; import { parseIdentifiersToState, useVocabularyStore } from '../../../../store'; -import { RelatedObjectType } from '../../../../types/graphql'; import { eVocabularySetID } from '../../../../types/server'; import ObjectSelectModal from '../../../Ingestion/components/Metadata/Model/ObjectSelectModal'; -import RelatedObjectsList from '../../../Ingestion/components/Metadata/Model/RelatedObjectsList'; import { useObjectDetails } from '../../hooks/useDetailsView'; import DetailsHeader from './DetailsHeader'; +import DetailsTab from './DetailsTab'; import DetailsThumbnail from './DetailsThumbnail'; -import ObjectNotFoundView from './ObjectNotFoundView'; import ObjectDetails from './ObjectDetails'; +import ObjectNotFoundView from './ObjectNotFoundView'; const useStyles = makeStyles(({ palette, breakpoints }) => ({ container: { @@ -54,7 +53,7 @@ function DetailsView(): React.ReactElement { return ; } - const { name, objectType, identifiers, retired, allowed, publishedState, thumbnail, unit, project, subject, item, objectAncestors, sourceObjects, derivedObjects } = data.getSystemObjectDetails; + const { name, objectType, identifiers, retired, allowed, assetDetails, publishedState, thumbnail, unit, project, subject, item, objectAncestors, sourceObjects, derivedObjects } = data.getSystemObjectDetails; const disabled: boolean = !allowed; @@ -119,23 +118,14 @@ function DetailsView(): React.ReactElement {
- - - - - + diff --git a/client/src/store/metadata/metadata.types.ts b/client/src/store/metadata/metadata.types.ts index d3ee23407..bca7c877f 100644 --- a/client/src/store/metadata/metadata.types.ts +++ b/client/src/store/metadata/metadata.types.ts @@ -3,7 +3,7 @@ * * Type definitions for the metadata store. */ -import { RelatedObject, ReferenceModel, ReferenceModelAction } from '../../types/graphql'; +import { RelatedObject, ReferenceModel, ReferenceModelAction, AssetDetail } from '../../types/graphql'; import { IngestionFile } from '../upload'; export type StateMetadata = { @@ -134,6 +134,8 @@ export type StateRelatedObject = RelatedObject; export type StateReferenceModel = ReferenceModel; +export type StateAssetDetail = AssetDetail; + export type ValidateFields = PhotogrammetryFields | ModelFields | SceneFields | OtherFields; export { ReferenceModelAction }; diff --git a/client/src/store/vocabulary.ts b/client/src/store/vocabulary.ts index 4fa9a3974..12f83b980 100644 --- a/client/src/store/vocabulary.ts +++ b/client/src/store/vocabulary.ts @@ -25,7 +25,7 @@ type VocabularyStore = { updateVocabularyEntries: () => Promise; getEntries: (eVocabularySetID: eVocabularySetID) => VocabularyOption[]; getInitialEntry: (eVocabularySetID: eVocabularySetID) => number | null; - getVocabularyTerm: (eVocabularySetID: eVocabularySetID) => string | null; + getVocabularyTerm: (eVocabularySetID: eVocabularySetID, idVocabulary: number) => string | null; getAssetType: (idVocabulary: number) => AssetType; }; @@ -93,12 +93,15 @@ export const useVocabularyStore = create((set: SetState { + getVocabularyTerm: (eVocabularySetID: eVocabularySetID, idVocabulary: number): string | null => { const { vocabularies } = get(); const vocabularyEntry = vocabularies.get(eVocabularySetID); if (vocabularyEntry && vocabularyEntry.length) { - return vocabularyEntry[0].Term; + for (let i = 0; i < vocabularyEntry.length; i++) { + const vocabulary = vocabularyEntry[i]; + if (vocabulary.idVocabulary === idVocabulary) return vocabulary.Term; + } } return null; From 45989b93d190c0a431edec1847beb0c8008d356a Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Tue, 8 Dec 2020 15:26:37 +0530 Subject: [PATCH 122/239] added getAssetDetailsForSystemObject and getVersionsForSystemObject queries --- client/src/types/graphql.tsx | 196 ++++++++++++++++-- server/graphql/api/index.ts | 32 ++- .../getAssetDetailsForSystemObject.ts | 19 ++ .../systemobject/getSystemObjectDetails.ts | 8 - .../getVersionsForSystemObject.ts | 18 ++ server/graphql/schema.graphql | 47 ++++- .../schema/systemobject/queries.graphql | 47 ++++- .../schema/systemobject/resolvers/index.ts | 6 +- .../queries/getAssetDetailsForSystemObject.ts | 39 ++++ .../queries/getSystemObjectDetails.ts | 29 --- .../queries/getVersionsForSystemObject.ts | 19 ++ server/types/graphql.ts | 62 +++++- 12 files changed, 428 insertions(+), 94 deletions(-) create mode 100644 server/graphql/api/queries/systemobject/getAssetDetailsForSystemObject.ts create mode 100644 server/graphql/api/queries/systemobject/getVersionsForSystemObject.ts create mode 100644 server/graphql/schema/systemobject/resolvers/queries/getAssetDetailsForSystemObject.ts create mode 100644 server/graphql/schema/systemobject/resolvers/queries/getVersionsForSystemObject.ts diff --git a/client/src/types/graphql.tsx b/client/src/types/graphql.tsx index ea75d8261..54e32b83a 100644 --- a/client/src/types/graphql.tsx +++ b/client/src/types/graphql.tsx @@ -21,6 +21,7 @@ export type Query = { areCameraSettingsUniform: AreCameraSettingsUniformResult; getAccessPolicy: GetAccessPolicyResult; getAsset: GetAssetResult; + getAssetDetailsForSystemObject: GetAssetDetailsForSystemObjectResult; getAssetVersionsDetails: GetAssetVersionsDetailsResult; getCaptureData: GetCaptureDataResult; getCaptureDataPhoto: GetCaptureDataPhotoResult; @@ -45,6 +46,7 @@ export type Query = { getUnit: GetUnitResult; getUploadedAssetVersion: GetUploadedAssetVersionResult; getUser: GetUserResult; + getVersionsForSystemObject: GetVersionsForSystemObjectResult; getVocabulary: GetVocabularyResult; getVocabularyEntries: GetVocabularyEntriesResult; getWorkflow: GetWorkflowResult; @@ -67,6 +69,11 @@ export type QueryGetAssetArgs = { }; +export type QueryGetAssetDetailsForSystemObjectArgs = { + input: GetAssetDetailsForSystemObjectInput; +}; + + export type QueryGetAssetVersionsDetailsArgs = { input: GetAssetVersionsDetailsInput; }; @@ -177,6 +184,11 @@ export type QueryGetUserArgs = { }; +export type QueryGetVersionsForSystemObjectArgs = { + input: GetVersionsForSystemObjectInput; +}; + + export type QueryGetVocabularyArgs = { input: GetVocabularyInput; }; @@ -1087,16 +1099,6 @@ export type RepositoryPath = { objectType: Scalars['Int']; }; -export type AssetDetail = { - __typename?: 'AssetDetail'; - name: Scalars['String']; - path: Scalars['String']; - assetType: Scalars['Int']; - version: Scalars['Int']; - dateCreated: Scalars['DateTime']; - size: Scalars['Int']; -}; - export type GetSystemObjectDetailsResult = { __typename?: 'GetSystemObjectDetailsResult'; name: Scalars['String']; @@ -1105,7 +1107,6 @@ export type GetSystemObjectDetailsResult = { allowed: Scalars['Boolean']; publishedState: Scalars['String']; thumbnail?: Maybe; - assetDetails: Array; identifiers: Array; objectAncestors: Array>; sourceObjects: Array; @@ -1131,6 +1132,45 @@ export type GetSourceObjectIdentiferResult = { sourceObjectIdentifiers: Array; }; +export type AssetDetail = { + __typename?: 'AssetDetail'; + idSystemObject: Scalars['Int']; + name: Scalars['String']; + path: Scalars['String']; + assetType: Scalars['Int']; + version: Scalars['Int']; + dateCreated: Scalars['DateTime']; + size: Scalars['Int']; +}; + +export type GetAssetDetailsForSystemObjectInput = { + idSystemObject: Scalars['Int']; +}; + +export type GetAssetDetailsForSystemObjectResult = { + __typename?: 'GetAssetDetailsForSystemObjectResult'; + assetDetails: Array; +}; + +export type DetailVersion = { + __typename?: 'DetailVersion'; + idSystemObject: Scalars['Int']; + version: Scalars['Int']; + name: Scalars['String']; + creator: Scalars['String']; + dateCreated: Scalars['DateTime']; + size: Scalars['Int']; +}; + +export type GetVersionsForSystemObjectInput = { + idSystemObject: Scalars['Int']; +}; + +export type GetVersionsForSystemObjectResult = { + __typename?: 'GetVersionsForSystemObjectResult'; + versions: Array; +}; + export type SystemObject = { __typename?: 'SystemObject'; idSystemObject: Scalars['Int']; @@ -2217,6 +2257,26 @@ export type GetSceneQuery = ( } ); +export type GetAssetDetailsForSystemObjectQueryVariables = Exact<{ + input: GetAssetDetailsForSystemObjectInput; +}>; + + +export type GetAssetDetailsForSystemObjectQuery = ( + { __typename?: 'Query' } + & { + getAssetDetailsForSystemObject: ( + { __typename?: 'GetAssetDetailsForSystemObjectResult' } + & { + assetDetails: Array<( + { __typename?: 'AssetDetail' } + & Pick + )> + } + ) + } +); + export type GetSourceObjectIdentiferQueryVariables = Exact<{ input: GetSourceObjectIdentiferInput; }>; @@ -2249,10 +2309,7 @@ export type GetSystemObjectDetailsQuery = ( { __typename?: 'GetSystemObjectDetailsResult' } & Pick & { - assetDetails: Array<( - { __typename?: 'AssetDetail' } - & Pick - )>, identifiers: Array<( + identifiers: Array<( { __typename?: 'IngestIdentifier' } & Pick )>, unit?: Maybe<( @@ -2282,6 +2339,26 @@ export type GetSystemObjectDetailsQuery = ( } ); +export type GetVersionsForSystemObjectQueryVariables = Exact<{ + input: GetVersionsForSystemObjectInput; +}>; + + +export type GetVersionsForSystemObjectQuery = ( + { __typename?: 'Query' } + & { + getVersionsForSystemObject: ( + { __typename?: 'GetVersionsForSystemObjectResult' } + & { + versions: Array<( + { __typename?: 'DetailVersion' } + & Pick + )> + } + ) + } +); + export type GetIngestionItemsForSubjectsQueryVariables = Exact<{ input: GetIngestionItemsForSubjectsInput; }>; @@ -3651,6 +3728,47 @@ export function useGetSceneLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions; export type GetSceneLazyQueryHookResult = ReturnType; export type GetSceneQueryResult = Apollo.QueryResult; +export const GetAssetDetailsForSystemObjectDocument = gql` + query getAssetDetailsForSystemObject($input: GetAssetDetailsForSystemObjectInput!) { + getAssetDetailsForSystemObject(input: $input) { + assetDetails { + idSystemObject + name + path + assetType + version + dateCreated + size + } + } +} + `; + +/** + * __useGetAssetDetailsForSystemObjectQuery__ + * + * To run a query within a React component, call `useGetAssetDetailsForSystemObjectQuery` and pass it any options that fit your needs. + * When your component renders, `useGetAssetDetailsForSystemObjectQuery` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. + * + * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useGetAssetDetailsForSystemObjectQuery({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ +export function useGetAssetDetailsForSystemObjectQuery(baseOptions?: Apollo.QueryHookOptions) { + return Apollo.useQuery(GetAssetDetailsForSystemObjectDocument, baseOptions); +} +export function useGetAssetDetailsForSystemObjectLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { + return Apollo.useLazyQuery(GetAssetDetailsForSystemObjectDocument, baseOptions); +} +export type GetAssetDetailsForSystemObjectQueryHookResult = ReturnType; +export type GetAssetDetailsForSystemObjectLazyQueryHookResult = ReturnType; +export type GetAssetDetailsForSystemObjectQueryResult = Apollo.QueryResult; export const GetSourceObjectIdentiferDocument = gql` query getSourceObjectIdentifer($input: GetSourceObjectIdentiferInput!) { getSourceObjectIdentifer(input: $input) { @@ -3696,14 +3814,6 @@ export const GetSystemObjectDetailsDocument = gql` allowed publishedState thumbnail - assetDetails { - name - path - assetType - version - dateCreated - size - } identifiers { identifier identifierType @@ -3774,6 +3884,46 @@ export function useGetSystemObjectDetailsLazyQuery(baseOptions?: Apollo.LazyQuer export type GetSystemObjectDetailsQueryHookResult = ReturnType; export type GetSystemObjectDetailsLazyQueryHookResult = ReturnType; export type GetSystemObjectDetailsQueryResult = Apollo.QueryResult; +export const GetVersionsForSystemObjectDocument = gql` + query getVersionsForSystemObject($input: GetVersionsForSystemObjectInput!) { + getVersionsForSystemObject(input: $input) { + versions { + idSystemObject + version + name + creator + dateCreated + size + } + } +} + `; + +/** + * __useGetVersionsForSystemObjectQuery__ + * + * To run a query within a React component, call `useGetVersionsForSystemObjectQuery` and pass it any options that fit your needs. + * When your component renders, `useGetVersionsForSystemObjectQuery` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. + * + * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useGetVersionsForSystemObjectQuery({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ +export function useGetVersionsForSystemObjectQuery(baseOptions?: Apollo.QueryHookOptions) { + return Apollo.useQuery(GetVersionsForSystemObjectDocument, baseOptions); +} +export function useGetVersionsForSystemObjectLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { + return Apollo.useLazyQuery(GetVersionsForSystemObjectDocument, baseOptions); +} +export type GetVersionsForSystemObjectQueryHookResult = ReturnType; +export type GetVersionsForSystemObjectLazyQueryHookResult = ReturnType; +export type GetVersionsForSystemObjectQueryResult = Apollo.QueryResult; export const GetIngestionItemsForSubjectsDocument = gql` query getIngestionItemsForSubjects($input: GetIngestionItemsForSubjectsInput!) { getIngestionItemsForSubjects(input: $input) { diff --git a/server/graphql/api/index.ts b/server/graphql/api/index.ts index 93107f188..e47707033 100644 --- a/server/graphql/api/index.ts +++ b/server/graphql/api/index.ts @@ -94,7 +94,11 @@ import { GetSourceObjectIdentiferInput, GetSourceObjectIdentiferResult, GetSystemObjectDetailsInput, - GetSystemObjectDetailsResult + GetSystemObjectDetailsResult, + GetAssetDetailsForSystemObjectInput, + GetAssetDetailsForSystemObjectResult, + GetVersionsForSystemObjectInput, + GetVersionsForSystemObjectResult, } from '../../types/graphql'; // Queries @@ -129,6 +133,8 @@ import getIntermediaryFile from './queries/scene/getIntermediaryFile'; import getObjectChildren from './queries/repository/getObjectChildren'; import getSourceObjectIdentifer from './queries/systemobject/getSourceObjectIdentifer'; import getSystemObjectDetails from './queries/systemobject/getSystemObjectDetails'; +import getAssetDetailsForSystemObject from './queries/systemobject/getAssetDetailsForSystemObject'; +import getVersionsForSystemObject from './queries/systemobject/getVersionsForSystemObject'; // Mutations import createUser from './mutations/user/createUser'; @@ -193,7 +199,9 @@ const allQueries = { discardUploadedAssetVersions, getObjectChildren, getSourceObjectIdentifer, - getSystemObjectDetails + getSystemObjectDetails, + getAssetDetailsForSystemObject, + getVersionsForSystemObject }; type GraphQLRequest = { @@ -544,6 +552,26 @@ class GraphQLApi { }); } + async getAssetDetailsForSystemObject(input: GetAssetDetailsForSystemObjectInput, context?: Context): Promise { + const operationName = 'getAssetDetailsForSystemObject'; + const variables = { input }; + return this.graphqlRequest({ + operationName, + variables, + context + }); + } + + async getVersionsForSystemObject(input: GetVersionsForSystemObjectInput, context?: Context): Promise { + const operationName = 'getVersionsForSystemObject'; + const variables = { input }; + return this.graphqlRequest({ + operationName, + variables, + context + }); + } + async createUnit(input: CreateUnitInput, context?: Context): Promise { const operationName = 'createUnit'; const variables = { input }; diff --git a/server/graphql/api/queries/systemobject/getAssetDetailsForSystemObject.ts b/server/graphql/api/queries/systemobject/getAssetDetailsForSystemObject.ts new file mode 100644 index 000000000..c1e7021ba --- /dev/null +++ b/server/graphql/api/queries/systemobject/getAssetDetailsForSystemObject.ts @@ -0,0 +1,19 @@ +import { gql } from 'apollo-server-express'; + +const getAssetDetailsForSystemObject = gql` + query getAssetDetailsForSystemObject($input: GetAssetDetailsForSystemObjectInput!) { + getAssetDetailsForSystemObject(input: $input) { + assetDetails { + idSystemObject + name + path + assetType + version + dateCreated + size + } + } + } +`; + +export default getAssetDetailsForSystemObject; diff --git a/server/graphql/api/queries/systemobject/getSystemObjectDetails.ts b/server/graphql/api/queries/systemobject/getSystemObjectDetails.ts index 0d9ce62cf..42c2ff5bf 100644 --- a/server/graphql/api/queries/systemobject/getSystemObjectDetails.ts +++ b/server/graphql/api/queries/systemobject/getSystemObjectDetails.ts @@ -9,14 +9,6 @@ const getSystemObjectDetails = gql` allowed publishedState thumbnail - assetDetails { - name - path - assetType - version - dateCreated - size - } identifiers { identifier identifierType diff --git a/server/graphql/api/queries/systemobject/getVersionsForSystemObject.ts b/server/graphql/api/queries/systemobject/getVersionsForSystemObject.ts new file mode 100644 index 000000000..be2ecbfa6 --- /dev/null +++ b/server/graphql/api/queries/systemobject/getVersionsForSystemObject.ts @@ -0,0 +1,18 @@ +import { gql } from 'apollo-server-express'; + +const getVersionsForSystemObject = gql` + query getVersionsForSystemObject($input: GetVersionsForSystemObjectInput!) { + getVersionsForSystemObject(input: $input) { + versions { + idSystemObject + version + name + creator + dateCreated + size + } + } + } +`; + +export default getVersionsForSystemObject; diff --git a/server/graphql/schema.graphql b/server/graphql/schema.graphql index a5ee0298b..decc5afd7 100644 --- a/server/graphql/schema.graphql +++ b/server/graphql/schema.graphql @@ -2,6 +2,7 @@ type Query { areCameraSettingsUniform(input: AreCameraSettingsUniformInput!): AreCameraSettingsUniformResult! getAccessPolicy(input: GetAccessPolicyInput!): GetAccessPolicyResult! getAsset(input: GetAssetInput!): GetAssetResult! + getAssetDetailsForSystemObject(input: GetAssetDetailsForSystemObjectInput!): GetAssetDetailsForSystemObjectResult! getAssetVersionsDetails(input: GetAssetVersionsDetailsInput!): GetAssetVersionsDetailsResult! getCaptureData(input: GetCaptureDataInput!): GetCaptureDataResult! getCaptureDataPhoto(input: GetCaptureDataPhotoInput!): GetCaptureDataPhotoResult! @@ -26,6 +27,7 @@ type Query { getUnit(input: GetUnitInput!): GetUnitResult! getUploadedAssetVersion: GetUploadedAssetVersionResult! getUser(input: GetUserInput!): GetUserResult! + getVersionsForSystemObject(input: GetVersionsForSystemObjectInput!): GetVersionsForSystemObjectResult! getVocabulary(input: GetVocabularyInput!): GetVocabularyResult! getVocabularyEntries(input: GetVocabularyEntriesInput!): GetVocabularyEntriesResult! getWorkflow(input: GetWorkflowInput!): GetWorkflowResult! @@ -796,15 +798,6 @@ type RepositoryPath { objectType: Int! } -type AssetDetail { - name: String! - path: String! - assetType: Int! - version: Int! - dateCreated: DateTime! - size: Int! -} - type GetSystemObjectDetailsResult { name: String! retired: Boolean! @@ -812,7 +805,6 @@ type GetSystemObjectDetailsResult { allowed: Boolean! publishedState: String! thumbnail: String - assetDetails: [AssetDetail!]! identifiers: [IngestIdentifier!]! objectAncestors: [[RepositoryPath!]!]! sourceObjects: [RelatedObject!]! @@ -836,6 +828,41 @@ type GetSourceObjectIdentiferResult { sourceObjectIdentifiers: [SourceObjectIdentifier!]! } +type AssetDetail { + idSystemObject: Int! + name: String! + path: String! + assetType: Int! + version: Int! + dateCreated: DateTime! + size: Int! +} + +input GetAssetDetailsForSystemObjectInput { + idSystemObject: Int! +} + +type GetAssetDetailsForSystemObjectResult { + assetDetails: [AssetDetail!]! +} + +type DetailVersion { + idSystemObject: Int! + version: Int! + name: String! + creator: String! + dateCreated: DateTime! + size: Int! +} + +input GetVersionsForSystemObjectInput { + idSystemObject: Int! +} + +type GetVersionsForSystemObjectResult { + versions: [DetailVersion!]! +} + type SystemObject { idSystemObject: Int! Retired: Boolean! diff --git a/server/graphql/schema/systemobject/queries.graphql b/server/graphql/schema/systemobject/queries.graphql index 6dc61c360..6a957bacf 100644 --- a/server/graphql/schema/systemobject/queries.graphql +++ b/server/graphql/schema/systemobject/queries.graphql @@ -3,6 +3,8 @@ scalar DateTime type Query { getSystemObjectDetails(input: GetSystemObjectDetailsInput!): GetSystemObjectDetailsResult! getSourceObjectIdentifer(input: GetSourceObjectIdentiferInput!): GetSourceObjectIdentiferResult! + getAssetDetailsForSystemObject(input: GetAssetDetailsForSystemObjectInput!): GetAssetDetailsForSystemObjectResult! + getVersionsForSystemObject(input: GetVersionsForSystemObjectInput!): GetVersionsForSystemObjectResult! } input GetSystemObjectDetailsInput { @@ -15,15 +17,6 @@ type RepositoryPath { objectType: Int! } -type AssetDetail { - name: String! - path: String! - assetType: Int! - version: Int! - dateCreated: DateTime! - size: Int! -} - type GetSystemObjectDetailsResult { name: String! retired: Boolean! @@ -31,7 +24,6 @@ type GetSystemObjectDetailsResult { allowed: Boolean! publishedState: String! thumbnail: String - assetDetails: [AssetDetail!]! identifiers: [IngestIdentifier!]! objectAncestors: [[RepositoryPath!]!]! sourceObjects: [RelatedObject!]! @@ -54,3 +46,38 @@ type SourceObjectIdentifier { type GetSourceObjectIdentiferResult { sourceObjectIdentifiers: [SourceObjectIdentifier!]! } + +type AssetDetail { + idSystemObject: Int! + name: String! + path: String! + assetType: Int! + version: Int! + dateCreated: DateTime! + size: Int! +} + +input GetAssetDetailsForSystemObjectInput { + idSystemObject: Int! +} + +type GetAssetDetailsForSystemObjectResult { + assetDetails: [AssetDetail!]! +} + +type DetailVersion { + idSystemObject: Int! + version: Int! + name: String! + creator: String! + dateCreated: DateTime! + size: Int! +} + +input GetVersionsForSystemObjectInput { + idSystemObject: Int! +} + +type GetVersionsForSystemObjectResult { + versions: [DetailVersion!]! +} diff --git a/server/graphql/schema/systemobject/resolvers/index.ts b/server/graphql/schema/systemobject/resolvers/index.ts index be31a4b0d..f9dfe7c12 100644 --- a/server/graphql/schema/systemobject/resolvers/index.ts +++ b/server/graphql/schema/systemobject/resolvers/index.ts @@ -4,11 +4,15 @@ import Identifier from './types/Identifier'; import Metadata from './types/Metadata'; import getSystemObjectDetails from './queries/getSystemObjectDetails'; import getSourceObjectIdentifer from './queries/getSourceObjectIdentifer'; +import getAssetDetailsForSystemObject from './queries/getAssetDetailsForSystemObject'; +import getVersionsForSystemObject from './queries/getVersionsForSystemObject'; const resolvers = { Query: { getSystemObjectDetails, - getSourceObjectIdentifer + getSourceObjectIdentifer, + getAssetDetailsForSystemObject, + getVersionsForSystemObject }, SystemObject, SystemObjectVersion, diff --git a/server/graphql/schema/systemobject/resolvers/queries/getAssetDetailsForSystemObject.ts b/server/graphql/schema/systemobject/resolvers/queries/getAssetDetailsForSystemObject.ts new file mode 100644 index 000000000..021bddfe5 --- /dev/null +++ b/server/graphql/schema/systemobject/resolvers/queries/getAssetDetailsForSystemObject.ts @@ -0,0 +1,39 @@ +import * as DBAPI from '../../../../../db'; +import { AssetDetail, GetAssetDetailsForSystemObjectResult, QueryGetAssetDetailsForSystemObjectArgs } from '../../../../../types/graphql'; +import { Parent } from '../../../../../types/resolvers'; + +export default async function getAssetDetailsForSystemObject(_: Parent, args: QueryGetAssetDetailsForSystemObjectArgs): Promise { + const { input } = args; + const { idSystemObject } = input; + + const assetDetails: AssetDetail[] = await getAssetDetails(idSystemObject); + + return { assetDetails }; +} + +async function getAssetDetails(idSystemObject: number): Promise { + const assetDetails: AssetDetail[] = []; + const assets: DBAPI.Asset[] | null = await DBAPI.Asset.fetchFromSystemObject(idSystemObject); + if (assets) { + for (const asset of assets) { + const assetVersions: DBAPI.AssetVersion[] | null = await DBAPI.AssetVersion.fetchFromAsset(asset.idAsset); + if (assetVersions) { + for (const assetVersion of assetVersions) { + const assetDetail: AssetDetail = { + idSystemObject: asset.idSystemObject || 0, + name: assetVersion.FileName, + path: asset.FilePath, + assetType: asset.idVAssetType, + version: assetVersion.Version, + dateCreated: assetVersion.DateCreated, + size: assetVersion.StorageSize + }; + + assetDetails.push(assetDetail); + } + } + } + } + + return assetDetails; +} \ No newline at end of file diff --git a/server/graphql/schema/systemobject/resolvers/queries/getSystemObjectDetails.ts b/server/graphql/schema/systemobject/resolvers/queries/getSystemObjectDetails.ts index b3a481a79..262d2ccc5 100644 --- a/server/graphql/schema/systemobject/resolvers/queries/getSystemObjectDetails.ts +++ b/server/graphql/schema/systemobject/resolvers/queries/getSystemObjectDetails.ts @@ -2,7 +2,6 @@ import * as CACHE from '../../../../../cache'; import * as DBAPI from '../../../../../db'; import { eObjectGraphMode, eSystemObjectType } from '../../../../../db'; import { - AssetDetail, GetSystemObjectDetailsResult, IngestIdentifier, QueryGetSystemObjectDetailsArgs, @@ -25,7 +24,6 @@ export default async function getSystemObjectDetails(_: Parent, args: QueryGetSy const sourceObjects: RelatedObject[] = await getRelatedObjects(idSystemObject, RelatedObjectType.Source); const derivedObjects: RelatedObject[] = await getRelatedObjects(idSystemObject, RelatedObjectType.Derived); const publishedState: string = await getPublishedState(idSystemObject); - const assetDetails: AssetDetail[] = await getAssetDetails(idSystemObject); const identifiers = await getIngestIdentifiers(idSystemObject); if (!oID) { @@ -48,7 +46,6 @@ export default async function getSystemObjectDetails(_: Parent, args: QueryGetSy objectType: oID.eObjectType, allowed: true, // TODO: True until Access control is implemented (Post MVP) publishedState, - assetDetails, thumbnail: null, unit, project, @@ -61,32 +58,6 @@ export default async function getSystemObjectDetails(_: Parent, args: QueryGetSy }; } -async function getAssetDetails(idSystemObject: number): Promise { - const assetDetails: AssetDetail[] = []; - const assets: DBAPI.Asset[] | null = await DBAPI.Asset.fetchFromSystemObject(idSystemObject); - if (assets) { - for (const asset of assets) { - const assetVersions: DBAPI.AssetVersion[] | null = await DBAPI.AssetVersion.fetchFromAsset(asset.idAsset); - if (assetVersions) { - for (const assetVersion of assetVersions) { - const assetDetail: AssetDetail = { - name: assetVersion.FileName, - path: asset.FilePath, - assetType: asset.idVAssetType, - version: assetVersion.Version, - dateCreated: assetVersion.DateCreated, - size: assetVersion.StorageSize - }; - - assetDetails.push(assetDetail); - } - } - } - } - - return assetDetails; -} - enum ePublishedState { eNotPublished = 'Not Published', eViewOnly = 'View Only', diff --git a/server/graphql/schema/systemobject/resolvers/queries/getVersionsForSystemObject.ts b/server/graphql/schema/systemobject/resolvers/queries/getVersionsForSystemObject.ts new file mode 100644 index 000000000..016b72d4e --- /dev/null +++ b/server/graphql/schema/systemobject/resolvers/queries/getVersionsForSystemObject.ts @@ -0,0 +1,19 @@ +import { DetailVersion, GetVersionsForSystemObjectResult, QueryGetVersionsForSystemObjectArgs } from '../../../../../types/graphql'; +import { Parent } from '../../../../../types/resolvers'; + +export default async function getVersionsForSystemObject(_: Parent, args: QueryGetVersionsForSystemObjectArgs): Promise { + const { input } = args; + const { idSystemObject } = input; + + const versions: DetailVersion[] = await getAssetVersions(idSystemObject); + + return { versions }; +} + +async function getAssetVersions(idSystemObject: number): Promise { + const versions: DetailVersion[] = []; + // TODO: KARAN: compute DetailVersion + console.log(idSystemObject); + + return versions; +} \ No newline at end of file diff --git a/server/types/graphql.ts b/server/types/graphql.ts index 85310a6f9..791ede436 100644 --- a/server/types/graphql.ts +++ b/server/types/graphql.ts @@ -17,6 +17,7 @@ export type Query = { areCameraSettingsUniform: AreCameraSettingsUniformResult; getAccessPolicy: GetAccessPolicyResult; getAsset: GetAssetResult; + getAssetDetailsForSystemObject: GetAssetDetailsForSystemObjectResult; getAssetVersionsDetails: GetAssetVersionsDetailsResult; getCaptureData: GetCaptureDataResult; getCaptureDataPhoto: GetCaptureDataPhotoResult; @@ -41,6 +42,7 @@ export type Query = { getUnit: GetUnitResult; getUploadedAssetVersion: GetUploadedAssetVersionResult; getUser: GetUserResult; + getVersionsForSystemObject: GetVersionsForSystemObjectResult; getVocabulary: GetVocabularyResult; getVocabularyEntries: GetVocabularyEntriesResult; getWorkflow: GetWorkflowResult; @@ -63,6 +65,11 @@ export type QueryGetAssetArgs = { }; +export type QueryGetAssetDetailsForSystemObjectArgs = { + input: GetAssetDetailsForSystemObjectInput; +}; + + export type QueryGetAssetVersionsDetailsArgs = { input: GetAssetVersionsDetailsInput; }; @@ -173,6 +180,11 @@ export type QueryGetUserArgs = { }; +export type QueryGetVersionsForSystemObjectArgs = { + input: GetVersionsForSystemObjectInput; +}; + + export type QueryGetVocabularyArgs = { input: GetVocabularyInput; }; @@ -1083,16 +1095,6 @@ export type RepositoryPath = { objectType: Scalars['Int']; }; -export type AssetDetail = { - __typename?: 'AssetDetail'; - name: Scalars['String']; - path: Scalars['String']; - assetType: Scalars['Int']; - version: Scalars['Int']; - dateCreated: Scalars['DateTime']; - size: Scalars['Int']; -}; - export type GetSystemObjectDetailsResult = { __typename?: 'GetSystemObjectDetailsResult'; name: Scalars['String']; @@ -1101,7 +1103,6 @@ export type GetSystemObjectDetailsResult = { allowed: Scalars['Boolean']; publishedState: Scalars['String']; thumbnail?: Maybe; - assetDetails: Array; identifiers: Array; objectAncestors: Array>; sourceObjects: Array; @@ -1127,6 +1128,45 @@ export type GetSourceObjectIdentiferResult = { sourceObjectIdentifiers: Array; }; +export type AssetDetail = { + __typename?: 'AssetDetail'; + idSystemObject: Scalars['Int']; + name: Scalars['String']; + path: Scalars['String']; + assetType: Scalars['Int']; + version: Scalars['Int']; + dateCreated: Scalars['DateTime']; + size: Scalars['Int']; +}; + +export type GetAssetDetailsForSystemObjectInput = { + idSystemObject: Scalars['Int']; +}; + +export type GetAssetDetailsForSystemObjectResult = { + __typename?: 'GetAssetDetailsForSystemObjectResult'; + assetDetails: Array; +}; + +export type DetailVersion = { + __typename?: 'DetailVersion'; + idSystemObject: Scalars['Int']; + version: Scalars['Int']; + name: Scalars['String']; + creator: Scalars['String']; + dateCreated: Scalars['DateTime']; + size: Scalars['Int']; +}; + +export type GetVersionsForSystemObjectInput = { + idSystemObject: Scalars['Int']; +}; + +export type GetVersionsForSystemObjectResult = { + __typename?: 'GetVersionsForSystemObjectResult'; + versions: Array; +}; + export type SystemObject = { __typename?: 'SystemObject'; idSystemObject: Scalars['Int']; From 11dd740a073523bf0e5e7873588b7e0e123bb887 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Tue, 8 Dec 2020 15:29:08 +0530 Subject: [PATCH 123/239] remove unused components --- .../DetailsView/AssetDetailsTable.tsx | 90 --------------- .../components/DetailsView/DetailsTab.tsx | 109 ------------------ 2 files changed, 199 deletions(-) delete mode 100644 client/src/pages/Repository/components/DetailsView/AssetDetailsTable.tsx delete mode 100644 client/src/pages/Repository/components/DetailsView/DetailsTab.tsx diff --git a/client/src/pages/Repository/components/DetailsView/AssetDetailsTable.tsx b/client/src/pages/Repository/components/DetailsView/AssetDetailsTable.tsx deleted file mode 100644 index 71b73043f..000000000 --- a/client/src/pages/Repository/components/DetailsView/AssetDetailsTable.tsx +++ /dev/null @@ -1,90 +0,0 @@ -/** - * AssetTable - * - * This component renders asset table tab for the DetailsTab component. - */ -import { Box, Typography } from '@material-ui/core'; -import { makeStyles } from '@material-ui/core/styles'; -import React from 'react'; -import { StateAssetDetail, useVocabularyStore } from '../../../../store'; -import { eVocabularySetID } from '../../../../types/server'; -import { formatBytes } from '../../../../utils/upload'; - -const useStyles = makeStyles(({ palette }) => ({ - table: { - width: '100%', - background: palette.secondary.light, - padding: 5, - borderRadius: 5 - }, - header: { - fontSize: '0.9em', - color: palette.primary.dark - }, - value: { - fontSize: '0.8em', - color: palette.primary.dark - }, -})); - -interface AssetDetailsTableProps { - assetDetails: StateAssetDetail[]; -} - -function AssetDetailsTable(props: AssetDetailsTableProps): React.ReactElement { - const classes = useStyles(); - const { assetDetails } = props; - const getVocabularyTerm = useVocabularyStore(state => state.getVocabularyTerm); - - const headers: string[] = [ - 'Name', - 'Path', - 'Asset Type', - 'Version', - 'Date Created', - 'Size', - ]; - - return ( - - - {headers.map((header, index: number) => ( - - ))} - - {assetDetails.map((assetDetail: StateAssetDetail, index: number) => ( - - - - - - - - - ))} - -
- {header} -
- {assetDetail.name} - - {assetDetail.path} - - {getVocabularyTerm(eVocabularySetID.eAssetAssetType, assetDetail.assetType)} - - {assetDetail.version} - - {assetDetail.dateCreated} - - {formatBytes(assetDetail.size)} -
- {!assetDetails.length && ( - - No assets found - - )} -
- ); -} - -export default AssetDetailsTable; \ No newline at end of file diff --git a/client/src/pages/Repository/components/DetailsView/DetailsTab.tsx b/client/src/pages/Repository/components/DetailsView/DetailsTab.tsx deleted file mode 100644 index b17a470ba..000000000 --- a/client/src/pages/Repository/components/DetailsView/DetailsTab.tsx +++ /dev/null @@ -1,109 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -/** - * DetailsTab - * - * This component renders details tab for the DetailsView component. - */ -import { Box, Tab, TabProps, Tabs } from '@material-ui/core'; -import { fade, makeStyles, withStyles } from '@material-ui/core/styles'; -import React, { useState } from 'react'; -import { StateAssetDetail, StateRelatedObject } from '../../../../store'; -import { RelatedObjectType } from '../../../../types/graphql'; -import RelatedObjectsList from '../../../Ingestion/components/Metadata/Model/RelatedObjectsList'; -import AssetDetailsTable from './AssetDetailsTable'; - -const useStyles = makeStyles(({ palette }) => ({ - tab: { - backgroundColor: fade(palette.primary.main, 0.25) - }, - tabpanel: { - backgroundColor: fade(palette.primary.main, 0.25) - } -})); - -type DetailsTabParams = { - disabled: boolean; - assetDetails: StateAssetDetail[]; - sourceObjects: StateRelatedObject[]; - derivedObjects: StateRelatedObject[]; - onAddSourceObject: () => void; - onAddDerivedObject: () => void; -}; - -function DetailsTab(props: DetailsTabParams): React.ReactElement { - const { disabled, assetDetails, sourceObjects, derivedObjects, onAddSourceObject, onAddDerivedObject } = props; - const [tab, setTab] = useState(0); - const classes = useStyles(); - const handleTabChange = (_, nextTab: number) => { - setTab(nextTab); - }; - - return ( - - - - - - - - - - - Details - - - - - - - ); -} - -function TabPanel(props: any): React.ReactElement { - const { children, value, index, ...rest } = props; - const classes = useStyles(); - - return ( - - ); -} - -const StyledTab = withStyles(({ palette }) => ({ - root: { - color: palette.background.paper, - '&:focus': { - opacity: 1 - }, - }, -}))((props: TabProps) => ); - -export default DetailsTab; \ No newline at end of file From 29d200233521cec725ea5bde57af03c47eb32154 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Tue, 8 Dec 2020 15:32:32 +0530 Subject: [PATCH 124/239] updated asset details component --- .../DetailsTab/AssetDetailsTable.tsx | 125 ++++++++++++++++++ .../components/DetailsView/index.tsx | 8 +- 2 files changed, 130 insertions(+), 3 deletions(-) create mode 100644 client/src/pages/Repository/components/DetailsView/DetailsTab/AssetDetailsTable.tsx diff --git a/client/src/pages/Repository/components/DetailsView/DetailsTab/AssetDetailsTable.tsx b/client/src/pages/Repository/components/DetailsView/DetailsTab/AssetDetailsTable.tsx new file mode 100644 index 000000000..4b03a1aaa --- /dev/null +++ b/client/src/pages/Repository/components/DetailsView/DetailsTab/AssetDetailsTable.tsx @@ -0,0 +1,125 @@ +/** + * AssetDetailsTable + * + * This component renders asset details table tab for the DetailsTab component. + */ +import { Box, Typography } from '@material-ui/core'; +import { makeStyles } from '@material-ui/core/styles'; +import clsx from 'clsx'; +import React from 'react'; +import { NewTabLink, Progress } from '../../../../../components'; +import { StateAssetDetail, useVocabularyStore } from '../../../../../store'; +import { eVocabularySetID } from '../../../../../types/server'; +import { getDetailsUrlForObject } from '../../../../../utils/repository'; +import { formatBytes } from '../../../../../utils/upload'; +import { useObjectAssets } from '../../../hooks/useDetailsView'; + +export const useStyles = makeStyles(({ palette }) => ({ + container: { + width: '100%', + background: palette.secondary.light, + padding: 5, + borderRadius: 5 + }, + header: { + fontSize: '0.9em', + color: palette.primary.dark + }, + value: { + fontSize: '0.8em', + color: palette.primary.dark + }, + empty: { + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + flex: 1, + background: palette.secondary.light, + padding: 40, + borderRadius: 5 + }, + link: { + textDecoration: 'underline' + } +})); + +interface AssetDetailsTableProps { + idSystemObject: number; +} + +function AssetDetailsTable(props: AssetDetailsTableProps): React.ReactElement { + const classes = useStyles(); + const { idSystemObject } = props; + const { data, loading } = useObjectAssets(idSystemObject); + const getVocabularyTerm = useVocabularyStore(state => state.getVocabularyTerm); + + const headers: string[] = [ + 'Name', + 'Path', + 'Asset Type', + 'Version', + 'Date Created', + 'Size', + ]; + + if (!data || loading) { + return ; + } + + const { assetDetails } = data.getAssetDetailsForSystemObject; + + return ( + + + {headers.map((header, index: number) => ( + + ))} + + {assetDetails.map((assetDetail: StateAssetDetail, index: number) => ( + + + + + + + + + ))} + +
+ {header} +
+ + {assetDetail.name} + + + {assetDetail.path} + + {getVocabularyTerm(eVocabularySetID.eAssetAssetType, assetDetail.assetType)} + + {assetDetail.version} + + {assetDetail.dateCreated} + + {formatBytes(assetDetail.size)} +
+ {!assetDetails.length && ( + + No assets found + + )} +
+ ); +} + +export function EmptyTable(): React.ReactElement { + const classes = useStyles(); + + return ( + + + + ); +} + +export default AssetDetailsTable; \ No newline at end of file diff --git a/client/src/pages/Repository/components/DetailsView/index.tsx b/client/src/pages/Repository/components/DetailsView/index.tsx index 0bba7b3c4..437e41e84 100644 --- a/client/src/pages/Repository/components/DetailsView/index.tsx +++ b/client/src/pages/Repository/components/DetailsView/index.tsx @@ -45,7 +45,8 @@ function DetailsView(): React.ReactElement { const params = useParams(); const [modalOpen, setModalOpen] = useState(false); - const { data, loading } = useObjectDetails(Number.parseInt(params.idSystemObject, 10)); + const idSystemObject: number = Number.parseInt(params.idSystemObject, 10); + const { data, loading } = useObjectDetails(idSystemObject); const getEntries = useVocabularyStore(state => state.getEntries); @@ -53,7 +54,7 @@ function DetailsView(): React.ReactElement { return ; } - const { name, objectType, identifiers, retired, allowed, assetDetails, publishedState, thumbnail, unit, project, subject, item, objectAncestors, sourceObjects, derivedObjects } = data.getSystemObjectDetails; + const { name, objectType, identifiers, retired, allowed, publishedState, thumbnail, unit, project, subject, item, objectAncestors, sourceObjects, derivedObjects } = data.getSystemObjectDetails; const disabled: boolean = !allowed; @@ -120,7 +121,8 @@ function DetailsView(): React.ReactElement { Date: Tue, 8 Dec 2020 15:33:18 +0530 Subject: [PATCH 125/239] updated store types and query hooks --- .../pages/Repository/hooks/useDetailsView.ts | 25 +++++++++++++++++-- client/src/store/metadata/metadata.types.ts | 4 ++- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/client/src/pages/Repository/hooks/useDetailsView.ts b/client/src/pages/Repository/hooks/useDetailsView.ts index 5c9fb2126..df0695fe1 100644 --- a/client/src/pages/Repository/hooks/useDetailsView.ts +++ b/client/src/pages/Repository/hooks/useDetailsView.ts @@ -1,5 +1,5 @@ import { useQuery } from '@apollo/client'; -import { GetSystemObjectDetailsDocument, GetSystemObjectDetailsQueryResult } from '../../../types/graphql'; +import { GetAssetDetailsForSystemObjectDocument, GetAssetDetailsForSystemObjectQueryResult, GetSystemObjectDetailsDocument, GetSystemObjectDetailsQueryResult, GetVersionsForSystemObjectDocument, GetVersionsForSystemObjectQueryResult } from '../../../types/graphql'; export function useObjectDetails(idSystemObject: number): GetSystemObjectDetailsQueryResult { return useQuery(GetSystemObjectDetailsDocument, { @@ -9,4 +9,25 @@ export function useObjectDetails(idSystemObject: number): GetSystemObjectDetails } } }); -} \ No newline at end of file +} + +export function useObjectAssets(idSystemObject: number): GetAssetDetailsForSystemObjectQueryResult { + return useQuery(GetAssetDetailsForSystemObjectDocument, { + variables: { + input: { + idSystemObject + } + } + }); +} + +export function useObjectVersions(idSystemObject: number): GetVersionsForSystemObjectQueryResult { + return useQuery(GetVersionsForSystemObjectDocument, { + variables: { + input: { + idSystemObject + } + } + }); +} + diff --git a/client/src/store/metadata/metadata.types.ts b/client/src/store/metadata/metadata.types.ts index bca7c877f..e9c1780e0 100644 --- a/client/src/store/metadata/metadata.types.ts +++ b/client/src/store/metadata/metadata.types.ts @@ -3,7 +3,7 @@ * * Type definitions for the metadata store. */ -import { RelatedObject, ReferenceModel, ReferenceModelAction, AssetDetail } from '../../types/graphql'; +import { AssetDetail, DetailVersion, ReferenceModel, ReferenceModelAction, RelatedObject } from '../../types/graphql'; import { IngestionFile } from '../upload'; export type StateMetadata = { @@ -136,6 +136,8 @@ export type StateReferenceModel = ReferenceModel; export type StateAssetDetail = AssetDetail; +export type StateDetailVersion = DetailVersion; + export type ValidateFields = PhotogrammetryFields | ModelFields | SceneFields | OtherFields; export { ReferenceModelAction }; From bd9b236faba20336e4f3a2d73be40dbe926c3208 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Tue, 8 Dec 2020 15:33:57 +0530 Subject: [PATCH 126/239] added asset versions table --- .../DetailsTab/AssetVersionsTable.tsx | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 client/src/pages/Repository/components/DetailsView/DetailsTab/AssetVersionsTable.tsx diff --git a/client/src/pages/Repository/components/DetailsView/DetailsTab/AssetVersionsTable.tsx b/client/src/pages/Repository/components/DetailsView/DetailsTab/AssetVersionsTable.tsx new file mode 100644 index 000000000..a8fdcc4ee --- /dev/null +++ b/client/src/pages/Repository/components/DetailsView/DetailsTab/AssetVersionsTable.tsx @@ -0,0 +1,83 @@ +/** + * AssetVersionsTable + * + * This component renders asset version table tab for the DetailsTab component. + */ +import { Box, Typography } from '@material-ui/core'; +import clsx from 'clsx'; +import React from 'react'; +import { NewTabLink } from '../../../../../components'; +import { StateDetailVersion } from '../../../../../store'; +import { getDetailsUrlForObject } from '../../../../../utils/repository'; +import { formatBytes } from '../../../../../utils/upload'; +import { useObjectVersions } from '../../../hooks/useDetailsView'; +import { EmptyTable, useStyles } from './AssetDetailsTable'; + + +interface AssetVersionsTableProps { + idSystemObject: number; +} + +function AssetVersionsTable(props: AssetVersionsTableProps): React.ReactElement { + const classes = useStyles(); + const { idSystemObject } = props; + const { data, loading } = useObjectVersions(idSystemObject); + + const headers: string[] = [ + 'Version', + 'Name', + 'Creator', + 'Date Created', + 'Size', + ]; + + if (!data || loading) { + return ; + } + + const { versions } = data.getVersionsForSystemObject; + + return ( + + + {headers.map((header, index: number) => ( + + ))} + + {versions.map((version: StateDetailVersion, index: number) => ( + + + + + + + + ))} + +
+ {header} +
+ + {version.version} + + + + {version.name} + + + {version.creator} + + {version.dateCreated} + + {formatBytes(version.size)} +
+ {!versions.length && ( + + No versions found + + )} +
+ ); +} + +export default AssetVersionsTable; \ No newline at end of file From 994853eb047d6e30856299724047a370ef606ab1 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Tue, 8 Dec 2020 15:34:37 +0530 Subject: [PATCH 127/239] Organise components for different detail for different objects --- .../DetailsView/DetailsTab/index.tsx | 285 ++++++++++++++++++ 1 file changed, 285 insertions(+) create mode 100644 client/src/pages/Repository/components/DetailsView/DetailsTab/index.tsx diff --git a/client/src/pages/Repository/components/DetailsView/DetailsTab/index.tsx b/client/src/pages/Repository/components/DetailsView/DetailsTab/index.tsx new file mode 100644 index 000000000..ca3cd9e99 --- /dev/null +++ b/client/src/pages/Repository/components/DetailsView/DetailsTab/index.tsx @@ -0,0 +1,285 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/** + * DetailsTab + * + * This component renders details tab for the DetailsView component. + */ +import { Box, Tab, TabProps, Tabs } from '@material-ui/core'; +import { fade, makeStyles, withStyles } from '@material-ui/core/styles'; +import React, { useState } from 'react'; +import { StateRelatedObject } from '../../../../../store'; +import { RelatedObjectType } from '../../../../../types/graphql'; +import { eSystemObjectType } from '../../../../../types/server'; +import RelatedObjectsList from '../../../../Ingestion/components/Metadata/Model/RelatedObjectsList'; +import AssetDetailsTable from './AssetDetailsTable'; +import AssetVersionsTable from './AssetVersionsTable'; + +const useStyles = makeStyles(({ palette }) => ({ + tab: { + backgroundColor: fade(palette.primary.main, 0.25) + }, + tabpanel: { + backgroundColor: fade(palette.primary.main, 0.25) + } +})); + +type DetailsTabParams = { + disabled: boolean; + idSystemObject: number; + objectType: eSystemObjectType; + sourceObjects: StateRelatedObject[]; + derivedObjects: StateRelatedObject[]; + onAddSourceObject: () => void; + onAddDerivedObject: () => void; +}; + +function DetailsTab(props: DetailsTabParams): React.ReactElement { + const { disabled, idSystemObject, objectType, sourceObjects, derivedObjects, onAddSourceObject, onAddDerivedObject } = props; + const [tab, setTab] = useState(0); + const classes = useStyles(); + const handleTabChange = (_, nextTab: number) => { + setTab(nextTab); + }; + + let tabs: string[] = []; + + let tabPanels: React.ReactNode = null; + + const RelatedTab = (index: number) => ( + + + + + ); + + switch (objectType) { + case eSystemObjectType.eUnit: + tabs = ['Details', 'Related']; + tabPanels = ( + + + Unit Details + + {RelatedTab(1)} + + ); + break; + case eSystemObjectType.eProject: + tabs = ['Details', 'Related']; + tabPanels = ( + + + Project Details + + {RelatedTab(1)} + + ); + break; + case eSystemObjectType.eSubject: + tabs = ['Assets', 'Details', 'Related']; + tabPanels = ( + + + + + + Subject Details + + {RelatedTab(2)} + + ); + break; + case eSystemObjectType.eItem: + tabs = ['Assets', 'Details', 'Related']; + tabPanels = ( + + + + + + Item Details + + {RelatedTab(2)} + + ); + break; + case eSystemObjectType.eCaptureData: + tabs = ['Assets', 'Details', 'Related']; + tabPanels = ( + + + + + + CaptureData Details + + {RelatedTab(2)} + + ); + break; + case eSystemObjectType.eModel: + tabs = ['Assets', 'Details', 'Related']; + tabPanels = ( + + + + + + Model Details + + {RelatedTab(2)} + + ); + break; + case eSystemObjectType.eScene: + tabs = ['Assets', 'Details', 'Related']; + tabPanels = ( + + + + + + Scene Details + + {RelatedTab(2)} + + ); + break; + case eSystemObjectType.eIntermediaryFile: + tabs = ['Assets', 'Details', 'Related']; + tabPanels = ( + + + + + + IntermediaryFile Details + + {RelatedTab(2)} + + ); + break; + case eSystemObjectType.eProjectDocumentation: + tabs = ['Assets', 'Details', 'Related']; + tabPanels = ( + + + + + + ProjectDocumentation Details + + {RelatedTab(2)} + + ); + break; + case eSystemObjectType.eAsset: + tabs = ['Versions', 'Details', 'Related']; + tabPanels = ( + + + + + + Asset Details + + {RelatedTab(2)} + + ); + break; + case eSystemObjectType.eAssetVersion: + tabs = ['Details', 'Related']; + tabPanels = ( + + + AssetVersion Details + + {RelatedTab(1)} + + ); + break; + case eSystemObjectType.eActor: + tabs = ['Details', 'Related']; + tabPanels = ( + + + Actor Details + + {RelatedTab(1)} + + ); + break; + case eSystemObjectType.eStakeholder: + tabs = ['Details', 'Related']; + tabPanels = ( + + + Stakeholder Details + + {RelatedTab(1)} + + ); + break; + default: + tabs = ['Unknown']; + break; + } + + return ( + + + {tabs.map((tab: string, index: number) => )} + + {tabPanels} + + ); +} + +function TabPanel(props: any): React.ReactElement { + const { children, value, index, ...rest } = props; + const classes = useStyles(); + + return ( + + ); +} + +const StyledTab = withStyles(({ palette }) => ({ + root: { + color: palette.background.paper, + '&:focus': { + opacity: 1 + }, + }, +}))((props: TabProps) => ); + +export default DetailsTab; \ No newline at end of file From 36903a6b44838a28c8d674f772ae12562f3deda8 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Thu, 10 Dec 2020 13:21:32 +0530 Subject: [PATCH 128/239] added getDetailsTabDataForObject query and resolver --- client/src/types/graphql.tsx | 285 ++++++++++++++++++ server/graphql/api/index.ts | 16 +- .../getDetailsTabDataForObject.ts | 143 +++++++++ server/graphql/schema.graphql | 22 ++ .../schema/systemobject/queries.graphql | 22 ++ .../queries/getDetailsTabDataForObject.ts | 74 +++++ .../getIngestionProjectsForSubjects.ts | 1 - server/types/graphql.ts | 28 ++ 8 files changed, 589 insertions(+), 2 deletions(-) create mode 100644 server/graphql/api/queries/systemobject/getDetailsTabDataForObject.ts create mode 100644 server/graphql/schema/systemobject/resolvers/queries/getDetailsTabDataForObject.ts diff --git a/client/src/types/graphql.tsx b/client/src/types/graphql.tsx index 54e32b83a..80dbcbd84 100644 --- a/client/src/types/graphql.tsx +++ b/client/src/types/graphql.tsx @@ -27,6 +27,7 @@ export type Query = { getCaptureDataPhoto: GetCaptureDataPhotoResult; getContentsForAssetVersions: GetContentsForAssetVersionsResult; getCurrentUser: GetCurrentUserResult; + getDetailsTabDataForObject: GetDetailsTabDataForObjectResult; getIngestionItemsForSubjects: GetIngestionItemsForSubjectsResult; getIngestionProjectsForSubjects: GetIngestionProjectsForSubjectsResult; getIntermediaryFile: GetIntermediaryFileResult; @@ -94,6 +95,11 @@ export type QueryGetContentsForAssetVersionsArgs = { }; +export type QueryGetDetailsTabDataForObjectArgs = { + input: GetDetailsTabDataForObjectInput; +}; + + export type QueryGetIngestionItemsForSubjectsArgs = { input: GetIngestionItemsForSubjectsInput; }; @@ -1088,6 +1094,28 @@ export type IntermediaryFile = { SystemObject?: Maybe; }; +export type GetDetailsTabDataForObjectInput = { + idSystemObject: Scalars['Int']; + objectType: Scalars['Int']; +}; + +export type GetDetailsTabDataForObjectResult = { + __typename?: 'GetDetailsTabDataForObjectResult'; + Unit?: Maybe; + Project?: Maybe; + Subject?: Maybe; + Item?: Maybe; + CaptureData?: Maybe; + Model?: Maybe; + Scene?: Maybe; + IntermediaryFile?: Maybe; + ProjectDocumentation?: Maybe; + Asset?: Maybe; + AssetVersion?: Maybe; + Actor?: Maybe; + Stakeholder?: Maybe; +}; + export type GetSystemObjectDetailsInput = { idSystemObject: Scalars['Int']; }; @@ -2277,6 +2305,98 @@ export type GetAssetDetailsForSystemObjectQuery = ( } ); +export type GetDetailsTabDataForObjectQueryVariables = Exact<{ + input: GetDetailsTabDataForObjectInput; +}>; + + +export type GetDetailsTabDataForObjectQuery = ( + { __typename?: 'Query' } + & { + getDetailsTabDataForObject: ( + { __typename?: 'GetDetailsTabDataForObjectResult' } + & { + Unit?: Maybe<( + { __typename?: 'Unit' } + & Pick + )>, Project?: Maybe<( + { __typename?: 'Project' } + & Pick + )>, Subject?: Maybe<( + { __typename?: 'Subject' } + & Pick + & { + GeoLocation?: Maybe<( + { __typename?: 'GeoLocation' } + & Pick + )> + } + )>, Item?: Maybe<( + { __typename?: 'Item' } + & Pick + & { + GeoLocation?: Maybe<( + { __typename?: 'GeoLocation' } + & Pick + )> + } + )>, CaptureData?: Maybe<( + { __typename?: 'IngestPhotogrammetry' } + & Pick + & { + folders: Array<( + { __typename?: 'IngestFolder' } + & Pick + )> + } + )>, Model?: Maybe<( + { __typename?: 'IngestModel' } + & Pick + & { + uvMaps: Array<( + { __typename?: 'IngestUVMap' } + & Pick + )> + } + )>, Scene?: Maybe<( + { __typename?: 'Scene' } + & Pick + )>, IntermediaryFile?: Maybe<( + { __typename?: 'IntermediaryFile' } + & Pick + )>, ProjectDocumentation?: Maybe<( + { __typename?: 'ProjectDocumentation' } + & Pick + )>, Asset?: Maybe<( + { __typename?: 'Asset' } + & Pick + & { + VAssetType?: Maybe<( + { __typename?: 'Vocabulary' } + & Pick + )> + } + )>, AssetVersion?: Maybe<( + { __typename?: 'AssetVersion' } + & Pick + & { + User?: Maybe<( + { __typename?: 'User' } + & Pick + )> + } + )>, Actor?: Maybe<( + { __typename?: 'Actor' } + & Pick + )>, Stakeholder?: Maybe<( + { __typename?: 'Stakeholder' } + & Pick + )> + } + ) + } +); + export type GetSourceObjectIdentiferQueryVariables = Exact<{ input: GetSourceObjectIdentiferInput; }>; @@ -3769,6 +3889,171 @@ export function useGetAssetDetailsForSystemObjectLazyQuery(baseOptions?: Apollo. export type GetAssetDetailsForSystemObjectQueryHookResult = ReturnType; export type GetAssetDetailsForSystemObjectLazyQueryHookResult = ReturnType; export type GetAssetDetailsForSystemObjectQueryResult = Apollo.QueryResult; +export const GetDetailsTabDataForObjectDocument = gql` + query getDetailsTabDataForObject($input: GetDetailsTabDataForObjectInput!) { + getDetailsTabDataForObject(input: $input) { + Unit { + idUnit + Abbreviation + ARKPrefix + } + Project { + idProject + Description + } + Subject { + idSubject + GeoLocation { + idGeoLocation + Altitude + Latitude + Longitude + R0 + R1 + R2 + R3 + TS0 + TS1 + TS2 + } + } + Item { + idItem + EntireSubject + GeoLocation { + idGeoLocation + Altitude + Latitude + Longitude + R0 + R1 + R2 + R3 + TS0 + TS1 + TS2 + } + } + CaptureData { + idAssetVersion + dateCaptured + datasetType + systemCreated + description + cameraSettingUniform + datasetFieldId + itemPositionType + itemPositionFieldId + itemArrangementFieldId + focusType + lightsourceType + backgroundRemovalMethod + clusterType + clusterGeometryFieldId + folders { + name + variantType + } + } + Model { + systemCreated + master + authoritative + creationMethod + modality + purpose + units + dateCaptured + modelFileType + uvMaps { + name + mapType + } + roughness + metalness + pointCount + faceCount + isWatertight + hasNormals + hasVertexColor + hasUVSpace + boundingBoxP1X + boundingBoxP1Y + boundingBoxP1Z + boundingBoxP2X + boundingBoxP2Y + boundingBoxP2Z + } + Scene { + idScene + } + IntermediaryFile { + idIntermediaryFile + } + ProjectDocumentation { + idProjectDocumentation + Description + } + Asset { + idAsset + FilePath + VAssetType { + idVocabulary + Term + } + } + AssetVersion { + idAssetVersion + DateCreated + StorageSize + Ingested + Version + User { + idUser + Name + } + } + Actor { + idActor + OrganizationName + } + Stakeholder { + idStakeholder + OrganizationName + EmailAddress + PhoneNumberMobile + PhoneNumberOffice + MailingAddress + } + } +} + `; + +/** + * __useGetDetailsTabDataForObjectQuery__ + * + * To run a query within a React component, call `useGetDetailsTabDataForObjectQuery` and pass it any options that fit your needs. + * When your component renders, `useGetDetailsTabDataForObjectQuery` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. + * + * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useGetDetailsTabDataForObjectQuery({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ +export function useGetDetailsTabDataForObjectQuery(baseOptions?: Apollo.QueryHookOptions) { + return Apollo.useQuery(GetDetailsTabDataForObjectDocument, baseOptions); +} +export function useGetDetailsTabDataForObjectLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { + return Apollo.useLazyQuery(GetDetailsTabDataForObjectDocument, baseOptions); +} +export type GetDetailsTabDataForObjectQueryHookResult = ReturnType; +export type GetDetailsTabDataForObjectLazyQueryHookResult = ReturnType; +export type GetDetailsTabDataForObjectQueryResult = Apollo.QueryResult; export const GetSourceObjectIdentiferDocument = gql` query getSourceObjectIdentifer($input: GetSourceObjectIdentiferInput!) { getSourceObjectIdentifer(input: $input) { diff --git a/server/graphql/api/index.ts b/server/graphql/api/index.ts index e47707033..c513baa59 100644 --- a/server/graphql/api/index.ts +++ b/server/graphql/api/index.ts @@ -99,6 +99,8 @@ import { GetAssetDetailsForSystemObjectResult, GetVersionsForSystemObjectInput, GetVersionsForSystemObjectResult, + GetDetailsTabDataForObjectInput, + GetDetailsTabDataForObjectResult } from '../../types/graphql'; // Queries @@ -135,6 +137,7 @@ import getSourceObjectIdentifer from './queries/systemobject/getSourceObjectIden import getSystemObjectDetails from './queries/systemobject/getSystemObjectDetails'; import getAssetDetailsForSystemObject from './queries/systemobject/getAssetDetailsForSystemObject'; import getVersionsForSystemObject from './queries/systemobject/getVersionsForSystemObject'; +import getDetailsTabDataForObject from './queries/systemobject/getDetailsTabDataForObject'; // Mutations import createUser from './mutations/user/createUser'; @@ -201,7 +204,8 @@ const allQueries = { getSourceObjectIdentifer, getSystemObjectDetails, getAssetDetailsForSystemObject, - getVersionsForSystemObject + getVersionsForSystemObject, + getDetailsTabDataForObject }; type GraphQLRequest = { @@ -572,6 +576,16 @@ class GraphQLApi { }); } + async getDetailsTabDataForObject(input: GetDetailsTabDataForObjectInput, context?: Context): Promise { + const operationName = 'getDetailsTabDataForObject'; + const variables = { input }; + return this.graphqlRequest({ + operationName, + variables, + context + }); + } + async createUnit(input: CreateUnitInput, context?: Context): Promise { const operationName = 'createUnit'; const variables = { input }; diff --git a/server/graphql/api/queries/systemobject/getDetailsTabDataForObject.ts b/server/graphql/api/queries/systemobject/getDetailsTabDataForObject.ts new file mode 100644 index 000000000..2ab7eb4c0 --- /dev/null +++ b/server/graphql/api/queries/systemobject/getDetailsTabDataForObject.ts @@ -0,0 +1,143 @@ +import { gql } from 'apollo-server-express'; + +const getDetailsTabDataForObject = gql` + query getDetailsTabDataForObject($input: GetDetailsTabDataForObjectInput!) { + getDetailsTabDataForObject(input: $input) { + Unit { + idUnit + Abbreviation + ARKPrefix + } + Project { + idProject + Description + } + Subject { + idSubject + GeoLocation { + idGeoLocation + Altitude + Latitude + Longitude + R0 + R1 + R2 + R3 + TS0 + TS1 + TS2 + } + } + Item { + idItem + EntireSubject + GeoLocation { + idGeoLocation + Altitude + Latitude + Longitude + R0 + R1 + R2 + R3 + TS0 + TS1 + TS2 + } + } + CaptureData { + idAssetVersion + dateCaptured + datasetType + systemCreated + description + cameraSettingUniform + datasetFieldId + itemPositionType + itemPositionFieldId + itemArrangementFieldId + focusType + lightsourceType + backgroundRemovalMethod + clusterType + clusterGeometryFieldId + folders { + name + variantType + } + } + Model { + systemCreated + master + authoritative + creationMethod + modality + purpose + units + dateCaptured + modelFileType + uvMaps { + name + mapType + } + roughness + metalness + pointCount + faceCount + isWatertight + hasNormals + hasVertexColor + hasUVSpace + boundingBoxP1X + boundingBoxP1Y + boundingBoxP1Z + boundingBoxP2X + boundingBoxP2Y + boundingBoxP2Z + } + Scene { + idScene + } + IntermediaryFile { + idIntermediaryFile + } + ProjectDocumentation { + idProjectDocumentation + Description + } + Asset { + idAsset + FilePath + VAssetType { + idVocabulary + Term + } + } + AssetVersion { + idAssetVersion + DateCreated + StorageSize + Ingested + Version + User { + idUser + Name + } + } + Actor { + idActor + OrganizationName + } + Stakeholder { + idStakeholder + OrganizationName + EmailAddress + PhoneNumberMobile + PhoneNumberOffice + MailingAddress + } + } + } +`; + +export default getDetailsTabDataForObject; diff --git a/server/graphql/schema.graphql b/server/graphql/schema.graphql index decc5afd7..9f9968c64 100644 --- a/server/graphql/schema.graphql +++ b/server/graphql/schema.graphql @@ -8,6 +8,7 @@ type Query { getCaptureDataPhoto(input: GetCaptureDataPhotoInput!): GetCaptureDataPhotoResult! getContentsForAssetVersions(input: GetContentsForAssetVersionsInput!): GetContentsForAssetVersionsResult! getCurrentUser: GetCurrentUserResult! + getDetailsTabDataForObject(input: GetDetailsTabDataForObjectInput!): GetDetailsTabDataForObjectResult! getIngestionItemsForSubjects(input: GetIngestionItemsForSubjectsInput!): GetIngestionItemsForSubjectsResult! getIngestionProjectsForSubjects(input: GetIngestionProjectsForSubjectsInput!): GetIngestionProjectsForSubjectsResult! getIntermediaryFile(input: GetIntermediaryFileInput!): GetIntermediaryFileResult! @@ -788,6 +789,27 @@ type IntermediaryFile { SystemObject: SystemObject } +input GetDetailsTabDataForObjectInput { + idSystemObject: Int! + objectType: Int! +} + +type GetDetailsTabDataForObjectResult { + Unit: Unit + Project: Project + Subject: Subject + Item: Item + CaptureData: IngestPhotogrammetry + Model: IngestModel + Scene: Scene + IntermediaryFile: IntermediaryFile + ProjectDocumentation: ProjectDocumentation + Asset: Asset + AssetVersion: AssetVersion + Actor: Actor + Stakeholder: Stakeholder +} + input GetSystemObjectDetailsInput { idSystemObject: Int! } diff --git a/server/graphql/schema/systemobject/queries.graphql b/server/graphql/schema/systemobject/queries.graphql index 6a957bacf..b1b0463a5 100644 --- a/server/graphql/schema/systemobject/queries.graphql +++ b/server/graphql/schema/systemobject/queries.graphql @@ -5,6 +5,28 @@ type Query { getSourceObjectIdentifer(input: GetSourceObjectIdentiferInput!): GetSourceObjectIdentiferResult! getAssetDetailsForSystemObject(input: GetAssetDetailsForSystemObjectInput!): GetAssetDetailsForSystemObjectResult! getVersionsForSystemObject(input: GetVersionsForSystemObjectInput!): GetVersionsForSystemObjectResult! + getDetailsTabDataForObject(input: GetDetailsTabDataForObjectInput!): GetDetailsTabDataForObjectResult! +} + +input GetDetailsTabDataForObjectInput { + idSystemObject: Int! + objectType: Int! +} + +type GetDetailsTabDataForObjectResult { + Unit: Unit + Project: Project + Subject: Subject + Item: Item + CaptureData: IngestPhotogrammetry + Model: IngestModel + Scene: Scene + IntermediaryFile: IntermediaryFile + ProjectDocumentation: ProjectDocumentation + Asset: Asset + AssetVersion: AssetVersion + Actor: Actor + Stakeholder: Stakeholder } input GetSystemObjectDetailsInput { diff --git a/server/graphql/schema/systemobject/resolvers/queries/getDetailsTabDataForObject.ts b/server/graphql/schema/systemobject/resolvers/queries/getDetailsTabDataForObject.ts new file mode 100644 index 000000000..9cf39dd50 --- /dev/null +++ b/server/graphql/schema/systemobject/resolvers/queries/getDetailsTabDataForObject.ts @@ -0,0 +1,74 @@ +import * as DBAPI from '../../../../../db'; +import { eSystemObjectType } from '../../../../../db'; +import { GetDetailsTabDataForObjectResult, QueryGetDetailsTabDataForObjectArgs } from '../../../../../types/graphql'; +import { Parent } from '../../../../../types/resolvers'; + +export default async function getDetailsTabDataForObject(_: Parent, args: QueryGetDetailsTabDataForObjectArgs): Promise { + const { input } = args; + const { idSystemObject, objectType } = input; + + const result: GetDetailsTabDataForObjectResult = { + Unit: null, + Project: null, + Subject: null, + Item: null, + CaptureData: null, + Model: null, + Scene: null, + IntermediaryFile: null, + ProjectDocumentation: null, + Asset: null, + AssetVersion: null, + Actor: null, + Stakeholder: null, + }; + + const systemObject: DBAPI.SystemObject | null = await DBAPI.SystemObject.fetch(idSystemObject); + + switch (objectType) { + case eSystemObjectType.eUnit: + if (systemObject?.idUnit) result.Unit = await DBAPI.Unit.fetch(systemObject.idUnit); + break; + case eSystemObjectType.eProject: + if (systemObject?.idProject) result.Project = await DBAPI.Project.fetch(systemObject.idProject); + break; + case eSystemObjectType.eSubject: + if (systemObject?.idSubject) result.Subject = await DBAPI.Subject.fetch(systemObject.idSubject); + break; + case eSystemObjectType.eItem: + if (systemObject?.idItem) result.Item = await DBAPI.Item.fetch(systemObject.idItem); + break; + case eSystemObjectType.eCaptureData: + // TODO: KARAN: fetch IngestPhotogrammetry + break; + case eSystemObjectType.eModel: + // TODO: KARAN: fetch IngestModel + break; + case eSystemObjectType.eScene: + if (systemObject?.idScene) result.Scene = await DBAPI.Scene.fetch(systemObject.idScene); + break; + case eSystemObjectType.eIntermediaryFile: + if (systemObject?.idIntermediaryFile) result.IntermediaryFile = await DBAPI.IntermediaryFile.fetch(systemObject.idIntermediaryFile); + break; + case eSystemObjectType.eProjectDocumentation: + if (systemObject?.idProjectDocumentation) result.ProjectDocumentation = await DBAPI.ProjectDocumentation.fetch(systemObject.idProjectDocumentation); + break; + case eSystemObjectType.eAsset: + if (systemObject?.idAsset) result.Asset = await DBAPI.Asset.fetch(systemObject.idAsset); + break; + case eSystemObjectType.eAssetVersion: + if (systemObject?.idAssetVersion) result.AssetVersion = await DBAPI.AssetVersion.fetch(systemObject.idAssetVersion); + break; + case eSystemObjectType.eActor: + if (systemObject?.idActor) result.Actor = await DBAPI.Actor.fetch(systemObject.idActor); + break; + case eSystemObjectType.eStakeholder: + if (systemObject?.idStakeholder) result.Stakeholder = await DBAPI.Stakeholder.fetch(systemObject.idStakeholder); + break; + default: + break; + } + + return result; +} + diff --git a/server/graphql/schema/unit/resolvers/queries/getIngestionProjectsForSubjects.ts b/server/graphql/schema/unit/resolvers/queries/getIngestionProjectsForSubjects.ts index 4a5424ee8..de2efa3b4 100644 --- a/server/graphql/schema/unit/resolvers/queries/getIngestionProjectsForSubjects.ts +++ b/server/graphql/schema/unit/resolvers/queries/getIngestionProjectsForSubjects.ts @@ -16,7 +16,6 @@ export default async function getIngestionProjectsForSubjects(_: Parent, args: Q } } - // TODO: KARAN: if projects are empty after fetchMasterFromSubjects, send all projects const AllProjects = await DBAPI.Project.fetchAll(); if (AllProjects) { diff --git a/server/types/graphql.ts b/server/types/graphql.ts index 791ede436..019e96a62 100644 --- a/server/types/graphql.ts +++ b/server/types/graphql.ts @@ -23,6 +23,7 @@ export type Query = { getCaptureDataPhoto: GetCaptureDataPhotoResult; getContentsForAssetVersions: GetContentsForAssetVersionsResult; getCurrentUser: GetCurrentUserResult; + getDetailsTabDataForObject: GetDetailsTabDataForObjectResult; getIngestionItemsForSubjects: GetIngestionItemsForSubjectsResult; getIngestionProjectsForSubjects: GetIngestionProjectsForSubjectsResult; getIntermediaryFile: GetIntermediaryFileResult; @@ -90,6 +91,11 @@ export type QueryGetContentsForAssetVersionsArgs = { }; +export type QueryGetDetailsTabDataForObjectArgs = { + input: GetDetailsTabDataForObjectInput; +}; + + export type QueryGetIngestionItemsForSubjectsArgs = { input: GetIngestionItemsForSubjectsInput; }; @@ -1084,6 +1090,28 @@ export type IntermediaryFile = { SystemObject?: Maybe; }; +export type GetDetailsTabDataForObjectInput = { + idSystemObject: Scalars['Int']; + objectType: Scalars['Int']; +}; + +export type GetDetailsTabDataForObjectResult = { + __typename?: 'GetDetailsTabDataForObjectResult'; + Unit?: Maybe; + Project?: Maybe; + Subject?: Maybe; + Item?: Maybe; + CaptureData?: Maybe; + Model?: Maybe; + Scene?: Maybe; + IntermediaryFile?: Maybe; + ProjectDocumentation?: Maybe; + Asset?: Maybe; + AssetVersion?: Maybe; + Actor?: Maybe; + Stakeholder?: Maybe; +}; + export type GetSystemObjectDetailsInput = { idSystemObject: Scalars['Int']; }; From 6826403741fec4524e0985a72e4e564582fcff44 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Thu, 10 Dec 2020 13:23:09 +0530 Subject: [PATCH 129/239] added empty table component --- client/src/components/index.ts | 4 ++- client/src/components/shared/EmptyTable.tsx | 33 +++++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 client/src/components/shared/EmptyTable.tsx diff --git a/client/src/components/index.ts b/client/src/components/index.ts index 7b747a72a..7786bc2a0 100644 --- a/client/src/components/index.ts +++ b/client/src/components/index.ts @@ -18,6 +18,7 @@ import IdInputField from './controls/IdInputField'; import DateInputField from './controls/DateInputField'; import BreadcrumbsView from './shared/BreadcrumbsView'; import NewTabLink from './shared/NewTabLink'; +import EmptyTable from './shared/EmptyTable'; export { Header, @@ -35,5 +36,6 @@ export { IdInputField, DateInputField, BreadcrumbsView, - NewTabLink + NewTabLink, + EmptyTable }; diff --git a/client/src/components/shared/EmptyTable.tsx b/client/src/components/shared/EmptyTable.tsx new file mode 100644 index 000000000..8347e0185 --- /dev/null +++ b/client/src/components/shared/EmptyTable.tsx @@ -0,0 +1,33 @@ +/** + * AssetDetailsTable + * + * This component renders asset details table tab for the DetailsTab component. + */ +import { Box } from '@material-ui/core'; +import { makeStyles } from '@material-ui/core/styles'; +import React from 'react'; +import Progress from './Progress'; + +export const useStyles = makeStyles(({ palette }) => ({ + empty: { + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + flex: 1, + background: palette.secondary.light, + padding: 40, + borderRadius: 5 + } +})); + +function EmptyTable(): React.ReactElement { + const classes = useStyles(); + + return ( + + + + ); +} + +export default EmptyTable; \ No newline at end of file From a90cf5362c06cb6e17115bc1a6941d3b2e2983e9 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Thu, 10 Dec 2020 13:41:46 +0530 Subject: [PATCH 130/239] Added wrapper components for all system object types --- .../DetailsView/DetailsTab/ActorDetails.tsx | 24 +++++++++++ .../DetailsView/DetailsTab/AssetDetails.tsx | 24 +++++++++++ .../DetailsTab/AssetDetailsTable.tsx | 12 +----- .../DetailsTab/AssetVersionDetails.tsx | 24 +++++++++++ .../DetailsTab/AssetVersionsTable.tsx | 5 +-- .../DetailsTab/CaptureDataDetails.tsx | 24 +++++++++++ .../DetailsTab/IntermediaryFileDetails.tsx | 24 +++++++++++ .../DetailsView/DetailsTab/ItemDetails.tsx | 24 +++++++++++ .../DetailsView/DetailsTab/ModelDetails.tsx | 24 +++++++++++ .../DetailsView/DetailsTab/ProjectDetails.tsx | 24 +++++++++++ .../ProjectDocumentationDetails.tsx | 24 +++++++++++ .../DetailsView/DetailsTab/SceneDetails.tsx | 24 +++++++++++ .../DetailsTab/StakeholderDetails.tsx | 24 +++++++++++ .../DetailsView/DetailsTab/SubjectDetails.tsx | 24 +++++++++++ .../DetailsView/DetailsTab/UnitDetails.tsx | 24 +++++++++++ .../DetailsView/DetailsTab/index.tsx | 43 +++++++++++++------ .../pages/Repository/hooks/useDetailsView.ts | 23 +++++++++- .../schema/systemobject/resolvers/index.ts | 4 +- 18 files changed, 370 insertions(+), 29 deletions(-) create mode 100644 client/src/pages/Repository/components/DetailsView/DetailsTab/ActorDetails.tsx create mode 100644 client/src/pages/Repository/components/DetailsView/DetailsTab/AssetDetails.tsx create mode 100644 client/src/pages/Repository/components/DetailsView/DetailsTab/AssetVersionDetails.tsx create mode 100644 client/src/pages/Repository/components/DetailsView/DetailsTab/CaptureDataDetails.tsx create mode 100644 client/src/pages/Repository/components/DetailsView/DetailsTab/IntermediaryFileDetails.tsx create mode 100644 client/src/pages/Repository/components/DetailsView/DetailsTab/ItemDetails.tsx create mode 100644 client/src/pages/Repository/components/DetailsView/DetailsTab/ModelDetails.tsx create mode 100644 client/src/pages/Repository/components/DetailsView/DetailsTab/ProjectDetails.tsx create mode 100644 client/src/pages/Repository/components/DetailsView/DetailsTab/ProjectDocumentationDetails.tsx create mode 100644 client/src/pages/Repository/components/DetailsView/DetailsTab/SceneDetails.tsx create mode 100644 client/src/pages/Repository/components/DetailsView/DetailsTab/StakeholderDetails.tsx create mode 100644 client/src/pages/Repository/components/DetailsView/DetailsTab/SubjectDetails.tsx create mode 100644 client/src/pages/Repository/components/DetailsView/DetailsTab/UnitDetails.tsx diff --git a/client/src/pages/Repository/components/DetailsView/DetailsTab/ActorDetails.tsx b/client/src/pages/Repository/components/DetailsView/DetailsTab/ActorDetails.tsx new file mode 100644 index 000000000..7a3a491d6 --- /dev/null +++ b/client/src/pages/Repository/components/DetailsView/DetailsTab/ActorDetails.tsx @@ -0,0 +1,24 @@ +/** + * ActorDetails + * + * This component renders details tab for Actor specific details used in DetailsTab component. + */ +import React from 'react'; +import { Loader } from '../../../../../components'; +import { GetDetailsTabDataForObjectQueryResult } from '../../../../../types/graphql'; + +interface ActorDetailsProps extends GetDetailsTabDataForObjectQueryResult { + disabled: boolean; +} + +function ActorDetails(props: ActorDetailsProps): React.ReactElement { + const { data, loading } = props; + + if (!data || loading) { + return ; + } + + return Actor Details; +} + +export default ActorDetails; \ No newline at end of file diff --git a/client/src/pages/Repository/components/DetailsView/DetailsTab/AssetDetails.tsx b/client/src/pages/Repository/components/DetailsView/DetailsTab/AssetDetails.tsx new file mode 100644 index 000000000..28574993a --- /dev/null +++ b/client/src/pages/Repository/components/DetailsView/DetailsTab/AssetDetails.tsx @@ -0,0 +1,24 @@ +/** + * AssetDetails + * + * This component renders details tab for Asset specific details used in DetailsTab component. + */ +import React from 'react'; +import { Loader } from '../../../../../components'; +import { GetDetailsTabDataForObjectQueryResult } from '../../../../../types/graphql'; + +interface AssetDetailsProps extends GetDetailsTabDataForObjectQueryResult { + disabled: boolean; +} + +function AssetDetails(props: AssetDetailsProps): React.ReactElement { + const { data, loading } = props; + + if (!data || loading) { + return ; + } + + return Asset Details; +} + +export default AssetDetails; \ No newline at end of file diff --git a/client/src/pages/Repository/components/DetailsView/DetailsTab/AssetDetailsTable.tsx b/client/src/pages/Repository/components/DetailsView/DetailsTab/AssetDetailsTable.tsx index 4b03a1aaa..f3acf441e 100644 --- a/client/src/pages/Repository/components/DetailsView/DetailsTab/AssetDetailsTable.tsx +++ b/client/src/pages/Repository/components/DetailsView/DetailsTab/AssetDetailsTable.tsx @@ -7,7 +7,7 @@ import { Box, Typography } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; import clsx from 'clsx'; import React from 'react'; -import { NewTabLink, Progress } from '../../../../../components'; +import { EmptyTable, NewTabLink } from '../../../../../components'; import { StateAssetDetail, useVocabularyStore } from '../../../../../store'; import { eVocabularySetID } from '../../../../../types/server'; import { getDetailsUrlForObject } from '../../../../../utils/repository'; @@ -112,14 +112,4 @@ function AssetDetailsTable(props: AssetDetailsTableProps): React.ReactElement { ); } -export function EmptyTable(): React.ReactElement { - const classes = useStyles(); - - return ( - - - - ); -} - export default AssetDetailsTable; \ No newline at end of file diff --git a/client/src/pages/Repository/components/DetailsView/DetailsTab/AssetVersionDetails.tsx b/client/src/pages/Repository/components/DetailsView/DetailsTab/AssetVersionDetails.tsx new file mode 100644 index 000000000..c525036b3 --- /dev/null +++ b/client/src/pages/Repository/components/DetailsView/DetailsTab/AssetVersionDetails.tsx @@ -0,0 +1,24 @@ +/** + * AssetVersionDetails + * + * This component renders details tab for AssetVersion specific details used in DetailsTab component. + */ +import React from 'react'; +import { Loader } from '../../../../../components'; +import { GetDetailsTabDataForObjectQueryResult } from '../../../../../types/graphql'; + +interface AssetVersionDetailsProps extends GetDetailsTabDataForObjectQueryResult { + disabled: boolean; +} + +function AssetVersionDetails(props: AssetVersionDetailsProps): React.ReactElement { + const { data, loading } = props; + + if (!data || loading) { + return ; + } + + return AssetVersion Details; +} + +export default AssetVersionDetails; \ No newline at end of file diff --git a/client/src/pages/Repository/components/DetailsView/DetailsTab/AssetVersionsTable.tsx b/client/src/pages/Repository/components/DetailsView/DetailsTab/AssetVersionsTable.tsx index a8fdcc4ee..6911ce827 100644 --- a/client/src/pages/Repository/components/DetailsView/DetailsTab/AssetVersionsTable.tsx +++ b/client/src/pages/Repository/components/DetailsView/DetailsTab/AssetVersionsTable.tsx @@ -6,13 +6,12 @@ import { Box, Typography } from '@material-ui/core'; import clsx from 'clsx'; import React from 'react'; -import { NewTabLink } from '../../../../../components'; +import { EmptyTable, NewTabLink } from '../../../../../components'; import { StateDetailVersion } from '../../../../../store'; import { getDetailsUrlForObject } from '../../../../../utils/repository'; import { formatBytes } from '../../../../../utils/upload'; import { useObjectVersions } from '../../../hooks/useDetailsView'; -import { EmptyTable, useStyles } from './AssetDetailsTable'; - +import { useStyles } from './AssetDetailsTable'; interface AssetVersionsTableProps { idSystemObject: number; diff --git a/client/src/pages/Repository/components/DetailsView/DetailsTab/CaptureDataDetails.tsx b/client/src/pages/Repository/components/DetailsView/DetailsTab/CaptureDataDetails.tsx new file mode 100644 index 000000000..50a196145 --- /dev/null +++ b/client/src/pages/Repository/components/DetailsView/DetailsTab/CaptureDataDetails.tsx @@ -0,0 +1,24 @@ +/** + * CaptureDataDetails + * + * This component renders details tab for CaptureData specific details used in DetailsTab component. + */ +import React from 'react'; +import { Loader } from '../../../../../components'; +import { GetDetailsTabDataForObjectQueryResult } from '../../../../../types/graphql'; + +interface CaptureDataDetailsProps extends GetDetailsTabDataForObjectQueryResult { + disabled: boolean; +} + +function CaptureDataDetails(props: CaptureDataDetailsProps): React.ReactElement { + const { data, loading } = props; + + if (!data || loading) { + return ; + } + + return CaptureData Details; +} + +export default CaptureDataDetails; \ No newline at end of file diff --git a/client/src/pages/Repository/components/DetailsView/DetailsTab/IntermediaryFileDetails.tsx b/client/src/pages/Repository/components/DetailsView/DetailsTab/IntermediaryFileDetails.tsx new file mode 100644 index 000000000..72fd73b1c --- /dev/null +++ b/client/src/pages/Repository/components/DetailsView/DetailsTab/IntermediaryFileDetails.tsx @@ -0,0 +1,24 @@ +/** + * IntermediaryFileDetails + * + * This component renders details tab for IntermediaryFile specific details used in DetailsTab component. + */ +import React from 'react'; +import { Loader } from '../../../../../components'; +import { GetDetailsTabDataForObjectQueryResult } from '../../../../../types/graphql'; + +interface IntermediaryFileDetailsProps extends GetDetailsTabDataForObjectQueryResult { + disabled: boolean; +} + +function IntermediaryFileDetails(props: IntermediaryFileDetailsProps): React.ReactElement { + const { data, loading } = props; + + if (!data || loading) { + return ; + } + + return IntermediaryFile Details; +} + +export default IntermediaryFileDetails; \ No newline at end of file diff --git a/client/src/pages/Repository/components/DetailsView/DetailsTab/ItemDetails.tsx b/client/src/pages/Repository/components/DetailsView/DetailsTab/ItemDetails.tsx new file mode 100644 index 000000000..a4cc89711 --- /dev/null +++ b/client/src/pages/Repository/components/DetailsView/DetailsTab/ItemDetails.tsx @@ -0,0 +1,24 @@ +/** + * ItemDetails + * + * This component renders details tab for Item specific details used in DetailsTab component. + */ +import React from 'react'; +import { Loader } from '../../../../../components'; +import { GetDetailsTabDataForObjectQueryResult } from '../../../../../types/graphql'; + +interface ItemDetailsProps extends GetDetailsTabDataForObjectQueryResult { + disabled: boolean; +} + +function ItemDetails(props: ItemDetailsProps): React.ReactElement { + const { data, loading } = props; + + if (!data || loading) { + return ; + } + + return Item Details; +} + +export default ItemDetails; \ No newline at end of file diff --git a/client/src/pages/Repository/components/DetailsView/DetailsTab/ModelDetails.tsx b/client/src/pages/Repository/components/DetailsView/DetailsTab/ModelDetails.tsx new file mode 100644 index 000000000..4d8c27934 --- /dev/null +++ b/client/src/pages/Repository/components/DetailsView/DetailsTab/ModelDetails.tsx @@ -0,0 +1,24 @@ +/** + * ModelDetails + * + * This component renders details tab for Model specific details used in DetailsTab component. + */ +import React from 'react'; +import { Loader } from '../../../../../components'; +import { GetDetailsTabDataForObjectQueryResult } from '../../../../../types/graphql'; + +interface ModelDetailsProps extends GetDetailsTabDataForObjectQueryResult { + disabled: boolean; +} + +function ModelDetails(props: ModelDetailsProps): React.ReactElement { + const { data, loading } = props; + + if (!data || loading) { + return ; + } + + return Model Details; +} + +export default ModelDetails; \ No newline at end of file diff --git a/client/src/pages/Repository/components/DetailsView/DetailsTab/ProjectDetails.tsx b/client/src/pages/Repository/components/DetailsView/DetailsTab/ProjectDetails.tsx new file mode 100644 index 000000000..a4ab18763 --- /dev/null +++ b/client/src/pages/Repository/components/DetailsView/DetailsTab/ProjectDetails.tsx @@ -0,0 +1,24 @@ +/** + * ProjectDetails + * + * This component renders details tab for Actor specific details used in DetailsTab component. + */ +import React from 'react'; +import { Loader } from '../../../../../components'; +import { GetDetailsTabDataForObjectQueryResult } from '../../../../../types/graphql'; + +interface ProjectDetailsProps extends GetDetailsTabDataForObjectQueryResult { + disabled: boolean; +} + +function ProjectDetails(props: ProjectDetailsProps): React.ReactElement { + const { data, loading } = props; + + if (!data || loading) { + return ; + } + + return Project Details; +} + +export default ProjectDetails; \ No newline at end of file diff --git a/client/src/pages/Repository/components/DetailsView/DetailsTab/ProjectDocumentationDetails.tsx b/client/src/pages/Repository/components/DetailsView/DetailsTab/ProjectDocumentationDetails.tsx new file mode 100644 index 000000000..0c73c8f00 --- /dev/null +++ b/client/src/pages/Repository/components/DetailsView/DetailsTab/ProjectDocumentationDetails.tsx @@ -0,0 +1,24 @@ +/** + * ProjectDocumentationDetails + * + * This component renders details tab for ProjectDocumentation specific details used in DetailsTab component. + */ +import React from 'react'; +import { Loader } from '../../../../../components'; +import { GetDetailsTabDataForObjectQueryResult } from '../../../../../types/graphql'; + +interface ProjectDocumentationDetailsProps extends GetDetailsTabDataForObjectQueryResult { + disabled: boolean; +} + +function ProjectDocumentationDetails(props: ProjectDocumentationDetailsProps): React.ReactElement { + const { data, loading } = props; + + if (!data || loading) { + return ; + } + + return ProjectDocumentation Details; +} + +export default ProjectDocumentationDetails; \ No newline at end of file diff --git a/client/src/pages/Repository/components/DetailsView/DetailsTab/SceneDetails.tsx b/client/src/pages/Repository/components/DetailsView/DetailsTab/SceneDetails.tsx new file mode 100644 index 000000000..4ae601412 --- /dev/null +++ b/client/src/pages/Repository/components/DetailsView/DetailsTab/SceneDetails.tsx @@ -0,0 +1,24 @@ +/** + * SceneDetails + * + * This component renders details tab for Scene specific details used in DetailsTab component. + */ +import React from 'react'; +import { Loader } from '../../../../../components'; +import { GetDetailsTabDataForObjectQueryResult } from '../../../../../types/graphql'; + +interface SceneDetailsProps extends GetDetailsTabDataForObjectQueryResult { + disabled: boolean; +} + +function SceneDetails(props: SceneDetailsProps): React.ReactElement { + const { data, loading } = props; + + if (!data || loading) { + return ; + } + + return Scene Details; +} + +export default SceneDetails; \ No newline at end of file diff --git a/client/src/pages/Repository/components/DetailsView/DetailsTab/StakeholderDetails.tsx b/client/src/pages/Repository/components/DetailsView/DetailsTab/StakeholderDetails.tsx new file mode 100644 index 000000000..fcd505488 --- /dev/null +++ b/client/src/pages/Repository/components/DetailsView/DetailsTab/StakeholderDetails.tsx @@ -0,0 +1,24 @@ +/** + * StakeholderDetails + * + * This component renders details tab for Stakeholder specific details used in DetailsTab component. + */ +import React from 'react'; +import { Loader } from '../../../../../components'; +import { GetDetailsTabDataForObjectQueryResult } from '../../../../../types/graphql'; + +interface StakeholderDetailsProps extends GetDetailsTabDataForObjectQueryResult { + disabled: boolean; +} + +function StakeholderDetails(props: StakeholderDetailsProps): React.ReactElement { + const { data, loading } = props; + + if (!data || loading) { + return ; + } + + return Stakeholder Details; +} + +export default StakeholderDetails; \ No newline at end of file diff --git a/client/src/pages/Repository/components/DetailsView/DetailsTab/SubjectDetails.tsx b/client/src/pages/Repository/components/DetailsView/DetailsTab/SubjectDetails.tsx new file mode 100644 index 000000000..799609584 --- /dev/null +++ b/client/src/pages/Repository/components/DetailsView/DetailsTab/SubjectDetails.tsx @@ -0,0 +1,24 @@ +/** + * SubjectDetails + * + * This component renders details tab for Actor specific details used in DetailsTab component. + */ +import React from 'react'; +import { Loader } from '../../../../../components'; +import { GetDetailsTabDataForObjectQueryResult } from '../../../../../types/graphql'; + +interface SubjectDetailsProps extends GetDetailsTabDataForObjectQueryResult { + disabled: boolean; +} + +function SubjectDetails(props: SubjectDetailsProps): React.ReactElement { + const { data, loading } = props; + + if (!data || loading) { + return ; + } + + return Subject Details; +} + +export default SubjectDetails; \ No newline at end of file diff --git a/client/src/pages/Repository/components/DetailsView/DetailsTab/UnitDetails.tsx b/client/src/pages/Repository/components/DetailsView/DetailsTab/UnitDetails.tsx new file mode 100644 index 000000000..1d4d47959 --- /dev/null +++ b/client/src/pages/Repository/components/DetailsView/DetailsTab/UnitDetails.tsx @@ -0,0 +1,24 @@ +/** + * UnitDetails + * + * This component renders details tab for Unit specific details used in DetailsTab component. + */ +import React from 'react'; +import { Loader } from '../../../../../components'; +import { GetDetailsTabDataForObjectQueryResult } from '../../../../../types/graphql'; + +interface UnitDetailsProps extends GetDetailsTabDataForObjectQueryResult { + disabled: boolean; +} + +function UnitDetails(props: UnitDetailsProps): React.ReactElement { + const { data, loading } = props; + + if (!data || loading) { + return ; + } + + return Unit Details; +} + +export default UnitDetails; \ No newline at end of file diff --git a/client/src/pages/Repository/components/DetailsView/DetailsTab/index.tsx b/client/src/pages/Repository/components/DetailsView/DetailsTab/index.tsx index ca3cd9e99..789042953 100644 --- a/client/src/pages/Repository/components/DetailsView/DetailsTab/index.tsx +++ b/client/src/pages/Repository/components/DetailsView/DetailsTab/index.tsx @@ -11,8 +11,22 @@ import { StateRelatedObject } from '../../../../../store'; import { RelatedObjectType } from '../../../../../types/graphql'; import { eSystemObjectType } from '../../../../../types/server'; import RelatedObjectsList from '../../../../Ingestion/components/Metadata/Model/RelatedObjectsList'; +import { useDetailsTabData } from '../../../hooks/useDetailsView'; +import ActorDetails from './ActorDetails'; +import AssetDetails from './AssetDetails'; import AssetDetailsTable from './AssetDetailsTable'; +import AssetVersionDetails from './AssetVersionDetails'; import AssetVersionsTable from './AssetVersionsTable'; +import CaptureDataDetails from './CaptureDataDetails'; +import IntermediaryFileDetails from './IntermediaryFileDetails'; +import ItemDetails from './ItemDetails'; +import ModelDetails from './ModelDetails'; +import ProjectDetails from './ProjectDetails'; +import ProjectDocumentationDetails from './ProjectDocumentationDetails'; +import SceneDetails from './SceneDetails'; +import StakeholderDetails from './StakeholderDetails'; +import SubjectDetails from './SubjectDetails'; +import UnitDetails from './UnitDetails'; const useStyles = makeStyles(({ palette }) => ({ tab: { @@ -37,10 +51,13 @@ function DetailsTab(props: DetailsTabParams): React.ReactElement { const { disabled, idSystemObject, objectType, sourceObjects, derivedObjects, onAddSourceObject, onAddDerivedObject } = props; const [tab, setTab] = useState(0); const classes = useStyles(); + const handleTabChange = (_, nextTab: number) => { setTab(nextTab); }; + const detailsQueryResult = useDetailsTabData(idSystemObject, objectType); + let tabs: string[] = []; let tabPanels: React.ReactNode = null; @@ -70,7 +87,7 @@ function DetailsTab(props: DetailsTabParams): React.ReactElement { tabPanels = ( - Unit Details + {RelatedTab(1)} @@ -81,7 +98,7 @@ function DetailsTab(props: DetailsTabParams): React.ReactElement { tabPanels = ( - Project Details + {RelatedTab(1)} @@ -95,7 +112,7 @@ function DetailsTab(props: DetailsTabParams): React.ReactElement { - Subject Details + {RelatedTab(2)} @@ -109,7 +126,7 @@ function DetailsTab(props: DetailsTabParams): React.ReactElement { - Item Details + {RelatedTab(2)} @@ -123,7 +140,7 @@ function DetailsTab(props: DetailsTabParams): React.ReactElement { - CaptureData Details + {RelatedTab(2)} @@ -137,7 +154,7 @@ function DetailsTab(props: DetailsTabParams): React.ReactElement { - Model Details + {RelatedTab(2)} @@ -151,7 +168,7 @@ function DetailsTab(props: DetailsTabParams): React.ReactElement { - Scene Details + {RelatedTab(2)} @@ -165,7 +182,7 @@ function DetailsTab(props: DetailsTabParams): React.ReactElement { - IntermediaryFile Details + {RelatedTab(2)} @@ -179,7 +196,7 @@ function DetailsTab(props: DetailsTabParams): React.ReactElement { - ProjectDocumentation Details + {RelatedTab(2)} @@ -193,7 +210,7 @@ function DetailsTab(props: DetailsTabParams): React.ReactElement { - Asset Details + {RelatedTab(2)} @@ -204,7 +221,7 @@ function DetailsTab(props: DetailsTabParams): React.ReactElement { tabPanels = ( - AssetVersion Details + {RelatedTab(1)} @@ -215,7 +232,7 @@ function DetailsTab(props: DetailsTabParams): React.ReactElement { tabPanels = ( - Actor Details + {RelatedTab(1)} @@ -226,7 +243,7 @@ function DetailsTab(props: DetailsTabParams): React.ReactElement { tabPanels = ( - Stakeholder Details + {RelatedTab(1)} diff --git a/client/src/pages/Repository/hooks/useDetailsView.ts b/client/src/pages/Repository/hooks/useDetailsView.ts index df0695fe1..7c91b29ec 100644 --- a/client/src/pages/Repository/hooks/useDetailsView.ts +++ b/client/src/pages/Repository/hooks/useDetailsView.ts @@ -1,5 +1,15 @@ import { useQuery } from '@apollo/client'; -import { GetAssetDetailsForSystemObjectDocument, GetAssetDetailsForSystemObjectQueryResult, GetSystemObjectDetailsDocument, GetSystemObjectDetailsQueryResult, GetVersionsForSystemObjectDocument, GetVersionsForSystemObjectQueryResult } from '../../../types/graphql'; +import { + GetAssetDetailsForSystemObjectDocument, + GetAssetDetailsForSystemObjectQueryResult, + GetSystemObjectDetailsDocument, + GetSystemObjectDetailsQueryResult, + GetVersionsForSystemObjectDocument, + GetVersionsForSystemObjectQueryResult, + GetDetailsTabDataForObjectDocument, + GetDetailsTabDataForObjectQueryResult +} from '../../../types/graphql'; +import { eSystemObjectType } from '../../../types/server'; export function useObjectDetails(idSystemObject: number): GetSystemObjectDetailsQueryResult { return useQuery(GetSystemObjectDetailsDocument, { @@ -31,3 +41,14 @@ export function useObjectVersions(idSystemObject: number): GetVersionsForSystemO }); } +export function useDetailsTabData(idSystemObject: number, objectType: eSystemObjectType): GetDetailsTabDataForObjectQueryResult { + return useQuery(GetDetailsTabDataForObjectDocument, { + variables: { + input: { + idSystemObject, + objectType + } + } + }); +} + diff --git a/server/graphql/schema/systemobject/resolvers/index.ts b/server/graphql/schema/systemobject/resolvers/index.ts index f9dfe7c12..319331cba 100644 --- a/server/graphql/schema/systemobject/resolvers/index.ts +++ b/server/graphql/schema/systemobject/resolvers/index.ts @@ -6,13 +6,15 @@ import getSystemObjectDetails from './queries/getSystemObjectDetails'; import getSourceObjectIdentifer from './queries/getSourceObjectIdentifer'; import getAssetDetailsForSystemObject from './queries/getAssetDetailsForSystemObject'; import getVersionsForSystemObject from './queries/getVersionsForSystemObject'; +import getDetailsTabDataForObject from './queries/getDetailsTabDataForObject'; const resolvers = { Query: { getSystemObjectDetails, getSourceObjectIdentifer, getAssetDetailsForSystemObject, - getVersionsForSystemObject + getVersionsForSystemObject, + getDetailsTabDataForObject }, SystemObject, SystemObjectVersion, From d3a9317051b137e6c750c0972480df69b3f5bca5 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Thu, 10 Dec 2020 20:43:39 +0530 Subject: [PATCH 131/239] minor fixes to asset detail tables --- .../DetailsTab/AssetDetailsTable.tsx | 82 ++++++++++--------- .../DetailsTab/AssetVersionsTable.tsx | 82 +++++++++++-------- 2 files changed, 91 insertions(+), 73 deletions(-) diff --git a/client/src/pages/Repository/components/DetailsView/DetailsTab/AssetDetailsTable.tsx b/client/src/pages/Repository/components/DetailsView/DetailsTab/AssetDetailsTable.tsx index f3acf441e..f090e31fe 100644 --- a/client/src/pages/Repository/components/DetailsView/DetailsTab/AssetDetailsTable.tsx +++ b/client/src/pages/Repository/components/DetailsView/DetailsTab/AssetDetailsTable.tsx @@ -70,44 +70,52 @@ function AssetDetailsTable(props: AssetDetailsTableProps): React.ReactElement { return ( - - {headers.map((header, index: number) => ( - - ))} - - {assetDetails.map((assetDetail: StateAssetDetail, index: number) => ( - - - - - - - + + + {headers.map((header, index: number) => ( + + ))} - ))} - + + + + {assetDetails.map((assetDetail: StateAssetDetail, index: number) => ( + + + + + + + + + ))} + + + + +
- {header} -
- - {assetDetail.name} - - - {assetDetail.path} - - {getVocabularyTerm(eVocabularySetID.eAssetAssetType, assetDetail.assetType)} - - {assetDetail.version} - - {assetDetail.dateCreated} - - {formatBytes(assetDetail.size)} -
+ {header} +
- {!assetDetails.length && ( - - No assets found - - )} -
+ + {assetDetail.name} + + + {assetDetail.path} + + {getVocabularyTerm(eVocabularySetID.eAssetAssetType, assetDetail.assetType)} + + {assetDetail.version} + + {assetDetail.dateCreated} + + {formatBytes(assetDetail.size)} +
+ {!assetDetails.length && ( + + No assets found + + )} +
); } diff --git a/client/src/pages/Repository/components/DetailsView/DetailsTab/AssetVersionsTable.tsx b/client/src/pages/Repository/components/DetailsView/DetailsTab/AssetVersionsTable.tsx index 6911ce827..495764e24 100644 --- a/client/src/pages/Repository/components/DetailsView/DetailsTab/AssetVersionsTable.tsx +++ b/client/src/pages/Repository/components/DetailsView/DetailsTab/AssetVersionsTable.tsx @@ -38,43 +38,53 @@ function AssetVersionsTable(props: AssetVersionsTableProps): React.ReactElement return ( - - {headers.map((header, index: number) => ( - - ))} - - {versions.map((version: StateDetailVersion, index: number) => ( - - - - - - + + + {headers.map((header, index: number) => ( + + ))} - ))} - + + + + + {versions.map((version: StateDetailVersion, index: number) => ( + + + + + + + + ))} + + + + + +
- {header} -
- - {version.version} - - - - {version.name} - - - {version.creator} - - {version.dateCreated} - - {formatBytes(version.size)} -
+ {header} +
- {!versions.length && ( - - No versions found - - )} -
+ + {version.version} + + + + {version.name} + + + {version.creator} + + {version.dateCreated} + + {formatBytes(version.size)} +
+ {!versions.length && ( + + No versions found + + )} +
); } From f4c1fae9a024011663ad3f21ce22cc4c592999ec Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Thu, 10 Dec 2020 20:44:27 +0530 Subject: [PATCH 132/239] move name to details header --- .../pages/Repository/components/DetailsView/DetailsHeader.tsx | 3 +++ .../pages/Repository/components/DetailsView/ObjectDetails.tsx | 4 +--- client/src/pages/Repository/components/DetailsView/index.tsx | 1 - 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/client/src/pages/Repository/components/DetailsView/DetailsHeader.tsx b/client/src/pages/Repository/components/DetailsView/DetailsHeader.tsx index a9f74294e..56e1390c4 100644 --- a/client/src/pages/Repository/components/DetailsView/DetailsHeader.tsx +++ b/client/src/pages/Repository/components/DetailsView/DetailsHeader.tsx @@ -51,6 +51,9 @@ function DetailsHeader(props: DetailsHeaderProps): React.ReactElement { {getTermForSystemObjectType(objectType)} + + {name} + {!!path.length && } diff --git a/client/src/pages/Repository/components/DetailsView/ObjectDetails.tsx b/client/src/pages/Repository/components/DetailsView/ObjectDetails.tsx index 48dbb69bf..650ed0c76 100644 --- a/client/src/pages/Repository/components/DetailsView/ObjectDetails.tsx +++ b/client/src/pages/Repository/components/DetailsView/ObjectDetails.tsx @@ -29,7 +29,6 @@ const useStyles = makeStyles(({ palette, typography }) => ({ })); interface ObjectDetailsProps { - name: string, unit?: RepositoryPath | null; project?: RepositoryPath | null; subject?: RepositoryPath | null; @@ -40,7 +39,7 @@ interface ObjectDetailsProps { } function ObjectDetails(props: ObjectDetailsProps): React.ReactElement { - const { name, unit, project, subject, item, publishedState, retired, disabled } = props; + const { unit, project, subject, item, publishedState, retired, disabled } = props; const updateRetired = () => { if (disabled) return; @@ -54,7 +53,6 @@ function ObjectDetails(props: ObjectDetailsProps): React.ReactElement { return ( - diff --git a/client/src/pages/Repository/components/DetailsView/index.tsx b/client/src/pages/Repository/components/DetailsView/index.tsx index 437e41e84..e182279d0 100644 --- a/client/src/pages/Repository/components/DetailsView/index.tsx +++ b/client/src/pages/Repository/components/DetailsView/index.tsx @@ -96,7 +96,6 @@ function DetailsView(): React.ReactElement { Date: Fri, 11 Dec 2020 13:26:17 +0530 Subject: [PATCH 133/239] added project, unit and project documentaion details component --- .../{IdInputField.tsx => InputField.tsx} | 25 ++++++--- client/src/components/index.ts | 4 +- .../components/Metadata/Model/index.tsx | 34 +++++++++++-- .../Metadata/Photogrammetry/Description.tsx | 11 +++- .../Metadata/Photogrammetry/index.tsx | 34 +++++++++++-- .../DetailsView/DetailsTab/ProjectDetails.tsx | 24 ++++++++- .../ProjectDocumentationDetails.tsx | 26 ++++++++-- .../DetailsView/DetailsTab/UnitDetails.tsx | 51 +++++++++++++++++-- 8 files changed, 179 insertions(+), 30 deletions(-) rename client/src/components/controls/{IdInputField.tsx => InputField.tsx} (68%) diff --git a/client/src/components/controls/IdInputField.tsx b/client/src/components/controls/InputField.tsx similarity index 68% rename from client/src/components/controls/IdInputField.tsx rename to client/src/components/controls/InputField.tsx index 545121a8d..233be1d7a 100644 --- a/client/src/components/controls/IdInputField.tsx +++ b/client/src/components/controls/InputField.tsx @@ -26,27 +26,38 @@ const useStyles = makeStyles(({ palette, typography, breakpoints }) => ({ } })); -interface IdInputFieldProps { +interface InputFieldProps { label: string; - value: number | null; + value?: number | string | null; name: string; onChange: (event: React.ChangeEvent) => void; + type?: string; + required?: boolean; + viewMode?: boolean; + disabled?: boolean; } -function IdInputField(props: IdInputFieldProps): React.ReactElement { - const { label, name, value, onChange } = props; +function InputField(props: InputFieldProps): React.ReactElement { + const { label, name, value, onChange, type, required = false, viewMode = false, disabled = false } = props; const classes = useStyles(); const rowFieldProps = { alignItems: 'center', justifyContent: 'space-between' }; return ( - + - - - - + + + + ({ interface DescriptionProps { value: string; onChange: (event: React.ChangeEvent) => void; + viewMode?: boolean; } function Description(props: DescriptionProps): React.ReactElement { - const { value, onChange } = props; + const { value, onChange, viewMode = false } = props; const classes = useStyles(); const rowFieldProps = { alignItems: 'center', justifyContent: 'space-between' }; return ( - + - + - - + + - + ({}); + + useEffect(() => { + if (data && !loading) { + const { Project } = data.getDetailsTabDataForObject; + setDetails({ + Description: Project?.Description + }); + } + }, [data, loading]); if (!data || loading) { return ; } - return Project Details; + const onSetField = (event: React.ChangeEvent) => { + const { value } = event.target; + setDetails(details => ({ ...details, Description: value })); + }; + + return ; } export default ProjectDetails; \ No newline at end of file diff --git a/client/src/pages/Repository/components/DetailsView/DetailsTab/ProjectDocumentationDetails.tsx b/client/src/pages/Repository/components/DetailsView/DetailsTab/ProjectDocumentationDetails.tsx index 0c73c8f00..abf1dafed 100644 --- a/client/src/pages/Repository/components/DetailsView/DetailsTab/ProjectDocumentationDetails.tsx +++ b/client/src/pages/Repository/components/DetailsView/DetailsTab/ProjectDocumentationDetails.tsx @@ -1,24 +1,44 @@ /** * ProjectDocumentationDetails * - * This component renders details tab for ProjectDocumentation specific details used in DetailsTab component. + * This component renders details tab for Actor specific details used in DetailsTab component. */ -import React from 'react'; +import React, { useEffect, useState } from 'react'; import { Loader } from '../../../../../components'; import { GetDetailsTabDataForObjectQueryResult } from '../../../../../types/graphql'; +import Description from '../../../../Ingestion/components/Metadata/Photogrammetry/Description'; interface ProjectDocumentationDetailsProps extends GetDetailsTabDataForObjectQueryResult { disabled: boolean; } +interface ProjectDocumentationDetailsFields { + Description?: string | null; +} + function ProjectDocumentationDetails(props: ProjectDocumentationDetailsProps): React.ReactElement { const { data, loading } = props; + const [details, setDetails] = useState({}); + + useEffect(() => { + if (data && !loading) { + const { ProjectDocumentation } = data.getDetailsTabDataForObject; + setDetails({ + Description: ProjectDocumentation?.Description + }); + } + }, [data, loading]); if (!data || loading) { return ; } - return ProjectDocumentation Details; + const onSetField = (event: React.ChangeEvent) => { + const { value } = event.target; + setDetails(details => ({ ...details, Description: value })); + }; + + return ; } export default ProjectDocumentationDetails; \ No newline at end of file diff --git a/client/src/pages/Repository/components/DetailsView/DetailsTab/UnitDetails.tsx b/client/src/pages/Repository/components/DetailsView/DetailsTab/UnitDetails.tsx index 1d4d47959..305d27a10 100644 --- a/client/src/pages/Repository/components/DetailsView/DetailsTab/UnitDetails.tsx +++ b/client/src/pages/Repository/components/DetailsView/DetailsTab/UnitDetails.tsx @@ -3,22 +3,65 @@ * * This component renders details tab for Unit specific details used in DetailsTab component. */ -import React from 'react'; -import { Loader } from '../../../../../components'; +import { Box } from '@material-ui/core'; +import React, { useEffect, useState } from 'react'; +import { Loader, InputField } from '../../../../../components'; import { GetDetailsTabDataForObjectQueryResult } from '../../../../../types/graphql'; interface UnitDetailsProps extends GetDetailsTabDataForObjectQueryResult { disabled: boolean; } +interface UnitDetailsFields { + Abbreviation?: string | null; + ARKPrefix?: string | null; +} + function UnitDetails(props: UnitDetailsProps): React.ReactElement { - const { data, loading } = props; + const { data, loading, disabled } = props; + const [details, setDetails] = useState({}); + + useEffect(() => { + if (data && !loading) { + const { Unit } = data.getDetailsTabDataForObject; + setDetails({ + Abbreviation: Unit?.Abbreviation, + ARKPrefix: Unit?.ARKPrefix + }); + } + }, [data, loading]); if (!data || loading) { return ; } - return Unit Details; + const onSetField = (event: React.ChangeEvent) => { + const { name, value } = event.target; + setDetails(details => ({ ...details, [name]: value })); + }; + + return ( + + + + + ); } export default UnitDetails; \ No newline at end of file From 979ca88a4ea9e49d688218285530f2eebde002fa Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Mon, 14 Dec 2020 13:12:33 +0530 Subject: [PATCH 134/239] added edgeLength to IngestModel --- .../components/Metadata/Model/UVContents.tsx | 55 +++++++++++++++++-- .../Metadata/Photogrammetry/AssetContents.tsx | 4 +- client/src/pages/Ingestion/hooks/useIngest.ts | 3 +- client/src/store/metadata/metadata.types.ts | 1 + client/src/store/utils.ts | 5 +- client/src/types/graphql.tsx | 5 +- .../queries/asset/getAssetVersionsDetails.ts | 1 + server/graphql/schema.graphql | 2 + server/graphql/schema/asset/queries.graphql | 1 + .../schema/ingestion/mutations.graphql | 1 + server/types/graphql.ts | 2 + 11 files changed, 68 insertions(+), 12 deletions(-) diff --git a/client/src/pages/Ingestion/components/Metadata/Model/UVContents.tsx b/client/src/pages/Ingestion/components/Metadata/Model/UVContents.tsx index 24b4945ff..6c3674c72 100644 --- a/client/src/pages/Ingestion/components/Metadata/Model/UVContents.tsx +++ b/client/src/pages/Ingestion/components/Metadata/Model/UVContents.tsx @@ -4,13 +4,13 @@ * This component renders the uv map type selector for contents present in * the uploaded assets */ -import { Box } from '@material-ui/core'; +import { Box, MenuItem, Select, Typography } from '@material-ui/core'; import React from 'react'; import { AiFillFileImage } from 'react-icons/ai'; import { FieldType } from '../../../../../components'; import { StateUVMap, VocabularyOption } from '../../../../../store'; import { palette } from '../../../../../theme'; -import { Content, ContentHeader, EmptyContent } from '../Photogrammetry/AssetContents'; +import { ContentHeader, EmptyContent, useStyles } from '../Photogrammetry/AssetContents'; interface UVContentsProps { initialEntry: number | null; @@ -24,19 +24,19 @@ function UVContents(props: UVContentsProps): React.ReactElement { return ( - + - {uvMaps.map(({ id, name, mapType }: StateUVMap, index: number) => { + {uvMaps.map(({ id, name, edgeLength, mapType }: StateUVMap, index: number) => { const update = ({ target }) => onUpdate(id, target.value); return ( } initialEntry={initialEntry} options={options} update={update} @@ -48,4 +48,47 @@ function UVContents(props: UVContentsProps): React.ReactElement { ); } +interface ContentProps { + fieldName: string; + value: number | null; + name: string; + edgeLength: number; + initialEntry: number | null; + options: VocabularyOption[]; + update: (event: React.ChangeEvent<{ + name?: string | undefined; + value: unknown; + }>) => void; +} + +export function Content(props: ContentProps): React.ReactElement { + const { fieldName, value, name, edgeLength, initialEntry, update, options } = props; + const classes = useStyles(); + + return ( + + + + + + {name} + + + {edgeLength} + + + + + + ); +} + export default UVContents; diff --git a/client/src/pages/Ingestion/components/Metadata/Photogrammetry/AssetContents.tsx b/client/src/pages/Ingestion/components/Metadata/Photogrammetry/AssetContents.tsx index f5016fcc6..550c38efa 100644 --- a/client/src/pages/Ingestion/components/Metadata/Photogrammetry/AssetContents.tsx +++ b/client/src/pages/Ingestion/components/Metadata/Photogrammetry/AssetContents.tsx @@ -12,7 +12,7 @@ import { FieldType } from '../../../../../components'; import { StateFolder, VocabularyOption } from '../../../../../store'; import { palette } from '../../../../../theme'; -const useStyles = makeStyles(({ palette, typography, breakpoints, spacing }) => ({ +export const useStyles = makeStyles(({ palette, typography, breakpoints, spacing }) => ({ header: { display: 'flex', flex: 1, @@ -137,7 +137,7 @@ interface ContentProps { }>) => void; } -export function Content(props: ContentProps): React.ReactElement { +function Content(props: ContentProps): React.ReactElement { const { fieldName, value, name, icon, initialEntry, update, options } = props; const classes = useStyles(); diff --git a/client/src/pages/Ingestion/hooks/useIngest.ts b/client/src/pages/Ingestion/hooks/useIngest.ts index 64dabe88f..5d12fc4ef 100644 --- a/client/src/pages/Ingestion/hooks/useIngest.ts +++ b/client/src/pages/Ingestion/hooks/useIngest.ts @@ -331,10 +331,11 @@ function useIngest(): UseIngest { const getIngestUVMaps = (uvMaps: StateUVMap[]): IngestUvMapInput[] => { const ingestUVMaps: IngestUvMapInput[] = []; lodash.forEach(uvMaps, (uvMap: StateUVMap) => { - const { name, mapType } = uvMap; + const { name, edgeLength, mapType } = uvMap; const uvMapData: IngestUvMapInput = { name, + edgeLength, mapType: nonNullValue('mapType', mapType) }; diff --git a/client/src/store/metadata/metadata.types.ts b/client/src/store/metadata/metadata.types.ts index e9c1780e0..f26e4358f 100644 --- a/client/src/store/metadata/metadata.types.ts +++ b/client/src/store/metadata/metadata.types.ts @@ -86,6 +86,7 @@ export type PhotogrammetryFields = { export type StateUVMap = { id: number; name: string; + edgeLength: number; mapType: number | null; }; diff --git a/client/src/store/utils.ts b/client/src/store/utils.ts index 2b7aa7705..7c67dda10 100644 --- a/client/src/store/utils.ts +++ b/client/src/store/utils.ts @@ -96,11 +96,12 @@ export function parseFoldersToState(folders: IngestFolder[]): StateFolder[] { } export function parseUVMapsToState(folders: IngestUvMap[]): StateUVMap[] { - const stateFolders: StateUVMap[] = folders.map(({ name, mapType }: IngestUvMap, index: number) => ({ + const uvMaps: StateUVMap[] = folders.map(({ name, edgeLength, mapType }: IngestUvMap, index: number) => ({ id: index, name, + edgeLength, mapType })); - return stateFolders; + return uvMaps; } diff --git a/client/src/types/graphql.tsx b/client/src/types/graphql.tsx index 80dbcbd84..f3fa4c7dd 100644 --- a/client/src/types/graphql.tsx +++ b/client/src/types/graphql.tsx @@ -430,6 +430,7 @@ export type IngestPhotogrammetry = { export type IngestUvMap = { __typename?: 'IngestUVMap'; name: Scalars['String']; + edgeLength: Scalars['Int']; mapType: Scalars['Int']; }; @@ -748,6 +749,7 @@ export type IngestPhotogrammetryInput = { export type IngestUvMapInput = { name: Scalars['String']; + edgeLength: Scalars['Int']; mapType: Scalars['Int']; }; @@ -2059,7 +2061,7 @@ export type GetAssetVersionsDetailsQuery = ( & Pick )>, uvMaps: Array<( { __typename?: 'IngestUVMap' } - & Pick + & Pick )> } )>, Scene?: Maybe<( @@ -3428,6 +3430,7 @@ export const GetAssetVersionsDetailsDocument = gql` } uvMaps { name + edgeLength mapType } roughness diff --git a/server/graphql/api/queries/asset/getAssetVersionsDetails.ts b/server/graphql/api/queries/asset/getAssetVersionsDetails.ts index 270207ac4..1461491a6 100644 --- a/server/graphql/api/queries/asset/getAssetVersionsDetails.ts +++ b/server/graphql/api/queries/asset/getAssetVersionsDetails.ts @@ -66,6 +66,7 @@ const getAssetVersionsDetails = gql` } uvMaps { name + edgeLength mapType } roughness diff --git a/server/graphql/schema.graphql b/server/graphql/schema.graphql index 9f9968c64..e05d6f804 100644 --- a/server/graphql/schema.graphql +++ b/server/graphql/schema.graphql @@ -168,6 +168,7 @@ type IngestPhotogrammetry { type IngestUVMap { name: String! + edgeLength: Int! mapType: Int! } @@ -465,6 +466,7 @@ input IngestPhotogrammetryInput { input IngestUVMapInput { name: String! + edgeLength: Int! mapType: Int! } diff --git a/server/graphql/schema/asset/queries.graphql b/server/graphql/schema/asset/queries.graphql index 5dd6995a0..e06acaa2a 100644 --- a/server/graphql/schema/asset/queries.graphql +++ b/server/graphql/schema/asset/queries.graphql @@ -42,6 +42,7 @@ type IngestPhotogrammetry { type IngestUVMap { name: String! + edgeLength: Int! mapType: Int! } diff --git a/server/graphql/schema/ingestion/mutations.graphql b/server/graphql/schema/ingestion/mutations.graphql index 36f956c4c..899c25ebe 100644 --- a/server/graphql/schema/ingestion/mutations.graphql +++ b/server/graphql/schema/ingestion/mutations.graphql @@ -53,6 +53,7 @@ input IngestPhotogrammetryInput { input IngestUVMapInput { name: String! + edgeLength: Int! mapType: Int! } diff --git a/server/types/graphql.ts b/server/types/graphql.ts index 019e96a62..fab18ddff 100644 --- a/server/types/graphql.ts +++ b/server/types/graphql.ts @@ -426,6 +426,7 @@ export type IngestPhotogrammetry = { export type IngestUvMap = { __typename?: 'IngestUVMap'; name: Scalars['String']; + edgeLength: Scalars['Int']; mapType: Scalars['Int']; }; @@ -744,6 +745,7 @@ export type IngestPhotogrammetryInput = { export type IngestUvMapInput = { name: Scalars['String']; + edgeLength: Scalars['Int']; mapType: Scalars['Int']; }; From d40894870c668d2e052f38311015623f6954ce3a Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Mon, 14 Dec 2020 14:21:45 +0530 Subject: [PATCH 135/239] added checkbox field --- .../src/components/controls/CheckboxField.tsx | 42 +++++++++++++++++++ client/src/components/index.ts | 6 ++- 2 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 client/src/components/controls/CheckboxField.tsx diff --git a/client/src/components/controls/CheckboxField.tsx b/client/src/components/controls/CheckboxField.tsx new file mode 100644 index 000000000..bc838175f --- /dev/null +++ b/client/src/components/controls/CheckboxField.tsx @@ -0,0 +1,42 @@ +/** + * CheckboxField + * + * This component renders checkbox field used in ingestion and repository UI. + */ +import { Checkbox } from '@material-ui/core'; +import React from 'react'; +import { withDefaultValueBoolean } from '../../utils/shared'; +import FieldType from '../shared/FieldType'; + +interface CheckboxFieldProps { + label: string; + name: string; + value: boolean | null; + onChange: ((event: React.ChangeEvent, checked: boolean) => void) | undefined; + required?: boolean; + viewMode?: boolean; +} + +function CheckboxField(props: CheckboxFieldProps): React.ReactElement { + const { label, name, value, onChange, required = false, viewMode = false } = props; + const rowFieldProps = { alignItems: 'center', justifyContent: 'space-between', style: { borderRadius: 0 } }; + + return ( + + + + ); +} + +export default CheckboxField; \ No newline at end of file diff --git a/client/src/components/index.ts b/client/src/components/index.ts index ed907d64d..e25226743 100644 --- a/client/src/components/index.ts +++ b/client/src/components/index.ts @@ -19,6 +19,8 @@ import DateInputField from './controls/DateInputField'; import BreadcrumbsView from './shared/BreadcrumbsView'; import NewTabLink from './shared/NewTabLink'; import EmptyTable from './shared/EmptyTable'; +import DebounceNumberInput from './controls/DebounceNumberInput'; +import CheckboxField from './controls/CheckboxField'; export { Header, @@ -37,5 +39,7 @@ export { DateInputField, BreadcrumbsView, NewTabLink, - EmptyTable + EmptyTable, + DebounceNumberInput, + CheckboxField }; From 28f0948bb717815effb6d7d7f1546ce9d30b1203 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Mon, 14 Dec 2020 14:22:26 +0530 Subject: [PATCH 136/239] added debounce number input --- .../controls/DebounceNumberInput.tsx | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 client/src/components/controls/DebounceNumberInput.tsx diff --git a/client/src/components/controls/DebounceNumberInput.tsx b/client/src/components/controls/DebounceNumberInput.tsx new file mode 100644 index 000000000..f939f4a38 --- /dev/null +++ b/client/src/components/controls/DebounceNumberInput.tsx @@ -0,0 +1,51 @@ +/** + * DebounceNumberInput + * + * This is the component render debounced number input. + */ +import { fade, makeStyles } from '@material-ui/core/styles'; +import React from 'react'; +import { DebounceInput } from 'react-debounce-input'; + +const useStyles = makeStyles(({ palette, typography, breakpoints }) => ({ + input: { + width: '16%', + outline: 'none', + border: `1px solid ${fade(palette.primary.contrastText, 0.4)}`, + padding: 8, + borderRadius: 5, + marginLeft: 5, + fontWeight: typography.fontWeightRegular, + fontFamily: typography.fontFamily, + [breakpoints.down('lg')]: { + fontSize: '0.8em', + minWidth: 50, + maxWidth: 50, + } + } +})); + +interface DebounceNumberInputProps { + value?: number | null; + name: string; + onChange: (event: React.ChangeEvent) => void; +} + +function DebounceNumberInput(props: DebounceNumberInputProps): React.ReactElement { + const { value, name, onChange } = props; + const classes = useStyles(); + + return ( + + ); +} + +export default DebounceNumberInput; \ No newline at end of file From 19110b98085e207eb2396e63e6bdef8509eff6da Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Mon, 14 Dec 2020 14:28:07 +0530 Subject: [PATCH 137/239] update model ingestion components for reusability --- .../components/controls/DateInputField.tsx | 1 - client/src/components/controls/InputField.tsx | 2 +- .../src/components/controls/SelectField.tsx | 2 +- .../Metadata/Model/BoundingBoxInput.tsx | 57 +++---------------- .../components/Metadata/Model/index.tsx | 46 ++------------- 5 files changed, 15 insertions(+), 93 deletions(-) diff --git a/client/src/components/controls/DateInputField.tsx b/client/src/components/controls/DateInputField.tsx index 32ee795b5..d8942f1ca 100644 --- a/client/src/components/controls/DateInputField.tsx +++ b/client/src/components/controls/DateInputField.tsx @@ -18,7 +18,6 @@ const useStyles = makeStyles(({ palette, typography, breakpoints }) => ({ border: `1px solid ${fade(palette.primary.contrastText, 0.4)}`, padding: '1px 8px', color: Colors.defaults.white, - borderRadius: 5, marginTop: 0, fontFamily: typography.fontFamily, [breakpoints.down('lg')]: { diff --git a/client/src/components/controls/InputField.tsx b/client/src/components/controls/InputField.tsx index 233be1d7a..80049fc9a 100644 --- a/client/src/components/controls/InputField.tsx +++ b/client/src/components/controls/InputField.tsx @@ -41,7 +41,7 @@ function InputField(props: InputFieldProps): React.ReactElement { const { label, name, value, onChange, type, required = false, viewMode = false, disabled = false } = props; const classes = useStyles(); - const rowFieldProps = { alignItems: 'center', justifyContent: 'space-between' }; + const rowFieldProps = { alignItems: 'center', justifyContent: 'space-between', style: { borderRadius: 0 } }; return ( - - - + + + - - - + + + ); } -const useStyles = makeStyles(({ palette, typography, breakpoints }) => ({ - input: { - width: '16%', - outline: 'none', - border: `1px solid ${fade(palette.primary.contrastText, 0.4)}`, - padding: 8, - borderRadius: 5, - marginLeft: 5, - fontWeight: typography.fontWeightRegular, - fontFamily: typography.fontFamily, - [breakpoints.down('lg')]: { - fontSize: '0.8em', - minWidth: 50, - maxWidth: 50, - } - } -})); - -interface NumberDebounceInputProps { - value: number | null; - name: string; - onChange: (event: React.ChangeEvent) => void; -} - -function NumberDebounceInput(props: NumberDebounceInputProps): React.ReactElement { - const { value, name, onChange } = props; - const classes = useStyles(); - - return ( - - ); -} - export default BoundingBoxInput; \ No newline at end of file diff --git a/client/src/pages/Ingestion/components/Metadata/Model/index.tsx b/client/src/pages/Ingestion/components/Metadata/Model/index.tsx index c105c54a4..eda17ead0 100644 --- a/client/src/pages/Ingestion/components/Metadata/Model/index.tsx +++ b/client/src/pages/Ingestion/components/Metadata/Model/index.tsx @@ -6,12 +6,12 @@ import { Box, Checkbox } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; import React, { useState } from 'react'; -import { AssetIdentifiers, DateInputField, FieldType, InputField, SelectField } from '../../../../../components'; +import { AssetIdentifiers, CheckboxField, DateInputField, FieldType, InputField, SelectField } from '../../../../../components'; import { StateIdentifier, StateRelatedObject, useMetadataStore, useVocabularyStore } from '../../../../../store'; import { MetadataType } from '../../../../../store/metadata'; import { RelatedObjectType } from '../../../../../types/graphql'; import { eVocabularySetID } from '../../../../../types/server'; -import { withDefaultValueBoolean, withDefaultValueNumber } from '../../../../../utils/shared'; +import { withDefaultValueNumber } from '../../../../../utils/shared'; import BoundingBoxInput from './BoundingBoxInput'; import ObjectSelectModal from './ObjectSelectModal'; import RelatedObjectsList from './RelatedObjectsList'; @@ -247,44 +247,10 @@ function Model(props: ModelProps): React.ReactElement { name='faceCount' onChange={setIdField} /> - - - - - - - - - - - - - - - - - - + + + + From 57019d71421791fc32152f20bb5d704d13e5b2ce Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Mon, 14 Dec 2020 14:28:40 +0530 Subject: [PATCH 138/239] added actor details --- .../DetailsView/DetailsTab/ActorDetails.tsx | 41 +++++++++++++++++-- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/client/src/pages/Repository/components/DetailsView/DetailsTab/ActorDetails.tsx b/client/src/pages/Repository/components/DetailsView/DetailsTab/ActorDetails.tsx index 7a3a491d6..37383559f 100644 --- a/client/src/pages/Repository/components/DetailsView/DetailsTab/ActorDetails.tsx +++ b/client/src/pages/Repository/components/DetailsView/DetailsTab/ActorDetails.tsx @@ -3,22 +3,55 @@ * * This component renders details tab for Actor specific details used in DetailsTab component. */ -import React from 'react'; -import { Loader } from '../../../../../components'; +import { Box } from '@material-ui/core'; +import React, { useEffect, useState } from 'react'; +import { InputField, Loader } from '../../../../../components'; import { GetDetailsTabDataForObjectQueryResult } from '../../../../../types/graphql'; interface ActorDetailsProps extends GetDetailsTabDataForObjectQueryResult { disabled: boolean; } +interface ActorDetailsFields { + OrganizationName?: string | null; +} + function ActorDetails(props: ActorDetailsProps): React.ReactElement { - const { data, loading } = props; + const { data, loading, disabled, } = props; + + const [details, setDetails] = useState({}); + + useEffect(() => { + if (data && !loading) { + const { Actor } = data.getDetailsTabDataForObject; + setDetails({ + OrganizationName: Actor?.OrganizationName, + }); + } + }, [data, loading]); if (!data || loading) { return ; } - return Actor Details; + const onSetField = (event: React.ChangeEvent) => { + const { name, value } = event.target; + setDetails(details => ({ ...details, [name]: value })); + }; + + return ( + + + + ); } export default ActorDetails; \ No newline at end of file From 261c7650e3ae5f978aa7bae9157c7b8128cbb2e7 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Mon, 14 Dec 2020 14:29:16 +0530 Subject: [PATCH 139/239] added stakeholder details component --- .../DetailsTab/StakeholderDetails.tsx | 85 ++++++++++++++++++- 1 file changed, 81 insertions(+), 4 deletions(-) diff --git a/client/src/pages/Repository/components/DetailsView/DetailsTab/StakeholderDetails.tsx b/client/src/pages/Repository/components/DetailsView/DetailsTab/StakeholderDetails.tsx index fcd505488..86cc7ca7d 100644 --- a/client/src/pages/Repository/components/DetailsView/DetailsTab/StakeholderDetails.tsx +++ b/client/src/pages/Repository/components/DetailsView/DetailsTab/StakeholderDetails.tsx @@ -3,22 +3,99 @@ * * This component renders details tab for Stakeholder specific details used in DetailsTab component. */ -import React from 'react'; -import { Loader } from '../../../../../components'; +import { Box } from '@material-ui/core'; +import React, { useEffect, useState } from 'react'; +import { InputField, Loader } from '../../../../../components'; import { GetDetailsTabDataForObjectQueryResult } from '../../../../../types/graphql'; interface StakeholderDetailsProps extends GetDetailsTabDataForObjectQueryResult { disabled: boolean; } +interface StakeholderDetailsFields { + OrganizationName?: string | null; + EmailAddress?: string | null; + PhoneNumberMobile?: string | null; + PhoneNumberOffice?: string | null; + MailingAddress?: string | null; +} + function StakeholderDetails(props: StakeholderDetailsProps): React.ReactElement { - const { data, loading } = props; + const { data, loading, disabled, } = props; + + const [details, setDetails] = useState({}); + + useEffect(() => { + if (data && !loading) { + const { Stakeholder } = data.getDetailsTabDataForObject; + setDetails({ + OrganizationName: Stakeholder?.OrganizationName, + EmailAddress: Stakeholder?.EmailAddress, + PhoneNumberMobile: Stakeholder?.PhoneNumberMobile, + PhoneNumberOffice: Stakeholder?.PhoneNumberOffice, + MailingAddress: Stakeholder?.MailingAddress, + }); + } + }, [data, loading]); if (!data || loading) { return ; } - return Stakeholder Details; + const onSetField = (event: React.ChangeEvent) => { + const { name, value } = event.target; + setDetails(details => ({ ...details, [name]: value })); + }; + + return ( + + + + + + + + ); } export default StakeholderDetails; \ No newline at end of file From 8f44c00f02d1cffd44b0bf5d62572be84d2e4d5d Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Mon, 14 Dec 2020 14:30:56 +0530 Subject: [PATCH 140/239] added items details component --- .../DetailsView/DetailsTab/SubjectDetails.tsx | 187 +++++++++++++++++- 1 file changed, 182 insertions(+), 5 deletions(-) diff --git a/client/src/pages/Repository/components/DetailsView/DetailsTab/SubjectDetails.tsx b/client/src/pages/Repository/components/DetailsView/DetailsTab/SubjectDetails.tsx index 799609584..819fe40d0 100644 --- a/client/src/pages/Repository/components/DetailsView/DetailsTab/SubjectDetails.tsx +++ b/client/src/pages/Repository/components/DetailsView/DetailsTab/SubjectDetails.tsx @@ -1,24 +1,201 @@ /** * SubjectDetails * - * This component renders details tab for Actor specific details used in DetailsTab component. + * This component renders details tab for Subject specific details used in DetailsTab component. */ -import React from 'react'; -import { Loader } from '../../../../../components'; +import { Box } from '@material-ui/core'; +import React, { useEffect, useState } from 'react'; +import { DebounceNumberInput, FieldType, InputField, Loader } from '../../../../../components'; import { GetDetailsTabDataForObjectQueryResult } from '../../../../../types/graphql'; interface SubjectDetailsProps extends GetDetailsTabDataForObjectQueryResult { disabled: boolean; } +export interface SubjectDetailsFields { + Latitude?: number | null; + Longitude?: number | null; + Altitude?: number | null; + TS0?: number | null; + TS1?: number | null; + TS2?: number | null; + R0?: number | null; + R1?: number | null; + R2?: number | null; + R3?: number | null; +} + function SubjectDetails(props: SubjectDetailsProps): React.ReactElement { - const { data, loading } = props; + const { data, loading, disabled, } = props; + + const [details, setDetails] = useState({}); + + useEffect(() => { + if (data && !loading) { + const { Subject } = data.getDetailsTabDataForObject; + setDetails({ + Latitude: Subject?.GeoLocation?.Latitude, + Longitude: Subject?.GeoLocation?.Longitude, + Altitude: Subject?.GeoLocation?.Altitude, + TS0: Subject?.GeoLocation?.TS0, + TS1: Subject?.GeoLocation?.TS1, + TS2: Subject?.GeoLocation?.TS2, + R0: Subject?.GeoLocation?.R0, + R1: Subject?.GeoLocation?.R1, + R2: Subject?.GeoLocation?.R2, + R3: Subject?.GeoLocation?.R3 + }); + } + }, [data, loading]); if (!data || loading) { return ; } - return Subject Details; + const onSetField = (event: React.ChangeEvent) => { + const { name, value } = event.target; + setDetails(details => ({ ...details, [name]: value })); + }; + + return ( + + + + ); +} + +interface SubjectFieldsProps extends SubjectDetailsFields { + disabled: boolean; + onChange: (event: React.ChangeEvent) => void; +} + +export function SubjectFields(props: SubjectFieldsProps): React.ReactElement { + const { + Latitude, + Longitude, + Altitude, + TS0, + TS1, + TS2, + R0, + R1, + R2, + R3, + disabled, + onChange + } = props; + + return ( + + + + + + + + ); +} + +interface RotationOriginInputProps { + TS0?: number | null; + TS1?: number | null; + TS2?: number | null; + onChange: (event: React.ChangeEvent) => void; +} + +function RotationOriginInput(props: RotationOriginInputProps): React.ReactElement { + const { TS0, TS1, TS2, onChange } = props; + + const rowFieldProps = { justifyContent: 'space-between', style: { borderRadius: 0 } }; + + return ( + + + + + + + + + + ); +} + +interface RotationQuaternionInputProps { + R0?: number | null; + R1?: number | null; + R2?: number | null; + R3?: number | null; + onChange: (event: React.ChangeEvent) => void; +} + +function RotationQuaternionInput(props: RotationQuaternionInputProps): React.ReactElement { + const { R0, R1, R2, R3, onChange } = props; + + const rowFieldProps = { justifyContent: 'space-between', style: { borderRadius: 0 } }; + + return ( + + + + + + + + + + + ); } export default SubjectDetails; \ No newline at end of file From e72f74bf5498924cf1f3118913f5a3d69ea7d91e Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Mon, 14 Dec 2020 14:32:09 +0530 Subject: [PATCH 141/239] added subjects details component --- .../DetailsView/DetailsTab/ItemDetails.tsx | 57 +++++++++++++++++-- .../DetailsView/DetailsTab/UnitDetails.tsx | 2 +- 2 files changed, 54 insertions(+), 5 deletions(-) diff --git a/client/src/pages/Repository/components/DetailsView/DetailsTab/ItemDetails.tsx b/client/src/pages/Repository/components/DetailsView/DetailsTab/ItemDetails.tsx index a4cc89711..35ad3fc2f 100644 --- a/client/src/pages/Repository/components/DetailsView/DetailsTab/ItemDetails.tsx +++ b/client/src/pages/Repository/components/DetailsView/DetailsTab/ItemDetails.tsx @@ -3,22 +3,71 @@ * * This component renders details tab for Item specific details used in DetailsTab component. */ -import React from 'react'; -import { Loader } from '../../../../../components'; +import { Box } from '@material-ui/core'; +import React, { useEffect, useState } from 'react'; +import { CheckboxField, Loader } from '../../../../../components'; import { GetDetailsTabDataForObjectQueryResult } from '../../../../../types/graphql'; +import { SubjectDetailsFields, SubjectFields } from './SubjectDetails'; interface ItemDetailsProps extends GetDetailsTabDataForObjectQueryResult { disabled: boolean; } +interface ItemDetailsFields extends SubjectDetailsFields { + EntireSubject?: boolean; +} + function ItemDetails(props: ItemDetailsProps): React.ReactElement { - const { data, loading } = props; + const { data, loading, disabled, } = props; + + const [details, setDetails] = useState({}); + + useEffect(() => { + if (data && !loading) { + const { Item } = data.getDetailsTabDataForObject; + setDetails({ + EntireSubject: Item?.EntireSubject, + Latitude: Item?.GeoLocation?.Latitude, + Longitude: Item?.GeoLocation?.Longitude, + Altitude: Item?.GeoLocation?.Altitude, + TS0: Item?.GeoLocation?.TS0, + TS1: Item?.GeoLocation?.TS1, + TS2: Item?.GeoLocation?.TS2, + R0: Item?.GeoLocation?.R0, + R1: Item?.GeoLocation?.R1, + R2: Item?.GeoLocation?.R2, + R3: Item?.GeoLocation?.R3 + }); + } + }, [data, loading]); if (!data || loading) { return ; } - return Item Details; + const onSetField = (event: React.ChangeEvent) => { + const { name, value } = event.target; + setDetails(details => ({ ...details, [name]: value })); + }; + + const setCheckboxField = ({ target }): void => { + const { name, checked } = target; + setDetails(details => ({ ...details, [name]: checked })); + }; + + return ( + + + + + ); } export default ItemDetails; \ No newline at end of file diff --git a/client/src/pages/Repository/components/DetailsView/DetailsTab/UnitDetails.tsx b/client/src/pages/Repository/components/DetailsView/DetailsTab/UnitDetails.tsx index 305d27a10..f605aedd3 100644 --- a/client/src/pages/Repository/components/DetailsView/DetailsTab/UnitDetails.tsx +++ b/client/src/pages/Repository/components/DetailsView/DetailsTab/UnitDetails.tsx @@ -5,7 +5,7 @@ */ import { Box } from '@material-ui/core'; import React, { useEffect, useState } from 'react'; -import { Loader, InputField } from '../../../../../components'; +import { InputField, Loader } from '../../../../../components'; import { GetDetailsTabDataForObjectQueryResult } from '../../../../../types/graphql'; interface UnitDetailsProps extends GetDetailsTabDataForObjectQueryResult { From ab62883165b05415931ad5b47c826db7710c113f Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Mon, 14 Dec 2020 20:29:34 +0530 Subject: [PATCH 142/239] minor fixes --- .../components/DetailsView/DetailsTab/SubjectDetails.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/pages/Repository/components/DetailsView/DetailsTab/SubjectDetails.tsx b/client/src/pages/Repository/components/DetailsView/DetailsTab/SubjectDetails.tsx index 819fe40d0..8a24b579b 100644 --- a/client/src/pages/Repository/components/DetailsView/DetailsTab/SubjectDetails.tsx +++ b/client/src/pages/Repository/components/DetailsView/DetailsTab/SubjectDetails.tsx @@ -149,7 +149,7 @@ function RotationOriginInput(props: RotationOriginInputProps): React.ReactElemen return ( Date: Mon, 14 Dec 2020 09:47:34 -0800 Subject: [PATCH 143/239] Apache Solr Integration: * Initial, temporary Dockerfile for solr * Initial integration of solr-client * Initial integration of solr configuration for packrat --- docker-compose.dev.yml | 13 + docker/dev.Dockerfile | 3 + docker/prod.Dockerfile | 5 +- docker/solr.Dockerfile | 2 + .../solr/data/packrat/conf/protwords.txt | 21 + .../config/solr/data/packrat/conf/schema.xml | 540 +++++++ .../solr/data/packrat/conf/solrconfig.xml | 1302 +++++++++++++++++ .../solr/data/packrat/conf/stopwords.txt | 14 + .../solr/data/packrat/conf/synonyms.txt | 29 + .../config/solr/data/packrat/core.properties | 3 + server/package.json | 1 + yarn.lock | 50 +- 12 files changed, 1979 insertions(+), 4 deletions(-) create mode 100644 docker/solr.Dockerfile create mode 100644 server/config/solr/data/packrat/conf/protwords.txt create mode 100644 server/config/solr/data/packrat/conf/schema.xml create mode 100644 server/config/solr/data/packrat/conf/solrconfig.xml create mode 100644 server/config/solr/data/packrat/conf/stopwords.txt create mode 100644 server/config/solr/data/packrat/conf/synonyms.txt create mode 100644 server/config/solr/data/packrat/core.properties diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 979eb9e24..64a919204 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -69,6 +69,19 @@ services: - .env volumes: - ./server/db/sql:/app/ + + packrat-solr: + container_name: packrat-solr + image: packrat-solr + restart: always + build: + context: . + dockerfile: ./docker/solr.Dockerfile + target: solr + ports: + - $PACKRAT_SOLR_PORT:8983 + env_file: + - .env networks: default: ipam: diff --git a/docker/dev.Dockerfile b/docker/dev.Dockerfile index cef2b38c3..a028f885a 100644 --- a/docker/dev.Dockerfile +++ b/docker/dev.Dockerfile @@ -35,3 +35,6 @@ COPY nginx.conf /etc/nginx/nginx.conf CMD ["nginx", "-g", "daemon off;"] FROM mariadb:10.5 as db + +FROM solr:8 as solr +COPY --chown=solr:solr ./server/config/solr/data/packrat/ /var/solr/data/packrat/ diff --git a/docker/prod.Dockerfile b/docker/prod.Dockerfile index 422c56230..1059391e2 100644 --- a/docker/prod.Dockerfile +++ b/docker/prod.Dockerfile @@ -47,4 +47,7 @@ FROM nginx:1.17.10 as proxy EXPOSE 80 RUN rm /usr/share/nginx/html/* COPY nginx.conf /etc/nginx/nginx.conf -CMD ["nginx", "-g", "daemon off;"] \ No newline at end of file +CMD ["nginx", "-g", "daemon off;"] + +FROM solr:8 as solr +COPY --chown=solr:solr ./server/config/solr/data/packrat/ /var/solr/data/packrat/ diff --git a/docker/solr.Dockerfile b/docker/solr.Dockerfile new file mode 100644 index 000000000..604434cf4 --- /dev/null +++ b/docker/solr.Dockerfile @@ -0,0 +1,2 @@ +FROM solr:8 as solr +COPY --chown=solr:solr ./server/config/solr/data/packrat/ /var/solr/data/packrat/ diff --git a/server/config/solr/data/packrat/conf/protwords.txt b/server/config/solr/data/packrat/conf/protwords.txt new file mode 100644 index 000000000..1dfc0abec --- /dev/null +++ b/server/config/solr/data/packrat/conf/protwords.txt @@ -0,0 +1,21 @@ +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +#----------------------------------------------------------------------- +# Use a protected word file to protect against the stemmer reducing two +# unrelated words to the same base word. + +# Some non-words that normally won't be encountered, +# just to test that they won't be stemmed. +dontstems +zwhacky + diff --git a/server/config/solr/data/packrat/conf/schema.xml b/server/config/solr/data/packrat/conf/schema.xml new file mode 100644 index 000000000..d55c90249 --- /dev/null +++ b/server/config/solr/data/packrat/conf/schema.xml @@ -0,0 +1,540 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + --> + --> + --> + --> + --> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + idSystemObject + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/server/config/solr/data/packrat/conf/solrconfig.xml b/server/config/solr/data/packrat/conf/solrconfig.xml new file mode 100644 index 000000000..9e07f1c9c --- /dev/null +++ b/server/config/solr/data/packrat/conf/solrconfig.xml @@ -0,0 +1,1302 @@ + + + + + + + + + + 8.7.0 + + + + + + + + + + + ${solr.data.dir:} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ${solr.lock.type:native} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ${solr.ulog.dir:} + ${solr.ulog.numVersionBuckets:65536} + + + + + ${solr.autoCommit.maxTime:15000} + false + + + + + + ${solr.autoSoftCommit.maxTime:-1} + + + + + + + + + + + + + + ${solr.max.booleanClauses:1024} + + + + + + + + + + + + + + + + + + + + + + + + true + + + + + + 20 + + + 200 + + + + + + + + + + + + + + + + + + + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + explicit + 10 + + + + + + + + + + + + + + + + explicit + json + true + + + + + + _text_ + + + + + + + + + text_general + + + + + + default + _text_ + solr.DirectSolrSpellChecker + + internal + + 0.5 + + 2 + + 1 + + 5 + + 4 + + 0.01 + + + + + + + + + + + + default + on + true + 10 + 5 + 5 + true + true + 10 + 5 + + + spellcheck + + + + + + + + + + true + false + + + terms + + + + + + + + + + + 100 + + + + + + + + 70 + + 0.5 + + [-\w ,/\n\"']{20,200} + + + + + + + ]]> + ]]> + + + + + + + + + + + + + + + + + + + + + + + + ,, + ,, + ,, + ,, + ,]]> + ]]> + + + + + + 10 + .,!? + + + + + + + WORD + + + en + US + + + + + + + + + + + + [^\w-\.] + _ + + + + + + + yyyy-MM-dd['T'[HH:mm[:ss[.SSS]][z + yyyy-MM-dd['T'[HH:mm[:ss[,SSS]][z + yyyy-MM-dd HH:mm[:ss[.SSS]][z + yyyy-MM-dd HH:mm[:ss[,SSS]][z + [EEE, ]dd MMM yyyy HH:mm[:ss] z + EEEE, dd-MMM-yy HH:mm:ss z + EEE MMM ppd HH:mm:ss [z ]yyyy + + + + + java.lang.String + text_general + + *_str + 256 + + + true + + + java.lang.Boolean + booleans + + + java.util.Date + pdates + + + java.lang.Long + java.lang.Integer + plongs + + + java.lang.Number + pdoubles + + + + + + + + + + + + + + + + + + + + text/plain; charset=UTF-8 + + + + + + + + + + + + + + diff --git a/server/config/solr/data/packrat/conf/stopwords.txt b/server/config/solr/data/packrat/conf/stopwords.txt new file mode 100644 index 000000000..ae1e83eeb --- /dev/null +++ b/server/config/solr/data/packrat/conf/stopwords.txt @@ -0,0 +1,14 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/server/config/solr/data/packrat/conf/synonyms.txt b/server/config/solr/data/packrat/conf/synonyms.txt new file mode 100644 index 000000000..eab4ee875 --- /dev/null +++ b/server/config/solr/data/packrat/conf/synonyms.txt @@ -0,0 +1,29 @@ +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +#----------------------------------------------------------------------- +#some test synonym mappings unlikely to appear in real input text +aaafoo => aaabar +bbbfoo => bbbfoo bbbbar +cccfoo => cccbar cccbaz +fooaaa,baraaa,bazaaa + +# Some synonym groups specific to this example +GB,gib,gigabyte,gigabytes +MB,mib,megabyte,megabytes +Television, Televisions, TV, TVs +#notice we use "gib" instead of "GiB" so any WordDelimiterGraphFilter coming +#after us won't split it into two words. + +# Synonym mappings can be used for spelling correction too +pixima => pixma + diff --git a/server/config/solr/data/packrat/core.properties b/server/config/solr/data/packrat/core.properties new file mode 100644 index 000000000..ef1f0005f --- /dev/null +++ b/server/config/solr/data/packrat/core.properties @@ -0,0 +1,3 @@ +#Written by CorePropertiesLocator +#Sat Dec 12 22:25:23 UTC 2020 +name=packrat diff --git a/server/package.json b/server/package.json index 034a55f69..4d99870e2 100644 --- a/server/package.json +++ b/server/package.json @@ -64,6 +64,7 @@ "node-stream-zip": "1.11.3", "passport": "0.4.1", "passport-local": "1.0.0", + "solr-client": "0.7.1", "supertest": "4.0.2", "uuid": "8.3.0", "winston": "3.3.3" diff --git a/yarn.lock b/yarn.lock index 4c5ec820b..37566bcf9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3936,6 +3936,14 @@ JSONStream@^1.0.4, JSONStream@^1.3.4: jsonparse "^1.2.0" through ">=2.2.7 <3" +JSONStream@~1.0.6: + version "1.0.7" + resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.0.7.tgz#700c8e4711fef1ce421f650bead55235bb21d7de" + integrity sha1-cAyORxH+8c5CH2UL6tVSNbsh194= + dependencies: + jsonparse "^1.1.0" + through ">=2.2.7 <3" + abab@^2.0.0: version "2.0.5" resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.5.tgz#c0b678fb32d60fc1219c784d6a826fe385aeb79a" @@ -4881,6 +4889,11 @@ big.js@^5.2.2: resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ== +bignumber.js@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-1.1.1.tgz#1a415d9ac014c13256af1feed9d1a3e5717a8cf7" + integrity sha1-GkFdmsAUwTJWrx/u2dGj5XF6jPc= + binary-extensions@^1.0.0: version "1.13.1" resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65" @@ -4898,7 +4911,7 @@ bindings@^1.5.0: dependencies: file-uri-to-path "1.0.0" -bluebird@^3.5.1, bluebird@^3.5.3, bluebird@^3.5.5, bluebird@^3.7.2: +bluebird@^3.5.0, bluebird@^3.5.1, bluebird@^3.5.3, bluebird@^3.5.5, bluebird@^3.7.2: version "3.7.2" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== @@ -6981,7 +6994,7 @@ duplexer3@^0.1.4: resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI= -duplexer@^0.1.1: +duplexer@^0.1.1, duplexer@~0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6" integrity sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg== @@ -8859,6 +8872,11 @@ hmac-drbg@^1.0.0: minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.1" +hnp@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/hnp/-/hnp-0.0.1.tgz#d91489a5dfcdf41ce741584298a73066576a9286" + integrity sha1-2RSJpd/N9BznQVhCmKcwZldqkoY= + hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" @@ -9058,6 +9076,13 @@ http-signature@~1.2.0: jsprim "^1.2.2" sshpk "^1.7.0" +httperror@~0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/httperror/-/httperror-0.2.3.tgz#c96e0d66cbcf6e0e19d80e4727a95a09d75fe0b8" + integrity sha1-yW4NZsvPbg4Z2A5HJ6laCddf4Lg= + dependencies: + hnp "0.0.1" + https-browserify@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" @@ -10415,6 +10440,13 @@ jsesc@~0.5.0: resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" integrity sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0= +json-bigint@~0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/json-bigint/-/json-bigint-0.1.4.tgz#b5d40b8a9009e92f157f7c079db097001830e01e" + integrity sha1-tdQLipAJ6S8Vf3wHnbCXABgw4B4= + dependencies: + bignumber.js "~1.1.1" + json-buffer@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898" @@ -10505,7 +10537,7 @@ jsonify@~0.0.0: resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" integrity sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM= -jsonparse@^1.2.0: +jsonparse@^1.1.0, jsonparse@^1.2.0: version "1.3.1" resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280" integrity sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA= @@ -15185,6 +15217,18 @@ socks@~2.3.2: ip "1.1.5" smart-buffer "^4.1.0" +solr-client@0.7.1: + version "0.7.1" + resolved "https://registry.yarnpkg.com/solr-client/-/solr-client-0.7.1.tgz#e9deee1cc4344877bd24731ce647d9a2651176c4" + integrity sha512-8jfyInG1o/fakCLlLCQUnS/BPyY0HJ67zLQPdUUtWYfswwzMkS5O8ej1SKGsmDTE0utwPdWFKiKQQhX0VmoxRQ== + dependencies: + JSONStream "~1.0.6" + bluebird "^3.5.0" + duplexer "~0.1.1" + httperror "~0.2.3" + json-bigint "~0.1.4" + request "^2.88.0" + sort-keys@^1.0.0: version "1.1.2" resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-1.1.2.tgz#441b6d4d346798f1b4e49e8920adfba0e543f9ad" From 093c4f27b64e4927c967820526ab7919a71f8992 Mon Sep 17 00:00:00 2001 From: Jon Tyson Date: Tue, 15 Dec 2020 01:33:12 -0800 Subject: [PATCH 144/239] Solr Integration & Initial Indexing: * Added /solrindex path to packrat-server for use in initiating a full reindexing of content by solr. This is a temporary approach, likely to be moved into a service * Added NAVIGATION_TYPE.SOLR enumeration * Extended SOLR schema with fields needed for hierarchy traversal * Added CaptureData.Name to schema * Added fetchAll methods to CaptureData, CaptureDataPhoto, Item, Subject; these methods should be updates to provide paged output * Placeholder implementation of NavigationSolr, to be replaced with a real Solr-based implementation * Initial implementation of a full indexing update for SOLR, for Units, Projects, Subjects, Items, and CaptureData. To be completed with the other object types, and to be updated with paged SQL queries --- client/src/types/graphql.tsx | 1 + server/config/index.ts | 5 +- .../config/solr/data/packrat/conf/schema.xml | 31 +- server/db/api/CaptureData.ts | 19 +- server/db/api/CaptureDataPhoto.ts | 10 + server/db/api/Item.ts | 10 + server/db/api/Subject.ts | 10 + server/db/prisma/schema.prisma | 1 + server/db/sql/scripts/Packrat.SCHEMA.sql | 1 + server/graphql/schema.graphql | 1 + .../schema/capturedata/mutations.graphql | 1 + .../resolvers/mutations/createCaptureData.ts | 2 + .../resolvers/mutations/ingestData.ts | 1 + server/index.ts | 7 + .../impl/NavigationSolr/NavigationSolr.ts | 384 +++++++++++++++++ .../impl/NavigationSolr/ReindexSolr.ts | 385 ++++++++++++++++++ .../impl/NavigationSolr/SolrClient.ts | 16 + .../navigation/impl/NavigationSolr/index.ts | 2 + .../navigation/interface/NavigationFactory.ts | 5 + .../tests/db/composite/ObjectGraph.setup.ts | 4 +- server/tests/db/dbcreation.test.ts | 52 ++- server/tests/graphql/utils/index.ts | 1 + server/types/graphql.ts | 1 + 23 files changed, 925 insertions(+), 25 deletions(-) create mode 100644 server/navigation/impl/NavigationSolr/NavigationSolr.ts create mode 100644 server/navigation/impl/NavigationSolr/ReindexSolr.ts create mode 100644 server/navigation/impl/NavigationSolr/SolrClient.ts create mode 100644 server/navigation/impl/NavigationSolr/index.ts diff --git a/client/src/types/graphql.tsx b/client/src/types/graphql.tsx index f3fa4c7dd..739742370 100644 --- a/client/src/types/graphql.tsx +++ b/client/src/types/graphql.tsx @@ -593,6 +593,7 @@ export type AssetGroup = { }; export type CreateCaptureDataInput = { + Name: Scalars['String']; idVCaptureMethod: Scalars['Int']; DateCaptured: Scalars['DateTime']; Description: Scalars['String']; diff --git a/server/config/index.ts b/server/config/index.ts index 5970d2eec..a17a8ed50 100644 --- a/server/config/index.ts +++ b/server/config/index.ts @@ -17,7 +17,8 @@ enum COLLECTION_TYPE { } enum NAVIGATION_TYPE { - DB = 'db' + DB = 'db', + SOLR = 'solr' } type ConfigType = { @@ -78,7 +79,7 @@ const Config: ConfigType = { rootStaging: process.env.OCFL_STAGING_ROOT ? process.env.OCFL_STAGING_ROOT : /* istanbul ignore next */ './var/Storage/Staging' }, navigation: { - type: NAVIGATION_TYPE.DB, + type: NAVIGATION_TYPE.SOLR, }, }; diff --git a/server/config/solr/data/packrat/conf/schema.xml b/server/config/solr/data/packrat/conf/schema.xml index d55c90249..bd520f9ea 100644 --- a/server/config/solr/data/packrat/conf/schema.xml +++ b/server/config/solr/data/packrat/conf/schema.xml @@ -5,9 +5,9 @@ --> - + - + @@ -92,7 +92,7 @@ - + @@ -101,16 +101,21 @@ - - - - - - - - - - + + + + + + + + + + + + + + + diff --git a/server/db/api/CaptureData.ts b/server/db/api/CaptureData.ts index ebaf3d906..a97d4625d 100644 --- a/server/db/api/CaptureData.ts +++ b/server/db/api/CaptureData.ts @@ -6,6 +6,7 @@ import * as LOG from '../../utils/logger'; export class CaptureData extends DBC.DBObject implements CaptureDataBase, SystemObjectBased { idCaptureData!: number; + Name!: string; idVCaptureMethod!: number; DateCaptured!: Date; Description!: string; @@ -23,11 +24,12 @@ export class CaptureData extends DBC.DBObject implements Captur protected async createWorker(): Promise { try { - const { idVCaptureMethod, DateCaptured, Description, idAssetThumbnail } = this; - ({ idCaptureData: this.idCaptureData, idVCaptureMethod: this.idVCaptureMethod, + const { Name, idVCaptureMethod, DateCaptured, Description, idAssetThumbnail } = this; + ({ idCaptureData: this.idCaptureData, Name: this.Name, idVCaptureMethod: this.idVCaptureMethod, DateCaptured: this.DateCaptured, Description: this.Description, idAssetThumbnail: this.idAssetThumbnail } = await DBC.DBConnection.prisma.captureData.create({ data: { + Name, Vocabulary: { connect: { idVocabulary: idVCaptureMethod }, }, DateCaptured, Description, @@ -44,10 +46,11 @@ export class CaptureData extends DBC.DBObject implements Captur protected async updateWorker(): Promise { try { - const { idCaptureData, idVCaptureMethod, DateCaptured, Description, idAssetThumbnail, idAssetThumbnailOrig } = this; + const { idCaptureData, Name, idVCaptureMethod, DateCaptured, Description, idAssetThumbnail, idAssetThumbnailOrig } = this; const retValue: boolean = await DBC.DBConnection.prisma.captureData.update({ where: { idCaptureData, }, data: { + Name, Vocabulary: { connect: { idVocabulary: idVCaptureMethod }, }, DateCaptured, Description, @@ -84,6 +87,16 @@ export class CaptureData extends DBC.DBObject implements Captur } } + static async fetchAll(): Promise { + try { + return DBC.CopyArray( + await DBC.DBConnection.prisma.captureData.findMany(), CaptureData); + } catch (error) /* istanbul ignore next */ { + LOG.logger.error('DBAPI.CaptureData.fetchAll', error); + return null; + } + } + static async fetchFromCaptureDataPhoto(idCaptureDataPhoto: number): Promise { if (!idCaptureDataPhoto) return null; diff --git a/server/db/api/CaptureDataPhoto.ts b/server/db/api/CaptureDataPhoto.ts index db99ebbe1..bd3c257a6 100644 --- a/server/db/api/CaptureDataPhoto.ts +++ b/server/db/api/CaptureDataPhoto.ts @@ -111,4 +111,14 @@ export class CaptureDataPhoto extends DBC.DBObject impleme return null; } } + + static async fetchAll(): Promise { + try { + return DBC.CopyArray( + await DBC.DBConnection.prisma.captureDataPhoto.findMany(), CaptureDataPhoto); + } catch (error) /* istanbul ignore next */ { + LOG.logger.error('DBAPI.CaptureDataPhoto.fetchAll', error); + return null; + } + } } \ No newline at end of file diff --git a/server/db/api/Item.ts b/server/db/api/Item.ts index f85e0e391..9999043c9 100644 --- a/server/db/api/Item.ts +++ b/server/db/api/Item.ts @@ -86,6 +86,16 @@ export class Item extends DBC.DBObject implements ItemBase, SystemObje } } + static async fetchAll(): Promise { + try { + return DBC.CopyArray( + await DBC.DBConnection.prisma.item.findMany(), Item); + } catch (error) /* istanbul ignore next */ { + LOG.logger.error('DBAPI.Item.fetchAll', error); + return null; + } + } + static async fetchDerivedFromSubject(idSubject: number): Promise { if (!idSubject) return null; diff --git a/server/db/api/Subject.ts b/server/db/api/Subject.ts index f9d350fe3..85378c098 100644 --- a/server/db/api/Subject.ts +++ b/server/db/api/Subject.ts @@ -92,6 +92,16 @@ export class Subject extends DBC.DBObject implements SubjectBase, S } } + static async fetchAll(): Promise { + try { + return DBC.CopyArray( + await DBC.DBConnection.prisma.subject.findMany(), Subject); + } catch (error) /* istanbul ignore next */ { + LOG.logger.error('DBAPI.Subject.fetchAll', error); + return null; + } + } + static async fetchFromUnit(idUnit: number): Promise { if (!idUnit) return null; diff --git a/server/db/prisma/schema.prisma b/server/db/prisma/schema.prisma index f247d9eac..89a4e4836 100644 --- a/server/db/prisma/schema.prisma +++ b/server/db/prisma/schema.prisma @@ -144,6 +144,7 @@ model AssetVersion { model CaptureData { idCaptureData Int @default(autoincrement()) @id + Name String idVCaptureMethod Int DateCaptured DateTime Description String diff --git a/server/db/sql/scripts/Packrat.SCHEMA.sql b/server/db/sql/scripts/Packrat.SCHEMA.sql index f9f1dee64..5d3586ff6 100644 --- a/server/db/sql/scripts/Packrat.SCHEMA.sql +++ b/server/db/sql/scripts/Packrat.SCHEMA.sql @@ -102,6 +102,7 @@ CREATE TABLE IF NOT EXISTS `AssetVersion` ( CREATE TABLE IF NOT EXISTS `CaptureData` ( `idCaptureData` int(11) NOT NULL AUTO_INCREMENT, + `Name` varchar(255) NOT NULL, `idVCaptureMethod` int(11) NOT NULL, `DateCaptured` datetime NOT NULL, `Description` varchar(8000) NOT NULL, diff --git a/server/graphql/schema.graphql b/server/graphql/schema.graphql index e05d6f804..50771a943 100644 --- a/server/graphql/schema.graphql +++ b/server/graphql/schema.graphql @@ -318,6 +318,7 @@ type AssetGroup { } input CreateCaptureDataInput { + Name: String! idVCaptureMethod: Int! DateCaptured: DateTime! Description: String! diff --git a/server/graphql/schema/capturedata/mutations.graphql b/server/graphql/schema/capturedata/mutations.graphql index 295353ca5..a44cd59ce 100644 --- a/server/graphql/schema/capturedata/mutations.graphql +++ b/server/graphql/schema/capturedata/mutations.graphql @@ -7,6 +7,7 @@ type Mutation { } input CreateCaptureDataInput { + Name: String! idVCaptureMethod: Int! DateCaptured: DateTime! Description: String! diff --git a/server/graphql/schema/capturedata/resolvers/mutations/createCaptureData.ts b/server/graphql/schema/capturedata/resolvers/mutations/createCaptureData.ts index 8168835e7..a43c777d0 100644 --- a/server/graphql/schema/capturedata/resolvers/mutations/createCaptureData.ts +++ b/server/graphql/schema/capturedata/resolvers/mutations/createCaptureData.ts @@ -5,6 +5,7 @@ import * as DBAPI from '../../../../../db'; export default async function CreateCaptureData(_: Parent, args: MutationCreateCaptureDataArgs): Promise { const { input } = args; const { + Name, idVCaptureMethod, DateCaptured, Description, @@ -13,6 +14,7 @@ export default async function CreateCaptureData(_: Parent, args: MutationCreateC const captureDataArgs = { idCaptureData: 0, + Name, idVCaptureMethod, DateCaptured, Description, diff --git a/server/graphql/schema/ingestion/resolvers/mutations/ingestData.ts b/server/graphql/schema/ingestion/resolvers/mutations/ingestData.ts index 0774d2a54..b036a3a0a 100644 --- a/server/graphql/schema/ingestion/resolvers/mutations/ingestData.ts +++ b/server/graphql/schema/ingestion/resolvers/mutations/ingestData.ts @@ -319,6 +319,7 @@ async function createPhotogrammetryObjects(photogrammetry: IngestPhotogrammetry, // create photogrammetry objects, identifiers, etc. const captureDataDB: DBAPI.CaptureData = new DBAPI.CaptureData({ + Name: '', // TODO: gather and wire this into place idVCaptureMethod: vocabulary.idVocabulary, DateCaptured: H.Helpers.convertStringToDate(photogrammetry.dateCaptured) || new Date(), Description: photogrammetry.description, diff --git a/server/index.ts b/server/index.ts index 270ae15e3..4f87115bc 100644 --- a/server/index.ts +++ b/server/index.ts @@ -11,6 +11,7 @@ import { serverOptions } from './graphql'; import * as LOG from './utils/logger'; import bodyParser from 'body-parser'; import { passport, authCorsConfig, authSession, AuthRouter } from './auth'; +import { ReindexSolr } from './navigation/impl/NavigationSolr/ReindexSolr'; import cookieParser from 'cookie-parser'; @@ -46,4 +47,10 @@ app.get('/logtest', (_: Request, response: Response) => { response.send('Got Here'); }); +app.get('/solrindex', async (_: Request, response: Response) => { + const reindexer: ReindexSolr = new ReindexSolr(); + const success: boolean = await reindexer.FullIndex(); + response.send(`Solr Reindexing Completed: ${success ? 'Success' : 'Failure'}`); +}); + export { app }; diff --git a/server/navigation/impl/NavigationSolr/NavigationSolr.ts b/server/navigation/impl/NavigationSolr/NavigationSolr.ts new file mode 100644 index 000000000..f56765f69 --- /dev/null +++ b/server/navigation/impl/NavigationSolr/NavigationSolr.ts @@ -0,0 +1,384 @@ +import * as NAV from '../../interface'; +import * as LOG from '../../../utils/logger'; +import * as CACHE from '../../../cache'; +import * as DBAPI from '../../../db'; +// import * as H from '../../../utils/helpers'; +import { eSystemObjectType } from '../../../db'; +import { ObjectIDAndType } from '../../../cache'; + +export class NavigationSolr implements NAV.INavigation { + async getObjectChildren(filter: NAV.NavigationFilter): Promise { + return (filter.idRoot == 0) + ? await NavigationSolr.getRoot(filter) + : await NavigationSolr.getChildren(filter); + } + + private static async getRoot(filter: NAV.NavigationFilter): Promise { + let entries: NAV.NavigationResultEntry[] = []; + + for (const eObjectType of filter.objectTypes) { + switch (eObjectType) { + case eSystemObjectType.eUnit: entries = entries.concat(await NavigationSolr.computeRootUnits(filter)); break; + case eSystemObjectType.eProject: entries = entries.concat(await NavigationSolr.computeRootProjects(filter)); break; + + case eSystemObjectType.eSubject: + case eSystemObjectType.eItem: + case eSystemObjectType.eCaptureData: + case eSystemObjectType.eModel: + case eSystemObjectType.eScene: + case eSystemObjectType.eIntermediaryFile: + case eSystemObjectType.eAsset: + case eSystemObjectType.eAssetVersion: + case eSystemObjectType.eProjectDocumentation: + case eSystemObjectType.eActor: + case eSystemObjectType.eStakeholder: + case eSystemObjectType.eUnknown: + default: + return { success: false, error: 'Not implemented', entries, metadataColumns: filter.metadataColumns }; + } + + } + return { success: true, error: '', entries, metadataColumns: filter.metadataColumns }; + } + + private static async getChildren(filter: NAV.NavigationFilter): Promise { + // LOG.logger.info(`NavigationSolr.getChildren(${JSON.stringify(filter)})`); + const oID: CACHE.ObjectIDAndType | undefined = await CACHE.SystemObjectCache.getObjectFromSystem(filter.idRoot); + if (!oID) + return { success: false, error: `NavigationSolr.getChildren unable to fetch information for filter ${JSON.stringify(filter)}`, entries: [], metadataColumns: filter.metadataColumns }; + + const OG: DBAPI.ObjectGraph = new DBAPI.ObjectGraph(filter.idRoot, DBAPI.eObjectGraphMode.eDescendents, 1); /* istanbul ignore if */ + if (!await OG.fetch()) + return { success: false, error: `NavigationSolr.getChildren unable to fetch descendents for filter ${JSON.stringify(filter)}`, entries: [], metadataColumns: filter.metadataColumns }; + + const entries: NAV.NavigationResultEntry[] = []; + + switch (oID.eObjectType) { + case eSystemObjectType.eUnit: await NavigationSolr.computeChildrenForUnitOrProject(filter, oID, OG, entries); break; + case eSystemObjectType.eProject: await NavigationSolr.computeChildrenForUnitOrProject(filter, oID, OG, entries); break; + case eSystemObjectType.eSubject: await NavigationSolr.computeChildrenForSubject(filter, oID, OG, entries); break; + case eSystemObjectType.eItem: await NavigationSolr.computeChildrenForItem(filter, oID, OG, entries); break; + + case eSystemObjectType.eCaptureData: + case eSystemObjectType.eModel: + case eSystemObjectType.eScene: + case eSystemObjectType.eIntermediaryFile: + case eSystemObjectType.eAsset: + case eSystemObjectType.eAssetVersion: + case eSystemObjectType.eProjectDocumentation: + case eSystemObjectType.eActor: + case eSystemObjectType.eStakeholder: /* istanbul ignore next */ + case eSystemObjectType.eUnknown: /* istanbul ignore next */ + default: + return { success: false, error: 'Not implemented', entries, metadataColumns: filter.metadataColumns }; + } + return { success: true, error: '', entries, metadataColumns: filter.metadataColumns }; + } + + /* #region Units */ + private static async computeRootUnits(filter: NAV.NavigationFilter): Promise { + const entries: NAV.NavigationResultEntry[] = []; + + const units: DBAPI.Unit[] | null = await DBAPI.Unit.fetchAll(); /* istanbul ignore if */ + if (!units) { + LOG.logger.error('NavigationSolr.getRoot unable to retrieve units'); + return []; + } + + for (const unit of units) { + const oID: CACHE.ObjectIDAndType = { idObject: unit.idUnit, eObjectType: eSystemObjectType.eUnit }; + const sID: CACHE.SystemObjectInfo | undefined = await CACHE.SystemObjectCache.getSystemFromObjectID(oID); /* istanbul ignore if */ + if (!sID) { + LOG.logger.error(`NavigationSolr.getRoot unable to compute idSystemObject for ${JSON.stringify(oID)}`); + continue; + } + + const entry: NAV.NavigationResultEntry = { + idSystemObject: sID.idSystemObject, + name: unit.Abbreviation || '', + objectType: eSystemObjectType.eUnit, + idObject: unit.idUnit, + metadata: NavigationSolr.computeMetadataForUnit(unit, filter.metadataColumns) + }; + entries.push(entry); + } + return entries; + } + + private static async computeChildrenForUnitOrProject(filter: NAV.NavigationFilter, oID: CACHE.ObjectIDAndType, OG: DBAPI.ObjectGraph, entries: NAV.NavigationResultEntry[]): Promise { + filter; oID; /* istanbul ignore else */ + if (OG.subject) { + for (const subject of OG.subject) { + const oIDSubject: ObjectIDAndType = { idObject: subject.idSubject, eObjectType: eSystemObjectType.eSubject }; + const sID: CACHE.SystemObjectInfo | undefined = await CACHE.SystemObjectCache.getSystemFromObjectID(oIDSubject); /* istanbul ignore else */ + if (sID) + entries.push({ + idSystemObject: sID.idSystemObject, + name: subject.Name, + objectType: eSystemObjectType.eSubject, + idObject: subject.idSubject, + metadata: await NavigationSolr.computeMetadataForSubject(subject, filter.metadataColumns) + }); + else + LOG.logger.error(`NavigateDB.computeChildrenForUnit unable to fetch information for ${JSON.stringify(oIDSubject)}`); + } + } + } + + private static computeMetadataForUnit(unit: DBAPI.Unit, metadataColumns: NAV.eMetadata[]): string[] { + const metadata: string[] = []; + for (const metadataColumn of metadataColumns) { + switch (metadataColumn) { + case NAV.eMetadata.eUnitAbbreviation: metadata.push(unit.Abbreviation || ''); break; /* istanbul ignore next */ + + default: + case NAV.eMetadata.eItemName: + case NAV.eMetadata.eSubjectIdentifier: + metadata.push(''); + break; + } + } + return metadata; + } + /* #endregion */ + + /* #region Projects */ + private static async computeRootProjects(filter: NAV.NavigationFilter): Promise { + const entries: NAV.NavigationResultEntry[] = []; + + const projects: DBAPI.Project[] | null = await DBAPI.Project.fetchAll(); /* istanbul ignore if */ + if (!projects) { + LOG.logger.error('NavigationSolr.getRoot unable to retrieve projects'); + return []; + } + + for (const project of projects) { + const oID: CACHE.ObjectIDAndType = { idObject: project.idProject, eObjectType: eSystemObjectType.eProject }; + const sID: CACHE.SystemObjectInfo | undefined = await CACHE.SystemObjectCache.getSystemFromObjectID(oID); /* istanbul ignore if */ + if (!sID) { + LOG.logger.error(`NavigationSolr.getRoot unable to compute idSystemObject for ${JSON.stringify(oID)}`); + continue; + } + + const entry: NAV.NavigationResultEntry = { + idSystemObject: sID.idSystemObject, + name: project.Name, + objectType: eSystemObjectType.eProject, + idObject: project.idProject, + metadata: await NavigationSolr.computeMetadataForProject(project, filter.metadataColumns) + }; + entries.push(entry); + } + return entries; + } + + // computeChildrenForUnitOrProject handles project's subject children, for the time being + + private static async computeMetadataForProject(project: DBAPI.Project, metadataColumns: NAV.eMetadata[]): Promise { + const metadata: string[] = []; + for (const metadataColumn of metadataColumns) { + switch (metadataColumn) { + case NAV.eMetadata.eUnitAbbreviation: { + const units: DBAPI.Unit[] | null = await DBAPI.Unit.fetchMasterFromProjects([project.idProject]); // TODO: consider placing this in a cache + let unitAbbreviation: string = ''; /* istanbul ignore else */ + if (units) { + for (let index = 0; index < units.length; index++) + unitAbbreviation += ((index > 0) ? ', ' : '') + units[index].Abbreviation; + } + metadata.push(unitAbbreviation); + } break; /* istanbul ignore next */ + + default: + case NAV.eMetadata.eItemName: + case NAV.eMetadata.eSubjectIdentifier: + metadata.push(''); + break; + } + } + return metadata; + } + /* #endregion */ + + /* #region Subjects */ + private static async computeChildrenForSubject(filter: NAV.NavigationFilter, oID: CACHE.ObjectIDAndType, OG: DBAPI.ObjectGraph, entries: NAV.NavigationResultEntry[]): Promise { + filter; oID; + const subject: DBAPI.Subject | null = (OG.subject) ? OG.subject[0] : /* istanbul ignore next */ await DBAPI.Subject.fetch(oID.idObject); /* istanbul ignore if */ + if (!subject) { + LOG.logger.error(`NavigateDB.computeChildrenForSubject unable to fetch subject information for ${JSON.stringify(oID)}`); + return; + } + + /* istanbul ignore else */ + if (OG.item) { + for (const item of OG.item) { + const oIDITem: ObjectIDAndType = { idObject: item.idItem, eObjectType: eSystemObjectType.eItem }; + const sID: CACHE.SystemObjectInfo | undefined = await CACHE.SystemObjectCache.getSystemFromObjectID(oIDITem); /* istanbul ignore else */ + if (sID) + entries.push({ + idSystemObject: sID.idSystemObject, + name: item.Name, + objectType: eSystemObjectType.eItem, + idObject: item.idItem, + metadata: await NavigationSolr.computeMetadataForItem(item, subject, filter.metadataColumns) + }); + else + LOG.logger.error(`NavigateDB.computeChildrenForSubject unable to fetch information for ${JSON.stringify(oIDITem)}`); + } + } + } + + private static async computeMetadataForSubject(subject: DBAPI.Subject, metadataColumns: NAV.eMetadata[]): Promise { + const metadata: string[] = []; + for (const metadataColumn of metadataColumns) { + switch (metadataColumn) { + case NAV.eMetadata.eUnitAbbreviation: { + const unit: DBAPI.Unit | null = await DBAPI.Unit.fetch(subject.idUnit); + metadata.push(unit ? (unit.Abbreviation || /* istanbul ignore next */ '') : /* istanbul ignore next */ ''); + } break; + + case NAV.eMetadata.eSubjectIdentifier: { + const identifier: DBAPI.Identifier | null = (subject.idIdentifierPreferred) + ? await DBAPI.Identifier.fetch(subject.idIdentifierPreferred) + : null; + metadata.push(identifier ? identifier.IdentifierValue : ''); + } break; /* istanbul ignore next */ + + default: + case NAV.eMetadata.eItemName: + metadata.push(''); + break; + } + } + return metadata; + } + /* #endregion */ + + /* #region Items */ + private static async computeChildrenForItem(filter: NAV.NavigationFilter, oID: CACHE.ObjectIDAndType, OG: DBAPI.ObjectGraph, entries: NAV.NavigationResultEntry[]): Promise { + filter; oID; + const item: DBAPI.Item | null = (OG.item) ? OG.item[0] : /* istanbul ignore next */ await DBAPI.Item.fetch(oID.idObject); /* istanbul ignore if */ + if (!item) { + LOG.logger.error(`NavigateDB.computeChildrenForItem unable to fetch item information for ${JSON.stringify(oID)}`); + return; + } /* istanbul ignore else */ + + if (OG.captureData) { + for (const captureData of OG.captureData) { + const vCaptureMethod: DBAPI.Vocabulary | undefined = await CACHE.VocabularyCache.vocabulary(captureData.idVCaptureMethod); + const oIDCD: ObjectIDAndType = { idObject: captureData.idCaptureData, eObjectType: eSystemObjectType.eCaptureData }; + const sID: CACHE.SystemObjectInfo | undefined = await CACHE.SystemObjectCache.getSystemFromObjectID(oIDCD); /* istanbul ignore else */ + if (sID) + entries.push({ + idSystemObject: sID.idSystemObject, + name: `Capture Set${vCaptureMethod ? ' ' + vCaptureMethod.Term : /* istanbul ignore next */ ''}`, /* : ${H.Helpers.convertDateToYYYYMMDD(captureData.DateCaptured)} */ + objectType: eSystemObjectType.eCaptureData, + idObject: captureData.idCaptureData, + metadata: await NavigationSolr.computeMetadataForItemChildren(/* captureData, */ item, filter.metadataColumns) + }); + else + LOG.logger.error(`NavigateDB.computeChildrenForItem unable to fetch information for ${JSON.stringify(oIDCD)}`); + } + } /* istanbul ignore else */ + + if (OG.model) { + for (const model of OG.model) { + const vPurpose: DBAPI.Vocabulary | undefined = await CACHE.VocabularyCache.vocabulary(model.idVPurpose); + const oIDModel: ObjectIDAndType = { idObject: model.idModel, eObjectType: eSystemObjectType.eModel }; + const sID: CACHE.SystemObjectInfo | undefined = await CACHE.SystemObjectCache.getSystemFromObjectID(oIDModel); /* istanbul ignore else */ + if (sID) + entries.push({ + idSystemObject: sID.idSystemObject, + name: `Model${vPurpose ? ' ' + vPurpose.Term : /* istanbul ignore next */ ''}`, /* : ${H.Helpers.convertDateToYYYYMMDD(model.DateCreated)} */ + objectType: eSystemObjectType.eModel, + idObject: model.idModel, + metadata: await NavigationSolr.computeMetadataForItemChildren(/* model, */ item, filter.metadataColumns) + }); + else + LOG.logger.error(`NavigateDB.computeChildrenForItem unable to fetch information for ${JSON.stringify(oIDModel)}`); + } + } /* istanbul ignore else */ + + if (OG.scene) { + for (const scene of OG.scene) { + const oIDScene: ObjectIDAndType = { idObject: scene.idScene, eObjectType: eSystemObjectType.eScene }; + const sID: CACHE.SystemObjectInfo | undefined = await CACHE.SystemObjectCache.getSystemFromObjectID(oIDScene); /* istanbul ignore else */ + if (sID) + entries.push({ + idSystemObject: sID.idSystemObject, + name: `Scene ${scene.Name}`, + objectType: eSystemObjectType.eScene, + idObject: scene.idScene, + metadata: await NavigationSolr.computeMetadataForItemChildren(/* scene, */ item, filter.metadataColumns) + }); + else + LOG.logger.error(`NavigateDB.computeChildrenForItem unable to fetch information for ${JSON.stringify(oIDScene)}`); + } + } + } + + private static async computeMetadataForItem(item: DBAPI.Item, subject: DBAPI.Subject, metadataColumns: NAV.eMetadata[]): Promise { + const metadata: string[] = []; + for (const metadataColumn of metadataColumns) { + switch (metadataColumn) { + case NAV.eMetadata.eUnitAbbreviation: { + const unit: DBAPI.Unit | null = await DBAPI.Unit.fetch(subject.idUnit); + metadata.push(unit ? (unit.Abbreviation || /* istanbul ignore next */ '') : /* istanbul ignore next */ ''); + } break; + + case NAV.eMetadata.eSubjectIdentifier: { + const identifier: DBAPI.Identifier | null = (subject.idIdentifierPreferred) + ? await DBAPI.Identifier.fetch(subject.idIdentifierPreferred) + : /* istanbul ignore next */ null; + metadata.push(identifier ? identifier.IdentifierValue : /* istanbul ignore next */ ''); + } break; + + case NAV.eMetadata.eItemName: + metadata.push(`Item ${item.Name}`); + break; + + /* istanbul ignore next */ + default: + metadata.push(''); + break; + } + } + return metadata; + } + /* #endregion */ + + private static async computeMetadataForItemChildren(item: DBAPI.Item, metadataColumns: NAV.eMetadata[]): Promise { + const subjects: DBAPI.Subject[] | null = await DBAPI.Subject.fetchMasterFromItems([item.idItem]); + const metadata: string[] = []; + for (const metadataColumn of metadataColumns) { + switch (metadataColumn) { + case NAV.eMetadata.eUnitAbbreviation: { /* istanbul ignore else */ + if (subjects && subjects.length > 0) { // TODO: deal with multiple subjects + const unit: DBAPI.Unit | null = await DBAPI.Unit.fetch(subjects[0].idUnit); + metadata.push(unit ? (unit.Abbreviation || /* istanbul ignore next */ '') : /* istanbul ignore next */ ''); + } else + metadata.push(''); + } break; + + case NAV.eMetadata.eSubjectIdentifier: { /* istanbul ignore else */ + if (subjects && subjects.length > 0) { // TODO: deal with multiple subjects + const identifier: DBAPI.Identifier | null = (subjects[0].idIdentifierPreferred) + ? await DBAPI.Identifier.fetch(subjects[0].idIdentifierPreferred) + : /* istanbul ignore next */ null; + metadata.push(identifier ? identifier.IdentifierValue : /* istanbul ignore next */ ''); + } else + metadata.push(''); + } break; + + case NAV.eMetadata.eItemName: + metadata.push(`Item ${item.Name}`); + break; + + /* istanbul ignore next */ + default: + metadata.push(''); + break; + } + } + return metadata; + } +} diff --git a/server/navigation/impl/NavigationSolr/ReindexSolr.ts b/server/navigation/impl/NavigationSolr/ReindexSolr.ts new file mode 100644 index 000000000..46f0e3508 --- /dev/null +++ b/server/navigation/impl/NavigationSolr/ReindexSolr.ts @@ -0,0 +1,385 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import * as LOG from '../../../utils/logger'; +import * as CACHE from '../../../cache'; +import * as DBAPI from '../../../db'; +import { eSystemObjectType } from '../../../db'; +import { SolrClient } from './SolrClient'; + +type SubjectInfo = { + Unit: string | null; + UnitID: number | null; + Project: string[]; + ProjectID: number[]; +}; + +type ItemInfo = { + Unit: string[]; + UnitID: number[]; + Project: string[]; + ProjectID: number[]; + Subject: string[]; + SubjectID: number[]; +}; + +export class ReindexSolr { + private SubjectInfoMap: Map = new Map(); // map of Subject.idSubject -> Unit/Project info + private ItemInfoMap: Map = new Map(); // map of Item.idItem -> Unit/Project/Subject + + async FullIndex(): Promise { + const solrClient: SolrClient = new SolrClient(null, null, null); + solrClient._client.autoCommit = true; + + solrClient._client.add(await this.computeUnits(), function(err, obj) { if (err) LOG.logger.error('ReindexSolr.FullIndex -> computeUnits()', err); else obj; }); + solrClient._client.add(await this.computeProjects(), function(err, obj) { if (err) LOG.logger.error('ReindexSolr.FullIndex -> computeProjects()', err); else obj; }); + solrClient._client.add(await this.computeSubjects(), function(err, obj) { if (err) LOG.logger.error('ReindexSolr.FullIndex -> computeSubjects()', err); else obj; }); + solrClient._client.add(await this.computeItems(), function(err, obj) { if (err) LOG.logger.error('ReindexSolr.FullIndex -> computeItems()', err); else obj; }); + solrClient._client.add(await this.computeCaptureData(), function(err, obj) { if (err) LOG.logger.error('ReindexSolr.FullIndex -> computeCaptureData()', err); else obj; }); + solrClient._client.commit(function(err, obj) { if (err) LOG.logger.error('ReindexSolr.FullIndex -> commit()', err); else obj; }); + return true; + } + + /* #region Units */ + private async computeUnits(): Promise { + LOG.logger.info('ReindexSolr.computeUnits starting'); + const docs: any[] = []; + + const units: DBAPI.Unit[] | null = await DBAPI.Unit.fetchAll(); /* istanbul ignore if */ + if (!units) { + LOG.logger.error('ReindexSolr.computeUnits unable to retrieve units'); + return []; + } + + for (const unit of units) { + const oID: CACHE.ObjectIDAndType = { idObject: unit.idUnit, eObjectType: eSystemObjectType.eUnit }; + const sID: CACHE.SystemObjectInfo | undefined = await CACHE.SystemObjectCache.getSystemFromObjectID(oID); /* istanbul ignore if */ + if (!sID) { + LOG.logger.error(`NavigationDB.getRoot unable to compute idSystemObject for ${JSON.stringify(oID)}`); + continue; + } + + const doc: any = { + idSystemObject: sID.idSystemObject, + ObjectType: 'Unit', + idObject: unit.idUnit, + Retired: sID.Retired, + Name: unit.Name, + Abbreviation: unit.Abbreviation, + ARKPrefix: unit.ARKPrefix, + Unit: unit.Abbreviation, + UnitID: sID.idSystemObject, + ParentID: 0, + Identifier: this.computeIdentifiers(sID.idSystemObject) + }; + docs.push(doc); + } + LOG.logger.info(`ReindexSolr.computeUnits computed ${docs.length} documents`); + return docs; + } + /* #endregion */ + + /* #region Projects */ + private async computeProjects(): Promise { + LOG.logger.info('ReindexSolr.computeProjects starting'); + const docs: any[] = []; + + const projects: DBAPI.Project[] | null = await DBAPI.Project.fetchAll(); /* istanbul ignore if */ + if (!projects) { + LOG.logger.error('ReindexSolr.computeProjects unable to retrieve projects'); + return []; + } + + for (const project of projects) { + const oID: CACHE.ObjectIDAndType = { idObject: project.idProject, eObjectType: eSystemObjectType.eProject }; + const sID: CACHE.SystemObjectInfo | undefined = await CACHE.SystemObjectCache.getSystemFromObjectID(oID); /* istanbul ignore if */ + if (!sID) { + LOG.logger.error(`ReindexSolr.computeProjects unable to compute idSystemObject for ${JSON.stringify(oID)}`); + continue; + } + + const Unit: string[] = []; + const UnitID: number[] = []; + + const units: DBAPI.Unit[] | null = await DBAPI.Unit.fetchMasterFromProjects([project.idProject]); // TODO: consider placing this in a cache + if (units) { + for (const unit of units) { + Unit.push(unit.Abbreviation || ''); + const SO: DBAPI.SystemObject | null = await unit.fetchSystemObject(); + UnitID.push(SO ? SO.idSystemObject : 0); + } + } + + const doc: any = { + idSystemObject: sID.idSystemObject, + ObjectType: 'Project', + idObject: project.idProject, + Retired: sID.Retired, + Name: project.Name, + Description: project.Description, + Unit: Unit.length == 1 ? Unit[0] : Unit, + UnitID: UnitID.length == 1 ? UnitID[0] : UnitID, + Project: project.Name, + ProjectID: sID.idSystemObject, + ParentID: 0, + Identifier: this.computeIdentifiers(sID.idSystemObject) + }; + docs.push(doc); + } + LOG.logger.info(`ReindexSolr.computeProjects computed ${docs.length} documents`); + return docs; + } + /* #endregion */ + + /* #region Subjects */ + private async computeSubjects(): Promise { + LOG.logger.info('ReindexSolr.computeSubjects starting'); + const docs: any[] = []; + + const subjects: DBAPI.Subject[] | null = await DBAPI.Subject.fetchAll(); /* istanbul ignore if */ + if (!subjects) { + LOG.logger.error('ReindexSolr.computeSubjects unable to retrieve subjects'); + return []; + } + + for (const subject of subjects) { + const oID: CACHE.ObjectIDAndType = { idObject: subject.idSubject, eObjectType: eSystemObjectType.eSubject }; + const sID: CACHE.SystemObjectInfo | undefined = await CACHE.SystemObjectCache.getSystemFromObjectID(oID); /* istanbul ignore if */ + if (!sID) { + LOG.logger.error(`ReindexSolr.computeSubjects unable to compute idSystemObject for ${JSON.stringify(oID)}`); + continue; + } + + let Unit: string | null = null; + let UnitID: number | null = null; + const Project: string[] = []; + const ProjectID: number[] = []; + let IdentifierPreferred: string | null = null; + + const unit: DBAPI.Unit | null = (subject.idUnit != 0) ? await DBAPI.Unit.fetch(subject.idUnit) : null; + if (unit) { + Unit = unit.Abbreviation; + const SO: DBAPI.SystemObject | null = await unit.fetchSystemObject(); + UnitID = SO ? SO.idSystemObject : 0; + } + + const projects: DBAPI.Project[] | null = await DBAPI.Project.fetchMasterFromSubjects([subject.idSubject]); + if (projects) { + for (const project of projects) { + Project.push(project.Name); + const SO: DBAPI.SystemObject | null = await project.fetchSystemObject(); + ProjectID.push(SO ? SO.idSystemObject : 0); + } + } + + if (subject.idIdentifierPreferred) { + const ID: DBAPI.Identifier | null = await DBAPI.Identifier.fetch(subject.idIdentifierPreferred); + if (ID) + IdentifierPreferred = ID.IdentifierValue; + } + + const doc: any = { + idSystemObject: sID.idSystemObject, + ObjectType: 'Subject', + idObject: subject.idSubject, + Retired: sID.Retired, + Name: subject.Name, + IdentifierPreferred, + Unit, + UnitID, + Project: Project.length == 1 ? Project[0] : Project, + ProjectID: ProjectID.length == 1 ? ProjectID[0] : ProjectID, + Subject: subject.Name, + SubjectID: sID.idSystemObject, + ParentID: UnitID, + Identifier: this.computeIdentifiers(sID.idSystemObject) + }; + docs.push(doc); + this.SubjectInfoMap.set(subject.idSubject, { Unit, UnitID, Project, ProjectID }); + } + LOG.logger.info(`ReindexSolr.computeSubjects computed ${docs.length} documents`); + return docs; + } + /* #endregion */ + + /* #region Items */ + private async computeItems(): Promise { + LOG.logger.info('ReindexSolr.computeItems starting'); + const docs: any[] = []; + + const items: DBAPI.Item[] | null = await DBAPI.Item.fetchAll(); /* istanbul ignore if */ + if (!items) { + LOG.logger.error('ReindexSolr.computeItems unable to retrieve items'); + return []; + } + + for (const item of items) { + const oID: CACHE.ObjectIDAndType = { idObject: item.idItem, eObjectType: eSystemObjectType.eItem }; + const sID: CACHE.SystemObjectInfo | undefined = await CACHE.SystemObjectCache.getSystemFromObjectID(oID); /* istanbul ignore if */ + if (!sID) { + LOG.logger.error(`ReindexSolr.computeItems unable to compute idSystemObject for ${JSON.stringify(oID)}`); + continue; + } + + const Unit: string[] = []; + const UnitID: number[] = []; + let Project: string[] = []; + let ProjectID: number[] = []; + const Subject: string[] = []; + const SubjectID: number[] = []; + + const subjects: DBAPI.Subject[] | null = await DBAPI.Subject.fetchMasterFromItems([item.idItem]); + if (subjects) { + for (const subject of subjects) { + Subject.push(subject.Name); + const SO: DBAPI.SystemObject | null = await subject.fetchSystemObject(); + SubjectID.push(SO ? SO.idSystemObject : 0); + + const subjectInfo: SubjectInfo | undefined = this.SubjectInfoMap.get(subject.idSubject); + if (subjectInfo) { + if (subjectInfo.Unit) + Unit.push(subjectInfo.Unit); + if (subjectInfo.UnitID) + UnitID.push(subjectInfo.UnitID); + Project = Project.concat(subjectInfo.Project); + ProjectID = ProjectID.concat(subjectInfo.ProjectID); + } + + } + } + + const doc: any = { + idSystemObject: sID.idSystemObject, + ObjectType: 'Item', + idObject: item.idItem, + Retired: sID.Retired, + Name: item.Name, + EntireSubject: item.EntireSubject, + Unit: Unit.length == 1 ? Unit[0] : Unit, + UnitID: UnitID.length == 1 ? UnitID[0] : UnitID, + Project: Project.length == 1 ? Project[0] : Project, + ProjectID: ProjectID.length == 1 ? ProjectID[0] : ProjectID, + Subject: Subject.length == 1 ? Subject[0] : Subject, + SubjectID: SubjectID.length == 1 ? SubjectID[0] : SubjectID, + Item: item.Name, + ItemID: sID.idSystemObject, + ParentID: SubjectID.length == 1 ? SubjectID[0] : SubjectID, + Identifier: this.computeIdentifiers(sID.idSystemObject) + }; + docs.push(doc); + this.ItemInfoMap.set(item.idItem, { Unit, UnitID, Project, ProjectID, Subject, SubjectID }); + } + LOG.logger.info(`ReindexSolr.computeItems computed ${docs.length} documents`); + return docs; + } + /* #endregion */ + + /* #region CaptureData */ + private async computeCaptureData(): Promise { + LOG.logger.info('ReindexSolr.computeCaptureData starting'); + const docs: any[] = []; + + const captureDataPhotos: DBAPI.CaptureDataPhoto[] | null = await DBAPI.CaptureDataPhoto.fetchAll(); /* istanbul ignore if */ + if (!captureDataPhotos) { + LOG.logger.error('ReindexSolr.computeCaptureData unable to retrieve CaptureDataPhoto'); + return []; + } + + for (const captureDataPhoto of captureDataPhotos) { + const captureData: DBAPI.CaptureData | null = await DBAPI.CaptureData.fetchFromCaptureDataPhoto(captureDataPhoto.idCaptureDataPhoto); + if (!captureData) { + LOG.logger.error(`ReindexSolr.computeCaptureData unable to compute CaptureData from CaptureDataPhoto ${JSON.stringify(captureDataPhoto)}`); + continue; + } + + const oID: CACHE.ObjectIDAndType = { idObject: captureData.idCaptureData, eObjectType: eSystemObjectType.eCaptureData }; + const sID: CACHE.SystemObjectInfo | undefined = await CACHE.SystemObjectCache.getSystemFromObjectID(oID); /* istanbul ignore if */ + if (!sID) { + LOG.logger.error(`ReindexSolr.computeCaptureData unable to compute idSystemObject for ${JSON.stringify(oID)}`); + continue; + } + + let Unit: string[] = []; + let UnitID: number[] = []; + let Project: string[] = []; + let ProjectID: number[] = []; + let Subject: string[] = []; + let SubjectID: number[] = []; + const Item: string[] = []; + const ItemID: number[] = []; + + const items: DBAPI.Item[] | null = await DBAPI.Item.fetchMasterFromCaptureDatas([captureData.idCaptureData]); + if (items) { + for (const item of items) { + Item.push(item.Name); + const SO: DBAPI.SystemObject | null = await item.fetchSystemObject(); + ItemID.push(SO ? SO.idSystemObject : 0); + + const itemInfo: ItemInfo | undefined = this.ItemInfoMap.get(item.idItem); + if (itemInfo) { + Unit = Unit.concat(itemInfo.Unit); + UnitID = UnitID.concat(itemInfo.UnitID); + Project = Project.concat(itemInfo.Project); + ProjectID = ProjectID.concat(itemInfo.ProjectID); + Subject = Subject.concat(itemInfo.Subject); + SubjectID = SubjectID.concat(itemInfo.SubjectID); + } + } + } + + const vCaptureMethod: DBAPI.Vocabulary | undefined = await CACHE.VocabularyCache.vocabulary(captureData.idVCaptureMethod); + const vCaptureDatasetType: DBAPI.Vocabulary | undefined = await CACHE.VocabularyCache.vocabulary(captureDataPhoto.idVCaptureDatasetType); + const vItemPositionType: DBAPI.Vocabulary | undefined = captureDataPhoto.idVItemPositionType ? await CACHE.VocabularyCache.vocabulary(captureDataPhoto.idVItemPositionType) : undefined; + const vFocusType: DBAPI.Vocabulary | undefined = captureDataPhoto.idVFocusType ? await CACHE.VocabularyCache.vocabulary(captureDataPhoto.idVFocusType) : undefined; + const vLightSourceType: DBAPI.Vocabulary | undefined = captureDataPhoto.idVLightSourceType ? await CACHE.VocabularyCache.vocabulary(captureDataPhoto.idVLightSourceType) : undefined; + const vBackgroundRemovalMethod: DBAPI.Vocabulary | undefined = captureDataPhoto.idVBackgroundRemovalMethod ? await CACHE.VocabularyCache.vocabulary(captureDataPhoto.idVBackgroundRemovalMethod) : undefined; + const vClusterType: DBAPI.Vocabulary | undefined = captureDataPhoto.idVClusterType ? await CACHE.VocabularyCache.vocabulary(captureDataPhoto.idVClusterType) : undefined; + + const doc: any = { + idSystemObject: sID.idSystemObject, + ObjectType: 'CaptureData', + idObject: captureData.idCaptureData, + Retired: sID.Retired, + + Name: captureData.Name, + Description: captureData.Description, + DateCreated: captureData.DateCaptured, + CaptureMethod: vCaptureMethod ? vCaptureMethod.Term : '', + CaptureDatasetType: vCaptureDatasetType ? vCaptureDatasetType.Term : '', + CaptureDatasetFieldID: captureDataPhoto.CaptureDatasetFieldID, + ItemPositionType: vItemPositionType ? vItemPositionType.Term : '', + ItemPositionFieldID: captureDataPhoto.ItemPositionFieldID, + ItemArrangementFieldID: captureDataPhoto.ItemArrangementFieldID, + FocusType: vFocusType ? vFocusType.Term : '', + LightSourceType: vLightSourceType ? vLightSourceType.Term : '', + BackgroundRemovalMethod: vBackgroundRemovalMethod ? vBackgroundRemovalMethod.Term : '', + ClusterType: vClusterType ? vClusterType.Term : '', + ClusterGeometryFieldID: captureDataPhoto.ClusterGeometryFieldID, + CameraSettingsUniform: captureDataPhoto.CameraSettingsUniform, + + Unit: Unit.length == 1 ? Unit[0] : Unit, + UnitID: UnitID.length == 1 ? UnitID[0] : UnitID, + Project: Project.length == 1 ? Project[0] : Project, + ProjectID: ProjectID.length == 1 ? ProjectID[0] : ProjectID, + Subject: Subject.length == 1 ? Subject[0] : Subject, + SubjectID: SubjectID.length == 1 ? SubjectID[0] : SubjectID, + Item: Item.length == 1 ? Item[0] : Item, + ItemID: ItemID.length == 1 ? ItemID[0] : ItemID, + ParentID: ItemID.length == 1 ? ItemID[0] : ItemID, + Identifier: this.computeIdentifiers(sID.idSystemObject) + }; + docs.push(doc); + } + LOG.logger.info(`ReindexSolr.computeCaptureData computed ${docs.length} documents`); + return docs; + } + /* #endregion */ + + private async computeIdentifiers(idSystemObject: number): Promise { + const identifiersRet: string[] = []; + const identifiers: DBAPI.Identifier[] | null = await DBAPI.Identifier.fetchFromSystemObject(idSystemObject); + if (identifiers) { + for (const identifier of identifiers) + identifiersRet.push(identifier.IdentifierValue); + } + return identifiersRet; + } +} \ No newline at end of file diff --git a/server/navigation/impl/NavigationSolr/SolrClient.ts b/server/navigation/impl/NavigationSolr/SolrClient.ts new file mode 100644 index 000000000..b58b864f5 --- /dev/null +++ b/server/navigation/impl/NavigationSolr/SolrClient.ts @@ -0,0 +1,16 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import solr from 'solr-client'; + +export class SolrClient { + _client: any; + + constructor(host: string | null, port: string | null, core: string | null) { + if (!host) + host = 'packrat-solr'; + if (!port) + port = '8983'; + if (!core) + core = 'packrat'; + this._client = solr.createClient({ host, port, core }); + } +} diff --git a/server/navigation/impl/NavigationSolr/index.ts b/server/navigation/impl/NavigationSolr/index.ts new file mode 100644 index 000000000..a4261a119 --- /dev/null +++ b/server/navigation/impl/NavigationSolr/index.ts @@ -0,0 +1,2 @@ +export * from './NavigationSolr'; +export * from './ReindexSolr'; diff --git a/server/navigation/interface/NavigationFactory.ts b/server/navigation/interface/NavigationFactory.ts index f3a3d0c2f..614308da2 100644 --- a/server/navigation/interface/NavigationFactory.ts +++ b/server/navigation/interface/NavigationFactory.ts @@ -1,5 +1,6 @@ import { INavigation } from './INavigation'; import { NavigationDB } from '../impl'; +import { NavigationSolr } from '../impl/NavigationSolr'; import Config, { NAVIGATION_TYPE } from '../../config'; // import * as LOG from '../../utils/logger'; @@ -16,6 +17,10 @@ export class NavigationFactory { NavigationFactory.instance = new NavigationDB(); break; } + case NAVIGATION_TYPE.SOLR: { + NavigationFactory.instance = new NavigationSolr(); + break; + } } } return NavigationFactory.instance; diff --git a/server/tests/db/composite/ObjectGraph.setup.ts b/server/tests/db/composite/ObjectGraph.setup.ts index 560fef6d5..f40136d54 100644 --- a/server/tests/db/composite/ObjectGraph.setup.ts +++ b/server/tests/db/composite/ObjectGraph.setup.ts @@ -103,12 +103,12 @@ export class ObjectGraphTestSetup { this.assetVersion1a = await UTIL.createAssetVersionTest({ idAsset: this.asset1.idAsset, idUserCreator: this.user1.idUser, DateCreated: UTIL.nowCleansed(), StorageHash: 'OA Test', StorageSize: 500, idAssetVersion: 0, Ingested: true, BulkIngest: false, FileName: '', StorageKeyStaging: '', Version: 0 }); this.assetVersion1b = await UTIL.createAssetVersionTest({ idAsset: this.asset1.idAsset, idUserCreator: this.user1.idUser, DateCreated: UTIL.nowCleansed(), StorageHash: 'OA Test', StorageSize: 500, idAssetVersion: 0, Ingested: true, BulkIngest: false, FileName: '', StorageKeyStaging: '', Version: 0 }); this.assetVersion1c = await UTIL.createAssetVersionTest({ idAsset: this.asset1.idAsset, idUserCreator: this.user1.idUser, DateCreated: UTIL.nowCleansed(), StorageHash: 'OA Test', StorageSize: 500, idAssetVersion: 0, Ingested: true, BulkIngest: false, FileName: '', StorageKeyStaging: '', Version: 0 }); - this.captureData1 = await UTIL.createCaptureDataTest({ idVCaptureMethod: this.v1.idVocabulary, DateCaptured: UTIL.nowCleansed(), Description: 'OA Test', idAssetThumbnail: null, idCaptureData: 0 }); + this.captureData1 = await UTIL.createCaptureDataTest({ Name: 'OA Test', idVCaptureMethod: this.v1.idVocabulary, DateCaptured: UTIL.nowCleansed(), Description: 'OA Test', idAssetThumbnail: null, idCaptureData: 0 }); assigned = await this.asset1.assignOwner(this.captureData1); expect(assigned).toBeTruthy(); this.assetT3 = await UTIL.createAssetTest({ FileName: 'OA Test', FilePath: '/OA Test', idAssetGroup: null, idVAssetType: this.v1.idVocabulary, idSystemObject: null, StorageKey: UTIL.randomStorageKey('/'), idAsset: 0 }); this.assetVersionT3 = await UTIL.createAssetVersionTest({ idAsset: this.assetT3.idAsset, idUserCreator: this.user1.idUser, DateCreated: UTIL.nowCleansed(), StorageHash: 'OA Test', StorageSize: 500, idAssetVersion: 0, Ingested: true, BulkIngest: false, FileName: '', StorageKeyStaging: '', Version: 0 }); - this.captureData2 = await UTIL.createCaptureDataTest({ idVCaptureMethod: this.v1.idVocabulary, DateCaptured: UTIL.nowCleansed(), Description: 'OA Test', idAssetThumbnail: this.assetT3.idAsset, idCaptureData: 0 }); + this.captureData2 = await UTIL.createCaptureDataTest({ Name: 'OA Test', idVCaptureMethod: this.v1.idVocabulary, DateCaptured: UTIL.nowCleansed(), Description: 'OA Test', idAssetThumbnail: this.assetT3.idAsset, idCaptureData: 0 }); assigned = await this.assetT3.assignOwner(this.captureData2); expect(assigned).toBeTruthy(); this.assetT4 = await UTIL.createAssetTest({ FileName: 'OA Test', FilePath: '/OA Test', idAssetGroup: null, idVAssetType: this.v1.idVocabulary, idSystemObject: null, StorageKey: UTIL.randomStorageKey('/'), idAsset: 0 }); diff --git a/server/tests/db/dbcreation.test.ts b/server/tests/db/dbcreation.test.ts index b9c9187e8..51d3ddc8a 100644 --- a/server/tests/db/dbcreation.test.ts +++ b/server/tests/db/dbcreation.test.ts @@ -554,6 +554,7 @@ describe('DB Creation Test Suite', () => { test('DB Creation: CaptureData', async () => { if (vocabulary && assetThumbnail) captureData = await UTIL.createCaptureDataTest({ + Name: 'Test Capture Data', idVCaptureMethod: vocabulary.idVocabulary, DateCaptured: UTIL.nowCleansed(), Description: 'Test Capture Data', @@ -566,6 +567,7 @@ describe('DB Creation Test Suite', () => { test('DB Creation: CaptureData With Nulls', async () => { if (vocabulary) captureDataNulls = await UTIL.createCaptureDataTest({ + Name: 'Test Capture Data Nulls', idVCaptureMethod: vocabulary.idVocabulary, DateCaptured: UTIL.nowCleansed(), Description: 'Test Capture Data Nulls', @@ -3478,7 +3480,7 @@ describe('DB Fetch Xref Test Suite', () => { // DB Fetch Special Test Suite // ******************************************************************* describe('DB Fetch Special Test Suite', () => { - test('DB FetchSpecial: Asset.assetType undefined', async() => { + test('DB Fetch Special: Asset.assetType undefined', async() => { let eVocabID: eVocabularyID | undefined = undefined; if (assetThumbnail) eVocabID = await assetThumbnail.assetType(); @@ -3486,7 +3488,7 @@ describe('DB Fetch Special Test Suite', () => { expect(eVocabID).toBeFalsy(); }); - test('DB FetchSpecial: Asset.assetType defined', async() => { + test('DB Fetch Special: Asset.assetType defined', async() => { let eVocabID: eVocabularyID | undefined = undefined; if (assetBulkIngest) eVocabID = await assetBulkIngest.assetType(); @@ -3495,7 +3497,7 @@ describe('DB Fetch Special Test Suite', () => { expect(eVocabID).toEqual(eVocabularyID.eAssetAssetTypeBulkIngestion); }); - test('DB FetchSpecial: Asset.setAssetType', async() => { + test('DB Fetch Special: Asset.setAssetType', async() => { expect(assetThumbnail).toBeTruthy(); if (assetThumbnail) { expect(await assetThumbnail.setAssetType(eVocabularyID.eNone)).toBeFalsy(); @@ -3512,14 +3514,14 @@ describe('DB Fetch Special Test Suite', () => { }); - test('DB FetchSpecial: Asset.fetchSourceSystemObject 1', async() => { + test('DB Fetch Special: Asset.fetchSourceSystemObject 1', async() => { let SOAsset: DBAPI.SystemObject | null = null; if (assetBulkIngest) SOAsset = await assetBulkIngest.fetchSourceSystemObject(); expect(SOAsset).toBeFalsy(); }); - test('DB FetchSpecial: Asset.fetchSourceSystemObject 2', async() => { + test('DB Fetch Special: Asset.fetchSourceSystemObject 2', async() => { let SOAssetSource: DBAPI.SystemObject | null = null; if (assetWithoutAG) SOAssetSource = await assetWithoutAG.fetchSourceSystemObject(); @@ -3551,6 +3553,26 @@ describe('DB Fetch Special Test Suite', () => { expect(captureDataFetch).toBeTruthy(); }); + test('DB Fetch Special: CaptureData.fetchAll', async () => { + let captureDataFetch: DBAPI.CaptureData[] | null = null; + if (captureData && captureDataNulls) { + captureDataFetch = await DBAPI.CaptureData.fetchAll(); + if (captureDataFetch) + expect(captureDataFetch).toEqual(expect.arrayContaining([captureData, captureDataNulls])); + } + expect(captureDataFetch).toBeTruthy(); + }); + + test('DB Fetch Special: CaptureDataPhoto.fetchAll', async () => { + let captureDataPhotoFetch: DBAPI.CaptureDataPhoto[] | null = null; + if (captureDataPhoto && captureDataPhotoNulls) { + captureDataPhotoFetch = await DBAPI.CaptureDataPhoto.fetchAll(); + if (captureDataPhotoFetch) + expect(captureDataPhotoFetch).toEqual(expect.arrayContaining([captureDataPhoto, captureDataPhotoNulls])); + } + expect(captureDataPhotoFetch).toBeTruthy(); + }); + test('DB Fetch Special: Identifier.fetchFromIdentifierValue', async () => { const identifierFetch: DBAPI.Identifier[] | null = await DBAPI.Identifier.fetchFromIdentifierValue(identifierValue); expect(identifierFetch).toBeTruthy(); @@ -3582,6 +3604,16 @@ describe('DB Fetch Special Test Suite', () => { expect(intermediaryFileFetch).toBeTruthy(); }); + test('DB Fetch Special: Item.fetchAll', async () => { + let itemFetch: DBAPI.Item[] | null = null; + if (item && itemNulls) { + itemFetch = await DBAPI.Item.fetchAll(); + if (itemFetch) + expect(itemFetch).toEqual(expect.arrayContaining([item, itemNulls])); + } + expect(itemFetch).toBeTruthy(); + }); + test('DB Fetch Special: Item.fetchDerivedFromSubject', async () => { let items: DBAPI.Item[] | null = null; if (subject && item && itemNulls) { @@ -3749,6 +3781,16 @@ describe('DB Fetch Special Test Suite', () => { expect(stakeholderFetch).toBeTruthy(); }); + test('DB Fetch Special: Subject.fetchAll', async () => { + let subjectFetch: DBAPI.Subject[] | null = null; + if (subject && subjectNulls) { + subjectFetch = await DBAPI.Subject.fetchAll(); + if (subjectFetch) + expect(subjectFetch).toEqual(expect.arrayContaining([subject, subjectNulls])); + } + expect(subjectFetch).toBeTruthy(); + }); + test('DB Fetch Special: SystemObject.fetchAll', async () => { const SOFetch: DBAPI.SystemObject[] | null = await DBAPI.SystemObject.fetchAll(); expect(SOFetch).toBeTruthy(); diff --git a/server/tests/graphql/utils/index.ts b/server/tests/graphql/utils/index.ts index 63b55c887..66c6778fd 100644 --- a/server/tests/graphql/utils/index.ts +++ b/server/tests/graphql/utils/index.ts @@ -117,6 +117,7 @@ class TestSuiteUtils { createCaptureDataInput = (idVocabulary: number): CreateCaptureDataInput => { return { + Name: 'Test Name', idVCaptureMethod: idVocabulary, DateCaptured: new Date(), Description: 'Test Description' diff --git a/server/types/graphql.ts b/server/types/graphql.ts index fab18ddff..ea1d22453 100644 --- a/server/types/graphql.ts +++ b/server/types/graphql.ts @@ -589,6 +589,7 @@ export type AssetGroup = { }; export type CreateCaptureDataInput = { + Name: Scalars['String']; idVCaptureMethod: Scalars['Int']; DateCaptured: Scalars['DateTime']; Description: Scalars['String']; From f2fe3c418041f8a5a659b93b94cf94dca87e9ec4 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Tue, 15 Dec 2020 20:14:47 +0530 Subject: [PATCH 145/239] added vocabulary to filter view --- .../components/DetailsView/index.tsx | 2 +- .../RepositoryFilterView/FilterSelect.tsx | 5 +- .../RepositoryFilterOptions.ts | 88 +++++++++++++++++++ .../components/RepositoryFilterView/index.tsx | 53 +++++------ client/src/store/repository.ts | 12 +-- client/src/store/vocabulary.ts | 3 +- 6 files changed, 128 insertions(+), 35 deletions(-) create mode 100644 client/src/pages/Repository/components/RepositoryFilterView/RepositoryFilterOptions.ts diff --git a/client/src/pages/Repository/components/DetailsView/index.tsx b/client/src/pages/Repository/components/DetailsView/index.tsx index e182279d0..1caabc455 100644 --- a/client/src/pages/Repository/components/DetailsView/index.tsx +++ b/client/src/pages/Repository/components/DetailsView/index.tsx @@ -83,7 +83,7 @@ function DetailsView(): React.ReactElement { }; const onAddDerivedObject = () => { - alert('TODO: KARAN: on add derived object'); + setModalOpen(true); }; return ( diff --git a/client/src/pages/Repository/components/RepositoryFilterView/FilterSelect.tsx b/client/src/pages/Repository/components/RepositoryFilterView/FilterSelect.tsx index 0f08fd7aa..2433f0b9f 100644 --- a/client/src/pages/Repository/components/RepositoryFilterView/FilterSelect.tsx +++ b/client/src/pages/Repository/components/RepositoryFilterView/FilterSelect.tsx @@ -7,6 +7,7 @@ import { Box, MenuItem, Select, Typography } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; import React from 'react'; import { useRepositoryStore } from '../../../../store'; +import { FilterOption } from './RepositoryFilterOptions'; const useStyles = makeStyles(({ palette, breakpoints }) => ({ label: { @@ -34,7 +35,7 @@ const useStyles = makeStyles(({ palette, breakpoints }) => ({ interface FilterSelectProps { label: string; name: string; - options: number[]; + options: FilterOption[]; multiple?: boolean; } @@ -72,7 +73,7 @@ function FilterSelect(props: FilterSelectProps): React.ReactElement { disableUnderline inputProps={inputProps} > - {options.map((v, index) => {v})} + {options.map(({ label, value }: FilterOption, index) => {label})} ); diff --git a/client/src/pages/Repository/components/RepositoryFilterView/RepositoryFilterOptions.ts b/client/src/pages/Repository/components/RepositoryFilterView/RepositoryFilterOptions.ts new file mode 100644 index 000000000..91f6111dc --- /dev/null +++ b/client/src/pages/Repository/components/RepositoryFilterView/RepositoryFilterOptions.ts @@ -0,0 +1,88 @@ +/** + * RepositoryFilterOptions + * + * Default options for repository filter view. + */ +import { Vocabulary } from '../../../../types/graphql'; +import { eMetadata, eSystemObjectType, eVocabularySetID } from '../../../../types/server'; +import { getTermForSystemObjectType } from '../../../../utils/repository'; + +export type FilterOption = { + label: string; + value: number; +}; + +type RepositoryFilterOptionsInput = { + getEntries: (eVocabularySetID: eVocabularySetID) => Pick[] +}; + +type RepositoryFilterOptionsResult = { + mockOptions: FilterOption[]; + repositoryRootTypesOptions: FilterOption[]; + objectToDisplayOptions: FilterOption[]; + metadataToDisplayOptions: FilterOption[]; + captureMethodOptions: FilterOption[]; + variantTypeOptions: FilterOption[]; + modelPurposeOptions: FilterOption[]; + fileTypeOptions: FilterOption[]; + hasOptions: FilterOption[]; + missingOptions: FilterOption[]; +}; + +export function getRepositoryFilterOptions({ getEntries }: RepositoryFilterOptionsInput): RepositoryFilterOptionsResult { + const systemObjectTypes: FilterOption[] = [ + { label: getTermForSystemObjectType(eSystemObjectType.eUnit), value: eSystemObjectType.eUnit }, + { label: getTermForSystemObjectType(eSystemObjectType.eProject), value: eSystemObjectType.eProject }, + { label: getTermForSystemObjectType(eSystemObjectType.eSubject), value: eSystemObjectType.eSubject }, + { label: getTermForSystemObjectType(eSystemObjectType.eItem), value: eSystemObjectType.eItem }, + { label: getTermForSystemObjectType(eSystemObjectType.eCaptureData), value: eSystemObjectType.eCaptureData }, + { label: getTermForSystemObjectType(eSystemObjectType.eModel), value: eSystemObjectType.eModel }, + { label: getTermForSystemObjectType(eSystemObjectType.eScene), value: eSystemObjectType.eScene }, + { label: getTermForSystemObjectType(eSystemObjectType.eIntermediaryFile), value: eSystemObjectType.eIntermediaryFile }, + { label: getTermForSystemObjectType(eSystemObjectType.eProjectDocumentation), value: eSystemObjectType.eProjectDocumentation }, + { label: getTermForSystemObjectType(eSystemObjectType.eAsset), value: eSystemObjectType.eAsset }, + { label: getTermForSystemObjectType(eSystemObjectType.eAssetVersion), value: eSystemObjectType.eAssetVersion }, + { label: getTermForSystemObjectType(eSystemObjectType.eActor), value: eSystemObjectType.eActor }, + { label: getTermForSystemObjectType(eSystemObjectType.eStakeholder), value: eSystemObjectType.eStakeholder }, + { label: getTermForSystemObjectType(eSystemObjectType.eUnknown), value: eSystemObjectType.eUnknown }, + ]; + + const mockOptions: FilterOption[] = [ + { label: '-', value: 0 }, + { label: '-', value: 1 } + ]; + + const repositoryRootTypesOptions: FilterOption[] = systemObjectTypes.slice(0, 2); + + const objectToDisplayOptions: FilterOption[] = systemObjectTypes.slice(0, 2); + + const metadataToDisplayOptions: FilterOption[] = [ + { label: 'Unit Abbreviation', value: eMetadata.eUnitAbbreviation }, + { label: 'Subject Identifier', value: eMetadata.eSubjectIdentifier }, + { label: 'Item Name', value: eMetadata.eItemName } + ]; + + const captureMethodOptions: FilterOption[] = vocabulariesToFilterOption(getEntries(eVocabularySetID.eCaptureDataCaptureMethod)); + const variantTypeOptions: FilterOption[] = vocabulariesToFilterOption(getEntries(eVocabularySetID.eCaptureDataFileVariantType)); + const modelPurposeOptions: FilterOption[] = vocabulariesToFilterOption(getEntries(eVocabularySetID.eModelPurpose)); + const fileTypeOptions: FilterOption[] = vocabulariesToFilterOption(getEntries(eVocabularySetID.eModelGeometryFileModelFileType)); + const hasOptions: FilterOption[] = systemObjectTypes; + const missingOptions: FilterOption[] = systemObjectTypes; + + return { + mockOptions, + repositoryRootTypesOptions, + objectToDisplayOptions, + metadataToDisplayOptions, + captureMethodOptions, + variantTypeOptions, + modelPurposeOptions, + fileTypeOptions, + hasOptions, + missingOptions + }; +} + +function vocabulariesToFilterOption(vocabularies: Pick[]): FilterOption[] { + return vocabularies.map(({ idVocabulary, Term }) => ({ label: Term, value: idVocabulary })); +} \ No newline at end of file diff --git a/client/src/pages/Repository/components/RepositoryFilterView/index.tsx b/client/src/pages/Repository/components/RepositoryFilterView/index.tsx index 975870aa6..09892bfed 100644 --- a/client/src/pages/Repository/components/RepositoryFilterView/index.tsx +++ b/client/src/pages/Repository/components/RepositoryFilterView/index.tsx @@ -10,11 +10,11 @@ import { FaChevronDown, FaChevronUp } from 'react-icons/fa'; import { FiLink2 } from 'react-icons/fi'; import { IoIosRemoveCircle } from 'react-icons/io'; import { toast } from 'react-toastify'; -import { useRepositoryStore } from '../../../../store'; +import { useRepositoryStore, useVocabularyStore } from '../../../../store'; import { Colors, palette } from '../../../../theme'; -import { eSystemObjectType } from '../../../../types/server'; import FilterDate from './FilterDate'; import FilterSelect from './FilterSelect'; +import { getRepositoryFilterOptions } from './RepositoryFilterOptions'; const useStyles = makeStyles(({ palette, typography, breakpoints }) => ({ container: { @@ -98,26 +98,29 @@ const StyledChip = withStyles(({ palette }) => ({ } }))(Chip); -const mockOptions: number[] = [0, 1, 2, 3]; -const repositoryRootTypes: eSystemObjectType[] = [eSystemObjectType.eUnit, eSystemObjectType.eProject]; - function RepositoryFilterView(): React.ReactElement { const [isExpanded, toggleFilter] = useRepositoryStore(state => [state.isExpanded, state.toggleFilter]); + const getEntries = useVocabularyStore(state => state.getEntries); + + const { + mockOptions, + repositoryRootTypesOptions, + objectToDisplayOptions, + metadataToDisplayOptions, + captureMethodOptions, + variantTypeOptions, + modelPurposeOptions, + fileTypeOptions, + hasOptions, + missingOptions + } = getRepositoryFilterOptions({ getEntries }); + const classes = useStyles(isExpanded); - const [chips] = useState([ - { - type: 'Unit', - name: 'NMNH' - }, - { - type: 'Project', - name: 'Seashell' - } - ]); + const [chips] = useState([]); const onCopyLink = (): void => { if ('clipboard' in navigator) { - navigator.clipboard.writeText(''); + navigator.clipboard.writeText(window.location.href); toast.success('Link has been copied to your clipboard'); } }; @@ -163,24 +166,24 @@ function RepositoryFilterView(): React.ReactElement { {content} - - - + + + - - + + - - - - + + + + diff --git a/client/src/store/repository.ts b/client/src/store/repository.ts index 69ed6f1c8..e1f7395cf 100644 --- a/client/src/store/repository.ts +++ b/client/src/store/repository.ts @@ -44,12 +44,12 @@ export const useRepositoryStore = create((set: SetState { const { initializeTree } = get(); set({ [name]: value, loading: true }); diff --git a/client/src/store/vocabulary.ts b/client/src/store/vocabulary.ts index 12f83b980..6fbb9019c 100644 --- a/client/src/store/vocabulary.ts +++ b/client/src/store/vocabulary.ts @@ -49,7 +49,8 @@ export const useVocabularyStore = create((set: SetState Date: Tue, 15 Dec 2020 20:22:15 +0530 Subject: [PATCH 146/239] fix server restart on ingestion --- server/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/package.json b/server/package.json index 034a55f69..9780bdba4 100644 --- a/server/package.json +++ b/server/package.json @@ -27,7 +27,7 @@ "scripts": { "start": "yarn build && concurrently 'yarn build:watch' 'yarn server:start'", "start:prod": "node build/index.js", - "server:start": "nodemon build/index.js", + "server:start": "nodemon build/index.js --ignore var/ --ignore build/", "build": "tsc --build && copyfiles db/**/*.* graphql/**/**.graphql build/", "build:watch": "tsc --watch", "clean": "rm -rf node_modules/ build/", From 9a7d8f6ba89488773ed4971ef0d0daa9e4bbe16c Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Wed, 16 Dec 2020 15:52:05 +0530 Subject: [PATCH 147/239] added getFilterViewData graphql query --- client/src/types/graphql.tsx | 85 +++++++++++++++++++ server/graphql/api/index.ts | 17 +++- .../queries/repository/getFilterViewData.ts | 24 ++++++ server/graphql/schema.graphql | 6 ++ .../graphql/schema/repository/queries.graphql | 6 ++ .../schema/repository/resolvers/index.ts | 4 +- .../resolvers/queries/getFilterViewData.ts | 22 +++++ server/types/graphql.ts | 7 ++ 8 files changed, 168 insertions(+), 3 deletions(-) create mode 100644 server/graphql/api/queries/repository/getFilterViewData.ts create mode 100644 server/graphql/schema/repository/resolvers/queries/getFilterViewData.ts diff --git a/client/src/types/graphql.tsx b/client/src/types/graphql.tsx index f3fa4c7dd..a91956ff6 100644 --- a/client/src/types/graphql.tsx +++ b/client/src/types/graphql.tsx @@ -28,6 +28,7 @@ export type Query = { getContentsForAssetVersions: GetContentsForAssetVersionsResult; getCurrentUser: GetCurrentUserResult; getDetailsTabDataForObject: GetDetailsTabDataForObjectResult; + getFilterViewData: GetFilterViewDataResult; getIngestionItemsForSubjects: GetIngestionItemsForSubjectsResult; getIngestionProjectsForSubjects: GetIngestionProjectsForSubjectsResult; getIntermediaryFile: GetIntermediaryFileResult; @@ -1035,6 +1036,12 @@ export type GetObjectChildrenResult = { metadataColumns: Array; }; +export type GetFilterViewDataResult = { + __typename?: 'GetFilterViewDataResult'; + units: Array; + projects: Array; +}; + export type CreateSceneInput = { Name: Scalars['String']; HasBeenQCd: Scalars['Boolean']; @@ -2226,6 +2233,39 @@ export type GetModelQuery = ( } ); +export type GetFilterViewDataQueryVariables = Exact<{ [key: string]: never; }>; + + +export type GetFilterViewDataQuery = ( + { __typename?: 'Query' } + & { + getFilterViewData: ( + { __typename?: 'GetFilterViewDataResult' } + & { + units: Array<( + { __typename?: 'Unit' } + & Pick + & { + SystemObject?: Maybe<( + { __typename?: 'SystemObject' } + & Pick + )> + } + )>, projects: Array<( + { __typename?: 'Project' } + & Pick + & { + SystemObject?: Maybe<( + { __typename?: 'SystemObject' } + & Pick + )> + } + )> + } + ) + } +); + export type GetObjectChildrenQueryVariables = Exact<{ input: GetObjectChildrenInput; }>; @@ -3739,6 +3779,51 @@ export function useGetModelLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions; export type GetModelLazyQueryHookResult = ReturnType; export type GetModelQueryResult = Apollo.QueryResult; +export const GetFilterViewDataDocument = gql` + query getFilterViewData { + getFilterViewData { + units { + idUnit + Name + SystemObject { + idSystemObject + } + } + projects { + idProject + Name + SystemObject { + idSystemObject + } + } + } +} + `; + +/** + * __useGetFilterViewDataQuery__ + * + * To run a query within a React component, call `useGetFilterViewDataQuery` and pass it any options that fit your needs. + * When your component renders, `useGetFilterViewDataQuery` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. + * + * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useGetFilterViewDataQuery({ + * variables: { + * }, + * }); + */ +export function useGetFilterViewDataQuery(baseOptions?: Apollo.QueryHookOptions) { + return Apollo.useQuery(GetFilterViewDataDocument, baseOptions); +} +export function useGetFilterViewDataLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { + return Apollo.useLazyQuery(GetFilterViewDataDocument, baseOptions); +} +export type GetFilterViewDataQueryHookResult = ReturnType; +export type GetFilterViewDataLazyQueryHookResult = ReturnType; +export type GetFilterViewDataQueryResult = Apollo.QueryResult; export const GetObjectChildrenDocument = gql` query getObjectChildren($input: GetObjectChildrenInput!) { getObjectChildren(input: $input) { diff --git a/server/graphql/api/index.ts b/server/graphql/api/index.ts index c513baa59..b1802c744 100644 --- a/server/graphql/api/index.ts +++ b/server/graphql/api/index.ts @@ -100,7 +100,8 @@ import { GetVersionsForSystemObjectInput, GetVersionsForSystemObjectResult, GetDetailsTabDataForObjectInput, - GetDetailsTabDataForObjectResult + GetDetailsTabDataForObjectResult, + GetFilterViewDataResult } from '../../types/graphql'; // Queries @@ -138,6 +139,7 @@ import getSystemObjectDetails from './queries/systemobject/getSystemObjectDetail import getAssetDetailsForSystemObject from './queries/systemobject/getAssetDetailsForSystemObject'; import getVersionsForSystemObject from './queries/systemobject/getVersionsForSystemObject'; import getDetailsTabDataForObject from './queries/systemobject/getDetailsTabDataForObject'; +import getFilterViewData from './queries/repository/getFilterViewData'; // Mutations import createUser from './mutations/user/createUser'; @@ -205,7 +207,8 @@ const allQueries = { getSystemObjectDetails, getAssetDetailsForSystemObject, getVersionsForSystemObject, - getDetailsTabDataForObject + getDetailsTabDataForObject, + getFilterViewData }; type GraphQLRequest = { @@ -586,6 +589,16 @@ class GraphQLApi { }); } + async getFilterViewData(context?: Context): Promise { + const operationName = 'getFilterViewData'; + const variables = {}; + return this.graphqlRequest({ + operationName, + variables, + context + }); + } + async createUnit(input: CreateUnitInput, context?: Context): Promise { const operationName = 'createUnit'; const variables = { input }; diff --git a/server/graphql/api/queries/repository/getFilterViewData.ts b/server/graphql/api/queries/repository/getFilterViewData.ts new file mode 100644 index 000000000..c5f8ee9b0 --- /dev/null +++ b/server/graphql/api/queries/repository/getFilterViewData.ts @@ -0,0 +1,24 @@ +import { gql } from 'apollo-server-express'; + +const getFilterViewData = gql` + query getFilterViewData { + getFilterViewData { + units { + idUnit + Name + SystemObject { + idSystemObject + } + } + projects { + idProject + Name + SystemObject { + idSystemObject + } + } + } + } +`; + +export default getFilterViewData; diff --git a/server/graphql/schema.graphql b/server/graphql/schema.graphql index e05d6f804..4f73c7996 100644 --- a/server/graphql/schema.graphql +++ b/server/graphql/schema.graphql @@ -9,6 +9,7 @@ type Query { getContentsForAssetVersions(input: GetContentsForAssetVersionsInput!): GetContentsForAssetVersionsResult! getCurrentUser: GetCurrentUserResult! getDetailsTabDataForObject(input: GetDetailsTabDataForObjectInput!): GetDetailsTabDataForObjectResult! + getFilterViewData: GetFilterViewDataResult! getIngestionItemsForSubjects(input: GetIngestionItemsForSubjectsInput!): GetIngestionItemsForSubjectsResult! getIngestionProjectsForSubjects(input: GetIngestionProjectsForSubjectsInput!): GetIngestionProjectsForSubjectsResult! getIntermediaryFile(input: GetIntermediaryFileInput!): GetIntermediaryFileResult! @@ -736,6 +737,11 @@ type GetObjectChildrenResult { metadataColumns: [Int!]! } +type GetFilterViewDataResult { + units: [Unit!]! + projects: [Project!]! +} + input CreateSceneInput { Name: String! HasBeenQCd: Boolean! diff --git a/server/graphql/schema/repository/queries.graphql b/server/graphql/schema/repository/queries.graphql index 753b75ec7..e47253fcf 100644 --- a/server/graphql/schema/repository/queries.graphql +++ b/server/graphql/schema/repository/queries.graphql @@ -1,5 +1,6 @@ type Query { getObjectChildren(input: GetObjectChildrenInput!): GetObjectChildrenResult! + getFilterViewData: GetFilterViewDataResult! } input GetObjectChildrenInput { @@ -22,3 +23,8 @@ type GetObjectChildrenResult { entries: [NavigationResultEntry!]! metadataColumns: [Int!]! } + +type GetFilterViewDataResult { + units: [Unit!]! + projects: [Project!]! +} diff --git a/server/graphql/schema/repository/resolvers/index.ts b/server/graphql/schema/repository/resolvers/index.ts index 94c1b4426..f334a97dd 100644 --- a/server/graphql/schema/repository/resolvers/index.ts +++ b/server/graphql/schema/repository/resolvers/index.ts @@ -1,8 +1,10 @@ import getObjectChildren from './queries/getObjectChildren'; +import getFilterViewData from './queries/getFilterViewData'; const resolvers = { Query: { - getObjectChildren + getObjectChildren, + getFilterViewData } }; diff --git a/server/graphql/schema/repository/resolvers/queries/getFilterViewData.ts b/server/graphql/schema/repository/resolvers/queries/getFilterViewData.ts new file mode 100644 index 000000000..32213b68a --- /dev/null +++ b/server/graphql/schema/repository/resolvers/queries/getFilterViewData.ts @@ -0,0 +1,22 @@ +import * as DBAPI from '../../../../../db'; +import { GetFilterViewDataResult } from '../../../../../types/graphql'; + +export default async function getFilterViewData(): Promise { + + const units: DBAPI.Unit[] = []; + const projects: DBAPI.Project[] = []; + + const Unit: DBAPI.Unit[] | null = await DBAPI.Unit.fetchAll(); + + if (Unit && Unit.length) { + units.push(...Unit); + } + + const Project: DBAPI.Project[] | null = await DBAPI.Project.fetchAll(); + + if (Project && Project.length) { + projects.push(...Project); + } + + return { units, projects }; +} diff --git a/server/types/graphql.ts b/server/types/graphql.ts index fab18ddff..8f0eb73f9 100644 --- a/server/types/graphql.ts +++ b/server/types/graphql.ts @@ -24,6 +24,7 @@ export type Query = { getContentsForAssetVersions: GetContentsForAssetVersionsResult; getCurrentUser: GetCurrentUserResult; getDetailsTabDataForObject: GetDetailsTabDataForObjectResult; + getFilterViewData: GetFilterViewDataResult; getIngestionItemsForSubjects: GetIngestionItemsForSubjectsResult; getIngestionProjectsForSubjects: GetIngestionProjectsForSubjectsResult; getIntermediaryFile: GetIntermediaryFileResult; @@ -1031,6 +1032,12 @@ export type GetObjectChildrenResult = { metadataColumns: Array; }; +export type GetFilterViewDataResult = { + __typename?: 'GetFilterViewDataResult'; + units: Array; + projects: Array; +}; + export type CreateSceneInput = { Name: Scalars['String']; HasBeenQCd: Scalars['Boolean']; From c8800bf8031a2070674ebcbf16986102cead7595 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Wed, 16 Dec 2020 15:52:58 +0530 Subject: [PATCH 148/239] integrate getFilterViewData with filter view --- .../RepositoryFilterOptions.ts | 45 ++++++++++--- .../components/RepositoryFilterView/index.tsx | 66 +++++++++++-------- 2 files changed, 75 insertions(+), 36 deletions(-) diff --git a/client/src/pages/Repository/components/RepositoryFilterView/RepositoryFilterOptions.ts b/client/src/pages/Repository/components/RepositoryFilterView/RepositoryFilterOptions.ts index 91f6111dc..bbca84501 100644 --- a/client/src/pages/Repository/components/RepositoryFilterView/RepositoryFilterOptions.ts +++ b/client/src/pages/Repository/components/RepositoryFilterView/RepositoryFilterOptions.ts @@ -3,7 +3,7 @@ * * Default options for repository filter view. */ -import { Vocabulary } from '../../../../types/graphql'; +import { GetFilterViewDataQuery, Vocabulary } from '../../../../types/graphql'; import { eMetadata, eSystemObjectType, eVocabularySetID } from '../../../../types/server'; import { getTermForSystemObjectType } from '../../../../utils/repository'; @@ -12,12 +12,23 @@ export type FilterOption = { value: number; }; +export type ChipOption = { + id: number; + type: eSystemObjectType; + name: string; +}; + type RepositoryFilterOptionsInput = { + units: number[]; + projects: number[]; + data: GetFilterViewDataQuery | undefined; getEntries: (eVocabularySetID: eVocabularySetID) => Pick[] }; type RepositoryFilterOptionsResult = { - mockOptions: FilterOption[]; + chipsOptions: ChipOption[]; + unitsOptions: FilterOption[]; + projectsOptions: FilterOption[]; repositoryRootTypesOptions: FilterOption[]; objectToDisplayOptions: FilterOption[]; metadataToDisplayOptions: FilterOption[]; @@ -29,7 +40,7 @@ type RepositoryFilterOptionsResult = { missingOptions: FilterOption[]; }; -export function getRepositoryFilterOptions({ getEntries }: RepositoryFilterOptionsInput): RepositoryFilterOptionsResult { +export function getRepositoryFilterOptions({ units, projects, data, getEntries }: RepositoryFilterOptionsInput): RepositoryFilterOptionsResult { const systemObjectTypes: FilterOption[] = [ { label: getTermForSystemObjectType(eSystemObjectType.eUnit), value: eSystemObjectType.eUnit }, { label: getTermForSystemObjectType(eSystemObjectType.eProject), value: eSystemObjectType.eProject }, @@ -47,10 +58,21 @@ export function getRepositoryFilterOptions({ getEntries }: RepositoryFilterOptio { label: getTermForSystemObjectType(eSystemObjectType.eUnknown), value: eSystemObjectType.eUnknown }, ]; - const mockOptions: FilterOption[] = [ - { label: '-', value: 0 }, - { label: '-', value: 1 } - ]; + const chipsOptions: ChipOption[] = []; + let unitsOptions: FilterOption[] = []; + let projectsOptions: FilterOption[] = []; + + const getFilterViewData = data?.getFilterViewData; + + if (getFilterViewData?.units && getFilterViewData.units.length) { + unitsOptions = getFilterViewData?.units.map(({ Name, SystemObject }) => ({ label: Name, value: SystemObject?.idSystemObject ?? 0 })); + chipsOptions.push(...filterOptionToChipOption(units, unitsOptions, eSystemObjectType.eUnit)); + } + + if (getFilterViewData?.projects && getFilterViewData.projects.length) { + projectsOptions = getFilterViewData?.projects.map(({ Name, SystemObject }) => ({ label: Name, value: SystemObject?.idSystemObject ?? 0 })); + chipsOptions.push(...filterOptionToChipOption(projects, projectsOptions, eSystemObjectType.eProject)); + } const repositoryRootTypesOptions: FilterOption[] = systemObjectTypes.slice(0, 2); @@ -70,7 +92,9 @@ export function getRepositoryFilterOptions({ getEntries }: RepositoryFilterOptio const missingOptions: FilterOption[] = systemObjectTypes; return { - mockOptions, + chipsOptions, + unitsOptions, + projectsOptions, repositoryRootTypesOptions, objectToDisplayOptions, metadataToDisplayOptions, @@ -85,4 +109,9 @@ export function getRepositoryFilterOptions({ getEntries }: RepositoryFilterOptio function vocabulariesToFilterOption(vocabularies: Pick[]): FilterOption[] { return vocabularies.map(({ idVocabulary, Term }) => ({ label: Term, value: idVocabulary })); +} + +function filterOptionToChipOption(selectedIds: number[], options: FilterOption[], type: eSystemObjectType): ChipOption[] { + const selectedOptions: FilterOption[] = options.filter(({ value }) => selectedIds.includes(value)); + return selectedOptions.map(({ label: name, value: id }: FilterOption) => ({ id, name, type })); } \ No newline at end of file diff --git a/client/src/pages/Repository/components/RepositoryFilterView/index.tsx b/client/src/pages/Repository/components/RepositoryFilterView/index.tsx index 09892bfed..c49632adb 100644 --- a/client/src/pages/Repository/components/RepositoryFilterView/index.tsx +++ b/client/src/pages/Repository/components/RepositoryFilterView/index.tsx @@ -5,16 +5,19 @@ */ import { Box, Chip, Typography } from '@material-ui/core'; import { fade, makeStyles, withStyles } from '@material-ui/core/styles'; -import React, { useState } from 'react'; +import React, { memo } from 'react'; import { FaChevronDown, FaChevronUp } from 'react-icons/fa'; import { FiLink2 } from 'react-icons/fi'; import { IoIosRemoveCircle } from 'react-icons/io'; import { toast } from 'react-toastify'; +import { Loader } from '../../../../components'; import { useRepositoryStore, useVocabularyStore } from '../../../../store'; import { Colors, palette } from '../../../../theme'; +import { useGetFilterViewDataQuery } from '../../../../types/graphql'; +import { getTermForSystemObjectType } from '../../../../utils/repository'; import FilterDate from './FilterDate'; import FilterSelect from './FilterSelect'; -import { getRepositoryFilterOptions } from './RepositoryFilterOptions'; +import { ChipOption, getRepositoryFilterOptions } from './RepositoryFilterOptions'; const useStyles = makeStyles(({ palette, typography, breakpoints }) => ({ container: { @@ -99,11 +102,16 @@ const StyledChip = withStyles(({ palette }) => ({ }))(Chip); function RepositoryFilterView(): React.ReactElement { - const [isExpanded, toggleFilter] = useRepositoryStore(state => [state.isExpanded, state.toggleFilter]); + const { data, loading } = useGetFilterViewDataQuery(); + const [units, projects, isExpanded] = useRepositoryStore(state => [state.units, state.projects, state.isExpanded]); + const [toggleFilter, removeUnitsOrProjects] = useRepositoryStore(state => [state.toggleFilter, state.removeUnitsOrProjects]); const getEntries = useVocabularyStore(state => state.getEntries); + const classes = useStyles(isExpanded); const { - mockOptions, + chipsOptions, + unitsOptions, + projectsOptions, repositoryRootTypesOptions, objectToDisplayOptions, metadataToDisplayOptions, @@ -113,10 +121,7 @@ function RepositoryFilterView(): React.ReactElement { fileTypeOptions, hasOptions, missingOptions - } = getRepositoryFilterOptions({ getEntries }); - - const classes = useStyles(isExpanded); - const [chips] = useState([]); + } = getRepositoryFilterOptions({ data, units, projects, getEntries }); const onCopyLink = (): void => { if ('clipboard' in navigator) { @@ -131,23 +136,24 @@ function RepositoryFilterView(): React.ReactElement { Unit: All - {chips.map((chip, index: number) => { - const { type, name } = chip; - const handleDelete = () => null; - const label = `${type}: ${name}`; - - return ( - } - className={classes.chip} - onDelete={handleDelete} - variant='outlined' - /> - ); - })} + + {chipsOptions.map((chip: ChipOption, index: number) => { + const { id, type, name } = chip; + const label: string = `${getTermForSystemObjectType(type)}: ${name}`; + + return ( + } + className={classes.chip} + onDelete={() => removeUnitsOrProjects(id, type)} + variant='outlined' + /> + ); + })} + ); @@ -172,8 +178,8 @@ function RepositoryFilterView(): React.ReactElement {
- - + + @@ -201,6 +207,10 @@ function RepositoryFilterView(): React.ReactElement { ); } + if (!data || loading) { + content = ; + } + return ( @@ -216,4 +226,4 @@ function RepositoryFilterView(): React.ReactElement { ); } -export default RepositoryFilterView; +export default memo(RepositoryFilterView); From ff1fe1c8821aeeaaeffbc3e0f26df15c3508ddca Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Wed, 16 Dec 2020 15:53:37 +0530 Subject: [PATCH 149/239] added removeUnitsOrProjects to repository store --- client/src/store/repository.ts | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/client/src/store/repository.ts b/client/src/store/repository.ts index e1f7395cf..927c89a7e 100644 --- a/client/src/store/repository.ts +++ b/client/src/store/repository.ts @@ -27,6 +27,7 @@ type RepositoryStore = { variantType: number; modelPurpose: number; modelFileType: number; + removeUnitsOrProjects: (id: number, type: eSystemObjectType) => void; updateFilterValue: (name: string, value: number | number[]) => void; initializeTree: () => Promise; getChildren: (nodeId: string) => Promise; @@ -42,8 +43,8 @@ export const useRepositoryStore = create((set: SetState((set: SetState { + const { units, projects } = get(); + let updatedUnits: number[] = units.slice(); + let updatedProjects: number[] = projects.slice(); + + switch (type) { + case eSystemObjectType.eUnit: { + if (updatedUnits.length === 1) updatedUnits = []; + else updatedUnits = updatedUnits.filter(unit => unit === id); + break; + } + case eSystemObjectType.eProject: { + if (updatedProjects.length === 1) updatedProjects = []; + else updatedProjects = updatedProjects.filter(project => project === id); + break; + } + } + + set({ units: updatedUnits, projects: updatedProjects }); } })); From 3423b1b7ba832baa83bbbb1bdeb8b4ba2423b4df Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Fri, 18 Dec 2020 15:53:22 +0530 Subject: [PATCH 150/239] Embedded, Sync filter state in url params --- .../RepositoryFilterView/FilterSelect.tsx | 2 +- .../components/RepositoryFilterView/index.tsx | 8 +- client/src/pages/Repository/index.tsx | 74 +++++++++++++++---- client/src/store/repository.ts | 20 ++++- client/src/utils/repository.tsx | 25 ++++--- 5 files changed, 102 insertions(+), 27 deletions(-) diff --git a/client/src/pages/Repository/components/RepositoryFilterView/FilterSelect.tsx b/client/src/pages/Repository/components/RepositoryFilterView/FilterSelect.tsx index 2433f0b9f..7c4b84fba 100644 --- a/client/src/pages/Repository/components/RepositoryFilterView/FilterSelect.tsx +++ b/client/src/pages/Repository/components/RepositoryFilterView/FilterSelect.tsx @@ -65,7 +65,7 @@ function FilterSelect(props: FilterSelectProps): React.ReactElement { {label} {options.map(({ idVocabulary, Term }, index) => {Term})} diff --git a/client/src/pages/Repository/components/DetailsView/DetailsTab/AssetDetails.tsx b/client/src/pages/Repository/components/DetailsView/DetailsTab/AssetDetails.tsx index 28574993a..a3bd7c9fe 100644 --- a/client/src/pages/Repository/components/DetailsView/DetailsTab/AssetDetails.tsx +++ b/client/src/pages/Repository/components/DetailsView/DetailsTab/AssetDetails.tsx @@ -3,22 +3,82 @@ * * This component renders details tab for Asset specific details used in DetailsTab component. */ -import React from 'react'; -import { Loader } from '../../../../../components'; +import { Box } from '@material-ui/core'; +import React, { useEffect, useState } from 'react'; +import { InputField, Loader, SelectField } from '../../../../../components'; +import { useVocabularyStore } from '../../../../../store'; import { GetDetailsTabDataForObjectQueryResult } from '../../../../../types/graphql'; +import { eVocabularySetID } from '../../../../../types/server'; +import { withDefaultValueNumber } from '../../../../../utils/shared'; interface AssetDetailsProps extends GetDetailsTabDataForObjectQueryResult { disabled: boolean; } +interface AssetDetailsFields { + FilePath?: string | null; + AssetType?: number | null; +} + function AssetDetails(props: AssetDetailsProps): React.ReactElement { - const { data, loading } = props; + const { data, loading, disabled, } = props; + + const [details, setDetails] = useState({}); + const [getEntries, getInitialEntry] = useVocabularyStore(state => [state.getEntries, state.getInitialEntry]); + + useEffect(() => { + if (data && !loading) { + const { Asset } = data.getDetailsTabDataForObject; + setDetails({ + FilePath: Asset?.FilePath, + AssetType: Asset?.VAssetType?.idVocabulary + }); + } + }, [data, loading]); if (!data || loading) { return ; } - return Asset Details; + const onSetField = (event: React.ChangeEvent) => { + const { name, value } = event.target; + setDetails(details => ({ ...details, [name]: value })); + }; + + const setIdField = ({ target }): void => { + const { name, value } = target; + let idFieldValue: number | null = null; + + if (value) { + idFieldValue = Number.parseInt(value, 10); + } + console.log({ [name]: idFieldValue }); + setDetails(details => ({ ...details, [name]: idFieldValue })); + }; + + return ( + + + + + ); } export default AssetDetails; \ No newline at end of file diff --git a/client/src/pages/Repository/components/DetailsView/DetailsTab/IntermediaryFileDetails.tsx b/client/src/pages/Repository/components/DetailsView/DetailsTab/IntermediaryFileDetails.tsx index 72fd73b1c..d27f91e24 100644 --- a/client/src/pages/Repository/components/DetailsView/DetailsTab/IntermediaryFileDetails.tsx +++ b/client/src/pages/Repository/components/DetailsView/DetailsTab/IntermediaryFileDetails.tsx @@ -18,7 +18,7 @@ function IntermediaryFileDetails(props: IntermediaryFileDetailsProps): React.Rea return ; } - return IntermediaryFile Details; + return
; } export default IntermediaryFileDetails; \ No newline at end of file From 37455a9d1c0eaeb967cd19ec3f538b0ef73c7910 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Mon, 28 Dec 2020 14:48:24 +0530 Subject: [PATCH 157/239] added asset version details component --- .../DetailsTab/AssetVersionDetails.tsx | 95 ++++++++++++++++++- 1 file changed, 92 insertions(+), 3 deletions(-) diff --git a/client/src/pages/Repository/components/DetailsView/DetailsTab/AssetVersionDetails.tsx b/client/src/pages/Repository/components/DetailsView/DetailsTab/AssetVersionDetails.tsx index c525036b3..1b8aad9f2 100644 --- a/client/src/pages/Repository/components/DetailsView/DetailsTab/AssetVersionDetails.tsx +++ b/client/src/pages/Repository/components/DetailsView/DetailsTab/AssetVersionDetails.tsx @@ -3,22 +3,111 @@ * * This component renders details tab for AssetVersion specific details used in DetailsTab component. */ -import React from 'react'; -import { Loader } from '../../../../../components'; +import { Box, makeStyles, Typography } from '@material-ui/core'; +import React, { useEffect, useState } from 'react'; +import { CheckboxField, FieldType, Loader } from '../../../../../components'; import { GetDetailsTabDataForObjectQueryResult } from '../../../../../types/graphql'; +import { formatBytes } from '../../../../../utils/upload'; + +export const useStyles = makeStyles(({ palette }) => ({ + value: { + fontSize: '0.8em', + color: palette.primary.dark + } +})); interface AssetVersionDetailsProps extends GetDetailsTabDataForObjectQueryResult { disabled: boolean; + +} + +interface AssetVersionDetailsFields { + Version?: number; + Creator?: string; + DateCreated?: Date; + StorageSize?: number; + Ingested?: boolean; } function AssetVersionDetails(props: AssetVersionDetailsProps): React.ReactElement { + const classes = useStyles(); const { data, loading } = props; + const [details, setDetails] = useState({}); + + useEffect(() => { + if (data && !loading) { + const { AssetVersion } = data.getDetailsTabDataForObject; + setDetails({ + Version: AssetVersion?.Version, + Creator: AssetVersion?.User?.Name, + DateCreated: AssetVersion?.DateCreated, + StorageSize: AssetVersion?.StorageSize, + Ingested: AssetVersion?.Ingested, + }); + } + }, [data, loading]); if (!data || loading) { return ; } - return AssetVersion Details; + const setCheckboxField = ({ target }): void => { + const { name, checked } = target; + setDetails(details => ({ ...details, [name]: checked })); + }; + + const rowFieldProps = { alignItems: 'center', justifyContent: 'space-between', style: { borderRadius: 0 } }; + + return ( + + + {details.Version} + + + {details.Creator} + + + {details.DateCreated} + + + {formatBytes(details.StorageSize ?? 0)} + + + + + + ); } export default AssetVersionDetails; \ No newline at end of file From 6aaacc463a15061d478a142841bc78d878ce976c Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Mon, 28 Dec 2020 15:46:40 +0530 Subject: [PATCH 158/239] added capturedata details component --- .../src/components/controls/CheckboxField.tsx | 4 +- .../components/controls/DateInputField.tsx | 4 +- .../Metadata/Photogrammetry/AssetContents.tsx | 11 +- .../DetailsTab/CaptureDataDetails.tsx | 236 +++++++++++++++++- 4 files changed, 246 insertions(+), 9 deletions(-) diff --git a/client/src/components/controls/CheckboxField.tsx b/client/src/components/controls/CheckboxField.tsx index bc838175f..17d6d9ecd 100644 --- a/client/src/components/controls/CheckboxField.tsx +++ b/client/src/components/controls/CheckboxField.tsx @@ -15,10 +15,11 @@ interface CheckboxFieldProps { onChange: ((event: React.ChangeEvent, checked: boolean) => void) | undefined; required?: boolean; viewMode?: boolean; + disabled?: boolean; } function CheckboxField(props: CheckboxFieldProps): React.ReactElement { - const { label, name, value, onChange, required = false, viewMode = false } = props; + const { label, name, value, onChange, required = false, viewMode = false, disabled = false } = props; const rowFieldProps = { alignItems: 'center', justifyContent: 'space-between', style: { borderRadius: 0 } }; return ( @@ -31,6 +32,7 @@ function CheckboxField(props: CheckboxFieldProps): React.ReactElement { > ({ interface DateInputFieldProps { value: Date; onChange: (date: MaterialUiPickersDate, value?: string | null | undefined) => void; + disabled?: boolean; } function DateInputField(props: DateInputFieldProps): React.ReactElement { - const { value, onChange } = props; + const { value, onChange, disabled = false } = props; const classes = useStyles(); return ( void; + viewMode?: boolean; + disabled?: boolean; } function AssetContents(props: AssetContentsProps): React.ReactElement { - const { folders, options, initialEntry, onUpdate } = props; + const { folders, options, initialEntry, onUpdate, viewMode = false, disabled = false } = props; return ( - + @@ -75,6 +77,7 @@ function AssetContents(props: AssetContentsProps): React.ReactElement { } @@ -135,10 +138,11 @@ interface ContentProps { name?: string | undefined; value: unknown; }>) => void; + disabled: boolean; } function Content(props: ContentProps): React.ReactElement { - const { fieldName, value, name, icon, initialEntry, update, options } = props; + const { fieldName, value, name, icon, initialEntry, update, options, disabled } = props; const classes = useStyles(); return ( @@ -151,6 +155,7 @@ function Content(props: ContentProps): React.ReactElement { - setDateField('DateCaptured', value)} /> + setDateField('dateCaptured', value)} /> Date: Tue, 29 Dec 2020 13:51:14 +0530 Subject: [PATCH 163/239] added model details component --- .../DetailsView/DetailsTab/ModelDetails.tsx | 303 +++++++++++++++++- 1 file changed, 299 insertions(+), 4 deletions(-) diff --git a/client/src/pages/Repository/components/DetailsView/DetailsTab/ModelDetails.tsx b/client/src/pages/Repository/components/DetailsView/DetailsTab/ModelDetails.tsx index 4d8c27934..07bf82ec1 100644 --- a/client/src/pages/Repository/components/DetailsView/DetailsTab/ModelDetails.tsx +++ b/client/src/pages/Repository/components/DetailsView/DetailsTab/ModelDetails.tsx @@ -3,22 +3,317 @@ * * This component renders details tab for Model specific details used in DetailsTab component. */ -import React from 'react'; -import { Loader } from '../../../../../components'; +import { Box, makeStyles, Typography } from '@material-ui/core'; +import React, { useEffect, useState } from 'react'; +import { CheckboxField, DateInputField, FieldType, InputField, Loader, SelectField } from '../../../../../components'; +import { parseUVMapsToState, StateUVMap, useVocabularyStore } from '../../../../../store'; import { GetDetailsTabDataForObjectQueryResult } from '../../../../../types/graphql'; +import { eVocabularySetID } from '../../../../../types/server'; +import { withDefaultValueNumber } from '../../../../../utils/shared'; +import { formatBytes } from '../../../../../utils/upload'; +import BoundingBoxInput from '../../../../Ingestion/components/Metadata/Model/BoundingBoxInput'; +import UVContents from '../../../../Ingestion/components/Metadata/Model/UVContents'; + +export const useStyles = makeStyles(({ palette }) => ({ + value: { + fontSize: '0.8em', + color: palette.primary.dark + } +})); interface ModelDetailsProps extends GetDetailsTabDataForObjectQueryResult { disabled: boolean; } +interface ModelDetailsFields { + size?: number; + master?: boolean | null; + authoritative?: boolean | null; + creationMethod?: number | null; + modality?: number | null; + purpose?: number | null; + units?: number | null; + dateCaptured?: string | null; + modelFileType?: number | null; + uvMaps?: StateUVMap[]; + roughness?: number | null; + metalness?: number | null; + pointCount?: number | null; + faceCount?: number | null; + isWatertight?: boolean | null; + hasNormals?: boolean | null; + hasVertexColor?: boolean | null; + hasUVSpace?: boolean | null; + boundingBoxP1X?: number | null; + boundingBoxP1Y?: number | null; + boundingBoxP1Z?: number | null; + boundingBoxP2X?: number | null; + boundingBoxP2Y?: number | null; + boundingBoxP2Z?: number | null; +} + function ModelDetails(props: ModelDetailsProps): React.ReactElement { - const { data, loading } = props; + const classes = useStyles(); + const { data, loading, disabled } = props; + const [details, setDetails] = useState({}); + const [getInitialEntry, getEntries] = useVocabularyStore(state => [state.getInitialEntry, state.getEntries]); + + useEffect(() => { + if (data && !loading) { + const { Model } = data.getDetailsTabDataForObject; + setDetails({ + size: 0, // TODO: KARAN: add size field + master: Model?.master, + authoritative: Model?.authoritative, + creationMethod: Model?.creationMethod, + modality: Model?.modality, + purpose: Model?.purpose, + units: Model?.units, + dateCaptured: Model?.dateCaptured, + modelFileType: Model?.modelFileType, + uvMaps: parseUVMapsToState(Model?.uvMaps ?? []), + roughness: Model?.roughness, + metalness: Model?.metalness, + pointCount: Model?.pointCount, + faceCount: Model?.faceCount, + isWatertight: Model?.isWatertight, + hasNormals: Model?.hasNormals, + hasVertexColor: Model?.hasVertexColor, + hasUVSpace: Model?.hasUVSpace, + boundingBoxP1X: Model?.boundingBoxP1X, + boundingBoxP1Y: Model?.boundingBoxP1Y, + boundingBoxP1Z: Model?.boundingBoxP1Z, + boundingBoxP2X: Model?.boundingBoxP2X, + boundingBoxP2Y: Model?.boundingBoxP2Y, + boundingBoxP2Z: Model?.boundingBoxP2Z, + }); + } + }, [data, loading]); + + console.log(details); if (!data || loading) { return ; } - return Model Details; + const updateUVMapsVariant = () => { + alert('TODO: Karan Update UV Maps'); + }; + + const setDateField = (name: string, value?: string | null): void => { + if (value) { + const date = new Date(value); + setDetails(details => ({ ...details, [name]: date })); + } + }; + + const setIdField = ({ target }): void => { + const { name, value } = target; + let idFieldValue: number | null = null; + + if (value) { + idFieldValue = Number.parseInt(value, 10); + } + + setDetails(details => ({ ...details, [name]: idFieldValue })); + }; + + + const setCheckboxField = ({ target }): void => { + const { name, checked } = target; + setDetails(details => ({ ...details, [name]: checked })); + }; + + const rowFieldProps = { alignItems: 'center', justifyContent: 'space-between', style: { borderRadius: 0 } }; + + return ( + + + + {formatBytes(details?.size ?? 0)} + + + setDateField('dateCaptured', value)} /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); } export default ModelDetails; \ No newline at end of file From bfc007443be78aa03a995eab724e387a11646520 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Wed, 6 Jan 2021 14:56:35 +0530 Subject: [PATCH 164/239] updated getDetailsTabDataForObject query and added updateObjectDetails mutation --- .../DetailsView/DetailsTab/ActorDetails.tsx | 8 +- .../DetailsView/DetailsTab/AssetDetails.tsx | 11 +- .../DetailsTab/AssetVersionDetails.tsx | 14 +- .../DetailsTab/CaptureDataDetails.tsx | 34 +- .../DetailsView/DetailsTab/ItemDetails.tsx | 30 +- .../DetailsView/DetailsTab/ModelDetails.tsx | 42 +- .../DetailsView/DetailsTab/ProjectDetails.tsx | 8 +- .../ProjectDocumentationDetails.tsx | 8 +- .../DetailsView/DetailsTab/SceneDetails.tsx | 2 +- .../DetailsTab/StakeholderDetails.tsx | 12 +- .../DetailsView/DetailsTab/SubjectDetails.tsx | 39 +- .../DetailsView/DetailsTab/UnitDetails.tsx | 9 +- .../pages/Repository/hooks/useDetailsView.ts | 19 +- client/src/types/graphql.tsx | 509 ++++++++++++++---- server/graphql/api/index.ts | 18 +- .../systemobject/updateObjectDetails.ts | 11 + .../getDetailsTabDataForObject.ts | 109 ++-- server/graphql/schema.graphql | 292 +++++++++- .../schema/systemobject/mutations.graphql | 147 +++++ .../schema/systemobject/queries.graphql | 147 ++++- .../schema/systemobject/resolvers/index.ts | 4 + .../mutations/updateObjectDetails.ts | 46 ++ .../queries/getDetailsTabDataForObject.ts | 6 +- server/types/graphql.ts | 311 ++++++++++- 24 files changed, 1460 insertions(+), 376 deletions(-) create mode 100644 server/graphql/api/mutations/systemobject/updateObjectDetails.ts create mode 100644 server/graphql/schema/systemobject/mutations.graphql create mode 100644 server/graphql/schema/systemobject/resolvers/mutations/updateObjectDetails.ts diff --git a/client/src/pages/Repository/components/DetailsView/DetailsTab/ActorDetails.tsx b/client/src/pages/Repository/components/DetailsView/DetailsTab/ActorDetails.tsx index 37383559f..863aec8b8 100644 --- a/client/src/pages/Repository/components/DetailsView/DetailsTab/ActorDetails.tsx +++ b/client/src/pages/Repository/components/DetailsView/DetailsTab/ActorDetails.tsx @@ -6,20 +6,16 @@ import { Box } from '@material-ui/core'; import React, { useEffect, useState } from 'react'; import { InputField, Loader } from '../../../../../components'; -import { GetDetailsTabDataForObjectQueryResult } from '../../../../../types/graphql'; +import { ActorDetailFields, GetDetailsTabDataForObjectQueryResult } from '../../../../../types/graphql'; interface ActorDetailsProps extends GetDetailsTabDataForObjectQueryResult { disabled: boolean; } -interface ActorDetailsFields { - OrganizationName?: string | null; -} - function ActorDetails(props: ActorDetailsProps): React.ReactElement { const { data, loading, disabled, } = props; - const [details, setDetails] = useState({}); + const [details, setDetails] = useState({}); useEffect(() => { if (data && !loading) { diff --git a/client/src/pages/Repository/components/DetailsView/DetailsTab/AssetDetails.tsx b/client/src/pages/Repository/components/DetailsView/DetailsTab/AssetDetails.tsx index 3fdaac99a..658c69378 100644 --- a/client/src/pages/Repository/components/DetailsView/DetailsTab/AssetDetails.tsx +++ b/client/src/pages/Repository/components/DetailsView/DetailsTab/AssetDetails.tsx @@ -7,7 +7,7 @@ import { Box } from '@material-ui/core'; import React, { useEffect, useState } from 'react'; import { InputField, Loader, SelectField } from '../../../../../components'; import { useVocabularyStore } from '../../../../../store'; -import { GetDetailsTabDataForObjectQueryResult } from '../../../../../types/graphql'; +import { AssetDetailFields, GetDetailsTabDataForObjectQueryResult } from '../../../../../types/graphql'; import { eVocabularySetID } from '../../../../../types/server'; import { withDefaultValueNumber } from '../../../../../utils/shared'; @@ -15,15 +15,10 @@ interface AssetDetailsProps extends GetDetailsTabDataForObjectQueryResult { disabled: boolean; } -interface AssetDetailsFields { - FilePath?: string | null; - AssetType?: number | null; -} - function AssetDetails(props: AssetDetailsProps): React.ReactElement { const { data, loading, disabled, } = props; - const [details, setDetails] = useState({}); + const [details, setDetails] = useState({}); const [getEntries, getInitialEntry] = useVocabularyStore(state => [state.getEntries, state.getInitialEntry]); useEffect(() => { @@ -31,7 +26,7 @@ function AssetDetails(props: AssetDetailsProps): React.ReactElement { const { Asset } = data.getDetailsTabDataForObject; setDetails({ FilePath: Asset?.FilePath, - AssetType: Asset?.VAssetType?.idVocabulary + AssetType: Asset?.AssetType }); } }, [data, loading]); diff --git a/client/src/pages/Repository/components/DetailsView/DetailsTab/AssetVersionDetails.tsx b/client/src/pages/Repository/components/DetailsView/DetailsTab/AssetVersionDetails.tsx index 1a4bc38e7..489bfa022 100644 --- a/client/src/pages/Repository/components/DetailsView/DetailsTab/AssetVersionDetails.tsx +++ b/client/src/pages/Repository/components/DetailsView/DetailsTab/AssetVersionDetails.tsx @@ -6,7 +6,7 @@ import { Box, makeStyles, Typography } from '@material-ui/core'; import React, { useEffect, useState } from 'react'; import { CheckboxField, FieldType, Loader } from '../../../../../components'; -import { GetDetailsTabDataForObjectQueryResult } from '../../../../../types/graphql'; +import { AssetVersionDetailFields, GetDetailsTabDataForObjectQueryResult } from '../../../../../types/graphql'; import { formatBytes } from '../../../../../utils/upload'; export const useStyles = makeStyles(({ palette }) => ({ @@ -20,25 +20,17 @@ interface AssetVersionDetailsProps extends GetDetailsTabDataForObjectQueryResult disabled: boolean; } -interface AssetVersionDetailsFields { - Version?: number; - Creator?: string; - DateCreated?: Date; - StorageSize?: number; - Ingested?: boolean; -} - function AssetVersionDetails(props: AssetVersionDetailsProps): React.ReactElement { const classes = useStyles(); const { data, loading } = props; - const [details, setDetails] = useState({}); + const [details, setDetails] = useState({}); useEffect(() => { if (data && !loading) { const { AssetVersion } = data.getDetailsTabDataForObject; setDetails({ Version: AssetVersion?.Version, - Creator: AssetVersion?.User?.Name, + Creator: AssetVersion?.Creator, DateCreated: AssetVersion?.DateCreated, StorageSize: AssetVersion?.StorageSize, Ingested: AssetVersion?.Ingested, diff --git a/client/src/pages/Repository/components/DetailsView/DetailsTab/CaptureDataDetails.tsx b/client/src/pages/Repository/components/DetailsView/DetailsTab/CaptureDataDetails.tsx index 1ec008f67..f4cfdcaea 100644 --- a/client/src/pages/Repository/components/DetailsView/DetailsTab/CaptureDataDetails.tsx +++ b/client/src/pages/Repository/components/DetailsView/DetailsTab/CaptureDataDetails.tsx @@ -6,8 +6,8 @@ import { Box } from '@material-ui/core'; import React, { useEffect, useState } from 'react'; import { CheckboxField, DateInputField, FieldType, InputField, Loader, SelectField } from '../../../../../components'; -import { parseFoldersToState, StateFolder, useVocabularyStore } from '../../../../../store'; -import { GetDetailsTabDataForObjectQueryResult } from '../../../../../types/graphql'; +import { parseFoldersToState, useVocabularyStore } from '../../../../../store'; +import { CaptureDataDetailFields, GetDetailsTabDataForObjectQueryResult } from '../../../../../types/graphql'; import { eVocabularySetID } from '../../../../../types/server'; import { withDefaultValueNumber } from '../../../../../utils/shared'; import AssetContents from '../../../../Ingestion/components/Metadata/Photogrammetry/AssetContents'; @@ -17,38 +17,22 @@ interface CaptureDataDetailsProps extends GetDetailsTabDataForObjectQueryResult disabled: boolean; } -interface CaptureDataDetailsFields { - captureMethod?: number; - folders?: StateFolder[]; - description?: string; - dateCaptured?: string; - datasetType?: number; - datasetFieldId?: number | null; - itemPositionType?: number | null; - itemPositionFieldId?: number | null; - itemArrangementFieldId?: number | null; - focusType?: number | null; - lightsourceType?: number | null; - backgroundRemovalMethod?: number | null; - clusterType?: number | null; - clusterGeometryFieldId?: number | null; - cameraSettingUniform?: boolean; -} - function CaptureDataDetails(props: CaptureDataDetailsProps): React.ReactElement { const { data, loading, disabled } = props; - const [details, setDetails] = useState({}); + const [details, setDetails] = useState({ + folders: [] + }); const [getInitialEntry, getEntries] = useVocabularyStore(state => [state.getInitialEntry, state.getEntries]); useEffect(() => { if (data && !loading) { const { CaptureData } = data.getDetailsTabDataForObject; setDetails({ - captureMethod: 0, // TODO: KARAN - description: CaptureData?.description ?? '', + captureMethod: CaptureData?.captureMethod, + description: CaptureData?.description, dateCaptured: CaptureData?.dateCaptured, datasetType: CaptureData?.datasetType, - folders: parseFoldersToState(CaptureData?.folders ?? []), + folders: CaptureData?.folders || [], datasetFieldId: CaptureData?.datasetFieldId, itemPositionType: CaptureData?.itemPositionType, itemPositionFieldId: CaptureData?.itemPositionFieldId, @@ -143,7 +127,7 @@ function CaptureDataDetails(props: CaptureDataDetailsProps): React.ReactElement viewMode disabled={disabled} initialEntry={getInitialEntry(eVocabularySetID.eCaptureDataFileVariantType)} - folders={details?.folders ?? []} + folders={parseFoldersToState(details?.folders ?? [])} options={getEntries(eVocabularySetID.eCaptureDataFileVariantType)} onUpdate={updateFolderVariant} /> diff --git a/client/src/pages/Repository/components/DetailsView/DetailsTab/ItemDetails.tsx b/client/src/pages/Repository/components/DetailsView/DetailsTab/ItemDetails.tsx index de2eb0b1b..5c2f1b613 100644 --- a/client/src/pages/Repository/components/DetailsView/DetailsTab/ItemDetails.tsx +++ b/client/src/pages/Repository/components/DetailsView/DetailsTab/ItemDetails.tsx @@ -6,37 +6,37 @@ import { Box } from '@material-ui/core'; import React, { useEffect, useState } from 'react'; import { CheckboxField, Loader } from '../../../../../components'; -import { GetDetailsTabDataForObjectQueryResult } from '../../../../../types/graphql'; -import { SubjectDetailsFields, SubjectFields } from './SubjectDetails'; +import { GetDetailsTabDataForObjectQueryResult, SubjectDetailFields } from '../../../../../types/graphql'; +import { SubjectFields } from './SubjectDetails'; interface ItemDetailsProps extends GetDetailsTabDataForObjectQueryResult { disabled: boolean; } -interface ItemDetailsFields extends SubjectDetailsFields { - EntireSubject?: boolean; +interface ItemDetailFields extends SubjectDetailFields { + EntireSubject?: boolean | null | undefined; } function ItemDetails(props: ItemDetailsProps): React.ReactElement { const { data, loading, disabled, } = props; - const [details, setDetails] = useState({}); + const [details, setDetails] = useState({}); useEffect(() => { if (data && !loading) { const { Item } = data.getDetailsTabDataForObject; setDetails({ EntireSubject: Item?.EntireSubject, - Latitude: Item?.GeoLocation?.Latitude, - Longitude: Item?.GeoLocation?.Longitude, - Altitude: Item?.GeoLocation?.Altitude, - TS0: Item?.GeoLocation?.TS0, - TS1: Item?.GeoLocation?.TS1, - TS2: Item?.GeoLocation?.TS2, - R0: Item?.GeoLocation?.R0, - R1: Item?.GeoLocation?.R1, - R2: Item?.GeoLocation?.R2, - R3: Item?.GeoLocation?.R3 + Latitude: Item?.Latitude, + Longitude: Item?.Longitude, + Altitude: Item?.Altitude, + TS0: Item?.TS0, + TS1: Item?.TS1, + TS2: Item?.TS2, + R0: Item?.R0, + R1: Item?.R1, + R2: Item?.R2, + R3: Item?.R3 }); } }, [data, loading]); diff --git a/client/src/pages/Repository/components/DetailsView/DetailsTab/ModelDetails.tsx b/client/src/pages/Repository/components/DetailsView/DetailsTab/ModelDetails.tsx index 07bf82ec1..4927a4033 100644 --- a/client/src/pages/Repository/components/DetailsView/DetailsTab/ModelDetails.tsx +++ b/client/src/pages/Repository/components/DetailsView/DetailsTab/ModelDetails.tsx @@ -6,8 +6,8 @@ import { Box, makeStyles, Typography } from '@material-ui/core'; import React, { useEffect, useState } from 'react'; import { CheckboxField, DateInputField, FieldType, InputField, Loader, SelectField } from '../../../../../components'; -import { parseUVMapsToState, StateUVMap, useVocabularyStore } from '../../../../../store'; -import { GetDetailsTabDataForObjectQueryResult } from '../../../../../types/graphql'; +import { parseUVMapsToState, useVocabularyStore } from '../../../../../store'; +import { GetDetailsTabDataForObjectQueryResult, ModelDetailFields } from '../../../../../types/graphql'; import { eVocabularySetID } from '../../../../../types/server'; import { withDefaultValueNumber } from '../../../../../utils/shared'; import { formatBytes } from '../../../../../utils/upload'; @@ -25,44 +25,20 @@ interface ModelDetailsProps extends GetDetailsTabDataForObjectQueryResult { disabled: boolean; } -interface ModelDetailsFields { - size?: number; - master?: boolean | null; - authoritative?: boolean | null; - creationMethod?: number | null; - modality?: number | null; - purpose?: number | null; - units?: number | null; - dateCaptured?: string | null; - modelFileType?: number | null; - uvMaps?: StateUVMap[]; - roughness?: number | null; - metalness?: number | null; - pointCount?: number | null; - faceCount?: number | null; - isWatertight?: boolean | null; - hasNormals?: boolean | null; - hasVertexColor?: boolean | null; - hasUVSpace?: boolean | null; - boundingBoxP1X?: number | null; - boundingBoxP1Y?: number | null; - boundingBoxP1Z?: number | null; - boundingBoxP2X?: number | null; - boundingBoxP2Y?: number | null; - boundingBoxP2Z?: number | null; -} - function ModelDetails(props: ModelDetailsProps): React.ReactElement { const classes = useStyles(); const { data, loading, disabled } = props; - const [details, setDetails] = useState({}); + const [details, setDetails] = useState({ + uvMaps: [] + }); + const [getInitialEntry, getEntries] = useVocabularyStore(state => [state.getInitialEntry, state.getEntries]); useEffect(() => { if (data && !loading) { const { Model } = data.getDetailsTabDataForObject; setDetails({ - size: 0, // TODO: KARAN: add size field + size: Model?.size, master: Model?.master, authoritative: Model?.authoritative, creationMethod: Model?.creationMethod, @@ -71,7 +47,7 @@ function ModelDetails(props: ModelDetailsProps): React.ReactElement { units: Model?.units, dateCaptured: Model?.dateCaptured, modelFileType: Model?.modelFileType, - uvMaps: parseUVMapsToState(Model?.uvMaps ?? []), + uvMaps: Model?.uvMaps || [], roughness: Model?.roughness, metalness: Model?.metalness, pointCount: Model?.pointCount, @@ -226,7 +202,7 @@ function ModelDetails(props: ModelDetailsProps): React.ReactElement { viewMode disabled={disabled} initialEntry={getInitialEntry(eVocabularySetID.eModelUVMapChannelUVMapType)} - uvMaps={details.uvMaps || []} + uvMaps={parseUVMapsToState(details?.uvMaps ?? [])} options={getEntries(eVocabularySetID.eModelUVMapChannelUVMapType)} onUpdate={updateUVMapsVariant} /> diff --git a/client/src/pages/Repository/components/DetailsView/DetailsTab/ProjectDetails.tsx b/client/src/pages/Repository/components/DetailsView/DetailsTab/ProjectDetails.tsx index 7ba8eaf5c..a1871497f 100644 --- a/client/src/pages/Repository/components/DetailsView/DetailsTab/ProjectDetails.tsx +++ b/client/src/pages/Repository/components/DetailsView/DetailsTab/ProjectDetails.tsx @@ -5,20 +5,16 @@ */ import React, { useEffect, useState } from 'react'; import { Loader } from '../../../../../components'; -import { GetDetailsTabDataForObjectQueryResult } from '../../../../../types/graphql'; +import { ProjectDetailFields, GetDetailsTabDataForObjectQueryResult } from '../../../../../types/graphql'; import Description from '../../../../Ingestion/components/Metadata/Photogrammetry/Description'; interface ProjectDetailsProps extends GetDetailsTabDataForObjectQueryResult { disabled: boolean; } -interface ProjectDetailsFields { - Description?: string | null; -} - function ProjectDetails(props: ProjectDetailsProps): React.ReactElement { const { data, loading } = props; - const [details, setDetails] = useState({}); + const [details, setDetails] = useState({}); useEffect(() => { if (data && !loading) { diff --git a/client/src/pages/Repository/components/DetailsView/DetailsTab/ProjectDocumentationDetails.tsx b/client/src/pages/Repository/components/DetailsView/DetailsTab/ProjectDocumentationDetails.tsx index abf1dafed..1a16cd75c 100644 --- a/client/src/pages/Repository/components/DetailsView/DetailsTab/ProjectDocumentationDetails.tsx +++ b/client/src/pages/Repository/components/DetailsView/DetailsTab/ProjectDocumentationDetails.tsx @@ -5,20 +5,16 @@ */ import React, { useEffect, useState } from 'react'; import { Loader } from '../../../../../components'; -import { GetDetailsTabDataForObjectQueryResult } from '../../../../../types/graphql'; +import { ProjectDocumentationDetailFields, GetDetailsTabDataForObjectQueryResult } from '../../../../../types/graphql'; import Description from '../../../../Ingestion/components/Metadata/Photogrammetry/Description'; interface ProjectDocumentationDetailsProps extends GetDetailsTabDataForObjectQueryResult { disabled: boolean; } -interface ProjectDocumentationDetailsFields { - Description?: string | null; -} - function ProjectDocumentationDetails(props: ProjectDocumentationDetailsProps): React.ReactElement { const { data, loading } = props; - const [details, setDetails] = useState({}); + const [details, setDetails] = useState({}); useEffect(() => { if (data && !loading) { diff --git a/client/src/pages/Repository/components/DetailsView/DetailsTab/SceneDetails.tsx b/client/src/pages/Repository/components/DetailsView/DetailsTab/SceneDetails.tsx index 4ae601412..73ede6383 100644 --- a/client/src/pages/Repository/components/DetailsView/DetailsTab/SceneDetails.tsx +++ b/client/src/pages/Repository/components/DetailsView/DetailsTab/SceneDetails.tsx @@ -10,7 +10,7 @@ import { GetDetailsTabDataForObjectQueryResult } from '../../../../../types/grap interface SceneDetailsProps extends GetDetailsTabDataForObjectQueryResult { disabled: boolean; } - +// TODO: KARAN: implement SceneDetails function SceneDetails(props: SceneDetailsProps): React.ReactElement { const { data, loading } = props; diff --git a/client/src/pages/Repository/components/DetailsView/DetailsTab/StakeholderDetails.tsx b/client/src/pages/Repository/components/DetailsView/DetailsTab/StakeholderDetails.tsx index 86cc7ca7d..08c95d7ff 100644 --- a/client/src/pages/Repository/components/DetailsView/DetailsTab/StakeholderDetails.tsx +++ b/client/src/pages/Repository/components/DetailsView/DetailsTab/StakeholderDetails.tsx @@ -6,24 +6,16 @@ import { Box } from '@material-ui/core'; import React, { useEffect, useState } from 'react'; import { InputField, Loader } from '../../../../../components'; -import { GetDetailsTabDataForObjectQueryResult } from '../../../../../types/graphql'; +import { StakeholderDetailFields, GetDetailsTabDataForObjectQueryResult } from '../../../../../types/graphql'; interface StakeholderDetailsProps extends GetDetailsTabDataForObjectQueryResult { disabled: boolean; } -interface StakeholderDetailsFields { - OrganizationName?: string | null; - EmailAddress?: string | null; - PhoneNumberMobile?: string | null; - PhoneNumberOffice?: string | null; - MailingAddress?: string | null; -} - function StakeholderDetails(props: StakeholderDetailsProps): React.ReactElement { const { data, loading, disabled, } = props; - const [details, setDetails] = useState({}); + const [details, setDetails] = useState({}); useEffect(() => { if (data && !loading) { diff --git a/client/src/pages/Repository/components/DetailsView/DetailsTab/SubjectDetails.tsx b/client/src/pages/Repository/components/DetailsView/DetailsTab/SubjectDetails.tsx index 8a24b579b..08cbb6d8d 100644 --- a/client/src/pages/Repository/components/DetailsView/DetailsTab/SubjectDetails.tsx +++ b/client/src/pages/Repository/components/DetailsView/DetailsTab/SubjectDetails.tsx @@ -6,44 +6,31 @@ import { Box } from '@material-ui/core'; import React, { useEffect, useState } from 'react'; import { DebounceNumberInput, FieldType, InputField, Loader } from '../../../../../components'; -import { GetDetailsTabDataForObjectQueryResult } from '../../../../../types/graphql'; +import { GetDetailsTabDataForObjectQueryResult, SubjectDetailFields } from '../../../../../types/graphql'; interface SubjectDetailsProps extends GetDetailsTabDataForObjectQueryResult { disabled: boolean; } -export interface SubjectDetailsFields { - Latitude?: number | null; - Longitude?: number | null; - Altitude?: number | null; - TS0?: number | null; - TS1?: number | null; - TS2?: number | null; - R0?: number | null; - R1?: number | null; - R2?: number | null; - R3?: number | null; -} - function SubjectDetails(props: SubjectDetailsProps): React.ReactElement { const { data, loading, disabled, } = props; - const [details, setDetails] = useState({}); + const [details, setDetails] = useState({}); useEffect(() => { if (data && !loading) { const { Subject } = data.getDetailsTabDataForObject; setDetails({ - Latitude: Subject?.GeoLocation?.Latitude, - Longitude: Subject?.GeoLocation?.Longitude, - Altitude: Subject?.GeoLocation?.Altitude, - TS0: Subject?.GeoLocation?.TS0, - TS1: Subject?.GeoLocation?.TS1, - TS2: Subject?.GeoLocation?.TS2, - R0: Subject?.GeoLocation?.R0, - R1: Subject?.GeoLocation?.R1, - R2: Subject?.GeoLocation?.R2, - R3: Subject?.GeoLocation?.R3 + Latitude: Subject?.Latitude, + Longitude: Subject?.Longitude, + Altitude: Subject?.Altitude, + TS0: Subject?.TS0, + TS1: Subject?.TS1, + TS2: Subject?.TS2, + R0: Subject?.R0, + R1: Subject?.R1, + R2: Subject?.R2, + R3: Subject?.R3 }); } }, [data, loading]); @@ -64,7 +51,7 @@ function SubjectDetails(props: SubjectDetailsProps): React.ReactElement { ); } -interface SubjectFieldsProps extends SubjectDetailsFields { +interface SubjectFieldsProps extends SubjectDetailFields { disabled: boolean; onChange: (event: React.ChangeEvent) => void; } diff --git a/client/src/pages/Repository/components/DetailsView/DetailsTab/UnitDetails.tsx b/client/src/pages/Repository/components/DetailsView/DetailsTab/UnitDetails.tsx index f605aedd3..c9183f400 100644 --- a/client/src/pages/Repository/components/DetailsView/DetailsTab/UnitDetails.tsx +++ b/client/src/pages/Repository/components/DetailsView/DetailsTab/UnitDetails.tsx @@ -6,20 +6,15 @@ import { Box } from '@material-ui/core'; import React, { useEffect, useState } from 'react'; import { InputField, Loader } from '../../../../../components'; -import { GetDetailsTabDataForObjectQueryResult } from '../../../../../types/graphql'; +import { GetDetailsTabDataForObjectQueryResult, UnitDetailFields } from '../../../../../types/graphql'; interface UnitDetailsProps extends GetDetailsTabDataForObjectQueryResult { disabled: boolean; } -interface UnitDetailsFields { - Abbreviation?: string | null; - ARKPrefix?: string | null; -} - function UnitDetails(props: UnitDetailsProps): React.ReactElement { const { data, loading, disabled } = props; - const [details, setDetails] = useState({}); + const [details, setDetails] = useState({}); useEffect(() => { if (data && !loading) { diff --git a/client/src/pages/Repository/hooks/useDetailsView.ts b/client/src/pages/Repository/hooks/useDetailsView.ts index 7c91b29ec..20f84d721 100644 --- a/client/src/pages/Repository/hooks/useDetailsView.ts +++ b/client/src/pages/Repository/hooks/useDetailsView.ts @@ -1,4 +1,4 @@ -import { useQuery } from '@apollo/client'; +import { useMutation, useQuery, MutationTuple } from '@apollo/client'; import { GetAssetDetailsForSystemObjectDocument, GetAssetDetailsForSystemObjectQueryResult, @@ -7,7 +7,11 @@ import { GetVersionsForSystemObjectDocument, GetVersionsForSystemObjectQueryResult, GetDetailsTabDataForObjectDocument, - GetDetailsTabDataForObjectQueryResult + GetDetailsTabDataForObjectQueryResult, + UpdateObjectDetailsDocument, + UpdateObjectDetailsMutationVariables, + UpdateObjectDetailsDataInput, + UpdateObjectDetailsMutation } from '../../../types/graphql'; import { eSystemObjectType } from '../../../types/server'; @@ -52,3 +56,14 @@ export function useDetailsTabData(idSystemObject: number, objectType: eSystemObj }); } +export function useDetailsTabDataUpdate(idSystemObject: number, objectType: eSystemObjectType, data: UpdateObjectDetailsDataInput): MutationTuple { + return useMutation(UpdateObjectDetailsDocument, { + variables: { + input: { + idSystemObject, + objectType, + data + } + } + }); +} diff --git a/client/src/types/graphql.tsx b/client/src/types/graphql.tsx index c8827ebe8..9b91217e7 100644 --- a/client/src/types/graphql.tsx +++ b/client/src/types/graphql.tsx @@ -289,6 +289,7 @@ export type Mutation = { createVocabularySet: CreateVocabularySetResult; discardUploadedAssetVersions: DiscardUploadedAssetVersionsResult; ingestData: IngestDataResult; + updateObjectDetails: UpdateObjectDetailsResult; uploadAsset: UploadAssetResult; }; @@ -358,6 +359,11 @@ export type MutationIngestDataArgs = { }; +export type MutationUpdateObjectDetailsArgs = { + input: UpdateObjectDetailsInput; +}; + + export type MutationUploadAssetArgs = { file: Scalars['Upload']; type: Scalars['Int']; @@ -1113,26 +1119,305 @@ export type IntermediaryFile = { SystemObject?: Maybe; }; +export type UpdateObjectDetailsInput = { + idSystemObject: Scalars['Int']; + objectType: Scalars['Int']; + data: UpdateObjectDetailsDataInput; +}; + +export type UnitDetailFieldsInput = { + Abbreviation?: Maybe; + ARKPrefix?: Maybe; +}; + +export type ProjectDetailFieldsInput = { + Description?: Maybe; +}; + +export type SubjectDetailFieldsInput = { + Altitude?: Maybe; + Latitude?: Maybe; + Longitude?: Maybe; + R0?: Maybe; + R1?: Maybe; + R2?: Maybe; + R3?: Maybe; + TS0?: Maybe; + TS1?: Maybe; + TS2?: Maybe; +}; + +export type ItemDetailFieldsInput = { + EntireSubject?: Maybe; + Altitude?: Maybe; + Latitude?: Maybe; + Longitude?: Maybe; + R0?: Maybe; + R1?: Maybe; + R2?: Maybe; + R3?: Maybe; + TS0?: Maybe; + TS1?: Maybe; + TS2?: Maybe; +}; + +export type CaptureDataDetailFieldsInput = { + captureMethod?: Maybe; + dateCaptured?: Maybe; + datasetType?: Maybe; + systemCreated?: Maybe; + description?: Maybe; + cameraSettingUniform?: Maybe; + datasetFieldId?: Maybe; + itemPositionType?: Maybe; + itemPositionFieldId?: Maybe; + itemArrangementFieldId?: Maybe; + focusType?: Maybe; + lightsourceType?: Maybe; + backgroundRemovalMethod?: Maybe; + clusterType?: Maybe; + clusterGeometryFieldId?: Maybe; + folders: Array; +}; + +export type ModelDetailFieldsInput = { + size?: Maybe; + master?: Maybe; + authoritative?: Maybe; + creationMethod?: Maybe; + modality?: Maybe; + purpose?: Maybe; + units?: Maybe; + dateCaptured?: Maybe; + modelFileType?: Maybe; + uvMaps: Array; + roughness?: Maybe; + metalness?: Maybe; + pointCount?: Maybe; + faceCount?: Maybe; + isWatertight?: Maybe; + hasNormals?: Maybe; + hasVertexColor?: Maybe; + hasUVSpace?: Maybe; + boundingBoxP1X?: Maybe; + boundingBoxP1Y?: Maybe; + boundingBoxP1Z?: Maybe; + boundingBoxP2X?: Maybe; + boundingBoxP2Y?: Maybe; + boundingBoxP2Z?: Maybe; +}; + +export type SceneDetailFieldsInput = { + Links: Array; + AssetType?: Maybe; + Tours?: Maybe; + Annotation?: Maybe; +}; + +export type ProjectDocumentationDetailFieldsInput = { + Description?: Maybe; +}; + +export type AssetDetailFieldsInput = { + FilePath?: Maybe; + AssetType?: Maybe; +}; + +export type AssetVersionDetailFieldsInput = { + Creator?: Maybe; + DateCreated?: Maybe; + Ingested?: Maybe; + Version?: Maybe; + StorageSize?: Maybe; +}; + +export type ActorDetailFieldsInput = { + OrganizationName?: Maybe; +}; + +export type StakeholderDetailFieldsInput = { + OrganizationName?: Maybe; + MailingAddress?: Maybe; + EmailAddress?: Maybe; + PhoneNumberMobile?: Maybe; + PhoneNumberOffice?: Maybe; +}; + +export type UpdateObjectDetailsDataInput = { + Unit?: Maybe; + Project?: Maybe; + Subject?: Maybe; + Item?: Maybe; + CaptureData?: Maybe; + Model?: Maybe; + Scene?: Maybe; + ProjectDocumentation?: Maybe; + Asset?: Maybe; + AssetVersion?: Maybe; + Actor?: Maybe; + Stakeholder?: Maybe; +}; + +export type UpdateObjectDetailsResult = { + __typename?: 'UpdateObjectDetailsResult'; + success: Scalars['Boolean']; +}; + export type GetDetailsTabDataForObjectInput = { idSystemObject: Scalars['Int']; objectType: Scalars['Int']; }; +export type UnitDetailFields = { + __typename?: 'UnitDetailFields'; + Abbreviation?: Maybe; + ARKPrefix?: Maybe; +}; + +export type ProjectDetailFields = { + __typename?: 'ProjectDetailFields'; + Description?: Maybe; +}; + +export type SubjectDetailFields = { + __typename?: 'SubjectDetailFields'; + Altitude?: Maybe; + Latitude?: Maybe; + Longitude?: Maybe; + R0?: Maybe; + R1?: Maybe; + R2?: Maybe; + R3?: Maybe; + TS0?: Maybe; + TS1?: Maybe; + TS2?: Maybe; +}; + +export type ItemDetailFields = { + __typename?: 'ItemDetailFields'; + EntireSubject?: Maybe; + Altitude?: Maybe; + Latitude?: Maybe; + Longitude?: Maybe; + R0?: Maybe; + R1?: Maybe; + R2?: Maybe; + R3?: Maybe; + TS0?: Maybe; + TS1?: Maybe; + TS2?: Maybe; +}; + +export type CaptureDataDetailFields = { + __typename?: 'CaptureDataDetailFields'; + captureMethod?: Maybe; + dateCaptured?: Maybe; + datasetType?: Maybe; + systemCreated?: Maybe; + description?: Maybe; + cameraSettingUniform?: Maybe; + datasetFieldId?: Maybe; + itemPositionType?: Maybe; + itemPositionFieldId?: Maybe; + itemArrangementFieldId?: Maybe; + focusType?: Maybe; + lightsourceType?: Maybe; + backgroundRemovalMethod?: Maybe; + clusterType?: Maybe; + clusterGeometryFieldId?: Maybe; + folders: Array; +}; + +export type ModelDetailFields = { + __typename?: 'ModelDetailFields'; + size?: Maybe; + master?: Maybe; + authoritative?: Maybe; + creationMethod?: Maybe; + modality?: Maybe; + purpose?: Maybe; + units?: Maybe; + dateCaptured?: Maybe; + modelFileType?: Maybe; + uvMaps: Array; + roughness?: Maybe; + metalness?: Maybe; + pointCount?: Maybe; + faceCount?: Maybe; + isWatertight?: Maybe; + hasNormals?: Maybe; + hasVertexColor?: Maybe; + hasUVSpace?: Maybe; + boundingBoxP1X?: Maybe; + boundingBoxP1Y?: Maybe; + boundingBoxP1Z?: Maybe; + boundingBoxP2X?: Maybe; + boundingBoxP2Y?: Maybe; + boundingBoxP2Z?: Maybe; +}; + +export type SceneDetailFields = { + __typename?: 'SceneDetailFields'; + Links: Array; + AssetType?: Maybe; + Tours?: Maybe; + Annotation?: Maybe; +}; + +export type IntermediaryFileDetailFields = { + __typename?: 'IntermediaryFileDetailFields'; + idIntermediaryFile: Scalars['Int']; +}; + +export type ProjectDocumentationDetailFields = { + __typename?: 'ProjectDocumentationDetailFields'; + Description?: Maybe; +}; + +export type AssetDetailFields = { + __typename?: 'AssetDetailFields'; + FilePath?: Maybe; + AssetType?: Maybe; +}; + +export type AssetVersionDetailFields = { + __typename?: 'AssetVersionDetailFields'; + Creator?: Maybe; + DateCreated?: Maybe; + Ingested?: Maybe; + Version?: Maybe; + StorageSize?: Maybe; +}; + +export type ActorDetailFields = { + __typename?: 'ActorDetailFields'; + OrganizationName?: Maybe; +}; + +export type StakeholderDetailFields = { + __typename?: 'StakeholderDetailFields'; + OrganizationName?: Maybe; + MailingAddress?: Maybe; + EmailAddress?: Maybe; + PhoneNumberMobile?: Maybe; + PhoneNumberOffice?: Maybe; +}; + export type GetDetailsTabDataForObjectResult = { __typename?: 'GetDetailsTabDataForObjectResult'; - Unit?: Maybe; - Project?: Maybe; - Subject?: Maybe; - Item?: Maybe; - CaptureData?: Maybe; - Model?: Maybe; - Scene?: Maybe; - IntermediaryFile?: Maybe; - ProjectDocumentation?: Maybe; - Asset?: Maybe; - AssetVersion?: Maybe; - Actor?: Maybe; - Stakeholder?: Maybe; + Unit?: Maybe; + Project?: Maybe; + Subject?: Maybe; + Item?: Maybe; + CaptureData?: Maybe; + Model?: Maybe; + Scene?: Maybe; + IntermediaryFile?: Maybe; + ProjectDocumentation?: Maybe; + Asset?: Maybe; + AssetVersion?: Maybe; + Actor?: Maybe; + Stakeholder?: Maybe; }; export type GetSystemObjectDetailsInput = { @@ -1852,6 +2137,21 @@ export type CreateSceneMutation = ( } ); +export type UpdateObjectDetailsMutationVariables = Exact<{ + input: UpdateObjectDetailsInput; +}>; + + +export type UpdateObjectDetailsMutation = ( + { __typename?: 'Mutation' } + & { + updateObjectDetails: ( + { __typename?: 'UpdateObjectDetailsResult' } + & Pick + ) + } +); + export type CreateItemMutationVariables = Exact<{ input: CreateItemInput; }>; @@ -2369,32 +2669,20 @@ export type GetDetailsTabDataForObjectQuery = ( { __typename?: 'GetDetailsTabDataForObjectResult' } & { Unit?: Maybe<( - { __typename?: 'Unit' } - & Pick + { __typename?: 'UnitDetailFields' } + & Pick )>, Project?: Maybe<( - { __typename?: 'Project' } - & Pick + { __typename?: 'ProjectDetailFields' } + & Pick )>, Subject?: Maybe<( - { __typename?: 'Subject' } - & Pick - & { - GeoLocation?: Maybe<( - { __typename?: 'GeoLocation' } - & Pick - )> - } + { __typename?: 'SubjectDetailFields' } + & Pick )>, Item?: Maybe<( - { __typename?: 'Item' } - & Pick - & { - GeoLocation?: Maybe<( - { __typename?: 'GeoLocation' } - & Pick - )> - } + { __typename?: 'ItemDetailFields' } + & Pick )>, CaptureData?: Maybe<( - { __typename?: 'IngestPhotogrammetry' } - & Pick + { __typename?: 'CaptureDataDetailFields' } + & Pick & { folders: Array<( { __typename?: 'IngestFolder' } @@ -2402,8 +2690,8 @@ export type GetDetailsTabDataForObjectQuery = ( )> } )>, Model?: Maybe<( - { __typename?: 'IngestModel' } - & Pick + { __typename?: 'ModelDetailFields' } + & Pick & { uvMaps: Array<( { __typename?: 'IngestUVMap' } @@ -2411,38 +2699,26 @@ export type GetDetailsTabDataForObjectQuery = ( )> } )>, Scene?: Maybe<( - { __typename?: 'Scene' } - & Pick + { __typename?: 'SceneDetailFields' } + & Pick )>, IntermediaryFile?: Maybe<( - { __typename?: 'IntermediaryFile' } - & Pick + { __typename?: 'IntermediaryFileDetailFields' } + & Pick )>, ProjectDocumentation?: Maybe<( - { __typename?: 'ProjectDocumentation' } - & Pick + { __typename?: 'ProjectDocumentationDetailFields' } + & Pick )>, Asset?: Maybe<( - { __typename?: 'Asset' } - & Pick - & { - VAssetType?: Maybe<( - { __typename?: 'Vocabulary' } - & Pick - )> - } + { __typename?: 'AssetDetailFields' } + & Pick )>, AssetVersion?: Maybe<( - { __typename?: 'AssetVersion' } - & Pick - & { - User?: Maybe<( - { __typename?: 'User' } - & Pick - )> - } + { __typename?: 'AssetVersionDetailFields' } + & Pick )>, Actor?: Maybe<( - { __typename?: 'Actor' } - & Pick + { __typename?: 'ActorDetailFields' } + & Pick )>, Stakeholder?: Maybe<( - { __typename?: 'Stakeholder' } - & Pick + { __typename?: 'StakeholderDetailFields' } + & Pick )> } ) @@ -3103,6 +3379,38 @@ export function useCreateSceneMutation(baseOptions?: Apollo.MutationHookOptions< export type CreateSceneMutationHookResult = ReturnType; export type CreateSceneMutationResult = Apollo.MutationResult; export type CreateSceneMutationOptions = Apollo.BaseMutationOptions; +export const UpdateObjectDetailsDocument = gql` + mutation updateObjectDetails($input: UpdateObjectDetailsInput!) { + updateObjectDetails(input: $input) { + success + } +} + `; +export type UpdateObjectDetailsMutationFn = Apollo.MutationFunction; + +/** + * __useUpdateObjectDetailsMutation__ + * + * To run a mutation, you first call `useUpdateObjectDetailsMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useUpdateObjectDetailsMutation` returns a tuple that includes: + * - A mutate function that you can call at any time to execute the mutation + * - An object with fields that represent the current status of the mutation's execution + * + * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; + * + * @example + * const [updateObjectDetailsMutation, { data, loading, error }] = useUpdateObjectDetailsMutation({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ +export function useUpdateObjectDetailsMutation(baseOptions?: Apollo.MutationHookOptions) { + return Apollo.useMutation(UpdateObjectDetailsDocument, baseOptions); +} +export type UpdateObjectDetailsMutationHookResult = ReturnType; +export type UpdateObjectDetailsMutationResult = Apollo.MutationResult; +export type UpdateObjectDetailsMutationOptions = Apollo.BaseMutationOptions; export const CreateItemDocument = gql` mutation createItem($input: CreateItemInput!) { createItem(input: $input) { @@ -3991,52 +4299,41 @@ export const GetDetailsTabDataForObjectDocument = gql` query getDetailsTabDataForObject($input: GetDetailsTabDataForObjectInput!) { getDetailsTabDataForObject(input: $input) { Unit { - idUnit Abbreviation ARKPrefix } Project { - idProject Description } Subject { - idSubject - GeoLocation { - idGeoLocation - Altitude - Latitude - Longitude - R0 - R1 - R2 - R3 - TS0 - TS1 - TS2 - } + Altitude + Latitude + Longitude + R0 + R1 + R2 + R3 + TS0 + TS1 + TS2 } Item { - idItem EntireSubject - GeoLocation { - idGeoLocation - Altitude - Latitude - Longitude - R0 - R1 - R2 - R3 - TS0 - TS1 - TS2 - } + Altitude + Latitude + Longitude + R0 + R1 + R2 + R3 + TS0 + TS1 + TS2 } CaptureData { - idAssetVersion + captureMethod dateCaptured datasetType - systemCreated description cameraSettingUniform datasetFieldId @@ -4054,7 +4351,7 @@ export const GetDetailsTabDataForObjectDocument = gql` } } Model { - systemCreated + size master authoritative creationMethod @@ -4084,40 +4381,32 @@ export const GetDetailsTabDataForObjectDocument = gql` boundingBoxP2Z } Scene { - idScene + Links + AssetType + Tours + Annotation } IntermediaryFile { idIntermediaryFile } ProjectDocumentation { - idProjectDocumentation Description } Asset { - idAsset FilePath - VAssetType { - idVocabulary - Term - } + AssetType } AssetVersion { - idAssetVersion + Creator DateCreated StorageSize Ingested Version - User { - idUser - Name - } } Actor { - idActor OrganizationName } Stakeholder { - idStakeholder OrganizationName EmailAddress PhoneNumberMobile diff --git a/server/graphql/api/index.ts b/server/graphql/api/index.ts index b1802c744..0d7ad68b2 100644 --- a/server/graphql/api/index.ts +++ b/server/graphql/api/index.ts @@ -101,7 +101,9 @@ import { GetVersionsForSystemObjectResult, GetDetailsTabDataForObjectInput, GetDetailsTabDataForObjectResult, - GetFilterViewDataResult + GetFilterViewDataResult, + UpdateObjectDetailsInput, + UpdateObjectDetailsResult } from '../../types/graphql'; // Queries @@ -156,6 +158,7 @@ import createVocabularySet from './mutations/vocabulary/createVocabularySet'; import uploadAsset from './mutations/asset/uploadAsset'; import ingestData from './mutations/ingestion/ingestData'; import discardUploadedAssetVersions from './mutations/asset/discardUploadedAssetVersions'; +import updateObjectDetails from './mutations/systemobject/updateObjectDetails'; import { Context } from '../../types/resolvers'; @@ -208,7 +211,8 @@ const allQueries = { getAssetDetailsForSystemObject, getVersionsForSystemObject, getDetailsTabDataForObject, - getFilterViewData + getFilterViewData, + updateObjectDetails }; type GraphQLRequest = { @@ -599,6 +603,16 @@ class GraphQLApi { }); } + async updateObjectDetails(input: UpdateObjectDetailsInput, context?: Context): Promise { + const operationName = 'updateObjectDetails'; + const variables = { input }; + return this.graphqlRequest({ + operationName, + variables, + context + }); + } + async createUnit(input: CreateUnitInput, context?: Context): Promise { const operationName = 'createUnit'; const variables = { input }; diff --git a/server/graphql/api/mutations/systemobject/updateObjectDetails.ts b/server/graphql/api/mutations/systemobject/updateObjectDetails.ts new file mode 100644 index 000000000..75d85e3be --- /dev/null +++ b/server/graphql/api/mutations/systemobject/updateObjectDetails.ts @@ -0,0 +1,11 @@ +import { gql } from 'apollo-server-express'; + +const updateObjectDetails = gql` + mutation updateObjectDetails($input: UpdateObjectDetailsInput!) { + updateObjectDetails(input: $input) { + success + } + } +`; + +export default updateObjectDetails; diff --git a/server/graphql/api/queries/systemobject/getDetailsTabDataForObject.ts b/server/graphql/api/queries/systemobject/getDetailsTabDataForObject.ts index 50593bfa7..b55bb0c03 100644 --- a/server/graphql/api/queries/systemobject/getDetailsTabDataForObject.ts +++ b/server/graphql/api/queries/systemobject/getDetailsTabDataForObject.ts @@ -4,70 +4,59 @@ const getDetailsTabDataForObject = gql` query getDetailsTabDataForObject($input: GetDetailsTabDataForObjectInput!) { getDetailsTabDataForObject(input: $input) { Unit { - idUnit Abbreviation ARKPrefix } Project { - idProject Description } Subject { - idSubject - GeoLocation { - idGeoLocation - Altitude - Latitude - Longitude - R0 - R1 - R2 - R3 - TS0 - TS1 - TS2 - } + Altitude + Latitude + Longitude + R0 + R1 + R2 + R3 + TS0 + TS1 + TS2 } Item { - idItem EntireSubject - GeoLocation { - idGeoLocation - Altitude - Latitude - Longitude - R0 - R1 - R2 - R3 - TS0 - TS1 - TS2 - } + Altitude + Latitude + Longitude + R0 + R1 + R2 + R3 + TS0 + TS1 + TS2 } CaptureData { - idAssetVersion - dateCaptured - datasetType - systemCreated - description - cameraSettingUniform - datasetFieldId - itemPositionType - itemPositionFieldId - itemArrangementFieldId - focusType - lightsourceType - backgroundRemovalMethod - clusterType - clusterGeometryFieldId - folders { - name - variantType - } + captureMethod + dateCaptured + datasetType + description + cameraSettingUniform + datasetFieldId + itemPositionType + itemPositionFieldId + itemArrangementFieldId + focusType + lightsourceType + backgroundRemovalMethod + clusterType + clusterGeometryFieldId + folders { + name + variantType + } } Model { - systemCreated + size master authoritative creationMethod @@ -97,40 +86,32 @@ const getDetailsTabDataForObject = gql` boundingBoxP2Z } Scene { - idScene + Links + AssetType + Tours + Annotation } IntermediaryFile { idIntermediaryFile } ProjectDocumentation { - idProjectDocumentation Description } Asset { - idAsset FilePath - VAssetType { - idVocabulary - Term - } + AssetType } AssetVersion { - idAssetVersion + Creator DateCreated StorageSize Ingested Version - User { - idUser - Name - } } Actor { - idActor OrganizationName } Stakeholder { - idStakeholder OrganizationName EmailAddress PhoneNumberMobile diff --git a/server/graphql/schema.graphql b/server/graphql/schema.graphql index 3decfcc9a..5eeda23b9 100644 --- a/server/graphql/schema.graphql +++ b/server/graphql/schema.graphql @@ -105,6 +105,7 @@ type Mutation { createVocabularySet(input: CreateVocabularySetInput!): CreateVocabularySetResult! discardUploadedAssetVersions(input: DiscardUploadedAssetVersionsInput!): DiscardUploadedAssetVersionsResult! ingestData(input: IngestDataInput!): IngestDataResult! + updateObjectDetails(input: UpdateObjectDetailsInput!): UpdateObjectDetailsResult! uploadAsset(file: Upload!, type: Int!): UploadAssetResult! } @@ -807,25 +808,290 @@ type IntermediaryFile { SystemObject: SystemObject } +input UpdateObjectDetailsInput { + idSystemObject: Int! + objectType: Int! + data: UpdateObjectDetailsDataInput! +} + +input UnitDetailFieldsInput { + Abbreviation: String + ARKPrefix: String +} + +input ProjectDetailFieldsInput { + Description: String +} + +input SubjectDetailFieldsInput { + Altitude: Float + Latitude: Float + Longitude: Float + R0: Float + R1: Float + R2: Float + R3: Float + TS0: Float + TS1: Float + TS2: Float +} + +input ItemDetailFieldsInput { + EntireSubject: Boolean + Altitude: Float + Latitude: Float + Longitude: Float + R0: Float + R1: Float + R2: Float + R3: Float + TS0: Float + TS1: Float + TS2: Float +} + +input CaptureDataDetailFieldsInput { + captureMethod: Int + dateCaptured: String + datasetType: Int + systemCreated: Boolean + description: String + cameraSettingUniform: Boolean + datasetFieldId: Int + itemPositionType: Int + itemPositionFieldId: Int + itemArrangementFieldId: Int + focusType: Int + lightsourceType: Int + backgroundRemovalMethod: Int + clusterType: Int + clusterGeometryFieldId: Int + folders: [IngestFolderInput!]! +} + +input ModelDetailFieldsInput { + size: Int + master: Boolean + authoritative: Boolean + creationMethod: Int + modality: Int + purpose: Int + units: Int + dateCaptured: String + modelFileType: Int + uvMaps: [IngestUVMapInput!]! + roughness: Int + metalness: Int + pointCount: Int + faceCount: Int + isWatertight: Boolean + hasNormals: Boolean + hasVertexColor: Boolean + hasUVSpace: Boolean + boundingBoxP1X: Float + boundingBoxP1Y: Float + boundingBoxP1Z: Float + boundingBoxP2X: Float + boundingBoxP2Y: Float + boundingBoxP2Z: Float +} + +input SceneDetailFieldsInput { + Links: [String!]! + AssetType: Int + Tours: Int + Annotation: Int +} + +input ProjectDocumentationDetailFieldsInput { + Description: String +} + +input AssetDetailFieldsInput { + FilePath: String + AssetType: Int +} + +input AssetVersionDetailFieldsInput { + Creator: String + DateCreated: DateTime + Ingested: Boolean + Version: Int + StorageSize: Int +} + +input ActorDetailFieldsInput { + OrganizationName: String +} + +input StakeholderDetailFieldsInput { + OrganizationName: String + MailingAddress: String + EmailAddress: String + PhoneNumberMobile: String + PhoneNumberOffice: String +} + +input UpdateObjectDetailsDataInput { + Unit: UnitDetailFieldsInput + Project: ProjectDetailFieldsInput + Subject: SubjectDetailFieldsInput + Item: ItemDetailFieldsInput + CaptureData: CaptureDataDetailFieldsInput + Model: ModelDetailFieldsInput + Scene: SceneDetailFieldsInput + ProjectDocumentation: ProjectDocumentationDetailFieldsInput + Asset: AssetDetailFieldsInput + AssetVersion: AssetVersionDetailFieldsInput + Actor: ActorDetailFieldsInput + Stakeholder: StakeholderDetailFieldsInput +} + +type UpdateObjectDetailsResult { + success: Boolean! +} + input GetDetailsTabDataForObjectInput { idSystemObject: Int! objectType: Int! } +type UnitDetailFields { + Abbreviation: String + ARKPrefix: String +} + +type ProjectDetailFields { + Description: String +} + +type SubjectDetailFields { + Altitude: Float + Latitude: Float + Longitude: Float + R0: Float + R1: Float + R2: Float + R3: Float + TS0: Float + TS1: Float + TS2: Float +} + +type ItemDetailFields { + EntireSubject: Boolean + Altitude: Float + Latitude: Float + Longitude: Float + R0: Float + R1: Float + R2: Float + R3: Float + TS0: Float + TS1: Float + TS2: Float +} + +type CaptureDataDetailFields { + captureMethod: Int + dateCaptured: String + datasetType: Int + systemCreated: Boolean + description: String + cameraSettingUniform: Boolean + datasetFieldId: Int + itemPositionType: Int + itemPositionFieldId: Int + itemArrangementFieldId: Int + focusType: Int + lightsourceType: Int + backgroundRemovalMethod: Int + clusterType: Int + clusterGeometryFieldId: Int + folders: [IngestFolder!]! +} + +type ModelDetailFields { + size: Int + master: Boolean + authoritative: Boolean + creationMethod: Int + modality: Int + purpose: Int + units: Int + dateCaptured: String + modelFileType: Int + uvMaps: [IngestUVMap!]! + roughness: Int + metalness: Int + pointCount: Int + faceCount: Int + isWatertight: Boolean + hasNormals: Boolean + hasVertexColor: Boolean + hasUVSpace: Boolean + boundingBoxP1X: Float + boundingBoxP1Y: Float + boundingBoxP1Z: Float + boundingBoxP2X: Float + boundingBoxP2Y: Float + boundingBoxP2Z: Float +} + +type SceneDetailFields { + Links: [String!]! + AssetType: Int + Tours: Int + Annotation: Int +} + +type IntermediaryFileDetailFields { + idIntermediaryFile: Int! +} + +type ProjectDocumentationDetailFields { + Description: String +} + +type AssetDetailFields { + FilePath: String + AssetType: Int +} + +type AssetVersionDetailFields { + Creator: String + DateCreated: DateTime + Ingested: Boolean + Version: Int + StorageSize: Int +} + +type ActorDetailFields { + OrganizationName: String +} + +type StakeholderDetailFields { + OrganizationName: String + MailingAddress: String + EmailAddress: String + PhoneNumberMobile: String + PhoneNumberOffice: String +} + type GetDetailsTabDataForObjectResult { - Unit: Unit - Project: Project - Subject: Subject - Item: Item - CaptureData: IngestPhotogrammetry - Model: IngestModel - Scene: Scene - IntermediaryFile: IntermediaryFile - ProjectDocumentation: ProjectDocumentation - Asset: Asset - AssetVersion: AssetVersion - Actor: Actor - Stakeholder: Stakeholder + Unit: UnitDetailFields + Project: ProjectDetailFields + Subject: SubjectDetailFields + Item: ItemDetailFields + CaptureData: CaptureDataDetailFields + Model: ModelDetailFields + Scene: SceneDetailFields + IntermediaryFile: IntermediaryFileDetailFields + ProjectDocumentation: ProjectDocumentationDetailFields + Asset: AssetDetailFields + AssetVersion: AssetVersionDetailFields + Actor: ActorDetailFields + Stakeholder: StakeholderDetailFields } input GetSystemObjectDetailsInput { diff --git a/server/graphql/schema/systemobject/mutations.graphql b/server/graphql/schema/systemobject/mutations.graphql new file mode 100644 index 000000000..6d04adfaa --- /dev/null +++ b/server/graphql/schema/systemobject/mutations.graphql @@ -0,0 +1,147 @@ +scalar DateTime + +type Mutation { + updateObjectDetails(input: UpdateObjectDetailsInput!): UpdateObjectDetailsResult! +} + +input UpdateObjectDetailsInput { + idSystemObject: Int! + objectType: Int! + data: UpdateObjectDetailsDataInput! +} + +input UnitDetailFieldsInput { + Abbreviation: String + ARKPrefix: String +} + +input ProjectDetailFieldsInput { + Description: String +} + +input SubjectDetailFieldsInput { + Altitude: Float + Latitude: Float + Longitude: Float + R0: Float + R1: Float + R2: Float + R3: Float + TS0: Float + TS1: Float + TS2: Float +} + +input ItemDetailFieldsInput { + EntireSubject: Boolean + Altitude: Float + Latitude: Float + Longitude: Float + R0: Float + R1: Float + R2: Float + R3: Float + TS0: Float + TS1: Float + TS2: Float +} + +input CaptureDataDetailFieldsInput { + captureMethod: Int + dateCaptured: String + datasetType: Int + systemCreated: Boolean + description: String + cameraSettingUniform: Boolean + datasetFieldId: Int + itemPositionType: Int + itemPositionFieldId: Int + itemArrangementFieldId: Int + focusType: Int + lightsourceType: Int + backgroundRemovalMethod: Int + clusterType: Int + clusterGeometryFieldId: Int + folders: [IngestFolderInput!]! +} + +input ModelDetailFieldsInput { + size: Int + master: Boolean + authoritative: Boolean + creationMethod: Int + modality: Int + purpose: Int + units: Int + dateCaptured: String + modelFileType: Int + uvMaps: [IngestUVMapInput!]! + roughness: Int + metalness: Int + pointCount: Int + faceCount: Int + isWatertight: Boolean + hasNormals: Boolean + hasVertexColor: Boolean + hasUVSpace: Boolean + boundingBoxP1X: Float + boundingBoxP1Y: Float + boundingBoxP1Z: Float + boundingBoxP2X: Float + boundingBoxP2Y: Float + boundingBoxP2Z: Float +} + +input SceneDetailFieldsInput { + Links: [String!]! + AssetType: Int + Tours: Int + Annotation: Int +} + +input ProjectDocumentationDetailFieldsInput { + Description: String +} +input AssetDetailFieldsInput { + FilePath: String + AssetType: Int +} + +input AssetVersionDetailFieldsInput { + Creator: String + DateCreated: DateTime + Ingested: Boolean + Version: Int + StorageSize: Int +} + +input ActorDetailFieldsInput { + OrganizationName: String +} + +input StakeholderDetailFieldsInput { + OrganizationName: String + MailingAddress: String + EmailAddress: String + PhoneNumberMobile: String + PhoneNumberOffice: String +} + +input UpdateObjectDetailsDataInput { + Unit: UnitDetailFieldsInput + Project: ProjectDetailFieldsInput + Subject: SubjectDetailFieldsInput + Item: ItemDetailFieldsInput + CaptureData: CaptureDataDetailFieldsInput + Model: ModelDetailFieldsInput + Scene: SceneDetailFieldsInput + ProjectDocumentation: ProjectDocumentationDetailFieldsInput + Asset: AssetDetailFieldsInput + AssetVersion: AssetVersionDetailFieldsInput + Actor: ActorDetailFieldsInput + Stakeholder: StakeholderDetailFieldsInput +} + +type UpdateObjectDetailsResult { + success: Boolean! +} diff --git a/server/graphql/schema/systemobject/queries.graphql b/server/graphql/schema/systemobject/queries.graphql index b1b0463a5..e519d1a17 100644 --- a/server/graphql/schema/systemobject/queries.graphql +++ b/server/graphql/schema/systemobject/queries.graphql @@ -13,20 +13,141 @@ input GetDetailsTabDataForObjectInput { objectType: Int! } +type UnitDetailFields { + Abbreviation: String + ARKPrefix: String +} + +type ProjectDetailFields { + Description: String +} + +type SubjectDetailFields { + Altitude: Float + Latitude: Float + Longitude: Float + R0: Float + R1: Float + R2: Float + R3: Float + TS0: Float + TS1: Float + TS2: Float +} + +type ItemDetailFields { + EntireSubject: Boolean + Altitude: Float + Latitude: Float + Longitude: Float + R0: Float + R1: Float + R2: Float + R3: Float + TS0: Float + TS1: Float + TS2: Float +} + +type CaptureDataDetailFields { + captureMethod: Int + dateCaptured: String + datasetType: Int + systemCreated: Boolean + description: String + cameraSettingUniform: Boolean + datasetFieldId: Int + itemPositionType: Int + itemPositionFieldId: Int + itemArrangementFieldId: Int + focusType: Int + lightsourceType: Int + backgroundRemovalMethod: Int + clusterType: Int + clusterGeometryFieldId: Int + folders: [IngestFolder!]! +} + +type ModelDetailFields { + size: Int + master: Boolean + authoritative: Boolean + creationMethod: Int + modality: Int + purpose: Int + units: Int + dateCaptured: String + modelFileType: Int + uvMaps: [IngestUVMap!]! + roughness: Int + metalness: Int + pointCount: Int + faceCount: Int + isWatertight: Boolean + hasNormals: Boolean + hasVertexColor: Boolean + hasUVSpace: Boolean + boundingBoxP1X: Float + boundingBoxP1Y: Float + boundingBoxP1Z: Float + boundingBoxP2X: Float + boundingBoxP2Y: Float + boundingBoxP2Z: Float +} + +type SceneDetailFields { + Links: [String!]! + AssetType: Int + Tours: Int + Annotation: Int +} + +type IntermediaryFileDetailFields { + idIntermediaryFile: Int! +} + +type ProjectDocumentationDetailFields { + Description: String +} +type AssetDetailFields { + FilePath: String + AssetType: Int +} + +type AssetVersionDetailFields { + Creator: String + DateCreated: DateTime + Ingested: Boolean + Version: Int + StorageSize: Int +} + +type ActorDetailFields { + OrganizationName: String +} + +type StakeholderDetailFields { + OrganizationName: String + MailingAddress: String + EmailAddress: String + PhoneNumberMobile: String + PhoneNumberOffice: String +} + type GetDetailsTabDataForObjectResult { - Unit: Unit - Project: Project - Subject: Subject - Item: Item - CaptureData: IngestPhotogrammetry - Model: IngestModel - Scene: Scene - IntermediaryFile: IntermediaryFile - ProjectDocumentation: ProjectDocumentation - Asset: Asset - AssetVersion: AssetVersion - Actor: Actor - Stakeholder: Stakeholder + Unit: UnitDetailFields + Project: ProjectDetailFields + Subject: SubjectDetailFields + Item: ItemDetailFields + CaptureData: CaptureDataDetailFields + Model: ModelDetailFields + Scene: SceneDetailFields + IntermediaryFile: IntermediaryFileDetailFields + ProjectDocumentation: ProjectDocumentationDetailFields + Asset: AssetDetailFields + AssetVersion: AssetVersionDetailFields + Actor: ActorDetailFields + Stakeholder: StakeholderDetailFields } input GetSystemObjectDetailsInput { diff --git a/server/graphql/schema/systemobject/resolvers/index.ts b/server/graphql/schema/systemobject/resolvers/index.ts index 319331cba..1564079a0 100644 --- a/server/graphql/schema/systemobject/resolvers/index.ts +++ b/server/graphql/schema/systemobject/resolvers/index.ts @@ -7,6 +7,7 @@ import getSourceObjectIdentifer from './queries/getSourceObjectIdentifer'; import getAssetDetailsForSystemObject from './queries/getAssetDetailsForSystemObject'; import getVersionsForSystemObject from './queries/getVersionsForSystemObject'; import getDetailsTabDataForObject from './queries/getDetailsTabDataForObject'; +import updateObjectDetails from './mutations/updateObjectDetails'; const resolvers = { Query: { @@ -16,6 +17,9 @@ const resolvers = { getVersionsForSystemObject, getDetailsTabDataForObject }, + Mutation: { + updateObjectDetails + }, SystemObject, SystemObjectVersion, Identifier, diff --git a/server/graphql/schema/systemobject/resolvers/mutations/updateObjectDetails.ts b/server/graphql/schema/systemobject/resolvers/mutations/updateObjectDetails.ts new file mode 100644 index 000000000..5bbc65e31 --- /dev/null +++ b/server/graphql/schema/systemobject/resolvers/mutations/updateObjectDetails.ts @@ -0,0 +1,46 @@ +import { eSystemObjectType } from '../../../../../db'; +import { UpdateObjectDetailsResult, MutationUpdateObjectDetailsArgs } from '../../../../../types/graphql'; +import { Parent } from '../../../../../types/resolvers'; +import * as LOG from '../../../../../utils'; + +export default async function updateObjectDetails(_: Parent, args: MutationUpdateObjectDetailsArgs): Promise { + const { input } = args; + const { objectType, data } = input; + + // TODO: KARAN: update {data} for {idSystemObject} + LOG.logger.info(data); + + switch (objectType) { + case eSystemObjectType.eUnit: + break; + case eSystemObjectType.eProject: + break; + case eSystemObjectType.eSubject: + break; + case eSystemObjectType.eItem: + break; + case eSystemObjectType.eCaptureData: + break; + case eSystemObjectType.eModel: + break; + case eSystemObjectType.eScene: + break; + case eSystemObjectType.eIntermediaryFile: + break; + case eSystemObjectType.eProjectDocumentation: + break; + case eSystemObjectType.eAsset: + break; + case eSystemObjectType.eAssetVersion: + break; + case eSystemObjectType.eActor: + break; + case eSystemObjectType.eStakeholder: + break; + default: + break; + } + + return { success: true }; +} + diff --git a/server/graphql/schema/systemobject/resolvers/queries/getDetailsTabDataForObject.ts b/server/graphql/schema/systemobject/resolvers/queries/getDetailsTabDataForObject.ts index 9cf39dd50..1d97e0c84 100644 --- a/server/graphql/schema/systemobject/resolvers/queries/getDetailsTabDataForObject.ts +++ b/server/graphql/schema/systemobject/resolvers/queries/getDetailsTabDataForObject.ts @@ -24,7 +24,7 @@ export default async function getDetailsTabDataForObject(_: Parent, args: QueryG }; const systemObject: DBAPI.SystemObject | null = await DBAPI.SystemObject.fetch(idSystemObject); - + // TODO: KARAN: complete all object types switch (objectType) { case eSystemObjectType.eUnit: if (systemObject?.idUnit) result.Unit = await DBAPI.Unit.fetch(systemObject.idUnit); @@ -33,19 +33,15 @@ export default async function getDetailsTabDataForObject(_: Parent, args: QueryG if (systemObject?.idProject) result.Project = await DBAPI.Project.fetch(systemObject.idProject); break; case eSystemObjectType.eSubject: - if (systemObject?.idSubject) result.Subject = await DBAPI.Subject.fetch(systemObject.idSubject); break; case eSystemObjectType.eItem: if (systemObject?.idItem) result.Item = await DBAPI.Item.fetch(systemObject.idItem); break; case eSystemObjectType.eCaptureData: - // TODO: KARAN: fetch IngestPhotogrammetry break; case eSystemObjectType.eModel: - // TODO: KARAN: fetch IngestModel break; case eSystemObjectType.eScene: - if (systemObject?.idScene) result.Scene = await DBAPI.Scene.fetch(systemObject.idScene); break; case eSystemObjectType.eIntermediaryFile: if (systemObject?.idIntermediaryFile) result.IntermediaryFile = await DBAPI.IntermediaryFile.fetch(systemObject.idIntermediaryFile); diff --git a/server/types/graphql.ts b/server/types/graphql.ts index 017b7f62a..762ec189c 100644 --- a/server/types/graphql.ts +++ b/server/types/graphql.ts @@ -285,6 +285,7 @@ export type Mutation = { createVocabularySet: CreateVocabularySetResult; discardUploadedAssetVersions: DiscardUploadedAssetVersionsResult; ingestData: IngestDataResult; + updateObjectDetails: UpdateObjectDetailsResult; uploadAsset: UploadAssetResult; }; @@ -354,6 +355,11 @@ export type MutationIngestDataArgs = { }; +export type MutationUpdateObjectDetailsArgs = { + input: UpdateObjectDetailsInput; +}; + + export type MutationUploadAssetArgs = { file: Scalars['Upload']; type: Scalars['Int']; @@ -1109,26 +1115,305 @@ export type IntermediaryFile = { SystemObject?: Maybe; }; +export type UpdateObjectDetailsInput = { + idSystemObject: Scalars['Int']; + objectType: Scalars['Int']; + data: UpdateObjectDetailsDataInput; +}; + +export type UnitDetailFieldsInput = { + Abbreviation?: Maybe; + ARKPrefix?: Maybe; +}; + +export type ProjectDetailFieldsInput = { + Description?: Maybe; +}; + +export type SubjectDetailFieldsInput = { + Altitude?: Maybe; + Latitude?: Maybe; + Longitude?: Maybe; + R0?: Maybe; + R1?: Maybe; + R2?: Maybe; + R3?: Maybe; + TS0?: Maybe; + TS1?: Maybe; + TS2?: Maybe; +}; + +export type ItemDetailFieldsInput = { + EntireSubject?: Maybe; + Altitude?: Maybe; + Latitude?: Maybe; + Longitude?: Maybe; + R0?: Maybe; + R1?: Maybe; + R2?: Maybe; + R3?: Maybe; + TS0?: Maybe; + TS1?: Maybe; + TS2?: Maybe; +}; + +export type CaptureDataDetailFieldsInput = { + captureMethod?: Maybe; + dateCaptured?: Maybe; + datasetType?: Maybe; + systemCreated?: Maybe; + description?: Maybe; + cameraSettingUniform?: Maybe; + datasetFieldId?: Maybe; + itemPositionType?: Maybe; + itemPositionFieldId?: Maybe; + itemArrangementFieldId?: Maybe; + focusType?: Maybe; + lightsourceType?: Maybe; + backgroundRemovalMethod?: Maybe; + clusterType?: Maybe; + clusterGeometryFieldId?: Maybe; + folders: Array; +}; + +export type ModelDetailFieldsInput = { + size?: Maybe; + master?: Maybe; + authoritative?: Maybe; + creationMethod?: Maybe; + modality?: Maybe; + purpose?: Maybe; + units?: Maybe; + dateCaptured?: Maybe; + modelFileType?: Maybe; + uvMaps: Array; + roughness?: Maybe; + metalness?: Maybe; + pointCount?: Maybe; + faceCount?: Maybe; + isWatertight?: Maybe; + hasNormals?: Maybe; + hasVertexColor?: Maybe; + hasUVSpace?: Maybe; + boundingBoxP1X?: Maybe; + boundingBoxP1Y?: Maybe; + boundingBoxP1Z?: Maybe; + boundingBoxP2X?: Maybe; + boundingBoxP2Y?: Maybe; + boundingBoxP2Z?: Maybe; +}; + +export type SceneDetailFieldsInput = { + Links: Array; + AssetType?: Maybe; + Tours?: Maybe; + Annotation?: Maybe; +}; + +export type ProjectDocumentationDetailFieldsInput = { + Description?: Maybe; +}; + +export type AssetDetailFieldsInput = { + FilePath?: Maybe; + AssetType?: Maybe; +}; + +export type AssetVersionDetailFieldsInput = { + Creator?: Maybe; + DateCreated?: Maybe; + Ingested?: Maybe; + Version?: Maybe; + StorageSize?: Maybe; +}; + +export type ActorDetailFieldsInput = { + OrganizationName?: Maybe; +}; + +export type StakeholderDetailFieldsInput = { + OrganizationName?: Maybe; + MailingAddress?: Maybe; + EmailAddress?: Maybe; + PhoneNumberMobile?: Maybe; + PhoneNumberOffice?: Maybe; +}; + +export type UpdateObjectDetailsDataInput = { + Unit?: Maybe; + Project?: Maybe; + Subject?: Maybe; + Item?: Maybe; + CaptureData?: Maybe; + Model?: Maybe; + Scene?: Maybe; + ProjectDocumentation?: Maybe; + Asset?: Maybe; + AssetVersion?: Maybe; + Actor?: Maybe; + Stakeholder?: Maybe; +}; + +export type UpdateObjectDetailsResult = { + __typename?: 'UpdateObjectDetailsResult'; + success: Scalars['Boolean']; +}; + export type GetDetailsTabDataForObjectInput = { idSystemObject: Scalars['Int']; objectType: Scalars['Int']; }; +export type UnitDetailFields = { + __typename?: 'UnitDetailFields'; + Abbreviation?: Maybe; + ARKPrefix?: Maybe; +}; + +export type ProjectDetailFields = { + __typename?: 'ProjectDetailFields'; + Description?: Maybe; +}; + +export type SubjectDetailFields = { + __typename?: 'SubjectDetailFields'; + Altitude?: Maybe; + Latitude?: Maybe; + Longitude?: Maybe; + R0?: Maybe; + R1?: Maybe; + R2?: Maybe; + R3?: Maybe; + TS0?: Maybe; + TS1?: Maybe; + TS2?: Maybe; +}; + +export type ItemDetailFields = { + __typename?: 'ItemDetailFields'; + EntireSubject?: Maybe; + Altitude?: Maybe; + Latitude?: Maybe; + Longitude?: Maybe; + R0?: Maybe; + R1?: Maybe; + R2?: Maybe; + R3?: Maybe; + TS0?: Maybe; + TS1?: Maybe; + TS2?: Maybe; +}; + +export type CaptureDataDetailFields = { + __typename?: 'CaptureDataDetailFields'; + captureMethod?: Maybe; + dateCaptured?: Maybe; + datasetType?: Maybe; + systemCreated?: Maybe; + description?: Maybe; + cameraSettingUniform?: Maybe; + datasetFieldId?: Maybe; + itemPositionType?: Maybe; + itemPositionFieldId?: Maybe; + itemArrangementFieldId?: Maybe; + focusType?: Maybe; + lightsourceType?: Maybe; + backgroundRemovalMethod?: Maybe; + clusterType?: Maybe; + clusterGeometryFieldId?: Maybe; + folders: Array; +}; + +export type ModelDetailFields = { + __typename?: 'ModelDetailFields'; + size?: Maybe; + master?: Maybe; + authoritative?: Maybe; + creationMethod?: Maybe; + modality?: Maybe; + purpose?: Maybe; + units?: Maybe; + dateCaptured?: Maybe; + modelFileType?: Maybe; + uvMaps: Array; + roughness?: Maybe; + metalness?: Maybe; + pointCount?: Maybe; + faceCount?: Maybe; + isWatertight?: Maybe; + hasNormals?: Maybe; + hasVertexColor?: Maybe; + hasUVSpace?: Maybe; + boundingBoxP1X?: Maybe; + boundingBoxP1Y?: Maybe; + boundingBoxP1Z?: Maybe; + boundingBoxP2X?: Maybe; + boundingBoxP2Y?: Maybe; + boundingBoxP2Z?: Maybe; +}; + +export type SceneDetailFields = { + __typename?: 'SceneDetailFields'; + Links: Array; + AssetType?: Maybe; + Tours?: Maybe; + Annotation?: Maybe; +}; + +export type IntermediaryFileDetailFields = { + __typename?: 'IntermediaryFileDetailFields'; + idIntermediaryFile: Scalars['Int']; +}; + +export type ProjectDocumentationDetailFields = { + __typename?: 'ProjectDocumentationDetailFields'; + Description?: Maybe; +}; + +export type AssetDetailFields = { + __typename?: 'AssetDetailFields'; + FilePath?: Maybe; + AssetType?: Maybe; +}; + +export type AssetVersionDetailFields = { + __typename?: 'AssetVersionDetailFields'; + Creator?: Maybe; + DateCreated?: Maybe; + Ingested?: Maybe; + Version?: Maybe; + StorageSize?: Maybe; +}; + +export type ActorDetailFields = { + __typename?: 'ActorDetailFields'; + OrganizationName?: Maybe; +}; + +export type StakeholderDetailFields = { + __typename?: 'StakeholderDetailFields'; + OrganizationName?: Maybe; + MailingAddress?: Maybe; + EmailAddress?: Maybe; + PhoneNumberMobile?: Maybe; + PhoneNumberOffice?: Maybe; +}; + export type GetDetailsTabDataForObjectResult = { __typename?: 'GetDetailsTabDataForObjectResult'; - Unit?: Maybe; - Project?: Maybe; - Subject?: Maybe; - Item?: Maybe; - CaptureData?: Maybe; - Model?: Maybe; - Scene?: Maybe; - IntermediaryFile?: Maybe; - ProjectDocumentation?: Maybe; - Asset?: Maybe; - AssetVersion?: Maybe; - Actor?: Maybe; - Stakeholder?: Maybe; + Unit?: Maybe; + Project?: Maybe; + Subject?: Maybe; + Item?: Maybe; + CaptureData?: Maybe; + Model?: Maybe; + Scene?: Maybe; + IntermediaryFile?: Maybe; + ProjectDocumentation?: Maybe; + Asset?: Maybe; + AssetVersion?: Maybe; + Actor?: Maybe; + Stakeholder?: Maybe; }; export type GetSystemObjectDetailsInput = { From ae4f7fab4281b7a52bc46e383c3b9e6bd9f642f5 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Wed, 6 Jan 2021 15:51:17 +0530 Subject: [PATCH 165/239] wire object update mutation with client components --- .../Metadata/Photogrammetry/Description.tsx | 4 +- .../DetailsView/DetailsTab/ActorDetails.tsx | 16 +++--- .../DetailsView/DetailsTab/AssetDetails.tsx | 16 +++--- .../DetailsTab/AssetVersionDetails.tsx | 17 +++--- .../DetailsTab/CaptureDataDetails.tsx | 16 +++--- .../DetailsTab/IntermediaryFileDetails.tsx | 8 +-- .../DetailsView/DetailsTab/ItemDetails.tsx | 16 +++--- .../DetailsView/DetailsTab/ModelDetails.tsx | 18 +++---- .../DetailsView/DetailsTab/ProjectDetails.tsx | 17 +++--- .../ProjectDocumentationDetails.tsx | 18 ++++--- .../DetailsView/DetailsTab/SceneDetails.tsx | 7 +-- .../DetailsTab/StakeholderDetails.tsx | 16 +++--- .../DetailsView/DetailsTab/SubjectDetails.tsx | 16 +++--- .../DetailsView/DetailsTab/UnitDetails.tsx | 16 +++--- .../DetailsView/DetailsTab/index.tsx | 54 +++++++++++++------ .../pages/Repository/hooks/useDetailsView.ts | 9 ++-- .../mutations/updateObjectDetails.ts | 2 +- 17 files changed, 155 insertions(+), 111 deletions(-) diff --git a/client/src/pages/Ingestion/components/Metadata/Photogrammetry/Description.tsx b/client/src/pages/Ingestion/components/Metadata/Photogrammetry/Description.tsx index 70b6a4763..68a98e2c6 100644 --- a/client/src/pages/Ingestion/components/Metadata/Photogrammetry/Description.tsx +++ b/client/src/pages/Ingestion/components/Metadata/Photogrammetry/Description.tsx @@ -26,10 +26,11 @@ interface DescriptionProps { value: string; onChange: (event: React.ChangeEvent) => void; viewMode?: boolean; + disabled?: boolean; } function Description(props: DescriptionProps): React.ReactElement { - const { value, onChange, viewMode = false } = props; + const { value, onChange, viewMode = false, disabled = false } = props; const classes = useStyles(); const rowFieldProps = { alignItems: 'center', justifyContent: 'space-between' }; @@ -46,6 +47,7 @@ function Description(props: DescriptionProps): React.ReactElement { element='textarea' className={classes.description} name='description' + disabled={disabled} value={value} onChange={onChange} forceNotifyByEnter={false} diff --git a/client/src/pages/Repository/components/DetailsView/DetailsTab/ActorDetails.tsx b/client/src/pages/Repository/components/DetailsView/DetailsTab/ActorDetails.tsx index 863aec8b8..9cfc73b98 100644 --- a/client/src/pages/Repository/components/DetailsView/DetailsTab/ActorDetails.tsx +++ b/client/src/pages/Repository/components/DetailsView/DetailsTab/ActorDetails.tsx @@ -1,3 +1,4 @@ +/* eslint-disable react-hooks/exhaustive-deps */ /** * ActorDetails * @@ -6,14 +7,11 @@ import { Box } from '@material-ui/core'; import React, { useEffect, useState } from 'react'; import { InputField, Loader } from '../../../../../components'; -import { ActorDetailFields, GetDetailsTabDataForObjectQueryResult } from '../../../../../types/graphql'; +import { ActorDetailFields } from '../../../../../types/graphql'; +import { DetailComponentProps } from './index'; -interface ActorDetailsProps extends GetDetailsTabDataForObjectQueryResult { - disabled: boolean; -} - -function ActorDetails(props: ActorDetailsProps): React.ReactElement { - const { data, loading, disabled, } = props; +function ActorDetails(props: DetailComponentProps): React.ReactElement { + const { data, loading, disabled, onUpdateDetail, objectType } = props; const [details, setDetails] = useState({}); @@ -26,6 +24,10 @@ function ActorDetails(props: ActorDetailsProps): React.ReactElement { } }, [data, loading]); + useEffect(() => { + onUpdateDetail(objectType, details); + }, [details]); + if (!data || loading) { return ; } diff --git a/client/src/pages/Repository/components/DetailsView/DetailsTab/AssetDetails.tsx b/client/src/pages/Repository/components/DetailsView/DetailsTab/AssetDetails.tsx index 658c69378..ae6fc9855 100644 --- a/client/src/pages/Repository/components/DetailsView/DetailsTab/AssetDetails.tsx +++ b/client/src/pages/Repository/components/DetailsView/DetailsTab/AssetDetails.tsx @@ -1,3 +1,4 @@ +/* eslint-disable react-hooks/exhaustive-deps */ /** * AssetDetails * @@ -7,16 +8,13 @@ import { Box } from '@material-ui/core'; import React, { useEffect, useState } from 'react'; import { InputField, Loader, SelectField } from '../../../../../components'; import { useVocabularyStore } from '../../../../../store'; -import { AssetDetailFields, GetDetailsTabDataForObjectQueryResult } from '../../../../../types/graphql'; +import { AssetDetailFields } from '../../../../../types/graphql'; import { eVocabularySetID } from '../../../../../types/server'; import { withDefaultValueNumber } from '../../../../../utils/shared'; +import { DetailComponentProps } from './index'; -interface AssetDetailsProps extends GetDetailsTabDataForObjectQueryResult { - disabled: boolean; -} - -function AssetDetails(props: AssetDetailsProps): React.ReactElement { - const { data, loading, disabled, } = props; +function AssetDetails(props: DetailComponentProps): React.ReactElement { + const { data, loading, disabled, onUpdateDetail, objectType } = props; const [details, setDetails] = useState({}); const [getEntries, getInitialEntry] = useVocabularyStore(state => [state.getEntries, state.getInitialEntry]); @@ -31,6 +29,10 @@ function AssetDetails(props: AssetDetailsProps): React.ReactElement { } }, [data, loading]); + useEffect(() => { + onUpdateDetail(objectType, details); + }, [details]); + if (!data || loading) { return ; } diff --git a/client/src/pages/Repository/components/DetailsView/DetailsTab/AssetVersionDetails.tsx b/client/src/pages/Repository/components/DetailsView/DetailsTab/AssetVersionDetails.tsx index 489bfa022..702cbeeb6 100644 --- a/client/src/pages/Repository/components/DetailsView/DetailsTab/AssetVersionDetails.tsx +++ b/client/src/pages/Repository/components/DetailsView/DetailsTab/AssetVersionDetails.tsx @@ -1,3 +1,4 @@ +/* eslint-disable react-hooks/exhaustive-deps */ /** * AssetVersionDetails * @@ -6,8 +7,9 @@ import { Box, makeStyles, Typography } from '@material-ui/core'; import React, { useEffect, useState } from 'react'; import { CheckboxField, FieldType, Loader } from '../../../../../components'; -import { AssetVersionDetailFields, GetDetailsTabDataForObjectQueryResult } from '../../../../../types/graphql'; +import { AssetVersionDetailFields } from '../../../../../types/graphql'; import { formatBytes } from '../../../../../utils/upload'; +import { DetailComponentProps } from './index'; export const useStyles = makeStyles(({ palette }) => ({ value: { @@ -16,13 +18,10 @@ export const useStyles = makeStyles(({ palette }) => ({ } })); -interface AssetVersionDetailsProps extends GetDetailsTabDataForObjectQueryResult { - disabled: boolean; -} - -function AssetVersionDetails(props: AssetVersionDetailsProps): React.ReactElement { +function AssetVersionDetails(props: DetailComponentProps): React.ReactElement { const classes = useStyles(); - const { data, loading } = props; + const { data, loading, onUpdateDetail, objectType } = props; + const [details, setDetails] = useState({}); useEffect(() => { @@ -38,6 +37,10 @@ function AssetVersionDetails(props: AssetVersionDetailsProps): React.ReactElemen } }, [data, loading]); + useEffect(() => { + onUpdateDetail(objectType, details); + }, [details]); + if (!data || loading) { return ; } diff --git a/client/src/pages/Repository/components/DetailsView/DetailsTab/CaptureDataDetails.tsx b/client/src/pages/Repository/components/DetailsView/DetailsTab/CaptureDataDetails.tsx index f4cfdcaea..59d5186eb 100644 --- a/client/src/pages/Repository/components/DetailsView/DetailsTab/CaptureDataDetails.tsx +++ b/client/src/pages/Repository/components/DetailsView/DetailsTab/CaptureDataDetails.tsx @@ -1,3 +1,4 @@ +/* eslint-disable react-hooks/exhaustive-deps */ /** * CaptureDataDetails * @@ -7,23 +8,26 @@ import { Box } from '@material-ui/core'; import React, { useEffect, useState } from 'react'; import { CheckboxField, DateInputField, FieldType, InputField, Loader, SelectField } from '../../../../../components'; import { parseFoldersToState, useVocabularyStore } from '../../../../../store'; -import { CaptureDataDetailFields, GetDetailsTabDataForObjectQueryResult } from '../../../../../types/graphql'; +import { CaptureDataDetailFields } from '../../../../../types/graphql'; import { eVocabularySetID } from '../../../../../types/server'; import { withDefaultValueNumber } from '../../../../../utils/shared'; import AssetContents from '../../../../Ingestion/components/Metadata/Photogrammetry/AssetContents'; import Description from '../../../../Ingestion/components/Metadata/Photogrammetry/Description'; +import { DetailComponentProps } from './index'; -interface CaptureDataDetailsProps extends GetDetailsTabDataForObjectQueryResult { - disabled: boolean; -} +function CaptureDataDetails(props: DetailComponentProps): React.ReactElement { + const { data, loading, disabled, onUpdateDetail, objectType } = props; -function CaptureDataDetails(props: CaptureDataDetailsProps): React.ReactElement { - const { data, loading, disabled } = props; const [details, setDetails] = useState({ folders: [] }); + const [getInitialEntry, getEntries] = useVocabularyStore(state => [state.getInitialEntry, state.getEntries]); + useEffect(() => { + onUpdateDetail(objectType, details); + }, [details]); + useEffect(() => { if (data && !loading) { const { CaptureData } = data.getDetailsTabDataForObject; diff --git a/client/src/pages/Repository/components/DetailsView/DetailsTab/IntermediaryFileDetails.tsx b/client/src/pages/Repository/components/DetailsView/DetailsTab/IntermediaryFileDetails.tsx index d27f91e24..63076665f 100644 --- a/client/src/pages/Repository/components/DetailsView/DetailsTab/IntermediaryFileDetails.tsx +++ b/client/src/pages/Repository/components/DetailsView/DetailsTab/IntermediaryFileDetails.tsx @@ -5,13 +5,9 @@ */ import React from 'react'; import { Loader } from '../../../../../components'; -import { GetDetailsTabDataForObjectQueryResult } from '../../../../../types/graphql'; +import { DetailComponentProps } from './index'; -interface IntermediaryFileDetailsProps extends GetDetailsTabDataForObjectQueryResult { - disabled: boolean; -} - -function IntermediaryFileDetails(props: IntermediaryFileDetailsProps): React.ReactElement { +function IntermediaryFileDetails(props: DetailComponentProps): React.ReactElement { const { data, loading } = props; if (!data || loading) { diff --git a/client/src/pages/Repository/components/DetailsView/DetailsTab/ItemDetails.tsx b/client/src/pages/Repository/components/DetailsView/DetailsTab/ItemDetails.tsx index 5c2f1b613..c88f9b54c 100644 --- a/client/src/pages/Repository/components/DetailsView/DetailsTab/ItemDetails.tsx +++ b/client/src/pages/Repository/components/DetailsView/DetailsTab/ItemDetails.tsx @@ -1,3 +1,4 @@ +/* eslint-disable react-hooks/exhaustive-deps */ /** * ItemDetails * @@ -6,22 +7,23 @@ import { Box } from '@material-ui/core'; import React, { useEffect, useState } from 'react'; import { CheckboxField, Loader } from '../../../../../components'; -import { GetDetailsTabDataForObjectQueryResult, SubjectDetailFields } from '../../../../../types/graphql'; +import { SubjectDetailFields } from '../../../../../types/graphql'; +import { DetailComponentProps } from './index'; import { SubjectFields } from './SubjectDetails'; -interface ItemDetailsProps extends GetDetailsTabDataForObjectQueryResult { - disabled: boolean; -} - interface ItemDetailFields extends SubjectDetailFields { EntireSubject?: boolean | null | undefined; } -function ItemDetails(props: ItemDetailsProps): React.ReactElement { - const { data, loading, disabled, } = props; +function ItemDetails(props: DetailComponentProps): React.ReactElement { + const { data, loading, disabled, onUpdateDetail, objectType } = props; const [details, setDetails] = useState({}); + useEffect(() => { + onUpdateDetail(objectType, details); + }, [details]); + useEffect(() => { if (data && !loading) { const { Item } = data.getDetailsTabDataForObject; diff --git a/client/src/pages/Repository/components/DetailsView/DetailsTab/ModelDetails.tsx b/client/src/pages/Repository/components/DetailsView/DetailsTab/ModelDetails.tsx index 4927a4033..c92aa5796 100644 --- a/client/src/pages/Repository/components/DetailsView/DetailsTab/ModelDetails.tsx +++ b/client/src/pages/Repository/components/DetailsView/DetailsTab/ModelDetails.tsx @@ -1,3 +1,4 @@ +/* eslint-disable react-hooks/exhaustive-deps */ /** * ModelDetails * @@ -7,12 +8,13 @@ import { Box, makeStyles, Typography } from '@material-ui/core'; import React, { useEffect, useState } from 'react'; import { CheckboxField, DateInputField, FieldType, InputField, Loader, SelectField } from '../../../../../components'; import { parseUVMapsToState, useVocabularyStore } from '../../../../../store'; -import { GetDetailsTabDataForObjectQueryResult, ModelDetailFields } from '../../../../../types/graphql'; +import { ModelDetailFields } from '../../../../../types/graphql'; import { eVocabularySetID } from '../../../../../types/server'; import { withDefaultValueNumber } from '../../../../../utils/shared'; import { formatBytes } from '../../../../../utils/upload'; import BoundingBoxInput from '../../../../Ingestion/components/Metadata/Model/BoundingBoxInput'; import UVContents from '../../../../Ingestion/components/Metadata/Model/UVContents'; +import { DetailComponentProps } from './index'; export const useStyles = makeStyles(({ palette }) => ({ value: { @@ -21,19 +23,19 @@ export const useStyles = makeStyles(({ palette }) => ({ } })); -interface ModelDetailsProps extends GetDetailsTabDataForObjectQueryResult { - disabled: boolean; -} - -function ModelDetails(props: ModelDetailsProps): React.ReactElement { +function ModelDetails(props: DetailComponentProps): React.ReactElement { const classes = useStyles(); - const { data, loading, disabled } = props; + const { data, loading, disabled, onUpdateDetail, objectType } = props; const [details, setDetails] = useState({ uvMaps: [] }); const [getInitialEntry, getEntries] = useVocabularyStore(state => [state.getInitialEntry, state.getEntries]); + useEffect(() => { + onUpdateDetail(objectType, details); + }, [details]); + useEffect(() => { if (data && !loading) { const { Model } = data.getDetailsTabDataForObject; @@ -66,8 +68,6 @@ function ModelDetails(props: ModelDetailsProps): React.ReactElement { } }, [data, loading]); - console.log(details); - if (!data || loading) { return ; } diff --git a/client/src/pages/Repository/components/DetailsView/DetailsTab/ProjectDetails.tsx b/client/src/pages/Repository/components/DetailsView/DetailsTab/ProjectDetails.tsx index a1871497f..2587a7f7d 100644 --- a/client/src/pages/Repository/components/DetailsView/DetailsTab/ProjectDetails.tsx +++ b/client/src/pages/Repository/components/DetailsView/DetailsTab/ProjectDetails.tsx @@ -1,3 +1,4 @@ +/* eslint-disable react-hooks/exhaustive-deps */ /** * ProjectDetails * @@ -5,17 +6,19 @@ */ import React, { useEffect, useState } from 'react'; import { Loader } from '../../../../../components'; -import { ProjectDetailFields, GetDetailsTabDataForObjectQueryResult } from '../../../../../types/graphql'; +import { ProjectDetailFields } from '../../../../../types/graphql'; import Description from '../../../../Ingestion/components/Metadata/Photogrammetry/Description'; +import { DetailComponentProps } from './index'; -interface ProjectDetailsProps extends GetDetailsTabDataForObjectQueryResult { - disabled: boolean; -} +function ProjectDetails(props: DetailComponentProps): React.ReactElement { + const { data, loading, disabled, onUpdateDetail, objectType } = props; -function ProjectDetails(props: ProjectDetailsProps): React.ReactElement { - const { data, loading } = props; const [details, setDetails] = useState({}); + useEffect(() => { + onUpdateDetail(objectType, details); + }, [details]); + useEffect(() => { if (data && !loading) { const { Project } = data.getDetailsTabDataForObject; @@ -34,7 +37,7 @@ function ProjectDetails(props: ProjectDetailsProps): React.ReactElement { setDetails(details => ({ ...details, Description: value })); }; - return ; + return ; } export default ProjectDetails; \ No newline at end of file diff --git a/client/src/pages/Repository/components/DetailsView/DetailsTab/ProjectDocumentationDetails.tsx b/client/src/pages/Repository/components/DetailsView/DetailsTab/ProjectDocumentationDetails.tsx index 1a16cd75c..be275acb8 100644 --- a/client/src/pages/Repository/components/DetailsView/DetailsTab/ProjectDocumentationDetails.tsx +++ b/client/src/pages/Repository/components/DetailsView/DetailsTab/ProjectDocumentationDetails.tsx @@ -1,3 +1,4 @@ +/* eslint-disable react-hooks/exhaustive-deps */ /** * ProjectDocumentationDetails * @@ -5,17 +6,18 @@ */ import React, { useEffect, useState } from 'react'; import { Loader } from '../../../../../components'; -import { ProjectDocumentationDetailFields, GetDetailsTabDataForObjectQueryResult } from '../../../../../types/graphql'; +import { ProjectDocumentationDetailFields } from '../../../../../types/graphql'; import Description from '../../../../Ingestion/components/Metadata/Photogrammetry/Description'; +import { DetailComponentProps } from './index'; -interface ProjectDocumentationDetailsProps extends GetDetailsTabDataForObjectQueryResult { - disabled: boolean; -} - -function ProjectDocumentationDetails(props: ProjectDocumentationDetailsProps): React.ReactElement { - const { data, loading } = props; +function ProjectDocumentationDetails(props: DetailComponentProps): React.ReactElement { + const { data, loading, disabled, onUpdateDetail, objectType } = props; const [details, setDetails] = useState({}); + useEffect(() => { + onUpdateDetail(objectType, details); + }, [details]); + useEffect(() => { if (data && !loading) { const { ProjectDocumentation } = data.getDetailsTabDataForObject; @@ -34,7 +36,7 @@ function ProjectDocumentationDetails(props: ProjectDocumentationDetailsProps): R setDetails(details => ({ ...details, Description: value })); }; - return ; + return ; } export default ProjectDocumentationDetails; \ No newline at end of file diff --git a/client/src/pages/Repository/components/DetailsView/DetailsTab/SceneDetails.tsx b/client/src/pages/Repository/components/DetailsView/DetailsTab/SceneDetails.tsx index 73ede6383..39bd0d794 100644 --- a/client/src/pages/Repository/components/DetailsView/DetailsTab/SceneDetails.tsx +++ b/client/src/pages/Repository/components/DetailsView/DetailsTab/SceneDetails.tsx @@ -5,13 +5,10 @@ */ import React from 'react'; import { Loader } from '../../../../../components'; -import { GetDetailsTabDataForObjectQueryResult } from '../../../../../types/graphql'; +import { DetailComponentProps } from './index'; -interface SceneDetailsProps extends GetDetailsTabDataForObjectQueryResult { - disabled: boolean; -} // TODO: KARAN: implement SceneDetails -function SceneDetails(props: SceneDetailsProps): React.ReactElement { +function SceneDetails(props: DetailComponentProps): React.ReactElement { const { data, loading } = props; if (!data || loading) { diff --git a/client/src/pages/Repository/components/DetailsView/DetailsTab/StakeholderDetails.tsx b/client/src/pages/Repository/components/DetailsView/DetailsTab/StakeholderDetails.tsx index 08c95d7ff..cab72887a 100644 --- a/client/src/pages/Repository/components/DetailsView/DetailsTab/StakeholderDetails.tsx +++ b/client/src/pages/Repository/components/DetailsView/DetailsTab/StakeholderDetails.tsx @@ -1,3 +1,4 @@ +/* eslint-disable react-hooks/exhaustive-deps */ /** * StakeholderDetails * @@ -6,17 +7,18 @@ import { Box } from '@material-ui/core'; import React, { useEffect, useState } from 'react'; import { InputField, Loader } from '../../../../../components'; -import { StakeholderDetailFields, GetDetailsTabDataForObjectQueryResult } from '../../../../../types/graphql'; +import { StakeholderDetailFields } from '../../../../../types/graphql'; +import { DetailComponentProps } from './index'; -interface StakeholderDetailsProps extends GetDetailsTabDataForObjectQueryResult { - disabled: boolean; -} - -function StakeholderDetails(props: StakeholderDetailsProps): React.ReactElement { - const { data, loading, disabled, } = props; +function StakeholderDetails(props: DetailComponentProps): React.ReactElement { + const { data, loading, disabled, onUpdateDetail, objectType } = props; const [details, setDetails] = useState({}); + useEffect(() => { + onUpdateDetail(objectType, details); + }, [details]); + useEffect(() => { if (data && !loading) { const { Stakeholder } = data.getDetailsTabDataForObject; diff --git a/client/src/pages/Repository/components/DetailsView/DetailsTab/SubjectDetails.tsx b/client/src/pages/Repository/components/DetailsView/DetailsTab/SubjectDetails.tsx index 08cbb6d8d..26e1784c9 100644 --- a/client/src/pages/Repository/components/DetailsView/DetailsTab/SubjectDetails.tsx +++ b/client/src/pages/Repository/components/DetailsView/DetailsTab/SubjectDetails.tsx @@ -1,3 +1,4 @@ +/* eslint-disable react-hooks/exhaustive-deps */ /** * SubjectDetails * @@ -6,17 +7,18 @@ import { Box } from '@material-ui/core'; import React, { useEffect, useState } from 'react'; import { DebounceNumberInput, FieldType, InputField, Loader } from '../../../../../components'; -import { GetDetailsTabDataForObjectQueryResult, SubjectDetailFields } from '../../../../../types/graphql'; +import { SubjectDetailFields } from '../../../../../types/graphql'; +import { DetailComponentProps } from './index'; -interface SubjectDetailsProps extends GetDetailsTabDataForObjectQueryResult { - disabled: boolean; -} - -function SubjectDetails(props: SubjectDetailsProps): React.ReactElement { - const { data, loading, disabled, } = props; +function SubjectDetails(props: DetailComponentProps): React.ReactElement { + const { data, loading, disabled, onUpdateDetail, objectType } = props; const [details, setDetails] = useState({}); + useEffect(() => { + onUpdateDetail(objectType, details); + }, [details]); + useEffect(() => { if (data && !loading) { const { Subject } = data.getDetailsTabDataForObject; diff --git a/client/src/pages/Repository/components/DetailsView/DetailsTab/UnitDetails.tsx b/client/src/pages/Repository/components/DetailsView/DetailsTab/UnitDetails.tsx index c9183f400..51917cfdc 100644 --- a/client/src/pages/Repository/components/DetailsView/DetailsTab/UnitDetails.tsx +++ b/client/src/pages/Repository/components/DetailsView/DetailsTab/UnitDetails.tsx @@ -1,3 +1,4 @@ +/* eslint-disable react-hooks/exhaustive-deps */ /** * UnitDetails * @@ -6,16 +7,17 @@ import { Box } from '@material-ui/core'; import React, { useEffect, useState } from 'react'; import { InputField, Loader } from '../../../../../components'; -import { GetDetailsTabDataForObjectQueryResult, UnitDetailFields } from '../../../../../types/graphql'; +import { UnitDetailFields } from '../../../../../types/graphql'; +import { DetailComponentProps } from './index'; -interface UnitDetailsProps extends GetDetailsTabDataForObjectQueryResult { - disabled: boolean; -} - -function UnitDetails(props: UnitDetailsProps): React.ReactElement { - const { data, loading, disabled } = props; +function UnitDetails(props: DetailComponentProps): React.ReactElement { + const { data, loading, disabled, onUpdateDetail, objectType } = props; const [details, setDetails] = useState({}); + useEffect(() => { + onUpdateDetail(objectType, details); + }, [details]); + useEffect(() => { if (data && !loading) { const { Unit } = data.getDetailsTabDataForObject; diff --git a/client/src/pages/Repository/components/DetailsView/DetailsTab/index.tsx b/client/src/pages/Repository/components/DetailsView/DetailsTab/index.tsx index 789042953..f631f8614 100644 --- a/client/src/pages/Repository/components/DetailsView/DetailsTab/index.tsx +++ b/client/src/pages/Repository/components/DetailsView/DetailsTab/index.tsx @@ -8,7 +8,22 @@ import { Box, Tab, TabProps, Tabs } from '@material-ui/core'; import { fade, makeStyles, withStyles } from '@material-ui/core/styles'; import React, { useState } from 'react'; import { StateRelatedObject } from '../../../../../store'; -import { RelatedObjectType } from '../../../../../types/graphql'; +import { + ActorDetailFieldsInput, + AssetDetailFieldsInput, + RelatedObjectType, + ProjectDetailFieldsInput, + SubjectDetailFieldsInput, + ItemDetailFieldsInput, + CaptureDataDetailFieldsInput, + ModelDetailFieldsInput, + SceneDetailFieldsInput, + ProjectDocumentationDetailFieldsInput, + AssetVersionDetailFieldsInput, + StakeholderDetailFieldsInput, + GetDetailsTabDataForObjectQueryResult, + UnitDetailFieldsInput, +} from '../../../../../types/graphql'; import { eSystemObjectType } from '../../../../../types/server'; import RelatedObjectsList from '../../../../Ingestion/components/Metadata/Model/RelatedObjectsList'; import { useDetailsTabData } from '../../../hooks/useDetailsView'; @@ -37,6 +52,14 @@ const useStyles = makeStyles(({ palette }) => ({ } })); +export interface DetailComponentProps extends GetDetailsTabDataForObjectQueryResult { + disabled: boolean; + objectType: number; + onUpdateDetail: (objectType: number, data: UpdateDataFields) => void; +} + +export type UpdateDataFields = UnitDetailFieldsInput | ProjectDetailFieldsInput | SubjectDetailFieldsInput | ItemDetailFieldsInput | CaptureDataDetailFieldsInput | ModelDetailFieldsInput | SceneDetailFieldsInput | ProjectDocumentationDetailFieldsInput | AssetDetailFieldsInput | AssetVersionDetailFieldsInput | ActorDetailFieldsInput | StakeholderDetailFieldsInput; + type DetailsTabParams = { disabled: boolean; idSystemObject: number; @@ -45,10 +68,11 @@ type DetailsTabParams = { derivedObjects: StateRelatedObject[]; onAddSourceObject: () => void; onAddDerivedObject: () => void; + onUpdateDetail: (objectType: number, data: UpdateDataFields) => void; }; function DetailsTab(props: DetailsTabParams): React.ReactElement { - const { disabled, idSystemObject, objectType, sourceObjects, derivedObjects, onAddSourceObject, onAddDerivedObject } = props; + const { disabled, idSystemObject, objectType, sourceObjects, derivedObjects, onAddSourceObject, onAddDerivedObject, onUpdateDetail } = props; const [tab, setTab] = useState(0); const classes = useStyles(); @@ -87,7 +111,7 @@ function DetailsTab(props: DetailsTabParams): React.ReactElement { tabPanels = ( - + {RelatedTab(1)} @@ -98,7 +122,7 @@ function DetailsTab(props: DetailsTabParams): React.ReactElement { tabPanels = ( - + {RelatedTab(1)} @@ -112,7 +136,7 @@ function DetailsTab(props: DetailsTabParams): React.ReactElement { - + {RelatedTab(2)} @@ -126,7 +150,7 @@ function DetailsTab(props: DetailsTabParams): React.ReactElement { - + {RelatedTab(2)} @@ -140,7 +164,7 @@ function DetailsTab(props: DetailsTabParams): React.ReactElement { - + {RelatedTab(2)} @@ -154,7 +178,7 @@ function DetailsTab(props: DetailsTabParams): React.ReactElement { - + {RelatedTab(2)} @@ -168,7 +192,7 @@ function DetailsTab(props: DetailsTabParams): React.ReactElement { - + {RelatedTab(2)} @@ -182,7 +206,7 @@ function DetailsTab(props: DetailsTabParams): React.ReactElement { - + {RelatedTab(2)} @@ -196,7 +220,7 @@ function DetailsTab(props: DetailsTabParams): React.ReactElement { - + {RelatedTab(2)} @@ -210,7 +234,7 @@ function DetailsTab(props: DetailsTabParams): React.ReactElement { - + {RelatedTab(2)} @@ -221,7 +245,7 @@ function DetailsTab(props: DetailsTabParams): React.ReactElement { tabPanels = ( - + {RelatedTab(1)} @@ -232,7 +256,7 @@ function DetailsTab(props: DetailsTabParams): React.ReactElement { tabPanels = ( - + {RelatedTab(1)} @@ -243,7 +267,7 @@ function DetailsTab(props: DetailsTabParams): React.ReactElement { tabPanels = ( - + {RelatedTab(1)} diff --git a/client/src/pages/Repository/hooks/useDetailsView.ts b/client/src/pages/Repository/hooks/useDetailsView.ts index 20f84d721..5c8bb3e42 100644 --- a/client/src/pages/Repository/hooks/useDetailsView.ts +++ b/client/src/pages/Repository/hooks/useDetailsView.ts @@ -1,4 +1,5 @@ -import { useMutation, useQuery, MutationTuple } from '@apollo/client'; +import { useQuery, FetchResult } from '@apollo/client'; +import { apolloClient } from '../../../graphql'; import { GetAssetDetailsForSystemObjectDocument, GetAssetDetailsForSystemObjectQueryResult, @@ -9,7 +10,6 @@ import { GetDetailsTabDataForObjectDocument, GetDetailsTabDataForObjectQueryResult, UpdateObjectDetailsDocument, - UpdateObjectDetailsMutationVariables, UpdateObjectDetailsDataInput, UpdateObjectDetailsMutation } from '../../../types/graphql'; @@ -56,8 +56,9 @@ export function useDetailsTabData(idSystemObject: number, objectType: eSystemObj }); } -export function useDetailsTabDataUpdate(idSystemObject: number, objectType: eSystemObjectType, data: UpdateObjectDetailsDataInput): MutationTuple { - return useMutation(UpdateObjectDetailsDocument, { +export function updateDetailsTabData(idSystemObject: number, objectType: eSystemObjectType, data: UpdateObjectDetailsDataInput): Promise> { + return apolloClient.mutate({ + mutation: UpdateObjectDetailsDocument, variables: { input: { idSystemObject, diff --git a/server/graphql/schema/systemobject/resolvers/mutations/updateObjectDetails.ts b/server/graphql/schema/systemobject/resolvers/mutations/updateObjectDetails.ts index 5bbc65e31..af3808faa 100644 --- a/server/graphql/schema/systemobject/resolvers/mutations/updateObjectDetails.ts +++ b/server/graphql/schema/systemobject/resolvers/mutations/updateObjectDetails.ts @@ -8,7 +8,7 @@ export default async function updateObjectDetails(_: Parent, args: MutationUpdat const { objectType, data } = input; // TODO: KARAN: update {data} for {idSystemObject} - LOG.logger.info(data); + LOG.logger.info(JSON.stringify(data, null, 2)); switch (objectType) { case eSystemObjectType.eUnit: From 15e5d2221efdb20783bd18f0bfdd2c36e87c4967 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Wed, 6 Jan 2021 17:35:49 +0530 Subject: [PATCH 166/239] highlight fields on update --- .../src/components/controls/CheckboxField.tsx | 5 +- .../components/controls/DateInputField.tsx | 8 +- .../controls/DebounceNumberInput.tsx | 8 +- client/src/components/controls/InputField.tsx | 8 +- .../src/components/controls/SelectField.tsx | 8 +- .../Metadata/Model/BoundingBoxInput.tsx | 62 ++++++++-- .../Metadata/Photogrammetry/Description.tsx | 8 +- .../components/SubjectItem/SearchList.tsx | 1 - .../DetailsView/DetailsTab/ActorDetails.tsx | 4 + .../DetailsView/DetailsTab/AssetDetails.tsx | 5 + .../DetailsTab/AssetVersionDetails.tsx | 9 +- .../DetailsTab/CaptureDataDetails.tsx | 33 ++++- .../DetailsView/DetailsTab/ItemDetails.tsx | 8 +- .../DetailsView/DetailsTab/ModelDetails.tsx | 32 ++++- .../DetailsView/DetailsTab/ProjectDetails.tsx | 13 +- .../ProjectDocumentationDetails.tsx | 13 +- .../DetailsTab/StakeholderDetails.tsx | 8 ++ .../DetailsView/DetailsTab/SubjectDetails.tsx | 80 ++++++++---- .../DetailsView/DetailsTab/UnitDetails.tsx | 5 + .../components/DetailsView/index.tsx | 117 +++++++++++++++++- client/src/utils/repository.tsx | 5 + 21 files changed, 376 insertions(+), 64 deletions(-) diff --git a/client/src/components/controls/CheckboxField.tsx b/client/src/components/controls/CheckboxField.tsx index 17d6d9ecd..2cc76aed4 100644 --- a/client/src/components/controls/CheckboxField.tsx +++ b/client/src/components/controls/CheckboxField.tsx @@ -16,10 +16,11 @@ interface CheckboxFieldProps { required?: boolean; viewMode?: boolean; disabled?: boolean; + updated?: boolean; } function CheckboxField(props: CheckboxFieldProps): React.ReactElement { - const { label, name, value, onChange, required = false, viewMode = false, disabled = false } = props; + const { label, name, value, onChange, required = false, viewMode = false, disabled = false, updated } = props; const rowFieldProps = { alignItems: 'center', justifyContent: 'space-between', style: { borderRadius: 0 } }; return ( @@ -34,7 +35,7 @@ function CheckboxField(props: CheckboxFieldProps): React.ReactElement { name={name} disabled={disabled} checked={withDefaultValueBoolean(value, false)} - color='primary' + color={updated ? 'secondary' : 'primary'} onChange={onChange} /> diff --git a/client/src/components/controls/DateInputField.tsx b/client/src/components/controls/DateInputField.tsx index 9ea69ab5b..22d465b56 100644 --- a/client/src/components/controls/DateInputField.tsx +++ b/client/src/components/controls/DateInputField.tsx @@ -15,7 +15,8 @@ const useStyles = makeStyles(({ palette, typography, breakpoints }) => ({ date: { width: '50%', background: palette.background.paper, - border: `1px solid ${fade(palette.primary.contrastText, 0.4)}`, + border: (updated: boolean) => `1px solid ${fade(updated ? palette.secondary.main : palette.primary.contrastText, 0.4)}`, + backgroundColor: (updated: boolean) => updated ? palette.secondary.light : palette.background.paper, padding: '1px 8px', color: Colors.defaults.white, marginTop: 0, @@ -34,11 +35,12 @@ interface DateInputFieldProps { value: Date; onChange: (date: MaterialUiPickersDate, value?: string | null | undefined) => void; disabled?: boolean; + updated?: boolean; } function DateInputField(props: DateInputFieldProps): React.ReactElement { - const { value, onChange, disabled = false } = props; - const classes = useStyles(); + const { value, onChange, disabled = false, updated = false } = props; + const classes = useStyles(updated); return ( diff --git a/client/src/components/controls/DebounceNumberInput.tsx b/client/src/components/controls/DebounceNumberInput.tsx index c7aea2b51..d13211f0c 100644 --- a/client/src/components/controls/DebounceNumberInput.tsx +++ b/client/src/components/controls/DebounceNumberInput.tsx @@ -11,7 +11,8 @@ const useStyles = makeStyles(({ palette, typography, breakpoints }) => ({ input: { width: '16%', outline: 'none', - border: `1px solid ${fade(palette.primary.contrastText, 0.4)}`, + border: (updated: boolean) => `1px solid ${fade(updated ? palette.secondary.main : palette.primary.contrastText, 0.4)}`, + backgroundColor: (updated: boolean) => updated ? palette.secondary.light : palette.background.paper, padding: 8, borderRadius: 5, marginLeft: 5, @@ -29,12 +30,13 @@ interface DebounceNumberInputProps { value?: number | null; name: string; disabled?: boolean; + updated?: boolean; onChange: (event: React.ChangeEvent) => void; } function DebounceNumberInput(props: DebounceNumberInputProps): React.ReactElement { - const { value, name, onChange, disabled = false } = props; - const classes = useStyles(); + const { value, name, onChange, disabled = false, updated = false } = props; + const classes = useStyles(updated); return ( ({ input: { width: '50%', outline: 'none', - border: `1px solid ${fade(palette.primary.contrastText, 0.4)}`, + border: (updated: boolean) => `1px solid ${fade(updated ? palette.secondary.main : palette.primary.contrastText, 0.4)}`, + backgroundColor: (updated: boolean) => updated ? palette.secondary.light : palette.background.paper, padding: 8, borderRadius: 5, fontWeight: typography.fontWeightRegular, @@ -35,11 +36,12 @@ interface InputFieldProps { required?: boolean; viewMode?: boolean; disabled?: boolean; + updated?: boolean; } function InputField(props: InputFieldProps): React.ReactElement { - const { label, name, value, onChange, type, required = false, viewMode = false, disabled = false } = props; - const classes = useStyles(); + const { label, name, value, onChange, type, required = false, viewMode = false, disabled = false, updated = false } = props; + const classes = useStyles(updated); const rowFieldProps = { alignItems: 'center', justifyContent: 'space-between', style: { borderRadius: 0 } }; diff --git a/client/src/components/controls/SelectField.tsx b/client/src/components/controls/SelectField.tsx index 3ae5d14d7..1d6dcb192 100644 --- a/client/src/components/controls/SelectField.tsx +++ b/client/src/components/controls/SelectField.tsx @@ -14,7 +14,8 @@ const useStyles = makeStyles(({ palette, typography, breakpoints }) => ({ width: '54%', padding: '0px 10px', background: palette.background.paper, - border: `1px solid ${fade(palette.primary.contrastText, 0.4)}`, + border: (updated: boolean) => `1px solid ${fade(updated ? palette.secondary.main : palette.primary.contrastText, 0.4)}`, + backgroundColor: (updated: boolean) => updated ? palette.secondary.light : palette.background.paper, borderRadius: 5, fontFamily: typography.fontFamily, [breakpoints.down('lg')]: { @@ -36,12 +37,13 @@ interface SelectFieldProps { onChange: (event: React.ChangeEvent) => void; viewMode?: boolean; disabled?: boolean; + updated?: boolean; } function SelectField(props: SelectFieldProps): React.ReactElement { - const { label, value, name, width, required, error, options, onChange, viewMode = false, disabled = false } = props; + const { label, value, name, width, required, error, options, onChange, viewMode = false, disabled = false, updated = false } = props; - const classes = useStyles(); + const classes = useStyles(updated); const rowFieldProps = { alignItems: 'center', justifyContent: 'space-between', style: { borderRadius: 0 } }; diff --git a/client/src/pages/Ingestion/components/Metadata/Model/BoundingBoxInput.tsx b/client/src/pages/Ingestion/components/Metadata/Model/BoundingBoxInput.tsx index 96eb1010a..ffea3ac84 100644 --- a/client/src/pages/Ingestion/components/Metadata/Model/BoundingBoxInput.tsx +++ b/client/src/pages/Ingestion/components/Metadata/Model/BoundingBoxInput.tsx @@ -7,8 +7,11 @@ import { Box } from '@material-ui/core'; import React from 'react'; import { DebounceNumberInput, FieldType } from '../../../../../components'; +import { ModelDetailFields } from '../../../../../types/graphql'; +import { isFieldUpdated } from '../../../../../utils/repository'; interface BoundingBoxInputProps { + modelFields?: ModelDetailFields | null; boundingBoxP1X?: number | null; boundingBoxP1Y?: number | null; boundingBoxP1Z?: number | null; @@ -21,10 +24,19 @@ interface BoundingBoxInputProps { } function BoundingBoxInput(props: BoundingBoxInputProps): React.ReactElement { - const { boundingBoxP1X, boundingBoxP1Y, boundingBoxP1Z, boundingBoxP2X, boundingBoxP2Y, boundingBoxP2Z, onChange, viewMode = false, disabled = false } = props; + const { modelFields, boundingBoxP1X, boundingBoxP1Y, boundingBoxP1Z, boundingBoxP2X, boundingBoxP2Y, boundingBoxP2Z, onChange, viewMode = false, disabled = false } = props; const rowFieldProps = { justifyContent: 'space-between' }; + const details = { + boundingBoxP1X, + boundingBoxP1Y, + boundingBoxP1Z, + boundingBoxP2X, + boundingBoxP2Y, + boundingBoxP2Z + }; + return ( - - - + + + - - - + + + diff --git a/client/src/pages/Ingestion/components/Metadata/Photogrammetry/Description.tsx b/client/src/pages/Ingestion/components/Metadata/Photogrammetry/Description.tsx index 68a98e2c6..062e5bb3b 100644 --- a/client/src/pages/Ingestion/components/Metadata/Photogrammetry/Description.tsx +++ b/client/src/pages/Ingestion/components/Metadata/Photogrammetry/Description.tsx @@ -15,7 +15,8 @@ const useStyles = makeStyles(({ palette, typography }) => ({ padding: 10, resize: 'none', overflow: 'scroll', - border: `1px solid ${fade(palette.primary.contrastText, 0.4)}`, + border: (updated: boolean) => `1px solid ${fade(updated ? palette.secondary.main : palette.primary.contrastText, 0.4)}`, + backgroundColor: (updated: boolean) => updated ? palette.secondary.light : palette.background.paper, borderRadius: 5, fontWeight: typography.fontWeightRegular, fontFamily: typography.fontFamily @@ -27,11 +28,12 @@ interface DescriptionProps { onChange: (event: React.ChangeEvent) => void; viewMode?: boolean; disabled?: boolean; + updated?: boolean; } function Description(props: DescriptionProps): React.ReactElement { - const { value, onChange, viewMode = false, disabled = false } = props; - const classes = useStyles(); + const { value, onChange, viewMode = false, disabled = false, updated = false } = props; + const classes = useStyles(updated); const rowFieldProps = { alignItems: 'center', justifyContent: 'space-between' }; diff --git a/client/src/pages/Ingestion/components/SubjectItem/SearchList.tsx b/client/src/pages/Ingestion/components/SubjectItem/SearchList.tsx index bb36612d5..6fb0b5089 100644 --- a/client/src/pages/Ingestion/components/SubjectItem/SearchList.tsx +++ b/client/src/pages/Ingestion/components/SubjectItem/SearchList.tsx @@ -34,7 +34,6 @@ const useStyles = makeStyles(({ palette, breakpoints }) => ({ [breakpoints.down('lg')]: { height: 30 } - } })); diff --git a/client/src/pages/Repository/components/DetailsView/DetailsTab/ActorDetails.tsx b/client/src/pages/Repository/components/DetailsView/DetailsTab/ActorDetails.tsx index 9cfc73b98..c739b3543 100644 --- a/client/src/pages/Repository/components/DetailsView/DetailsTab/ActorDetails.tsx +++ b/client/src/pages/Repository/components/DetailsView/DetailsTab/ActorDetails.tsx @@ -8,6 +8,7 @@ import { Box } from '@material-ui/core'; import React, { useEffect, useState } from 'react'; import { InputField, Loader } from '../../../../../components'; import { ActorDetailFields } from '../../../../../types/graphql'; +import { isFieldUpdated } from '../../../../../utils/repository'; import { DetailComponentProps } from './index'; function ActorDetails(props: DetailComponentProps): React.ReactElement { @@ -37,11 +38,14 @@ function ActorDetails(props: DetailComponentProps): React.ReactElement { setDetails(details => ({ ...details, [name]: value })); }; + const actorData = data.getDetailsTabDataForObject?.Actor; + return ( ({ ...details, [name]: idFieldValue })); }; + const assetData = data.getDetailsTabDataForObject?.Asset; + return ( ({ function AssetVersionDetails(props: DetailComponentProps): React.ReactElement { const classes = useStyles(); - const { data, loading, onUpdateDetail, objectType } = props; + const { data, loading, onUpdateDetail, objectType, disabled } = props; const [details, setDetails] = useState({}); @@ -52,6 +53,8 @@ function AssetVersionDetails(props: DetailComponentProps): React.ReactElement { const rowFieldProps = { alignItems: 'center', justifyContent: 'space-between', style: { borderRadius: 0 } }; + const assetVersionData = data.getDetailsTabDataForObject?.AssetVersion; + return ( diff --git a/client/src/pages/Repository/components/DetailsView/DetailsTab/CaptureDataDetails.tsx b/client/src/pages/Repository/components/DetailsView/DetailsTab/CaptureDataDetails.tsx index 59d5186eb..15cbb5924 100644 --- a/client/src/pages/Repository/components/DetailsView/DetailsTab/CaptureDataDetails.tsx +++ b/client/src/pages/Repository/components/DetailsView/DetailsTab/CaptureDataDetails.tsx @@ -10,6 +10,7 @@ import { CheckboxField, DateInputField, FieldType, InputField, Loader, SelectFie import { parseFoldersToState, useVocabularyStore } from '../../../../../store'; import { CaptureDataDetailFields } from '../../../../../types/graphql'; import { eVocabularySetID } from '../../../../../types/server'; +import { isFieldUpdated } from '../../../../../utils/repository'; import { withDefaultValueNumber } from '../../../../../utils/shared'; import AssetContents from '../../../../Ingestion/components/Metadata/Photogrammetry/AssetContents'; import Description from '../../../../Ingestion/components/Metadata/Photogrammetry/Description'; @@ -56,7 +57,7 @@ function CaptureDataDetails(props: DetailComponentProps): React.ReactElement { } const updateFolderVariant = () => { - alert('TODO: Karan Update Folder Variant'); + alert('TODO: KARAN: Update Folder Variant'); }; const onSetField = (event: React.ChangeEvent) => { @@ -89,12 +90,14 @@ function CaptureDataDetails(props: DetailComponentProps): React.ReactElement { }; const rowFieldProps = { alignItems: 'center', justifyContent: 'space-between', style: { borderRadius: 0 } }; + const captureDataData = data.getDetailsTabDataForObject?.CaptureData; return ( - + @@ -113,12 +122,18 @@ function CaptureDataDetails(props: DetailComponentProps): React.ReactElement { containerProps={rowFieldProps} width='auto' > - setDateField('dateCaptured', value)} /> + setDateField('dateCaptured', value)} + /> ({ ...details, [name]: checked })); }; + const itemData = data.getDetailsTabDataForObject?.Item; + return ( - + ); } diff --git a/client/src/pages/Repository/components/DetailsView/DetailsTab/ModelDetails.tsx b/client/src/pages/Repository/components/DetailsView/DetailsTab/ModelDetails.tsx index c92aa5796..9f57455fb 100644 --- a/client/src/pages/Repository/components/DetailsView/DetailsTab/ModelDetails.tsx +++ b/client/src/pages/Repository/components/DetailsView/DetailsTab/ModelDetails.tsx @@ -10,6 +10,7 @@ import { CheckboxField, DateInputField, FieldType, InputField, Loader, SelectFie import { parseUVMapsToState, useVocabularyStore } from '../../../../../store'; import { ModelDetailFields } from '../../../../../types/graphql'; import { eVocabularySetID } from '../../../../../types/server'; +import { isFieldUpdated } from '../../../../../utils/repository'; import { withDefaultValueNumber } from '../../../../../utils/shared'; import { formatBytes } from '../../../../../utils/upload'; import BoundingBoxInput from '../../../../Ingestion/components/Metadata/Model/BoundingBoxInput'; @@ -73,7 +74,7 @@ function ModelDetails(props: DetailComponentProps): React.ReactElement { } const updateUVMapsVariant = () => { - alert('TODO: Karan Update UV Maps'); + alert('TODO: KARAN: Update UV Maps'); }; const setDateField = (name: string, value?: string | null): void => { @@ -102,6 +103,8 @@ function ModelDetails(props: DetailComponentProps): React.ReactElement { const rowFieldProps = { alignItems: 'center', justifyContent: 'space-between', style: { borderRadius: 0 } }; + const modelData = data.getDetailsTabDataForObject?.Model; + return ( @@ -121,12 +124,18 @@ function ModelDetails(props: DetailComponentProps): React.ReactElement { width='auto' containerProps={rowFieldProps} > - setDateField('dateCaptured', value)} /> + setDateField('dateCaptured', value)} + /> ({ ...details, Description: value })); }; - return ; + const projectData = data.getDetailsTabDataForObject?.Project; + + return ( + + ); } export default ProjectDetails; \ No newline at end of file diff --git a/client/src/pages/Repository/components/DetailsView/DetailsTab/ProjectDocumentationDetails.tsx b/client/src/pages/Repository/components/DetailsView/DetailsTab/ProjectDocumentationDetails.tsx index be275acb8..558635dae 100644 --- a/client/src/pages/Repository/components/DetailsView/DetailsTab/ProjectDocumentationDetails.tsx +++ b/client/src/pages/Repository/components/DetailsView/DetailsTab/ProjectDocumentationDetails.tsx @@ -7,6 +7,7 @@ import React, { useEffect, useState } from 'react'; import { Loader } from '../../../../../components'; import { ProjectDocumentationDetailFields } from '../../../../../types/graphql'; +import { isFieldUpdated } from '../../../../../utils/repository'; import Description from '../../../../Ingestion/components/Metadata/Photogrammetry/Description'; import { DetailComponentProps } from './index'; @@ -36,7 +37,17 @@ function ProjectDocumentationDetails(props: DetailComponentProps): React.ReactEl setDetails(details => ({ ...details, Description: value })); }; - return ; + const projectDocumentationData = data.getDetailsTabDataForObject?.ProjectDocumentation; + + return ( + + ); } export default ProjectDocumentationDetails; \ No newline at end of file diff --git a/client/src/pages/Repository/components/DetailsView/DetailsTab/StakeholderDetails.tsx b/client/src/pages/Repository/components/DetailsView/DetailsTab/StakeholderDetails.tsx index cab72887a..540dade02 100644 --- a/client/src/pages/Repository/components/DetailsView/DetailsTab/StakeholderDetails.tsx +++ b/client/src/pages/Repository/components/DetailsView/DetailsTab/StakeholderDetails.tsx @@ -8,6 +8,7 @@ import { Box } from '@material-ui/core'; import React, { useEffect, useState } from 'react'; import { InputField, Loader } from '../../../../../components'; import { StakeholderDetailFields } from '../../../../../types/graphql'; +import { isFieldUpdated } from '../../../../../utils/repository'; import { DetailComponentProps } from './index'; function StakeholderDetails(props: DetailComponentProps): React.ReactElement { @@ -41,11 +42,14 @@ function StakeholderDetails(props: DetailComponentProps): React.ReactElement { setDetails(details => ({ ...details, [name]: value })); }; + const stakeholderData = data.getDetailsTabDataForObject?.Stakeholder; + return ( ({ ...details, [name]: value })); }; + const subjectData = data.getDetailsTabDataForObject?.Subject; + return ( - + ); } interface SubjectFieldsProps extends SubjectDetailFields { disabled: boolean; + originalFields?: SubjectDetailFields | ItemDetailFields | null; onChange: (event: React.ChangeEvent) => void; } export function SubjectFields(props: SubjectFieldsProps): React.ReactElement { const { + originalFields, Latitude, Longitude, Altitude, @@ -74,15 +79,29 @@ export function SubjectFields(props: SubjectFieldsProps): React.ReactElement { onChange } = props; + const details = { + Latitude, + Longitude, + Altitude, + TS0, + TS1, + TS2, + R0, + R1, + R2, + R3 + }; + return ( @@ -90,9 +109,10 @@ export function SubjectFields(props: SubjectFieldsProps): React.ReactElement { viewMode required type='number' + updated={isFieldUpdated(details, originalFields, 'Longitude')} disabled={disabled} label='Longitude' - value={Longitude} + value={details.Longitude} name='Longitude' onChange={onChange} /> @@ -100,23 +120,26 @@ export function SubjectFields(props: SubjectFieldsProps): React.ReactElement { viewMode required type='number' + updated={isFieldUpdated(details, originalFields, 'Altitude')} disabled={disabled} label='Altitude' - value={Altitude} + value={details.Altitude} name='Altitude' onChange={onChange} /> @@ -127,14 +150,21 @@ interface RotationOriginInputProps { TS0?: number | null; TS1?: number | null; TS2?: number | null; + originalFields?: SubjectDetailFields | ItemDetailFields | null; onChange: (event: React.ChangeEvent) => void; } function RotationOriginInput(props: RotationOriginInputProps): React.ReactElement { - const { TS0, TS1, TS2, onChange } = props; + const { TS0, TS1, TS2, onChange, originalFields } = props; const rowFieldProps = { justifyContent: 'space-between', style: { borderRadius: 0 } }; + const details = { + TS0, + TS1, + TS2 + }; + return ( - - - + + + @@ -159,14 +189,22 @@ interface RotationQuaternionInputProps { R1?: number | null; R2?: number | null; R3?: number | null; + originalFields?: SubjectDetailFields | ItemDetailFields | null; onChange: (event: React.ChangeEvent) => void; } function RotationQuaternionInput(props: RotationQuaternionInputProps): React.ReactElement { - const { R0, R1, R2, R3, onChange } = props; + const { R0, R1, R2, R3, onChange, originalFields } = props; const rowFieldProps = { justifyContent: 'space-between', style: { borderRadius: 0 } }; + const details = { + R0, + R1, + R2, + R3, + }; + return ( - - - - + + + + diff --git a/client/src/pages/Repository/components/DetailsView/DetailsTab/UnitDetails.tsx b/client/src/pages/Repository/components/DetailsView/DetailsTab/UnitDetails.tsx index 51917cfdc..0d059d6dc 100644 --- a/client/src/pages/Repository/components/DetailsView/DetailsTab/UnitDetails.tsx +++ b/client/src/pages/Repository/components/DetailsView/DetailsTab/UnitDetails.tsx @@ -8,6 +8,7 @@ import { Box } from '@material-ui/core'; import React, { useEffect, useState } from 'react'; import { InputField, Loader } from '../../../../../components'; import { UnitDetailFields } from '../../../../../types/graphql'; +import { isFieldUpdated } from '../../../../../utils/repository'; import { DetailComponentProps } from './index'; function UnitDetails(props: DetailComponentProps): React.ReactElement { @@ -37,11 +38,14 @@ function UnitDetails(props: DetailComponentProps): React.ReactElement { setDetails(details => ({ ...details, [name]: value })); }; + const unitData = data.getDetailsTabDataForObject?.Unit; + return ( ({ maxHeight: 'calc(100vh - 120px)', padding: 10 } + }, + updateButton: { + height: 35, + width: 100, + marginTop: 10, + color: palette.background.paper, + [breakpoints.down('lg')]: { + height: 30 + } } })); @@ -44,9 +70,11 @@ function DetailsView(): React.ReactElement { const classes = useStyles(); const params = useParams(); const [modalOpen, setModalOpen] = useState(false); + const [isUpdatingData, setIsUpdatingData] = useState(false); const idSystemObject: number = Number.parseInt(params.idSystemObject, 10); const { data, loading } = useObjectDetails(idSystemObject); + const [updatedData, setUpdatedData] = useState({}); const getEntries = useVocabularyStore(state => state.getEntries); @@ -86,6 +114,77 @@ function DetailsView(): React.ReactElement { setModalOpen(true); }; + const onUpdateDetail = (objectType: number, data: UpdateDataFields): void => { + const updatedDataFields: UpdateObjectDetailsDataInput = { ...updatedData }; + + switch (objectType) { + case eSystemObjectType.eUnit: + updatedDataFields.Unit = data as UnitDetailFieldsInput; + break; + case eSystemObjectType.eProject: + updatedDataFields.Project = data as ProjectDetailFieldsInput; + break; + case eSystemObjectType.eSubject: + updatedDataFields.Subject = data as SubjectDetailFieldsInput; + break; + case eSystemObjectType.eItem: + updatedDataFields.Item = data as ItemDetailFieldsInput; + break; + case eSystemObjectType.eCaptureData: + updatedDataFields.CaptureData = data as CaptureDataDetailFieldsInput; + break; + case eSystemObjectType.eModel: + updatedDataFields.Model = data as ModelDetailFieldsInput; + break; + case eSystemObjectType.eScene: + updatedDataFields.Scene = data as SceneDetailFieldsInput; + break; + case eSystemObjectType.eIntermediaryFile: + break; + case eSystemObjectType.eProjectDocumentation: + updatedDataFields.ProjectDocumentation = data as ProjectDocumentationDetailFieldsInput; + break; + case eSystemObjectType.eAsset: + updatedDataFields.Asset = data as AssetDetailFieldsInput; + break; + case eSystemObjectType.eAssetVersion: + updatedDataFields.AssetVersion = data as AssetVersionDetailFieldsInput; + break; + case eSystemObjectType.eActor: + updatedDataFields.Actor = data as ActorDetailFieldsInput; + break; + case eSystemObjectType.eStakeholder: + updatedDataFields.Stakeholder = data as StakeholderDetailFieldsInput; + break; + default: + break; + } + + setUpdatedData(updatedDataFields); + }; + + const updateData = async (): Promise => { + const confirmed: boolean = global.confirm('Are you sure you want to update data'); + if (!confirmed) return; + + setIsUpdatingData(true); + try { + const { data, errors } = await updateDetailsTabData(idSystemObject, objectType, updatedData); + + if (errors?.length) { + throw new Error(); + } + + if (data?.updateObjectDetails?.success) { + toast.success('Data saved successfully'); + } + } catch { + toast.error('Failed to save updated data'); + } finally { + setIsUpdatingData(false); + } + }; + return ( - + + + Update + + (value: T[], defaultValue: T[]): T[] { } return result; +} + +export function isFieldUpdated(updatedData: any, originalData: any, fieldName: string): boolean { + return originalData?.[fieldName] !== updatedData?.[fieldName]; } \ No newline at end of file From 62ffe344de6c143a1d15c2ae09cca9391629cfbc Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Wed, 6 Jan 2021 18:22:12 +0530 Subject: [PATCH 167/239] Update updateObjectDetails for name and retired status --- client/src/components/controls/InputField.tsx | 1 - .../components/DetailsView/DetailsHeader.tsx | 35 +++++++++---- .../components/DetailsView/ObjectDetails.tsx | 35 ++++++------- .../components/DetailsView/index.tsx | 50 +++++++++++++++---- .../pages/Repository/hooks/useDetailsView.ts | 3 +- client/src/types/graphql.tsx | 2 + client/src/utils/shared.ts | 2 +- server/graphql/schema.graphql | 2 + .../schema/systemobject/mutations.graphql | 2 + server/types/graphql.ts | 2 + 10 files changed, 93 insertions(+), 41 deletions(-) diff --git a/client/src/components/controls/InputField.tsx b/client/src/components/controls/InputField.tsx index bfbec785f..e34f73593 100644 --- a/client/src/components/controls/InputField.tsx +++ b/client/src/components/controls/InputField.tsx @@ -56,7 +56,6 @@ function InputField(props: InputFieldProps): React.ReactElement { ({ @@ -25,21 +25,26 @@ const useStyles = makeStyles(({ palette }) => ({ borderRadius: 5, marginRight: 20, color: palette.primary.dark, - backgroundColor: Colors.defaults.white, - border: `0.5px solid ${palette.primary.contrastText}`, + border: (updated: boolean) => `1px solid ${fade(updated ? palette.secondary.main : palette.primary.contrastText, 0.4)}`, + backgroundColor: (updated: boolean) => updated ? palette.secondary.light : palette.background.paper, fontSize: '0.8em' } })); interface DetailsHeaderProps { + originalFields: GetSystemObjectDetailsResult; objectType: eSystemObjectType; path: RepositoryPath[][]; - name: string; + name?: string | null; + disabled: boolean; + onNameUpdate: (event: React.ChangeEvent) => void; } function DetailsHeader(props: DetailsHeaderProps): React.ReactElement { - const { objectType, path, name } = props; - const classes = useStyles(); + const { objectType, path, name, onNameUpdate, disabled, originalFields } = props; + const updated: boolean = isFieldUpdated({ name }, originalFields, 'name'); + + const classes = useStyles(updated); const title = getHeaderTitle(`${name} ${getTermForSystemObjectType(objectType)}`); @@ -51,8 +56,16 @@ function DetailsHeader(props: DetailsHeaderProps): React.ReactElement { {getTermForSystemObjectType(objectType)} - - {name} + + {!!path.length && } diff --git a/client/src/pages/Repository/components/DetailsView/ObjectDetails.tsx b/client/src/pages/Repository/components/DetailsView/ObjectDetails.tsx index 650ed0c76..1a11c1cf4 100644 --- a/client/src/pages/Repository/components/DetailsView/ObjectDetails.tsx +++ b/client/src/pages/Repository/components/DetailsView/ObjectDetails.tsx @@ -3,14 +3,13 @@ * * This component renders object details for the Repository Details UI. */ -import { Box, Typography } from '@material-ui/core'; +import { Box, Checkbox, Typography } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; import React from 'react'; -import { MdCheckBox, MdCheckBoxOutlineBlank } from 'react-icons/md'; import { NewTabLink } from '../../../../components'; -import { palette } from '../../../../theme'; -import { RepositoryPath } from '../../../../types/graphql'; -import { getDetailsUrlForObject } from '../../../../utils/repository'; +import { GetSystemObjectDetailsResult, RepositoryPath } from '../../../../types/graphql'; +import { getDetailsUrlForObject, isFieldUpdated } from '../../../../utils/repository'; +import { withDefaultValueBoolean } from '../../../../utils/shared'; const useStyles = makeStyles(({ palette, typography }) => ({ detail: { @@ -36,20 +35,12 @@ interface ObjectDetailsProps { disabled: boolean; publishedState: string; retired: boolean; + originalFields: GetSystemObjectDetailsResult; + onRetiredUpdate: (event: React.ChangeEvent, checked: boolean) => void; } function ObjectDetails(props: ObjectDetailsProps): React.ReactElement { - const { unit, project, subject, item, publishedState, retired, disabled } = props; - - const updateRetired = () => { - if (disabled) return; - }; - - const retiredValueComponent = ( - - {retired ? : } - - ); + const { unit, project, subject, item, publishedState, retired, disabled, originalFields, onRetiredUpdate } = props; return ( @@ -58,7 +49,17 @@ function ObjectDetails(props: ObjectDetailsProps): React.ReactElement { - + + } + /> + ); } diff --git a/client/src/pages/Repository/components/DetailsView/index.tsx b/client/src/pages/Repository/components/DetailsView/index.tsx index 729af2ef7..1bc1036b2 100644 --- a/client/src/pages/Repository/components/DetailsView/index.tsx +++ b/client/src/pages/Repository/components/DetailsView/index.tsx @@ -5,7 +5,7 @@ */ import { Box } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; -import React, { useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { useParams } from 'react-router'; import { toast } from 'react-toastify'; import { LoadingButton } from '../../../../components'; @@ -27,6 +27,7 @@ import { UpdateObjectDetailsDataInput } from '../../../../types/graphql'; import { eSystemObjectType, eVocabularySetID } from '../../../../types/server'; +import { withDefaultValueBoolean } from '../../../../utils/shared'; import ObjectSelectModal from '../../../Ingestion/components/Metadata/Model/ObjectSelectModal'; import { updateDetailsTabData, useObjectDetails } from '../../hooks/useDetailsView'; import DetailsHeader from './DetailsHeader'; @@ -66,10 +67,16 @@ type DetailsParams = { idSystemObject: string; }; +type DetailsFields = { + name?: string; + retired?: boolean; +}; + function DetailsView(): React.ReactElement { const classes = useStyles(); const params = useParams(); const [modalOpen, setModalOpen] = useState(false); + const [details, setDetails] = useState({}); const [isUpdatingData, setIsUpdatingData] = useState(false); const idSystemObject: number = Number.parseInt(params.idSystemObject, 10); @@ -78,11 +85,18 @@ function DetailsView(): React.ReactElement { const getEntries = useVocabularyStore(state => state.getEntries); + useEffect(() => { + if (data && !loading) { + const { name, retired } = data.getSystemObjectDetails; + setDetails({ name, retired }); + } + }, [data, loading]); + if (!data || !params.idSystemObject) { return ; } - const { name, objectType, identifiers, retired, allowed, publishedState, thumbnail, unit, project, subject, item, objectAncestors, sourceObjects, derivedObjects } = data.getSystemObjectDetails; + const { objectType, identifiers, allowed, publishedState, thumbnail, unit, project, subject, item, objectAncestors, sourceObjects, derivedObjects } = data.getSystemObjectDetails; const disabled: boolean = !allowed; @@ -114,6 +128,13 @@ function DetailsView(): React.ReactElement { setModalOpen(true); }; + const onNameUpdate = ({ target }): void => { + const updatedDataFields: UpdateObjectDetailsDataInput = { ...updatedData }; + setDetails(details => ({ ...details, name: target.value })); + updatedDataFields.Name = target.value; + setUpdatedData(updatedDataFields); + }; + const onUpdateDetail = (objectType: number, data: UpdateDataFields): void => { const updatedDataFields: UpdateObjectDetailsDataInput = { ...updatedData }; @@ -169,28 +190,35 @@ function DetailsView(): React.ReactElement { setIsUpdatingData(true); try { - const { data, errors } = await updateDetailsTabData(idSystemObject, objectType, updatedData); - - if (errors?.length) { - throw new Error(); - } + const { data } = await updateDetailsTabData(idSystemObject, objectType, updatedData); if (data?.updateObjectDetails?.success) { toast.success('Data saved successfully'); } - } catch { + } catch (e) { + console.log(JSON.stringify(e)); toast.error('Failed to save updated data'); } finally { setIsUpdatingData(false); } }; + const onRetiredUpdate = ({ target }): void => { + const updatedDataFields: UpdateObjectDetailsDataInput = { ...updatedData }; + setDetails(details => ({ ...details, retired: target.checked })); + updatedDataFields.Retired = target.checked; + setUpdatedData(updatedDataFields); + }; + return ( @@ -199,8 +227,10 @@ function DetailsView(): React.ReactElement { project={project} subject={subject} item={item} + onRetiredUpdate={onRetiredUpdate} publishedState={publishedState} - retired={retired} + originalFields={data.getSystemObjectDetails} + retired={withDefaultValueBoolean(details.retired, false)} disabled={disabled} /> diff --git a/client/src/pages/Repository/hooks/useDetailsView.ts b/client/src/pages/Repository/hooks/useDetailsView.ts index 5c8bb3e42..fd5c2befe 100644 --- a/client/src/pages/Repository/hooks/useDetailsView.ts +++ b/client/src/pages/Repository/hooks/useDetailsView.ts @@ -65,6 +65,7 @@ export function updateDetailsTabData(idSystemObject: number, objectType: eSystem objectType, data } - } + }, + refetchQueries: ['getSystemObjectDetails', 'getDetailsTabDataForObject'] }); } diff --git a/client/src/types/graphql.tsx b/client/src/types/graphql.tsx index 9b91217e7..0340c4a76 100644 --- a/client/src/types/graphql.tsx +++ b/client/src/types/graphql.tsx @@ -1244,6 +1244,8 @@ export type StakeholderDetailFieldsInput = { }; export type UpdateObjectDetailsDataInput = { + Name?: Maybe; + Retired?: Maybe; Unit?: Maybe; Project?: Maybe; Subject?: Maybe; diff --git a/client/src/utils/shared.ts b/client/src/utils/shared.ts index 047f68118..797eaf145 100644 --- a/client/src/utils/shared.ts +++ b/client/src/utils/shared.ts @@ -6,7 +6,7 @@ import { CSSProperties } from '@material-ui/core/styles/withStyles'; import { Colors, palette } from '../theme'; -export const withDefaultValueBoolean = (value: boolean | null, defaultValue: boolean): boolean => value || defaultValue; +export const withDefaultValueBoolean = (value: boolean | undefined | null, defaultValue: boolean): boolean => value || defaultValue; export const withDefaultValueNumber = (value: number | undefined | null, defaultValue: number | null): number => { if (value) { diff --git a/server/graphql/schema.graphql b/server/graphql/schema.graphql index 5eeda23b9..c52ac0c2d 100644 --- a/server/graphql/schema.graphql +++ b/server/graphql/schema.graphql @@ -933,6 +933,8 @@ input StakeholderDetailFieldsInput { } input UpdateObjectDetailsDataInput { + Name: String + Retired: Boolean Unit: UnitDetailFieldsInput Project: ProjectDetailFieldsInput Subject: SubjectDetailFieldsInput diff --git a/server/graphql/schema/systemobject/mutations.graphql b/server/graphql/schema/systemobject/mutations.graphql index 6d04adfaa..1f447825e 100644 --- a/server/graphql/schema/systemobject/mutations.graphql +++ b/server/graphql/schema/systemobject/mutations.graphql @@ -128,6 +128,8 @@ input StakeholderDetailFieldsInput { } input UpdateObjectDetailsDataInput { + Name: String + Retired: Boolean Unit: UnitDetailFieldsInput Project: ProjectDetailFieldsInput Subject: SubjectDetailFieldsInput diff --git a/server/types/graphql.ts b/server/types/graphql.ts index 762ec189c..911a694fa 100644 --- a/server/types/graphql.ts +++ b/server/types/graphql.ts @@ -1240,6 +1240,8 @@ export type StakeholderDetailFieldsInput = { }; export type UpdateObjectDetailsDataInput = { + Name?: Maybe; + Retired?: Maybe; Unit?: Maybe; Project?: Maybe; Subject?: Maybe; From 4a9acb5dbd96777e5d10283b684ae57e1d65b544 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Wed, 6 Jan 2021 18:23:15 +0530 Subject: [PATCH 168/239] =?UTF-8?q?efactor=20=E2=80=9Cviewable=20component?= =?UTF-8?q?s=E2=80=9D=20props=20into=20single=20interface?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/components/controls/CheckboxField.tsx | 6 ++---- client/src/components/controls/DateInputField.tsx | 5 ++--- client/src/components/controls/DebounceNumberInput.tsx | 5 ++--- client/src/components/controls/InputField.tsx | 7 ++----- client/src/components/controls/SelectField.tsx | 7 ++----- client/src/components/shared/IdentifierList.tsx | 5 ++--- .../components/Metadata/Model/BoundingBoxInput.tsx | 5 ++--- .../components/Metadata/Model/RelatedObjectsList.tsx | 5 ++--- .../Ingestion/components/Metadata/Model/UVContents.tsx | 5 ++--- .../components/Metadata/Photogrammetry/AssetContents.tsx | 5 ++--- .../components/Metadata/Photogrammetry/Description.tsx | 6 ++---- client/src/types/repository.ts | 6 ++++++ 12 files changed, 28 insertions(+), 39 deletions(-) create mode 100644 client/src/types/repository.ts diff --git a/client/src/components/controls/CheckboxField.tsx b/client/src/components/controls/CheckboxField.tsx index 2cc76aed4..3cf77536b 100644 --- a/client/src/components/controls/CheckboxField.tsx +++ b/client/src/components/controls/CheckboxField.tsx @@ -5,18 +5,16 @@ */ import { Checkbox } from '@material-ui/core'; import React from 'react'; +import { ViewableProps } from '../../types/repository'; import { withDefaultValueBoolean } from '../../utils/shared'; import FieldType from '../shared/FieldType'; -interface CheckboxFieldProps { +interface CheckboxFieldProps extends ViewableProps { label: string; name: string; value: boolean | null; onChange: ((event: React.ChangeEvent, checked: boolean) => void) | undefined; required?: boolean; - viewMode?: boolean; - disabled?: boolean; - updated?: boolean; } function CheckboxField(props: CheckboxFieldProps): React.ReactElement { diff --git a/client/src/components/controls/DateInputField.tsx b/client/src/components/controls/DateInputField.tsx index 22d465b56..92119bd7d 100644 --- a/client/src/components/controls/DateInputField.tsx +++ b/client/src/components/controls/DateInputField.tsx @@ -10,6 +10,7 @@ import { KeyboardDatePicker, MuiPickersUtilsProvider } from '@material-ui/picker import { MaterialUiPickersDate } from '@material-ui/pickers/typings/date'; import React from 'react'; import { Colors } from '../../theme'; +import { ViewableProps } from '../../types/repository'; const useStyles = makeStyles(({ palette, typography, breakpoints }) => ({ date: { @@ -31,11 +32,9 @@ const useStyles = makeStyles(({ palette, typography, breakpoints }) => ({ } })); -interface DateInputFieldProps { +interface DateInputFieldProps extends ViewableProps { value: Date; onChange: (date: MaterialUiPickersDate, value?: string | null | undefined) => void; - disabled?: boolean; - updated?: boolean; } function DateInputField(props: DateInputFieldProps): React.ReactElement { diff --git a/client/src/components/controls/DebounceNumberInput.tsx b/client/src/components/controls/DebounceNumberInput.tsx index d13211f0c..f750b85eb 100644 --- a/client/src/components/controls/DebounceNumberInput.tsx +++ b/client/src/components/controls/DebounceNumberInput.tsx @@ -6,6 +6,7 @@ import { fade, makeStyles } from '@material-ui/core/styles'; import React from 'react'; import { DebounceInput } from 'react-debounce-input'; +import { ViewableProps } from '../../types/repository'; const useStyles = makeStyles(({ palette, typography, breakpoints }) => ({ input: { @@ -26,11 +27,9 @@ const useStyles = makeStyles(({ palette, typography, breakpoints }) => ({ } })); -interface DebounceNumberInputProps { +interface DebounceNumberInputProps extends ViewableProps { value?: number | null; name: string; - disabled?: boolean; - updated?: boolean; onChange: (event: React.ChangeEvent) => void; } diff --git a/client/src/components/controls/InputField.tsx b/client/src/components/controls/InputField.tsx index e34f73593..86739abe5 100644 --- a/client/src/components/controls/InputField.tsx +++ b/client/src/components/controls/InputField.tsx @@ -7,6 +7,7 @@ import { fade, makeStyles } from '@material-ui/core/styles'; import React from 'react'; import { DebounceInput } from 'react-debounce-input'; +import { ViewableProps } from '../../types/repository'; import FieldType from '../shared/FieldType'; const useStyles = makeStyles(({ palette, typography, breakpoints }) => ({ @@ -27,16 +28,12 @@ const useStyles = makeStyles(({ palette, typography, breakpoints }) => ({ } })); -interface InputFieldProps { +interface InputFieldProps extends ViewableProps { label: string; value?: number | string | null; name: string; onChange: (event: React.ChangeEvent) => void; type?: string; - required?: boolean; - viewMode?: boolean; - disabled?: boolean; - updated?: boolean; } function InputField(props: InputFieldProps): React.ReactElement { diff --git a/client/src/components/controls/SelectField.tsx b/client/src/components/controls/SelectField.tsx index 1d6dcb192..55ea039f4 100644 --- a/client/src/components/controls/SelectField.tsx +++ b/client/src/components/controls/SelectField.tsx @@ -7,6 +7,7 @@ import { MenuItem, Select } from '@material-ui/core'; import { fade, makeStyles } from '@material-ui/core/styles'; import React from 'react'; import { VocabularyOption } from '../../store'; +import { ViewableProps } from '../../types/repository'; import FieldType from '../shared/FieldType'; const useStyles = makeStyles(({ palette, typography, breakpoints }) => ({ @@ -26,18 +27,14 @@ const useStyles = makeStyles(({ palette, typography, breakpoints }) => ({ }, })); -interface SelectFieldProps { +interface SelectFieldProps extends ViewableProps { label: string; value: number | null; name: string; options: VocabularyOption[]; - required?: boolean; error?: boolean; width?: string; onChange: (event: React.ChangeEvent) => void; - viewMode?: boolean; - disabled?: boolean; - updated?: boolean; } function SelectField(props: SelectFieldProps): React.ReactElement { diff --git a/client/src/components/shared/IdentifierList.tsx b/client/src/components/shared/IdentifierList.tsx index 0c795c57f..c81e64dc1 100644 --- a/client/src/components/shared/IdentifierList.tsx +++ b/client/src/components/shared/IdentifierList.tsx @@ -9,6 +9,7 @@ import React from 'react'; import { DebounceInput } from 'react-debounce-input'; import { MdRemoveCircleOutline } from 'react-icons/md'; import { StateIdentifier, VocabularyOption } from '../../store'; +import { ViewableProps } from '../../types/repository'; import { sharedButtonProps, sharedLabelProps } from '../../utils/shared'; import FieldType from './FieldType'; @@ -59,14 +60,12 @@ const useStyles = makeStyles(({ palette, typography, breakpoints }) => ({ addIdentifierButton: sharedButtonProps })); -interface IdentifierListProps { +interface IdentifierListProps extends ViewableProps { identifiers: StateIdentifier[] onAdd: (initialEntry: number | null) => void; onUpdate: (id: number, fieldName: string, fieldValue: number | string | boolean) => void; onRemove: (id: number) => void; identifierTypes: VocabularyOption[]; - disabled?: boolean; - viewMode?: boolean; } function IdentifierList(props: IdentifierListProps): React.ReactElement { diff --git a/client/src/pages/Ingestion/components/Metadata/Model/BoundingBoxInput.tsx b/client/src/pages/Ingestion/components/Metadata/Model/BoundingBoxInput.tsx index ffea3ac84..c0e87d7e7 100644 --- a/client/src/pages/Ingestion/components/Metadata/Model/BoundingBoxInput.tsx +++ b/client/src/pages/Ingestion/components/Metadata/Model/BoundingBoxInput.tsx @@ -8,9 +8,10 @@ import { Box } from '@material-ui/core'; import React from 'react'; import { DebounceNumberInput, FieldType } from '../../../../../components'; import { ModelDetailFields } from '../../../../../types/graphql'; +import { ViewableProps } from '../../../../../types/repository'; import { isFieldUpdated } from '../../../../../utils/repository'; -interface BoundingBoxInputProps { +interface BoundingBoxInputProps extends ViewableProps { modelFields?: ModelDetailFields | null; boundingBoxP1X?: number | null; boundingBoxP1Y?: number | null; @@ -18,8 +19,6 @@ interface BoundingBoxInputProps { boundingBoxP2X?: number | null; boundingBoxP2Y?: number | null; boundingBoxP2Z?: number | null; - viewMode?: boolean; - disabled?: boolean; onChange: (event: React.ChangeEvent) => void; } diff --git a/client/src/pages/Ingestion/components/Metadata/Model/RelatedObjectsList.tsx b/client/src/pages/Ingestion/components/Metadata/Model/RelatedObjectsList.tsx index 8a0b5b434..844d0b515 100644 --- a/client/src/pages/Ingestion/components/Metadata/Model/RelatedObjectsList.tsx +++ b/client/src/pages/Ingestion/components/Metadata/Model/RelatedObjectsList.tsx @@ -11,6 +11,7 @@ import { MdRemoveCircleOutline } from 'react-icons/md'; import { NewTabLink } from '../../../../../components'; import { StateRelatedObject } from '../../../../../store'; import { RelatedObjectType } from '../../../../../types/graphql'; +import { ViewableProps } from '../../../../../types/repository'; import { getDetailsUrlForObject, getTermForSystemObjectType } from '../../../../../utils/repository'; import { sharedButtonProps, sharedLabelProps } from '../../../../../utils/shared'; @@ -48,13 +49,11 @@ const useStyles = makeStyles(({ palette }) => ({ } })); -interface RelatedObjectsListProps { +interface RelatedObjectsListProps extends ViewableProps { relatedObjects: StateRelatedObject[]; type: RelatedObjectType; onAdd: () => void; onRemove?: (id: number) => void; - viewMode?: boolean; - disabled?: boolean; } function RelatedObjectsList(props: RelatedObjectsListProps): React.ReactElement { diff --git a/client/src/pages/Ingestion/components/Metadata/Model/UVContents.tsx b/client/src/pages/Ingestion/components/Metadata/Model/UVContents.tsx index d2f92d7ee..a8c1ee945 100644 --- a/client/src/pages/Ingestion/components/Metadata/Model/UVContents.tsx +++ b/client/src/pages/Ingestion/components/Metadata/Model/UVContents.tsx @@ -10,15 +10,14 @@ import { AiFillFileImage } from 'react-icons/ai'; import { FieldType } from '../../../../../components'; import { StateUVMap, VocabularyOption } from '../../../../../store'; import { palette } from '../../../../../theme'; +import { ViewableProps } from '../../../../../types/repository'; import { ContentHeader, EmptyContent, useStyles } from '../Photogrammetry/AssetContents'; -interface UVContentsProps { +interface UVContentsProps extends ViewableProps { initialEntry: number | null; uvMaps: StateUVMap[]; options: VocabularyOption[]; onUpdate: (id: number, mapType: number) => void; - viewMode?: boolean; - disabled?: boolean; } function UVContents(props: UVContentsProps): React.ReactElement { diff --git a/client/src/pages/Ingestion/components/Metadata/Photogrammetry/AssetContents.tsx b/client/src/pages/Ingestion/components/Metadata/Photogrammetry/AssetContents.tsx index 95918496f..abd5a8ced 100644 --- a/client/src/pages/Ingestion/components/Metadata/Photogrammetry/AssetContents.tsx +++ b/client/src/pages/Ingestion/components/Metadata/Photogrammetry/AssetContents.tsx @@ -11,6 +11,7 @@ import { AiFillFolder } from 'react-icons/ai'; import { FieldType } from '../../../../../components'; import { StateFolder, VocabularyOption } from '../../../../../store'; import { palette } from '../../../../../theme'; +import { ViewableProps } from '../../../../../types/repository'; export const useStyles = makeStyles(({ palette, typography, breakpoints, spacing }) => ({ header: { @@ -53,13 +54,11 @@ export const useStyles = makeStyles(({ palette, typography, breakpoints, spacing }, })); -interface AssetContentsProps { +interface AssetContentsProps extends ViewableProps { initialEntry: number | null; folders: StateFolder[]; options: VocabularyOption[]; onUpdate: (id: number, variantType: number) => void; - viewMode?: boolean; - disabled?: boolean; } function AssetContents(props: AssetContentsProps): React.ReactElement { diff --git a/client/src/pages/Ingestion/components/Metadata/Photogrammetry/Description.tsx b/client/src/pages/Ingestion/components/Metadata/Photogrammetry/Description.tsx index 062e5bb3b..f55d3ca85 100644 --- a/client/src/pages/Ingestion/components/Metadata/Photogrammetry/Description.tsx +++ b/client/src/pages/Ingestion/components/Metadata/Photogrammetry/Description.tsx @@ -7,6 +7,7 @@ import { fade, makeStyles } from '@material-ui/core/styles'; import React from 'react'; import { DebounceInput } from 'react-debounce-input'; import { FieldType } from '../../../../../components'; +import { ViewableProps } from '../../../../../types/repository'; const useStyles = makeStyles(({ palette, typography }) => ({ description: { @@ -23,12 +24,9 @@ const useStyles = makeStyles(({ palette, typography }) => ({ } })); -interface DescriptionProps { +interface DescriptionProps extends ViewableProps { value: string; onChange: (event: React.ChangeEvent) => void; - viewMode?: boolean; - disabled?: boolean; - updated?: boolean; } function Description(props: DescriptionProps): React.ReactElement { diff --git a/client/src/types/repository.ts b/client/src/types/repository.ts new file mode 100644 index 000000000..96ce1576e --- /dev/null +++ b/client/src/types/repository.ts @@ -0,0 +1,6 @@ +export interface ViewableProps { + required?: boolean; + viewMode?: boolean; + disabled?: boolean; + updated?: boolean; +} \ No newline at end of file From 157c274b33d6871149e3b182e4a43ca7c0d64033 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Wed, 13 Jan 2021 16:27:15 +0530 Subject: [PATCH 169/239] updated types with object id --- .../components/DetailsView/index.tsx | 46 +- .../pages/Repository/hooks/useDetailsView.ts | 3 +- client/src/types/graphql.tsx | 2576 +++++++---------- .../systemobject/getSystemObjectDetails.ts | 1 + server/graphql/schema.graphql | 2 + .../schema/systemobject/mutations.graphql | 1 + .../schema/systemobject/queries.graphql | 1 + server/types/graphql.ts | 51 +- 8 files changed, 1039 insertions(+), 1642 deletions(-) diff --git a/client/src/pages/Repository/components/DetailsView/index.tsx b/client/src/pages/Repository/components/DetailsView/index.tsx index 1bc1036b2..4cc025f22 100644 --- a/client/src/pages/Repository/components/DetailsView/index.tsx +++ b/client/src/pages/Repository/components/DetailsView/index.tsx @@ -96,7 +96,21 @@ function DetailsView(): React.ReactElement { return ; } - const { objectType, identifiers, allowed, publishedState, thumbnail, unit, project, subject, item, objectAncestors, sourceObjects, derivedObjects } = data.getSystemObjectDetails; + const { + idObject, + objectType, + identifiers, + allowed, + publishedState, + thumbnail, + unit, + project, + subject, + item, + objectAncestors, + sourceObjects, + derivedObjects + } = data.getSystemObjectDetails; const disabled: boolean = !allowed; @@ -136,7 +150,11 @@ function DetailsView(): React.ReactElement { }; const onUpdateDetail = (objectType: number, data: UpdateDataFields): void => { - const updatedDataFields: UpdateObjectDetailsDataInput = { ...updatedData }; + const updatedDataFields: UpdateObjectDetailsDataInput = { + ...updatedData, + Name: details.name, + Retired: details.retired + }; switch (objectType) { case eSystemObjectType.eUnit: @@ -190,13 +208,15 @@ function DetailsView(): React.ReactElement { setIsUpdatingData(true); try { - const { data } = await updateDetailsTabData(idSystemObject, objectType, updatedData); + const { data } = await updateDetailsTabData(idSystemObject, idObject, objectType, updatedData); if (data?.updateObjectDetails?.success) { toast.success('Data saved successfully'); + } else { + throw new Error('Update request returned success: false'); } - } catch (e) { - console.log(JSON.stringify(e)); + } catch (error) { + console.log(JSON.stringify(error)); toast.error('Failed to save updated data'); } finally { setIsUpdatingData(false); @@ -262,23 +282,13 @@ function DetailsView(): React.ReactElement { - + Update - + ); } -export default DetailsView; \ No newline at end of file +export default DetailsView; diff --git a/client/src/pages/Repository/hooks/useDetailsView.ts b/client/src/pages/Repository/hooks/useDetailsView.ts index fd5c2befe..2b00eb2c8 100644 --- a/client/src/pages/Repository/hooks/useDetailsView.ts +++ b/client/src/pages/Repository/hooks/useDetailsView.ts @@ -56,12 +56,13 @@ export function useDetailsTabData(idSystemObject: number, objectType: eSystemObj }); } -export function updateDetailsTabData(idSystemObject: number, objectType: eSystemObjectType, data: UpdateObjectDetailsDataInput): Promise> { +export function updateDetailsTabData(idSystemObject: number, idObject: number, objectType: eSystemObjectType, data: UpdateObjectDetailsDataInput): Promise> { return apolloClient.mutate({ mutation: UpdateObjectDetailsDocument, variables: { input: { idSystemObject, + idObject, objectType, data } diff --git a/client/src/types/graphql.tsx b/client/src/types/graphql.tsx index 0340c4a76..7d2cc7173 100644 --- a/client/src/types/graphql.tsx +++ b/client/src/types/graphql.tsx @@ -55,162 +55,130 @@ export type Query = { searchIngestionSubjects: SearchIngestionSubjectsResult; }; - export type QueryAreCameraSettingsUniformArgs = { input: AreCameraSettingsUniformInput; }; - export type QueryGetAccessPolicyArgs = { input: GetAccessPolicyInput; }; - export type QueryGetAssetArgs = { input: GetAssetInput; }; - export type QueryGetAssetDetailsForSystemObjectArgs = { input: GetAssetDetailsForSystemObjectInput; }; - export type QueryGetAssetVersionsDetailsArgs = { input: GetAssetVersionsDetailsInput; }; - export type QueryGetCaptureDataArgs = { input: GetCaptureDataInput; }; - export type QueryGetCaptureDataPhotoArgs = { input: GetCaptureDataPhotoInput; }; - export type QueryGetContentsForAssetVersionsArgs = { input: GetContentsForAssetVersionsInput; }; - export type QueryGetDetailsTabDataForObjectArgs = { input: GetDetailsTabDataForObjectInput; }; - export type QueryGetIngestionItemsForSubjectsArgs = { input: GetIngestionItemsForSubjectsInput; }; - export type QueryGetIngestionProjectsForSubjectsArgs = { input: GetIngestionProjectsForSubjectsInput; }; - export type QueryGetIntermediaryFileArgs = { input: GetIntermediaryFileInput; }; - export type QueryGetItemArgs = { input: GetItemInput; }; - export type QueryGetItemsForSubjectArgs = { input: GetItemsForSubjectInput; }; - export type QueryGetLicenseArgs = { input: GetLicenseInput; }; - export type QueryGetModelArgs = { input: GetModelInput; }; - export type QueryGetObjectChildrenArgs = { input: GetObjectChildrenInput; }; - export type QueryGetObjectsForItemArgs = { input: GetObjectsForItemInput; }; - export type QueryGetProjectArgs = { input: GetProjectInput; }; - export type QueryGetProjectDocumentationArgs = { input: GetProjectDocumentationInput; }; - export type QueryGetSceneArgs = { input: GetSceneInput; }; - export type QueryGetSourceObjectIdentiferArgs = { input: GetSourceObjectIdentiferInput; }; - export type QueryGetSubjectArgs = { input: GetSubjectInput; }; - export type QueryGetSubjectsForUnitArgs = { input: GetSubjectsForUnitInput; }; - export type QueryGetSystemObjectDetailsArgs = { input: GetSystemObjectDetailsInput; }; - export type QueryGetUnitArgs = { input: GetUnitInput; }; - export type QueryGetUserArgs = { input: GetUserInput; }; - export type QueryGetVersionsForSystemObjectArgs = { input: GetVersionsForSystemObjectInput; }; - export type QueryGetVocabularyArgs = { input: GetVocabularyInput; }; - export type QueryGetVocabularyEntriesArgs = { input: GetVocabularyEntriesInput; }; - export type QueryGetWorkflowArgs = { input: GetWorkflowInput; }; - export type QuerySearchIngestionSubjectsArgs = { input: SearchIngestionSubjectsInput; }; @@ -224,7 +192,6 @@ export type GetAccessPolicyResult = { AccessPolicy?: Maybe; }; - export type AccessAction = { __typename?: 'AccessAction'; idAccessAction: Scalars['Int']; @@ -273,7 +240,6 @@ export type AccessRole = { AccessAction?: Maybe>>; }; - export type Mutation = { __typename?: 'Mutation'; createCaptureData: CreateCaptureDataResult; @@ -293,77 +259,62 @@ export type Mutation = { uploadAsset: UploadAssetResult; }; - export type MutationCreateCaptureDataArgs = { input: CreateCaptureDataInput; }; - export type MutationCreateCaptureDataPhotoArgs = { input: CreateCaptureDataPhotoInput; }; - export type MutationCreateItemArgs = { input: CreateItemInput; }; - export type MutationCreateModelArgs = { input: CreateModelInput; }; - export type MutationCreateProjectArgs = { input: CreateProjectInput; }; - export type MutationCreateSceneArgs = { input: CreateSceneInput; }; - export type MutationCreateSubjectArgs = { input: CreateSubjectInput; }; - export type MutationCreateUnitArgs = { input: CreateUnitInput; }; - export type MutationCreateUserArgs = { input: CreateUserInput; }; - export type MutationCreateVocabularyArgs = { input: CreateVocabularyInput; }; - export type MutationCreateVocabularySetArgs = { input: CreateVocabularySetInput; }; - export type MutationDiscardUploadedAssetVersionsArgs = { input: DiscardUploadedAssetVersionsInput; }; - export type MutationIngestDataArgs = { input: IngestDataInput; }; - export type MutationUpdateObjectDetailsArgs = { input: UpdateObjectDetailsInput; }; - export type MutationUploadAssetArgs = { file: Scalars['Upload']; type: Scalars['Int']; @@ -1121,6 +1072,7 @@ export type IntermediaryFile = { export type UpdateObjectDetailsInput = { idSystemObject: Scalars['Int']; + idObject: Scalars['Int']; objectType: Scalars['Int']; data: UpdateObjectDetailsDataInput; }; @@ -1435,6 +1387,7 @@ export type RepositoryPath = { export type GetSystemObjectDetailsResult = { __typename?: 'GetSystemObjectDetailsResult'; + idObject: Scalars['Int']; name: Scalars['String']; retired: Scalars['Boolean']; objectType: Scalars['Int']; @@ -2017,1142 +1970,608 @@ export type DiscardUploadedAssetVersionsMutationVariables = Exact<{ input: DiscardUploadedAssetVersionsInput; }>; - -export type DiscardUploadedAssetVersionsMutation = ( - { __typename?: 'Mutation' } - & { - discardUploadedAssetVersions: ( - { __typename?: 'DiscardUploadedAssetVersionsResult' } - & Pick - ) - } -); +export type DiscardUploadedAssetVersionsMutation = { __typename?: 'Mutation' } & { + discardUploadedAssetVersions: { __typename?: 'DiscardUploadedAssetVersionsResult' } & Pick; +}; export type UploadAssetMutationVariables = Exact<{ file: Scalars['Upload']; type: Scalars['Int']; }>; - -export type UploadAssetMutation = ( - { __typename?: 'Mutation' } - & { - uploadAsset: ( - { __typename?: 'UploadAssetResult' } - & Pick - ) - } -); +export type UploadAssetMutation = { __typename?: 'Mutation' } & { + uploadAsset: { __typename?: 'UploadAssetResult' } & Pick; +}; export type CreateCaptureDataMutationVariables = Exact<{ input: CreateCaptureDataInput; }>; - -export type CreateCaptureDataMutation = ( - { __typename?: 'Mutation' } - & { - createCaptureData: ( - { __typename?: 'CreateCaptureDataResult' } - & { - CaptureData?: Maybe<( - { __typename?: 'CaptureData' } - & Pick - )> - } - ) - } -); +export type CreateCaptureDataMutation = { __typename?: 'Mutation' } & { + createCaptureData: { __typename?: 'CreateCaptureDataResult' } & { CaptureData?: Maybe<{ __typename?: 'CaptureData' } & Pick> }; +}; export type CreateCaptureDataPhotoMutationVariables = Exact<{ input: CreateCaptureDataPhotoInput; }>; - -export type CreateCaptureDataPhotoMutation = ( - { __typename?: 'Mutation' } - & { - createCaptureDataPhoto: ( - { __typename?: 'CreateCaptureDataPhotoResult' } - & { - CaptureDataPhoto?: Maybe<( - { __typename?: 'CaptureDataPhoto' } - & Pick - )> - } - ) - } -); +export type CreateCaptureDataPhotoMutation = { __typename?: 'Mutation' } & { + createCaptureDataPhoto: { __typename?: 'CreateCaptureDataPhotoResult' } & { + CaptureDataPhoto?: Maybe<{ __typename?: 'CaptureDataPhoto' } & Pick>; + }; +}; export type IngestDataMutationVariables = Exact<{ input: IngestDataInput; }>; - -export type IngestDataMutation = ( - { __typename?: 'Mutation' } - & { - ingestData: ( - { __typename?: 'IngestDataResult' } - & Pick - ) - } -); +export type IngestDataMutation = { __typename?: 'Mutation' } & { ingestData: { __typename?: 'IngestDataResult' } & Pick }; export type CreateModelMutationVariables = Exact<{ input: CreateModelInput; }>; - -export type CreateModelMutation = ( - { __typename?: 'Mutation' } - & { - createModel: ( - { __typename?: 'CreateModelResult' } - & { - Model?: Maybe<( - { __typename?: 'Model' } - & Pick - )> - } - ) - } -); +export type CreateModelMutation = { __typename?: 'Mutation' } & { + createModel: { __typename?: 'CreateModelResult' } & { Model?: Maybe<{ __typename?: 'Model' } & Pick> }; +}; export type CreateSceneMutationVariables = Exact<{ input: CreateSceneInput; }>; - -export type CreateSceneMutation = ( - { __typename?: 'Mutation' } - & { - createScene: ( - { __typename?: 'CreateSceneResult' } - & { - Scene?: Maybe<( - { __typename?: 'Scene' } - & Pick - )> - } - ) - } -); +export type CreateSceneMutation = { __typename?: 'Mutation' } & { + createScene: { __typename?: 'CreateSceneResult' } & { Scene?: Maybe<{ __typename?: 'Scene' } & Pick> }; +}; export type UpdateObjectDetailsMutationVariables = Exact<{ input: UpdateObjectDetailsInput; }>; - -export type UpdateObjectDetailsMutation = ( - { __typename?: 'Mutation' } - & { - updateObjectDetails: ( - { __typename?: 'UpdateObjectDetailsResult' } - & Pick - ) - } -); +export type UpdateObjectDetailsMutation = { __typename?: 'Mutation' } & { + updateObjectDetails: { __typename?: 'UpdateObjectDetailsResult' } & Pick; +}; export type CreateItemMutationVariables = Exact<{ input: CreateItemInput; }>; - -export type CreateItemMutation = ( - { __typename?: 'Mutation' } - & { - createItem: ( - { __typename?: 'CreateItemResult' } - & { - Item?: Maybe<( - { __typename?: 'Item' } - & Pick - )> - } - ) - } -); +export type CreateItemMutation = { __typename?: 'Mutation' } & { + createItem: { __typename?: 'CreateItemResult' } & { Item?: Maybe<{ __typename?: 'Item' } & Pick> }; +}; export type CreateProjectMutationVariables = Exact<{ input: CreateProjectInput; }>; - -export type CreateProjectMutation = ( - { __typename?: 'Mutation' } - & { - createProject: ( - { __typename?: 'CreateProjectResult' } - & { - Project?: Maybe<( - { __typename?: 'Project' } - & Pick - )> - } - ) - } -); +export type CreateProjectMutation = { __typename?: 'Mutation' } & { + createProject: { __typename?: 'CreateProjectResult' } & { Project?: Maybe<{ __typename?: 'Project' } & Pick> }; +}; export type CreateSubjectMutationVariables = Exact<{ input: CreateSubjectInput; }>; - -export type CreateSubjectMutation = ( - { __typename?: 'Mutation' } - & { - createSubject: ( - { __typename?: 'CreateSubjectResult' } - & { - Subject?: Maybe<( - { __typename?: 'Subject' } - & Pick - )> - } - ) - } -); +export type CreateSubjectMutation = { __typename?: 'Mutation' } & { + createSubject: { __typename?: 'CreateSubjectResult' } & { Subject?: Maybe<{ __typename?: 'Subject' } & Pick> }; +}; export type CreateUnitMutationVariables = Exact<{ input: CreateUnitInput; }>; - -export type CreateUnitMutation = ( - { __typename?: 'Mutation' } - & { - createUnit: ( - { __typename?: 'CreateUnitResult' } - & { - Unit?: Maybe<( - { __typename?: 'Unit' } - & Pick - )> - } - ) - } -); +export type CreateUnitMutation = { __typename?: 'Mutation' } & { + createUnit: { __typename?: 'CreateUnitResult' } & { Unit?: Maybe<{ __typename?: 'Unit' } & Pick> }; +}; export type CreateUserMutationVariables = Exact<{ input: CreateUserInput; }>; - -export type CreateUserMutation = ( - { __typename?: 'Mutation' } - & { - createUser: ( - { __typename?: 'CreateUserResult' } - & { - User?: Maybe<( - { __typename?: 'User' } - & Pick - )> - } - ) - } -); +export type CreateUserMutation = { __typename?: 'Mutation' } & { + createUser: { __typename?: 'CreateUserResult' } & { User?: Maybe<{ __typename?: 'User' } & Pick> }; +}; export type CreateVocabularyMutationVariables = Exact<{ input: CreateVocabularyInput; }>; - -export type CreateVocabularyMutation = ( - { __typename?: 'Mutation' } - & { - createVocabulary: ( - { __typename?: 'CreateVocabularyResult' } - & { - Vocabulary?: Maybe<( - { __typename?: 'Vocabulary' } - & Pick - )> - } - ) - } -); +export type CreateVocabularyMutation = { __typename?: 'Mutation' } & { + createVocabulary: { __typename?: 'CreateVocabularyResult' } & { Vocabulary?: Maybe<{ __typename?: 'Vocabulary' } & Pick> }; +}; export type CreateVocabularySetMutationVariables = Exact<{ input: CreateVocabularySetInput; }>; - -export type CreateVocabularySetMutation = ( - { __typename?: 'Mutation' } - & { - createVocabularySet: ( - { __typename?: 'CreateVocabularySetResult' } - & { - VocabularySet?: Maybe<( - { __typename?: 'VocabularySet' } - & Pick - )> - } - ) - } -); +export type CreateVocabularySetMutation = { __typename?: 'Mutation' } & { + createVocabularySet: { __typename?: 'CreateVocabularySetResult' } & { VocabularySet?: Maybe<{ __typename?: 'VocabularySet' } & Pick> }; +}; export type GetAccessPolicyQueryVariables = Exact<{ input: GetAccessPolicyInput; }>; - -export type GetAccessPolicyQuery = ( - { __typename?: 'Query' } - & { - getAccessPolicy: ( - { __typename?: 'GetAccessPolicyResult' } - & { - AccessPolicy?: Maybe<( - { __typename?: 'AccessPolicy' } - & Pick - )> - } - ) - } -); +export type GetAccessPolicyQuery = { __typename?: 'Query' } & { + getAccessPolicy: { __typename?: 'GetAccessPolicyResult' } & { AccessPolicy?: Maybe<{ __typename?: 'AccessPolicy' } & Pick> }; +}; export type GetAssetQueryVariables = Exact<{ input: GetAssetInput; }>; - -export type GetAssetQuery = ( - { __typename?: 'Query' } - & { - getAsset: ( - { __typename?: 'GetAssetResult' } - & { - Asset?: Maybe<( - { __typename?: 'Asset' } - & Pick - )> - } - ) - } -); +export type GetAssetQuery = { __typename?: 'Query' } & { getAsset: { __typename?: 'GetAssetResult' } & { Asset?: Maybe<{ __typename?: 'Asset' } & Pick> } }; export type GetAssetVersionsDetailsQueryVariables = Exact<{ input: GetAssetVersionsDetailsInput; }>; - -export type GetAssetVersionsDetailsQuery = ( - { __typename?: 'Query' } - & { - getAssetVersionsDetails: ( - { __typename?: 'GetAssetVersionsDetailsResult' } - & Pick - & { - Details: Array<( - { __typename?: 'GetAssetVersionDetailResult' } - & Pick - & { - SubjectUnitIdentifier?: Maybe<( - { __typename?: 'SubjectUnitIdentifier' } - & Pick - )>, Project?: Maybe - )>>, Item?: Maybe<( - { __typename?: 'Item' } - & Pick - )>, CaptureDataPhoto?: Maybe<( - { __typename?: 'IngestPhotogrammetry' } - & Pick - & { - folders: Array<( - { __typename?: 'IngestFolder' } - & Pick - )>, identifiers: Array<( - { __typename?: 'IngestIdentifier' } - & Pick - )> - } - )>, Model?: Maybe<( - { __typename?: 'IngestModel' } - & Pick - & { - identifiers: Array<( - { __typename?: 'IngestIdentifier' } - & Pick - )>, uvMaps: Array<( - { __typename?: 'IngestUVMap' } - & Pick - )> - } - )>, Scene?: Maybe<( - { __typename?: 'IngestScene' } - & Pick - & { - identifiers: Array<( - { __typename?: 'IngestIdentifier' } - & Pick - )> - } - )> - } - )> +export type GetAssetVersionsDetailsQuery = { __typename?: 'Query' } & { + getAssetVersionsDetails: { __typename?: 'GetAssetVersionsDetailsResult' } & Pick & { + Details: Array< + { __typename?: 'GetAssetVersionDetailResult' } & Pick & { + SubjectUnitIdentifier?: Maybe< + { __typename?: 'SubjectUnitIdentifier' } & Pick< + SubjectUnitIdentifier, + 'idSubject' | 'SubjectName' | 'UnitAbbreviation' | 'IdentifierPublic' | 'IdentifierCollection' + > + >; + Project?: Maybe>>; + Item?: Maybe<{ __typename?: 'Item' } & Pick>; + CaptureDataPhoto?: Maybe< + { __typename?: 'IngestPhotogrammetry' } & Pick< + IngestPhotogrammetry, + | 'idAssetVersion' + | 'dateCaptured' + | 'datasetType' + | 'systemCreated' + | 'description' + | 'cameraSettingUniform' + | 'datasetFieldId' + | 'itemPositionType' + | 'itemPositionFieldId' + | 'itemArrangementFieldId' + | 'focusType' + | 'lightsourceType' + | 'backgroundRemovalMethod' + | 'clusterType' + | 'clusterGeometryFieldId' + | 'directory' + > & { + folders: Array<{ __typename?: 'IngestFolder' } & Pick>; + identifiers: Array<{ __typename?: 'IngestIdentifier' } & Pick>; } - ) - } -); + >; + Model?: Maybe< + { __typename?: 'IngestModel' } & Pick< + IngestModel, + | 'idAssetVersion' + | 'systemCreated' + | 'master' + | 'authoritative' + | 'creationMethod' + | 'modality' + | 'purpose' + | 'units' + | 'dateCaptured' + | 'modelFileType' + | 'directory' + | 'roughness' + | 'metalness' + | 'pointCount' + | 'faceCount' + | 'isWatertight' + | 'hasNormals' + | 'hasVertexColor' + | 'hasUVSpace' + | 'boundingBoxP1X' + | 'boundingBoxP1Y' + | 'boundingBoxP1Z' + | 'boundingBoxP2X' + | 'boundingBoxP2Y' + | 'boundingBoxP2Z' + > & { + identifiers: Array<{ __typename?: 'IngestIdentifier' } & Pick>; + uvMaps: Array<{ __typename?: 'IngestUVMap' } & Pick>; + } + >; + Scene?: Maybe< + { __typename?: 'IngestScene' } & Pick & { + identifiers: Array<{ __typename?: 'IngestIdentifier' } & Pick>; + } + >; + } + >; + }; +}; export type GetContentsForAssetVersionsQueryVariables = Exact<{ input: GetContentsForAssetVersionsInput; }>; +export type GetContentsForAssetVersionsQuery = { __typename?: 'Query' } & { + getContentsForAssetVersions: { __typename?: 'GetContentsForAssetVersionsResult' } & { + AssetVersionContent: Array<{ __typename?: 'AssetVersionContent' } & Pick>; + }; +}; -export type GetContentsForAssetVersionsQuery = ( - { __typename?: 'Query' } - & { - getContentsForAssetVersions: ( - { __typename?: 'GetContentsForAssetVersionsResult' } - & { - AssetVersionContent: Array<( - { __typename?: 'AssetVersionContent' } - & Pick - )> - } - ) - } -); - -export type GetUploadedAssetVersionQueryVariables = Exact<{ [key: string]: never; }>; - - -export type GetUploadedAssetVersionQuery = ( - { __typename?: 'Query' } - & { - getUploadedAssetVersion: ( - { __typename?: 'GetUploadedAssetVersionResult' } - & { - AssetVersion: Array<( - { __typename?: 'AssetVersion' } - & Pick - & { - Asset?: Maybe<( - { __typename?: 'Asset' } - & Pick - & { - VAssetType?: Maybe<( - { __typename?: 'Vocabulary' } - & Pick - )> - } - )> - } - )> - } - ) - } -); +export type GetUploadedAssetVersionQueryVariables = Exact<{ [key: string]: never }>; + +export type GetUploadedAssetVersionQuery = { __typename?: 'Query' } & { + getUploadedAssetVersion: { __typename?: 'GetUploadedAssetVersionResult' } & { + AssetVersion: Array< + { __typename?: 'AssetVersion' } & Pick & { + Asset?: Maybe< + { __typename?: 'Asset' } & Pick & { VAssetType?: Maybe<{ __typename?: 'Vocabulary' } & Pick> } + >; + } + >; + }; +}; export type GetCaptureDataQueryVariables = Exact<{ input: GetCaptureDataInput; }>; - -export type GetCaptureDataQuery = ( - { __typename?: 'Query' } - & { - getCaptureData: ( - { __typename?: 'GetCaptureDataResult' } - & { - CaptureData?: Maybe<( - { __typename?: 'CaptureData' } - & Pick - )> - } - ) - } -); +export type GetCaptureDataQuery = { __typename?: 'Query' } & { + getCaptureData: { __typename?: 'GetCaptureDataResult' } & { CaptureData?: Maybe<{ __typename?: 'CaptureData' } & Pick> }; +}; export type GetCaptureDataPhotoQueryVariables = Exact<{ input: GetCaptureDataPhotoInput; }>; - -export type GetCaptureDataPhotoQuery = ( - { __typename?: 'Query' } - & { - getCaptureDataPhoto: ( - { __typename?: 'GetCaptureDataPhotoResult' } - & { - CaptureDataPhoto?: Maybe<( - { __typename?: 'CaptureDataPhoto' } - & Pick - )> - } - ) - } -); +export type GetCaptureDataPhotoQuery = { __typename?: 'Query' } & { + getCaptureDataPhoto: { __typename?: 'GetCaptureDataPhotoResult' } & { + CaptureDataPhoto?: Maybe<{ __typename?: 'CaptureDataPhoto' } & Pick>; + }; +}; export type AreCameraSettingsUniformQueryVariables = Exact<{ input: AreCameraSettingsUniformInput; }>; - -export type AreCameraSettingsUniformQuery = ( - { __typename?: 'Query' } - & { - areCameraSettingsUniform: ( - { __typename?: 'AreCameraSettingsUniformResult' } - & Pick - ) - } -); +export type AreCameraSettingsUniformQuery = { __typename?: 'Query' } & { + areCameraSettingsUniform: { __typename?: 'AreCameraSettingsUniformResult' } & Pick; +}; export type GetLicenseQueryVariables = Exact<{ input: GetLicenseInput; }>; - -export type GetLicenseQuery = ( - { __typename?: 'Query' } - & { - getLicense: ( - { __typename?: 'GetLicenseResult' } - & { - License?: Maybe<( - { __typename?: 'License' } - & Pick - )> - } - ) - } -); +export type GetLicenseQuery = { __typename?: 'Query' } & { + getLicense: { __typename?: 'GetLicenseResult' } & { License?: Maybe<{ __typename?: 'License' } & Pick> }; +}; export type GetModelQueryVariables = Exact<{ input: GetModelInput; }>; +export type GetModelQuery = { __typename?: 'Query' } & { getModel: { __typename?: 'GetModelResult' } & { Model?: Maybe<{ __typename?: 'Model' } & Pick> } }; -export type GetModelQuery = ( - { __typename?: 'Query' } - & { - getModel: ( - { __typename?: 'GetModelResult' } - & { - Model?: Maybe<( - { __typename?: 'Model' } - & Pick - )> - } - ) - } -); - -export type GetFilterViewDataQueryVariables = Exact<{ [key: string]: never; }>; - - -export type GetFilterViewDataQuery = ( - { __typename?: 'Query' } - & { - getFilterViewData: ( - { __typename?: 'GetFilterViewDataResult' } - & { - units: Array<( - { __typename?: 'Unit' } - & Pick - & { - SystemObject?: Maybe<( - { __typename?: 'SystemObject' } - & Pick - )> - } - )>, projects: Array<( - { __typename?: 'Project' } - & Pick - & { - SystemObject?: Maybe<( - { __typename?: 'SystemObject' } - & Pick - )> - } - )> - } - ) - } -); +export type GetFilterViewDataQueryVariables = Exact<{ [key: string]: never }>; + +export type GetFilterViewDataQuery = { __typename?: 'Query' } & { + getFilterViewData: { __typename?: 'GetFilterViewDataResult' } & { + units: Array<{ __typename?: 'Unit' } & Pick & { SystemObject?: Maybe<{ __typename?: 'SystemObject' } & Pick> }>; + projects: Array< + { __typename?: 'Project' } & Pick & { SystemObject?: Maybe<{ __typename?: 'SystemObject' } & Pick> } + >; + }; +}; export type GetObjectChildrenQueryVariables = Exact<{ input: GetObjectChildrenInput; }>; - -export type GetObjectChildrenQuery = ( - { __typename?: 'Query' } - & { - getObjectChildren: ( - { __typename?: 'GetObjectChildrenResult' } - & Pick - & { - entries: Array<( - { __typename?: 'NavigationResultEntry' } - & Pick - )> - } - ) - } -); +export type GetObjectChildrenQuery = { __typename?: 'Query' } & { + getObjectChildren: { __typename?: 'GetObjectChildrenResult' } & Pick & { + entries: Array<{ __typename?: 'NavigationResultEntry' } & Pick>; + }; +}; export type GetIntermediaryFileQueryVariables = Exact<{ input: GetIntermediaryFileInput; }>; - -export type GetIntermediaryFileQuery = ( - { __typename?: 'Query' } - & { - getIntermediaryFile: ( - { __typename?: 'GetIntermediaryFileResult' } - & { - IntermediaryFile?: Maybe<( - { __typename?: 'IntermediaryFile' } - & Pick - )> - } - ) - } -); +export type GetIntermediaryFileQuery = { __typename?: 'Query' } & { + getIntermediaryFile: { __typename?: 'GetIntermediaryFileResult' } & { + IntermediaryFile?: Maybe<{ __typename?: 'IntermediaryFile' } & Pick>; + }; +}; export type GetSceneQueryVariables = Exact<{ input: GetSceneInput; }>; - -export type GetSceneQuery = ( - { __typename?: 'Query' } - & { - getScene: ( - { __typename?: 'GetSceneResult' } - & { - Scene?: Maybe<( - { __typename?: 'Scene' } - & Pick - )> - } - ) - } -); +export type GetSceneQuery = { __typename?: 'Query' } & { getScene: { __typename?: 'GetSceneResult' } & { Scene?: Maybe<{ __typename?: 'Scene' } & Pick> } }; export type GetAssetDetailsForSystemObjectQueryVariables = Exact<{ input: GetAssetDetailsForSystemObjectInput; }>; - -export type GetAssetDetailsForSystemObjectQuery = ( - { __typename?: 'Query' } - & { - getAssetDetailsForSystemObject: ( - { __typename?: 'GetAssetDetailsForSystemObjectResult' } - & { - assetDetails: Array<( - { __typename?: 'AssetDetail' } - & Pick - )> - } - ) - } -); +export type GetAssetDetailsForSystemObjectQuery = { __typename?: 'Query' } & { + getAssetDetailsForSystemObject: { __typename?: 'GetAssetDetailsForSystemObjectResult' } & { + assetDetails: Array<{ __typename?: 'AssetDetail' } & Pick>; + }; +}; export type GetDetailsTabDataForObjectQueryVariables = Exact<{ input: GetDetailsTabDataForObjectInput; }>; - -export type GetDetailsTabDataForObjectQuery = ( - { __typename?: 'Query' } - & { - getDetailsTabDataForObject: ( - { __typename?: 'GetDetailsTabDataForObjectResult' } - & { - Unit?: Maybe<( - { __typename?: 'UnitDetailFields' } - & Pick - )>, Project?: Maybe<( - { __typename?: 'ProjectDetailFields' } - & Pick - )>, Subject?: Maybe<( - { __typename?: 'SubjectDetailFields' } - & Pick - )>, Item?: Maybe<( - { __typename?: 'ItemDetailFields' } - & Pick - )>, CaptureData?: Maybe<( - { __typename?: 'CaptureDataDetailFields' } - & Pick - & { - folders: Array<( - { __typename?: 'IngestFolder' } - & Pick - )> - } - )>, Model?: Maybe<( - { __typename?: 'ModelDetailFields' } - & Pick - & { - uvMaps: Array<( - { __typename?: 'IngestUVMap' } - & Pick - )> - } - )>, Scene?: Maybe<( - { __typename?: 'SceneDetailFields' } - & Pick - )>, IntermediaryFile?: Maybe<( - { __typename?: 'IntermediaryFileDetailFields' } - & Pick - )>, ProjectDocumentation?: Maybe<( - { __typename?: 'ProjectDocumentationDetailFields' } - & Pick - )>, Asset?: Maybe<( - { __typename?: 'AssetDetailFields' } - & Pick - )>, AssetVersion?: Maybe<( - { __typename?: 'AssetVersionDetailFields' } - & Pick - )>, Actor?: Maybe<( - { __typename?: 'ActorDetailFields' } - & Pick - )>, Stakeholder?: Maybe<( - { __typename?: 'StakeholderDetailFields' } - & Pick - )> - } - ) - } -); +export type GetDetailsTabDataForObjectQuery = { __typename?: 'Query' } & { + getDetailsTabDataForObject: { __typename?: 'GetDetailsTabDataForObjectResult' } & { + Unit?: Maybe<{ __typename?: 'UnitDetailFields' } & Pick>; + Project?: Maybe<{ __typename?: 'ProjectDetailFields' } & Pick>; + Subject?: Maybe< + { __typename?: 'SubjectDetailFields' } & Pick + >; + Item?: Maybe< + { __typename?: 'ItemDetailFields' } & Pick< + ItemDetailFields, + 'EntireSubject' | 'Altitude' | 'Latitude' | 'Longitude' | 'R0' | 'R1' | 'R2' | 'R3' | 'TS0' | 'TS1' | 'TS2' + > + >; + CaptureData?: Maybe< + { __typename?: 'CaptureDataDetailFields' } & Pick< + CaptureDataDetailFields, + | 'captureMethod' + | 'dateCaptured' + | 'datasetType' + | 'description' + | 'cameraSettingUniform' + | 'datasetFieldId' + | 'itemPositionType' + | 'itemPositionFieldId' + | 'itemArrangementFieldId' + | 'focusType' + | 'lightsourceType' + | 'backgroundRemovalMethod' + | 'clusterType' + | 'clusterGeometryFieldId' + > & { folders: Array<{ __typename?: 'IngestFolder' } & Pick> } + >; + Model?: Maybe< + { __typename?: 'ModelDetailFields' } & Pick< + ModelDetailFields, + | 'size' + | 'master' + | 'authoritative' + | 'creationMethod' + | 'modality' + | 'purpose' + | 'units' + | 'dateCaptured' + | 'modelFileType' + | 'roughness' + | 'metalness' + | 'pointCount' + | 'faceCount' + | 'isWatertight' + | 'hasNormals' + | 'hasVertexColor' + | 'hasUVSpace' + | 'boundingBoxP1X' + | 'boundingBoxP1Y' + | 'boundingBoxP1Z' + | 'boundingBoxP2X' + | 'boundingBoxP2Y' + | 'boundingBoxP2Z' + > & { uvMaps: Array<{ __typename?: 'IngestUVMap' } & Pick> } + >; + Scene?: Maybe<{ __typename?: 'SceneDetailFields' } & Pick>; + IntermediaryFile?: Maybe<{ __typename?: 'IntermediaryFileDetailFields' } & Pick>; + ProjectDocumentation?: Maybe<{ __typename?: 'ProjectDocumentationDetailFields' } & Pick>; + Asset?: Maybe<{ __typename?: 'AssetDetailFields' } & Pick>; + AssetVersion?: Maybe<{ __typename?: 'AssetVersionDetailFields' } & Pick>; + Actor?: Maybe<{ __typename?: 'ActorDetailFields' } & Pick>; + Stakeholder?: Maybe< + { __typename?: 'StakeholderDetailFields' } & Pick< + StakeholderDetailFields, + 'OrganizationName' | 'EmailAddress' | 'PhoneNumberMobile' | 'PhoneNumberOffice' | 'MailingAddress' + > + >; + }; +}; export type GetSourceObjectIdentiferQueryVariables = Exact<{ input: GetSourceObjectIdentiferInput; }>; - -export type GetSourceObjectIdentiferQuery = ( - { __typename?: 'Query' } - & { - getSourceObjectIdentifer: ( - { __typename?: 'GetSourceObjectIdentiferResult' } - & { - sourceObjectIdentifiers: Array<( - { __typename?: 'SourceObjectIdentifier' } - & Pick - )> - } - ) - } -); +export type GetSourceObjectIdentiferQuery = { __typename?: 'Query' } & { + getSourceObjectIdentifer: { __typename?: 'GetSourceObjectIdentiferResult' } & { + sourceObjectIdentifiers: Array<{ __typename?: 'SourceObjectIdentifier' } & Pick>; + }; +}; export type GetSystemObjectDetailsQueryVariables = Exact<{ input: GetSystemObjectDetailsInput; }>; - -export type GetSystemObjectDetailsQuery = ( - { __typename?: 'Query' } - & { - getSystemObjectDetails: ( - { __typename?: 'GetSystemObjectDetailsResult' } - & Pick - & { - identifiers: Array<( - { __typename?: 'IngestIdentifier' } - & Pick - )>, unit?: Maybe<( - { __typename?: 'RepositoryPath' } - & Pick - )>, project?: Maybe<( - { __typename?: 'RepositoryPath' } - & Pick - )>, subject?: Maybe<( - { __typename?: 'RepositoryPath' } - & Pick - )>, item?: Maybe<( - { __typename?: 'RepositoryPath' } - & Pick - )>, objectAncestors: Array - )>>, sourceObjects: Array<( - { __typename?: 'RelatedObject' } - & Pick - )>, derivedObjects: Array<( - { __typename?: 'RelatedObject' } - & Pick - )> - } - ) - } -); +export type GetSystemObjectDetailsQuery = { __typename?: 'Query' } & { + getSystemObjectDetails: { __typename?: 'GetSystemObjectDetailsResult' } & Pick< + GetSystemObjectDetailsResult, + 'idObject' | 'name' | 'retired' | 'objectType' | 'allowed' | 'publishedState' | 'thumbnail' + > & { + identifiers: Array<{ __typename?: 'IngestIdentifier' } & Pick>; + unit?: Maybe<{ __typename?: 'RepositoryPath' } & Pick>; + project?: Maybe<{ __typename?: 'RepositoryPath' } & Pick>; + subject?: Maybe<{ __typename?: 'RepositoryPath' } & Pick>; + item?: Maybe<{ __typename?: 'RepositoryPath' } & Pick>; + objectAncestors: Array>>; + sourceObjects: Array<{ __typename?: 'RelatedObject' } & Pick>; + derivedObjects: Array<{ __typename?: 'RelatedObject' } & Pick>; + }; +}; export type GetVersionsForSystemObjectQueryVariables = Exact<{ input: GetVersionsForSystemObjectInput; }>; - -export type GetVersionsForSystemObjectQuery = ( - { __typename?: 'Query' } - & { - getVersionsForSystemObject: ( - { __typename?: 'GetVersionsForSystemObjectResult' } - & { - versions: Array<( - { __typename?: 'DetailVersion' } - & Pick - )> - } - ) - } -); +export type GetVersionsForSystemObjectQuery = { __typename?: 'Query' } & { + getVersionsForSystemObject: { __typename?: 'GetVersionsForSystemObjectResult' } & { + versions: Array<{ __typename?: 'DetailVersion' } & Pick>; + }; +}; export type GetIngestionItemsForSubjectsQueryVariables = Exact<{ input: GetIngestionItemsForSubjectsInput; }>; - -export type GetIngestionItemsForSubjectsQuery = ( - { __typename?: 'Query' } - & { - getIngestionItemsForSubjects: ( - { __typename?: 'GetIngestionItemsForSubjectsResult' } - & { - Item: Array<( - { __typename?: 'Item' } - & Pick - )> - } - ) - } -); +export type GetIngestionItemsForSubjectsQuery = { __typename?: 'Query' } & { + getIngestionItemsForSubjects: { __typename?: 'GetIngestionItemsForSubjectsResult' } & { + Item: Array<{ __typename?: 'Item' } & Pick>; + }; +}; export type GetIngestionProjectsForSubjectsQueryVariables = Exact<{ input: GetIngestionProjectsForSubjectsInput; }>; - -export type GetIngestionProjectsForSubjectsQuery = ( - { __typename?: 'Query' } - & { - getIngestionProjectsForSubjects: ( - { __typename?: 'GetIngestionProjectsForSubjectsResult' } - & { - Project: Array<( - { __typename?: 'Project' } - & Pick - )> - } - ) - } -); +export type GetIngestionProjectsForSubjectsQuery = { __typename?: 'Query' } & { + getIngestionProjectsForSubjects: { __typename?: 'GetIngestionProjectsForSubjectsResult' } & { + Project: Array<{ __typename?: 'Project' } & Pick>; + }; +}; export type GetItemQueryVariables = Exact<{ input: GetItemInput; }>; - -export type GetItemQuery = ( - { __typename?: 'Query' } - & { - getItem: ( - { __typename?: 'GetItemResult' } - & { - Item?: Maybe<( - { __typename?: 'Item' } - & Pick - )> - } - ) - } -); +export type GetItemQuery = { __typename?: 'Query' } & { getItem: { __typename?: 'GetItemResult' } & { Item?: Maybe<{ __typename?: 'Item' } & Pick> } }; export type GetItemsForSubjectQueryVariables = Exact<{ input: GetItemsForSubjectInput; }>; - -export type GetItemsForSubjectQuery = ( - { __typename?: 'Query' } - & { - getItemsForSubject: ( - { __typename?: 'GetItemsForSubjectResult' } - & { - Item: Array<( - { __typename?: 'Item' } - & Pick - )> - } - ) - } -); +export type GetItemsForSubjectQuery = { __typename?: 'Query' } & { + getItemsForSubject: { __typename?: 'GetItemsForSubjectResult' } & { Item: Array<{ __typename?: 'Item' } & Pick> }; +}; export type GetObjectsForItemQueryVariables = Exact<{ input: GetObjectsForItemInput; }>; - -export type GetObjectsForItemQuery = ( - { __typename?: 'Query' } - & { - getObjectsForItem: ( - { __typename?: 'GetObjectsForItemResult' } - & { - CaptureData: Array<( - { __typename?: 'CaptureData' } - & Pick - )>, Model: Array<( - { __typename?: 'Model' } - & Pick - )>, Scene: Array<( - { __typename?: 'Scene' } - & Pick - )>, IntermediaryFile: Array<( - { __typename?: 'IntermediaryFile' } - & Pick - )>, ProjectDocumentation: Array<( - { __typename?: 'ProjectDocumentation' } - & Pick - )> - } - ) - } -); +export type GetObjectsForItemQuery = { __typename?: 'Query' } & { + getObjectsForItem: { __typename?: 'GetObjectsForItemResult' } & { + CaptureData: Array<{ __typename?: 'CaptureData' } & Pick>; + Model: Array<{ __typename?: 'Model' } & Pick>; + Scene: Array<{ __typename?: 'Scene' } & Pick>; + IntermediaryFile: Array<{ __typename?: 'IntermediaryFile' } & Pick>; + ProjectDocumentation: Array<{ __typename?: 'ProjectDocumentation' } & Pick>; + }; +}; export type GetProjectQueryVariables = Exact<{ input: GetProjectInput; }>; - -export type GetProjectQuery = ( - { __typename?: 'Query' } - & { - getProject: ( - { __typename?: 'GetProjectResult' } - & { - Project?: Maybe<( - { __typename?: 'Project' } - & Pick - )> - } - ) - } -); +export type GetProjectQuery = { __typename?: 'Query' } & { + getProject: { __typename?: 'GetProjectResult' } & { Project?: Maybe<{ __typename?: 'Project' } & Pick> }; +}; export type GetProjectDocumentationQueryVariables = Exact<{ input: GetProjectDocumentationInput; }>; - -export type GetProjectDocumentationQuery = ( - { __typename?: 'Query' } - & { - getProjectDocumentation: ( - { __typename?: 'GetProjectDocumentationResult' } - & { - ProjectDocumentation?: Maybe<( - { __typename?: 'ProjectDocumentation' } - & Pick - )> - } - ) - } -); +export type GetProjectDocumentationQuery = { __typename?: 'Query' } & { + getProjectDocumentation: { __typename?: 'GetProjectDocumentationResult' } & { + ProjectDocumentation?: Maybe<{ __typename?: 'ProjectDocumentation' } & Pick>; + }; +}; export type GetSubjectQueryVariables = Exact<{ input: GetSubjectInput; }>; - -export type GetSubjectQuery = ( - { __typename?: 'Query' } - & { - getSubject: ( - { __typename?: 'GetSubjectResult' } - & { - Subject?: Maybe<( - { __typename?: 'Subject' } - & Pick - )> - } - ) - } -); +export type GetSubjectQuery = { __typename?: 'Query' } & { + getSubject: { __typename?: 'GetSubjectResult' } & { Subject?: Maybe<{ __typename?: 'Subject' } & Pick> }; +}; export type GetSubjectsForUnitQueryVariables = Exact<{ input: GetSubjectsForUnitInput; }>; - -export type GetSubjectsForUnitQuery = ( - { __typename?: 'Query' } - & { - getSubjectsForUnit: ( - { __typename?: 'GetSubjectsForUnitResult' } - & { - Subject: Array<( - { __typename?: 'Subject' } - & Pick - )> - } - ) - } -); +export type GetSubjectsForUnitQuery = { __typename?: 'Query' } & { + getSubjectsForUnit: { __typename?: 'GetSubjectsForUnitResult' } & { Subject: Array<{ __typename?: 'Subject' } & Pick> }; +}; export type GetUnitQueryVariables = Exact<{ input: GetUnitInput; }>; - -export type GetUnitQuery = ( - { __typename?: 'Query' } - & { - getUnit: ( - { __typename?: 'GetUnitResult' } - & { - Unit?: Maybe<( - { __typename?: 'Unit' } - & Pick - )> - } - ) - } -); +export type GetUnitQuery = { __typename?: 'Query' } & { getUnit: { __typename?: 'GetUnitResult' } & { Unit?: Maybe<{ __typename?: 'Unit' } & Pick> } }; export type SearchIngestionSubjectsQueryVariables = Exact<{ input: SearchIngestionSubjectsInput; }>; +export type SearchIngestionSubjectsQuery = { __typename?: 'Query' } & { + searchIngestionSubjects: { __typename?: 'SearchIngestionSubjectsResult' } & { + SubjectUnitIdentifier: Array< + { __typename?: 'SubjectUnitIdentifier' } & Pick + >; + }; +}; -export type SearchIngestionSubjectsQuery = ( - { __typename?: 'Query' } - & { - searchIngestionSubjects: ( - { __typename?: 'SearchIngestionSubjectsResult' } - & { - SubjectUnitIdentifier: Array<( - { __typename?: 'SubjectUnitIdentifier' } - & Pick - )> - } - ) - } -); - -export type GetCurrentUserQueryVariables = Exact<{ [key: string]: never; }>; - +export type GetCurrentUserQueryVariables = Exact<{ [key: string]: never }>; -export type GetCurrentUserQuery = ( - { __typename?: 'Query' } - & { - getCurrentUser: ( - { __typename?: 'GetCurrentUserResult' } - & { - User?: Maybe<( - { __typename?: 'User' } - & Pick - )> - } - ) - } -); +export type GetCurrentUserQuery = { __typename?: 'Query' } & { + getCurrentUser: { __typename?: 'GetCurrentUserResult' } & { + User?: Maybe< + { __typename?: 'User' } & Pick< + User, + 'idUser' | 'Name' | 'Active' | 'DateActivated' | 'DateDisabled' | 'EmailAddress' | 'EmailSettings' | 'SecurityID' | 'WorkflowNotificationTime' + > + >; + }; +}; export type GetUserQueryVariables = Exact<{ input: GetUserInput; }>; - -export type GetUserQuery = ( - { __typename?: 'Query' } - & { - getUser: ( - { __typename?: 'GetUserResult' } - & { - User?: Maybe<( - { __typename?: 'User' } - & Pick - )> - } - ) - } -); +export type GetUserQuery = { __typename?: 'Query' } & { + getUser: { __typename?: 'GetUserResult' } & { User?: Maybe<{ __typename?: 'User' } & Pick> }; +}; export type GetVocabularyQueryVariables = Exact<{ input: GetVocabularyInput; }>; - -export type GetVocabularyQuery = ( - { __typename?: 'Query' } - & { - getVocabulary: ( - { __typename?: 'GetVocabularyResult' } - & { - Vocabulary?: Maybe<( - { __typename?: 'Vocabulary' } - & Pick - )> - } - ) - } -); +export type GetVocabularyQuery = { __typename?: 'Query' } & { + getVocabulary: { __typename?: 'GetVocabularyResult' } & { Vocabulary?: Maybe<{ __typename?: 'Vocabulary' } & Pick> }; +}; export type GetVocabularyEntriesQueryVariables = Exact<{ input: GetVocabularyEntriesInput; }>; - -export type GetVocabularyEntriesQuery = ( - { __typename?: 'Query' } - & { - getVocabularyEntries: ( - { __typename?: 'GetVocabularyEntriesResult' } - & { - VocabularyEntries: Array<( - { __typename?: 'VocabularyEntry' } - & Pick - & { - Vocabulary: Array<( - { __typename?: 'Vocabulary' } - & Pick - )> - } - )> - } - ) - } -); +export type GetVocabularyEntriesQuery = { __typename?: 'Query' } & { + getVocabularyEntries: { __typename?: 'GetVocabularyEntriesResult' } & { + VocabularyEntries: Array< + { __typename?: 'VocabularyEntry' } & Pick & { + Vocabulary: Array<{ __typename?: 'Vocabulary' } & Pick>; + } + >; + }; +}; export type GetWorkflowQueryVariables = Exact<{ input: GetWorkflowInput; }>; - -export type GetWorkflowQuery = ( - { __typename?: 'Query' } - & { - getWorkflow: ( - { __typename?: 'GetWorkflowResult' } - & { - Workflow?: Maybe<( - { __typename?: 'Workflow' } - & Pick - )> - } - ) - } -); - +export type GetWorkflowQuery = { __typename?: 'Query' } & { + getWorkflow: { __typename?: 'GetWorkflowResult' } & { Workflow?: Maybe<{ __typename?: 'Workflow' } & Pick> }; +}; export const DiscardUploadedAssetVersionsDocument = gql` mutation discardUploadedAssetVersions($input: DiscardUploadedAssetVersionsInput!) { - discardUploadedAssetVersions(input: $input) { - success - } -} - `; + discardUploadedAssetVersions(input: $input) { + success + } + } +`; export type DiscardUploadedAssetVersionsMutationFn = Apollo.MutationFunction; /** @@ -3172,7 +2591,9 @@ export type DiscardUploadedAssetVersionsMutationFn = Apollo.MutationFunction) { +export function useDiscardUploadedAssetVersionsMutation( + baseOptions?: Apollo.MutationHookOptions +) { return Apollo.useMutation(DiscardUploadedAssetVersionsDocument, baseOptions); } export type DiscardUploadedAssetVersionsMutationHookResult = ReturnType; @@ -3180,13 +2601,13 @@ export type DiscardUploadedAssetVersionsMutationResult = Apollo.MutationResult; export const UploadAssetDocument = gql` mutation uploadAsset($file: Upload!, $type: Int!) { - uploadAsset(file: $file, type: $type) { - status - idAssetVersions - error - } -} - `; + uploadAsset(file: $file, type: $type) { + status + idAssetVersions + error + } + } +`; export type UploadAssetMutationFn = Apollo.MutationFunction; /** @@ -3215,13 +2636,13 @@ export type UploadAssetMutationResult = Apollo.MutationResult; export const CreateCaptureDataDocument = gql` mutation createCaptureData($input: CreateCaptureDataInput!) { - createCaptureData(input: $input) { - CaptureData { - idCaptureData + createCaptureData(input: $input) { + CaptureData { + idCaptureData + } + } } - } -} - `; +`; export type CreateCaptureDataMutationFn = Apollo.MutationFunction; /** @@ -3249,13 +2670,13 @@ export type CreateCaptureDataMutationResult = Apollo.MutationResult; export const CreateCaptureDataPhotoDocument = gql` mutation createCaptureDataPhoto($input: CreateCaptureDataPhotoInput!) { - createCaptureDataPhoto(input: $input) { - CaptureDataPhoto { - idCaptureDataPhoto + createCaptureDataPhoto(input: $input) { + CaptureDataPhoto { + idCaptureDataPhoto + } + } } - } -} - `; +`; export type CreateCaptureDataPhotoMutationFn = Apollo.MutationFunction; /** @@ -3283,11 +2704,11 @@ export type CreateCaptureDataPhotoMutationResult = Apollo.MutationResult; export const IngestDataDocument = gql` mutation ingestData($input: IngestDataInput!) { - ingestData(input: $input) { - success - } -} - `; + ingestData(input: $input) { + success + } + } +`; export type IngestDataMutationFn = Apollo.MutationFunction; /** @@ -3315,13 +2736,13 @@ export type IngestDataMutationResult = Apollo.MutationResult export type IngestDataMutationOptions = Apollo.BaseMutationOptions; export const CreateModelDocument = gql` mutation createModel($input: CreateModelInput!) { - createModel(input: $input) { - Model { - idModel + createModel(input: $input) { + Model { + idModel + } + } } - } -} - `; +`; export type CreateModelMutationFn = Apollo.MutationFunction; /** @@ -3349,13 +2770,13 @@ export type CreateModelMutationResult = Apollo.MutationResult; export const CreateSceneDocument = gql` mutation createScene($input: CreateSceneInput!) { - createScene(input: $input) { - Scene { - idScene + createScene(input: $input) { + Scene { + idScene + } + } } - } -} - `; +`; export type CreateSceneMutationFn = Apollo.MutationFunction; /** @@ -3383,11 +2804,11 @@ export type CreateSceneMutationResult = Apollo.MutationResult; export const UpdateObjectDetailsDocument = gql` mutation updateObjectDetails($input: UpdateObjectDetailsInput!) { - updateObjectDetails(input: $input) { - success - } -} - `; + updateObjectDetails(input: $input) { + success + } + } +`; export type UpdateObjectDetailsMutationFn = Apollo.MutationFunction; /** @@ -3415,13 +2836,13 @@ export type UpdateObjectDetailsMutationResult = Apollo.MutationResult; export const CreateItemDocument = gql` mutation createItem($input: CreateItemInput!) { - createItem(input: $input) { - Item { - idItem + createItem(input: $input) { + Item { + idItem + } + } } - } -} - `; +`; export type CreateItemMutationFn = Apollo.MutationFunction; /** @@ -3449,13 +2870,13 @@ export type CreateItemMutationResult = Apollo.MutationResult export type CreateItemMutationOptions = Apollo.BaseMutationOptions; export const CreateProjectDocument = gql` mutation createProject($input: CreateProjectInput!) { - createProject(input: $input) { - Project { - idProject + createProject(input: $input) { + Project { + idProject + } + } } - } -} - `; +`; export type CreateProjectMutationFn = Apollo.MutationFunction; /** @@ -3483,13 +2904,13 @@ export type CreateProjectMutationResult = Apollo.MutationResult; export const CreateSubjectDocument = gql` mutation createSubject($input: CreateSubjectInput!) { - createSubject(input: $input) { - Subject { - idSubject + createSubject(input: $input) { + Subject { + idSubject + } + } } - } -} - `; +`; export type CreateSubjectMutationFn = Apollo.MutationFunction; /** @@ -3517,13 +2938,13 @@ export type CreateSubjectMutationResult = Apollo.MutationResult; export const CreateUnitDocument = gql` mutation createUnit($input: CreateUnitInput!) { - createUnit(input: $input) { - Unit { - idUnit + createUnit(input: $input) { + Unit { + idUnit + } + } } - } -} - `; +`; export type CreateUnitMutationFn = Apollo.MutationFunction; /** @@ -3551,16 +2972,16 @@ export type CreateUnitMutationResult = Apollo.MutationResult export type CreateUnitMutationOptions = Apollo.BaseMutationOptions; export const CreateUserDocument = gql` mutation createUser($input: CreateUserInput!) { - createUser(input: $input) { - User { - idUser - Name - Active - DateActivated + createUser(input: $input) { + User { + idUser + Name + Active + DateActivated + } + } } - } -} - `; +`; export type CreateUserMutationFn = Apollo.MutationFunction; /** @@ -3588,13 +3009,13 @@ export type CreateUserMutationResult = Apollo.MutationResult export type CreateUserMutationOptions = Apollo.BaseMutationOptions; export const CreateVocabularyDocument = gql` mutation createVocabulary($input: CreateVocabularyInput!) { - createVocabulary(input: $input) { - Vocabulary { - idVocabulary + createVocabulary(input: $input) { + Vocabulary { + idVocabulary + } + } } - } -} - `; +`; export type CreateVocabularyMutationFn = Apollo.MutationFunction; /** @@ -3622,13 +3043,13 @@ export type CreateVocabularyMutationResult = Apollo.MutationResult; export const CreateVocabularySetDocument = gql` mutation createVocabularySet($input: CreateVocabularySetInput!) { - createVocabularySet(input: $input) { - VocabularySet { - idVocabularySet + createVocabularySet(input: $input) { + VocabularySet { + idVocabularySet + } + } } - } -} - `; +`; export type CreateVocabularySetMutationFn = Apollo.MutationFunction; /** @@ -3656,13 +3077,13 @@ export type CreateVocabularySetMutationResult = Apollo.MutationResult; export const GetAccessPolicyDocument = gql` query getAccessPolicy($input: GetAccessPolicyInput!) { - getAccessPolicy(input: $input) { - AccessPolicy { - idAccessPolicy + getAccessPolicy(input: $input) { + AccessPolicy { + idAccessPolicy + } + } } - } -} - `; +`; /** * __useGetAccessPolicyQuery__ @@ -3691,13 +3112,13 @@ export type GetAccessPolicyLazyQueryHookResult = ReturnType; export const GetAssetDocument = gql` query getAsset($input: GetAssetInput!) { - getAsset(input: $input) { - Asset { - idAsset + getAsset(input: $input) { + Asset { + idAsset + } + } } - } -} - `; +`; /** * __useGetAssetQuery__ @@ -3726,99 +3147,99 @@ export type GetAssetLazyQueryHookResult = ReturnType; export const GetAssetVersionsDetailsDocument = gql` query getAssetVersionsDetails($input: GetAssetVersionsDetailsInput!) { - getAssetVersionsDetails(input: $input) { - valid - Details { - idAssetVersion - SubjectUnitIdentifier { - idSubject - SubjectName - UnitAbbreviation - IdentifierPublic - IdentifierCollection - } - Project { - idProject - Name - } - Item { - idItem - Name - EntireSubject - } - CaptureDataPhoto { - idAssetVersion - dateCaptured - datasetType - systemCreated - description - cameraSettingUniform - datasetFieldId - itemPositionType - itemPositionFieldId - itemArrangementFieldId - focusType - lightsourceType - backgroundRemovalMethod - clusterType - clusterGeometryFieldId - directory - folders { - name - variantType - } - identifiers { - identifier - identifierType - } - } - Model { - idAssetVersion - systemCreated - master - authoritative - creationMethod - modality - purpose - units - dateCaptured - modelFileType - directory - identifiers { - identifier - identifierType - } - uvMaps { - name - edgeLength - mapType - } - roughness - metalness - pointCount - faceCount - isWatertight - hasNormals - hasVertexColor - hasUVSpace - boundingBoxP1X - boundingBoxP1Y - boundingBoxP1Z - boundingBoxP2X - boundingBoxP2Y - boundingBoxP2Z - } - Scene { - idAssetVersion - identifiers { - identifier - identifierType + getAssetVersionsDetails(input: $input) { + valid + Details { + idAssetVersion + SubjectUnitIdentifier { + idSubject + SubjectName + UnitAbbreviation + IdentifierPublic + IdentifierCollection + } + Project { + idProject + Name + } + Item { + idItem + Name + EntireSubject + } + CaptureDataPhoto { + idAssetVersion + dateCaptured + datasetType + systemCreated + description + cameraSettingUniform + datasetFieldId + itemPositionType + itemPositionFieldId + itemArrangementFieldId + focusType + lightsourceType + backgroundRemovalMethod + clusterType + clusterGeometryFieldId + directory + folders { + name + variantType + } + identifiers { + identifier + identifierType + } + } + Model { + idAssetVersion + systemCreated + master + authoritative + creationMethod + modality + purpose + units + dateCaptured + modelFileType + directory + identifiers { + identifier + identifierType + } + uvMaps { + name + edgeLength + mapType + } + roughness + metalness + pointCount + faceCount + isWatertight + hasNormals + hasVertexColor + hasUVSpace + boundingBoxP1X + boundingBoxP1Y + boundingBoxP1Z + boundingBoxP2X + boundingBoxP2Y + boundingBoxP2Z + } + Scene { + idAssetVersion + identifiers { + identifier + identifierType + } + } + } } - } } - } -} - `; +`; /** * __useGetAssetVersionsDetailsQuery__ @@ -3847,15 +3268,15 @@ export type GetAssetVersionsDetailsLazyQueryHookResult = ReturnType; export const GetContentsForAssetVersionsDocument = gql` query getContentsForAssetVersions($input: GetContentsForAssetVersionsInput!) { - getContentsForAssetVersions(input: $input) { - AssetVersionContent { - idAssetVersion - folders - all + getContentsForAssetVersions(input: $input) { + AssetVersionContent { + idAssetVersion + folders + all + } + } } - } -} - `; +`; /** * __useGetContentsForAssetVersionsQuery__ @@ -3884,23 +3305,23 @@ export type GetContentsForAssetVersionsLazyQueryHookResult = ReturnType; export const GetUploadedAssetVersionDocument = gql` query getUploadedAssetVersion { - getUploadedAssetVersion { - AssetVersion { - idAssetVersion - StorageSize - FileName - DateCreated - Asset { - idAsset - VAssetType { - idVocabulary - Term + getUploadedAssetVersion { + AssetVersion { + idAssetVersion + StorageSize + FileName + DateCreated + Asset { + idAsset + VAssetType { + idVocabulary + Term + } + } + } } - } } - } -} - `; +`; /** * __useGetUploadedAssetVersionQuery__ @@ -3928,13 +3349,13 @@ export type GetUploadedAssetVersionLazyQueryHookResult = ReturnType; export const GetCaptureDataDocument = gql` query getCaptureData($input: GetCaptureDataInput!) { - getCaptureData(input: $input) { - CaptureData { - idCaptureData + getCaptureData(input: $input) { + CaptureData { + idCaptureData + } + } } - } -} - `; +`; /** * __useGetCaptureDataQuery__ @@ -3963,13 +3384,13 @@ export type GetCaptureDataLazyQueryHookResult = ReturnType; export const GetCaptureDataPhotoDocument = gql` query getCaptureDataPhoto($input: GetCaptureDataPhotoInput!) { - getCaptureDataPhoto(input: $input) { - CaptureDataPhoto { - idCaptureDataPhoto + getCaptureDataPhoto(input: $input) { + CaptureDataPhoto { + idCaptureDataPhoto + } + } } - } -} - `; +`; /** * __useGetCaptureDataPhotoQuery__ @@ -3998,11 +3419,11 @@ export type GetCaptureDataPhotoLazyQueryHookResult = ReturnType; export const AreCameraSettingsUniformDocument = gql` query areCameraSettingsUniform($input: AreCameraSettingsUniformInput!) { - areCameraSettingsUniform(input: $input) { - isUniform - } -} - `; + areCameraSettingsUniform(input: $input) { + isUniform + } + } +`; /** * __useAreCameraSettingsUniformQuery__ @@ -4031,13 +3452,13 @@ export type AreCameraSettingsUniformLazyQueryHookResult = ReturnType; export const GetLicenseDocument = gql` query getLicense($input: GetLicenseInput!) { - getLicense(input: $input) { - License { - idLicense + getLicense(input: $input) { + License { + idLicense + } + } } - } -} - `; +`; /** * __useGetLicenseQuery__ @@ -4066,13 +3487,13 @@ export type GetLicenseLazyQueryHookResult = ReturnType; export const GetModelDocument = gql` query getModel($input: GetModelInput!) { - getModel(input: $input) { - Model { - idModel + getModel(input: $input) { + Model { + idModel + } + } } - } -} - `; +`; /** * __useGetModelQuery__ @@ -4101,24 +3522,24 @@ export type GetModelLazyQueryHookResult = ReturnType; export const GetFilterViewDataDocument = gql` query getFilterViewData { - getFilterViewData { - units { - idUnit - Name - SystemObject { - idSystemObject - } - } - projects { - idProject - Name - SystemObject { - idSystemObject - } + getFilterViewData { + units { + idUnit + Name + SystemObject { + idSystemObject + } + } + projects { + idProject + Name + SystemObject { + idSystemObject + } + } + } } - } -} - `; +`; /** * __useGetFilterViewDataQuery__ @@ -4146,20 +3567,20 @@ export type GetFilterViewDataLazyQueryHookResult = ReturnType; export const GetObjectChildrenDocument = gql` query getObjectChildren($input: GetObjectChildrenInput!) { - getObjectChildren(input: $input) { - success - error - entries { - idSystemObject - name - objectType - idObject - metadata + getObjectChildren(input: $input) { + success + error + entries { + idSystemObject + name + objectType + idObject + metadata + } + metadataColumns + } } - metadataColumns - } -} - `; +`; /** * __useGetObjectChildrenQuery__ @@ -4188,13 +3609,13 @@ export type GetObjectChildrenLazyQueryHookResult = ReturnType; export const GetIntermediaryFileDocument = gql` query getIntermediaryFile($input: GetIntermediaryFileInput!) { - getIntermediaryFile(input: $input) { - IntermediaryFile { - idIntermediaryFile + getIntermediaryFile(input: $input) { + IntermediaryFile { + idIntermediaryFile + } + } } - } -} - `; +`; /** * __useGetIntermediaryFileQuery__ @@ -4223,13 +3644,13 @@ export type GetIntermediaryFileLazyQueryHookResult = ReturnType; export const GetSceneDocument = gql` query getScene($input: GetSceneInput!) { - getScene(input: $input) { - Scene { - idScene + getScene(input: $input) { + Scene { + idScene + } + } } - } -} - `; +`; /** * __useGetSceneQuery__ @@ -4258,19 +3679,19 @@ export type GetSceneLazyQueryHookResult = ReturnType; export const GetAssetDetailsForSystemObjectDocument = gql` query getAssetDetailsForSystemObject($input: GetAssetDetailsForSystemObjectInput!) { - getAssetDetailsForSystemObject(input: $input) { - assetDetails { - idSystemObject - name - path - assetType - version - dateCreated - size + getAssetDetailsForSystemObject(input: $input) { + assetDetails { + idSystemObject + name + path + assetType + version + dateCreated + size + } + } } - } -} - `; +`; /** * __useGetAssetDetailsForSystemObjectQuery__ @@ -4291,7 +3712,9 @@ export const GetAssetDetailsForSystemObjectDocument = gql` export function useGetAssetDetailsForSystemObjectQuery(baseOptions?: Apollo.QueryHookOptions) { return Apollo.useQuery(GetAssetDetailsForSystemObjectDocument, baseOptions); } -export function useGetAssetDetailsForSystemObjectLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { +export function useGetAssetDetailsForSystemObjectLazyQuery( + baseOptions?: Apollo.LazyQueryHookOptions +) { return Apollo.useLazyQuery(GetAssetDetailsForSystemObjectDocument, baseOptions); } export type GetAssetDetailsForSystemObjectQueryHookResult = ReturnType; @@ -4299,125 +3722,125 @@ export type GetAssetDetailsForSystemObjectLazyQueryHookResult = ReturnType; export const GetDetailsTabDataForObjectDocument = gql` query getDetailsTabDataForObject($input: GetDetailsTabDataForObjectInput!) { - getDetailsTabDataForObject(input: $input) { - Unit { - Abbreviation - ARKPrefix - } - Project { - Description - } - Subject { - Altitude - Latitude - Longitude - R0 - R1 - R2 - R3 - TS0 - TS1 - TS2 - } - Item { - EntireSubject - Altitude - Latitude - Longitude - R0 - R1 - R2 - R3 - TS0 - TS1 - TS2 - } - CaptureData { - captureMethod - dateCaptured - datasetType - description - cameraSettingUniform - datasetFieldId - itemPositionType - itemPositionFieldId - itemArrangementFieldId - focusType - lightsourceType - backgroundRemovalMethod - clusterType - clusterGeometryFieldId - folders { - name - variantType - } - } - Model { - size - master - authoritative - creationMethod - modality - purpose - units - dateCaptured - modelFileType - uvMaps { - name - edgeLength - mapType - } - roughness - metalness - pointCount - faceCount - isWatertight - hasNormals - hasVertexColor - hasUVSpace - boundingBoxP1X - boundingBoxP1Y - boundingBoxP1Z - boundingBoxP2X - boundingBoxP2Y - boundingBoxP2Z - } - Scene { - Links - AssetType - Tours - Annotation - } - IntermediaryFile { - idIntermediaryFile - } - ProjectDocumentation { - Description - } - Asset { - FilePath - AssetType - } - AssetVersion { - Creator - DateCreated - StorageSize - Ingested - Version - } - Actor { - OrganizationName - } - Stakeholder { - OrganizationName - EmailAddress - PhoneNumberMobile - PhoneNumberOffice - MailingAddress + getDetailsTabDataForObject(input: $input) { + Unit { + Abbreviation + ARKPrefix + } + Project { + Description + } + Subject { + Altitude + Latitude + Longitude + R0 + R1 + R2 + R3 + TS0 + TS1 + TS2 + } + Item { + EntireSubject + Altitude + Latitude + Longitude + R0 + R1 + R2 + R3 + TS0 + TS1 + TS2 + } + CaptureData { + captureMethod + dateCaptured + datasetType + description + cameraSettingUniform + datasetFieldId + itemPositionType + itemPositionFieldId + itemArrangementFieldId + focusType + lightsourceType + backgroundRemovalMethod + clusterType + clusterGeometryFieldId + folders { + name + variantType + } + } + Model { + size + master + authoritative + creationMethod + modality + purpose + units + dateCaptured + modelFileType + uvMaps { + name + edgeLength + mapType + } + roughness + metalness + pointCount + faceCount + isWatertight + hasNormals + hasVertexColor + hasUVSpace + boundingBoxP1X + boundingBoxP1Y + boundingBoxP1Z + boundingBoxP2X + boundingBoxP2Y + boundingBoxP2Z + } + Scene { + Links + AssetType + Tours + Annotation + } + IntermediaryFile { + idIntermediaryFile + } + ProjectDocumentation { + Description + } + Asset { + FilePath + AssetType + } + AssetVersion { + Creator + DateCreated + StorageSize + Ingested + Version + } + Actor { + OrganizationName + } + Stakeholder { + OrganizationName + EmailAddress + PhoneNumberMobile + PhoneNumberOffice + MailingAddress + } + } } - } -} - `; +`; /** * __useGetDetailsTabDataForObjectQuery__ @@ -4446,14 +3869,14 @@ export type GetDetailsTabDataForObjectLazyQueryHookResult = ReturnType; export const GetSourceObjectIdentiferDocument = gql` query getSourceObjectIdentifer($input: GetSourceObjectIdentiferInput!) { - getSourceObjectIdentifer(input: $input) { - sourceObjectIdentifiers { - idSystemObject - identifier + getSourceObjectIdentifer(input: $input) { + sourceObjectIdentifiers { + idSystemObject + identifier + } + } } - } -} - `; +`; /** * __useGetSourceObjectIdentiferQuery__ @@ -4482,57 +3905,58 @@ export type GetSourceObjectIdentiferLazyQueryHookResult = ReturnType; export const GetSystemObjectDetailsDocument = gql` query getSystemObjectDetails($input: GetSystemObjectDetailsInput!) { - getSystemObjectDetails(input: $input) { - name - retired - objectType - allowed - publishedState - thumbnail - identifiers { - identifier - identifierType - } - unit { - idSystemObject - name - objectType - } - project { - idSystemObject - name - objectType - } - subject { - idSystemObject - name - objectType - } - item { - idSystemObject - name - objectType - } - objectAncestors { - idSystemObject - name - objectType - } - sourceObjects { - idSystemObject - name - identifier - objectType - } - derivedObjects { - idSystemObject - name - identifier - objectType + getSystemObjectDetails(input: $input) { + idObject + name + retired + objectType + allowed + publishedState + thumbnail + identifiers { + identifier + identifierType + } + unit { + idSystemObject + name + objectType + } + project { + idSystemObject + name + objectType + } + subject { + idSystemObject + name + objectType + } + item { + idSystemObject + name + objectType + } + objectAncestors { + idSystemObject + name + objectType + } + sourceObjects { + idSystemObject + name + identifier + objectType + } + derivedObjects { + idSystemObject + name + identifier + objectType + } + } } - } -} - `; +`; /** * __useGetSystemObjectDetailsQuery__ @@ -4561,18 +3985,18 @@ export type GetSystemObjectDetailsLazyQueryHookResult = ReturnType; export const GetVersionsForSystemObjectDocument = gql` query getVersionsForSystemObject($input: GetVersionsForSystemObjectInput!) { - getVersionsForSystemObject(input: $input) { - versions { - idSystemObject - version - name - creator - dateCreated - size + getVersionsForSystemObject(input: $input) { + versions { + idSystemObject + version + name + creator + dateCreated + size + } + } } - } -} - `; +`; /** * __useGetVersionsForSystemObjectQuery__ @@ -4601,15 +4025,15 @@ export type GetVersionsForSystemObjectLazyQueryHookResult = ReturnType; export const GetIngestionItemsForSubjectsDocument = gql` query getIngestionItemsForSubjects($input: GetIngestionItemsForSubjectsInput!) { - getIngestionItemsForSubjects(input: $input) { - Item { - idItem - EntireSubject - Name + getIngestionItemsForSubjects(input: $input) { + Item { + idItem + EntireSubject + Name + } + } } - } -} - `; +`; /** * __useGetIngestionItemsForSubjectsQuery__ @@ -4638,14 +4062,14 @@ export type GetIngestionItemsForSubjectsLazyQueryHookResult = ReturnType; export const GetIngestionProjectsForSubjectsDocument = gql` query getIngestionProjectsForSubjects($input: GetIngestionProjectsForSubjectsInput!) { - getIngestionProjectsForSubjects(input: $input) { - Project { - idProject - Name + getIngestionProjectsForSubjects(input: $input) { + Project { + idProject + Name + } + } } - } -} - `; +`; /** * __useGetIngestionProjectsForSubjectsQuery__ @@ -4663,10 +4087,14 @@ export const GetIngestionProjectsForSubjectsDocument = gql` * }, * }); */ -export function useGetIngestionProjectsForSubjectsQuery(baseOptions?: Apollo.QueryHookOptions) { +export function useGetIngestionProjectsForSubjectsQuery( + baseOptions?: Apollo.QueryHookOptions +) { return Apollo.useQuery(GetIngestionProjectsForSubjectsDocument, baseOptions); } -export function useGetIngestionProjectsForSubjectsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { +export function useGetIngestionProjectsForSubjectsLazyQuery( + baseOptions?: Apollo.LazyQueryHookOptions +) { return Apollo.useLazyQuery(GetIngestionProjectsForSubjectsDocument, baseOptions); } export type GetIngestionProjectsForSubjectsQueryHookResult = ReturnType; @@ -4674,13 +4102,13 @@ export type GetIngestionProjectsForSubjectsLazyQueryHookResult = ReturnType; export const GetItemDocument = gql` query getItem($input: GetItemInput!) { - getItem(input: $input) { - Item { - idItem + getItem(input: $input) { + Item { + idItem + } + } } - } -} - `; +`; /** * __useGetItemQuery__ @@ -4709,14 +4137,14 @@ export type GetItemLazyQueryHookResult = ReturnType; export type GetItemQueryResult = Apollo.QueryResult; export const GetItemsForSubjectDocument = gql` query getItemsForSubject($input: GetItemsForSubjectInput!) { - getItemsForSubject(input: $input) { - Item { - idItem - Name + getItemsForSubject(input: $input) { + Item { + idItem + Name + } + } } - } -} - `; +`; /** * __useGetItemsForSubjectQuery__ @@ -4745,35 +4173,35 @@ export type GetItemsForSubjectLazyQueryHookResult = ReturnType; export const GetObjectsForItemDocument = gql` query getObjectsForItem($input: GetObjectsForItemInput!) { - getObjectsForItem(input: $input) { - CaptureData { - idCaptureData - DateCaptured - Description - } - Model { - idModel - Authoritative - DateCreated - } - Scene { - idScene - HasBeenQCd - IsOriented - Name - } - IntermediaryFile { - idIntermediaryFile - DateCreated - } - ProjectDocumentation { - idProjectDocumentation - Description - Name + getObjectsForItem(input: $input) { + CaptureData { + idCaptureData + DateCaptured + Description + } + Model { + idModel + Authoritative + DateCreated + } + Scene { + idScene + HasBeenQCd + IsOriented + Name + } + IntermediaryFile { + idIntermediaryFile + DateCreated + } + ProjectDocumentation { + idProjectDocumentation + Description + Name + } + } } - } -} - `; +`; /** * __useGetObjectsForItemQuery__ @@ -4802,13 +4230,13 @@ export type GetObjectsForItemLazyQueryHookResult = ReturnType; export const GetProjectDocument = gql` query getProject($input: GetProjectInput!) { - getProject(input: $input) { - Project { - idProject + getProject(input: $input) { + Project { + idProject + } + } } - } -} - `; +`; /** * __useGetProjectQuery__ @@ -4837,13 +4265,13 @@ export type GetProjectLazyQueryHookResult = ReturnType; export const GetProjectDocumentationDocument = gql` query getProjectDocumentation($input: GetProjectDocumentationInput!) { - getProjectDocumentation(input: $input) { - ProjectDocumentation { - idProjectDocumentation + getProjectDocumentation(input: $input) { + ProjectDocumentation { + idProjectDocumentation + } + } } - } -} - `; +`; /** * __useGetProjectDocumentationQuery__ @@ -4872,13 +4300,13 @@ export type GetProjectDocumentationLazyQueryHookResult = ReturnType; export const GetSubjectDocument = gql` query getSubject($input: GetSubjectInput!) { - getSubject(input: $input) { - Subject { - idSubject + getSubject(input: $input) { + Subject { + idSubject + } + } } - } -} - `; +`; /** * __useGetSubjectQuery__ @@ -4907,14 +4335,14 @@ export type GetSubjectLazyQueryHookResult = ReturnType; export const GetSubjectsForUnitDocument = gql` query getSubjectsForUnit($input: GetSubjectsForUnitInput!) { - getSubjectsForUnit(input: $input) { - Subject { - idSubject - Name + getSubjectsForUnit(input: $input) { + Subject { + idSubject + Name + } + } } - } -} - `; +`; /** * __useGetSubjectsForUnitQuery__ @@ -4943,13 +4371,13 @@ export type GetSubjectsForUnitLazyQueryHookResult = ReturnType; export const GetUnitDocument = gql` query getUnit($input: GetUnitInput!) { - getUnit(input: $input) { - Unit { - idUnit + getUnit(input: $input) { + Unit { + idUnit + } + } } - } -} - `; +`; /** * __useGetUnitQuery__ @@ -4978,17 +4406,17 @@ export type GetUnitLazyQueryHookResult = ReturnType; export type GetUnitQueryResult = Apollo.QueryResult; export const SearchIngestionSubjectsDocument = gql` query searchIngestionSubjects($input: SearchIngestionSubjectsInput!) { - searchIngestionSubjects(input: $input) { - SubjectUnitIdentifier { - idSubject - SubjectName - UnitAbbreviation - IdentifierPublic - IdentifierCollection + searchIngestionSubjects(input: $input) { + SubjectUnitIdentifier { + idSubject + SubjectName + UnitAbbreviation + IdentifierPublic + IdentifierCollection + } + } } - } -} - `; +`; /** * __useSearchIngestionSubjectsQuery__ @@ -5017,21 +4445,21 @@ export type SearchIngestionSubjectsLazyQueryHookResult = ReturnType; export const GetCurrentUserDocument = gql` query getCurrentUser { - getCurrentUser { - User { - idUser - Name - Active - DateActivated - DateDisabled - EmailAddress - EmailSettings - SecurityID - WorkflowNotificationTime + getCurrentUser { + User { + idUser + Name + Active + DateActivated + DateDisabled + EmailAddress + EmailSettings + SecurityID + WorkflowNotificationTime + } + } } - } -} - `; +`; /** * __useGetCurrentUserQuery__ @@ -5059,16 +4487,16 @@ export type GetCurrentUserLazyQueryHookResult = ReturnType; export const GetUserDocument = gql` query getUser($input: GetUserInput!) { - getUser(input: $input) { - User { - idUser - Name - Active - DateActivated + getUser(input: $input) { + User { + idUser + Name + Active + DateActivated + } + } } - } -} - `; +`; /** * __useGetUserQuery__ @@ -5097,13 +4525,13 @@ export type GetUserLazyQueryHookResult = ReturnType; export type GetUserQueryResult = Apollo.QueryResult; export const GetVocabularyDocument = gql` query getVocabulary($input: GetVocabularyInput!) { - getVocabulary(input: $input) { - Vocabulary { - idVocabulary + getVocabulary(input: $input) { + Vocabulary { + idVocabulary + } + } } - } -} - `; +`; /** * __useGetVocabularyQuery__ @@ -5132,17 +4560,17 @@ export type GetVocabularyLazyQueryHookResult = ReturnType; export const GetVocabularyEntriesDocument = gql` query getVocabularyEntries($input: GetVocabularyEntriesInput!) { - getVocabularyEntries(input: $input) { - VocabularyEntries { - eVocabSetID - Vocabulary { - idVocabulary - Term - } + getVocabularyEntries(input: $input) { + VocabularyEntries { + eVocabSetID + Vocabulary { + idVocabulary + Term + } + } + } } - } -} - `; +`; /** * __useGetVocabularyEntriesQuery__ @@ -5171,13 +4599,13 @@ export type GetVocabularyEntriesLazyQueryHookResult = ReturnType; export const GetWorkflowDocument = gql` query getWorkflow($input: GetWorkflowInput!) { - getWorkflow(input: $input) { - Workflow { - idWorkflow + getWorkflow(input: $input) { + Workflow { + idWorkflow + } + } } - } -} - `; +`; /** * __useGetWorkflowQuery__ @@ -5203,4 +4631,4 @@ export function useGetWorkflowLazyQuery(baseOptions?: Apollo.LazyQueryHookOption } export type GetWorkflowQueryHookResult = ReturnType; export type GetWorkflowLazyQueryHookResult = ReturnType; -export type GetWorkflowQueryResult = Apollo.QueryResult; \ No newline at end of file +export type GetWorkflowQueryResult = Apollo.QueryResult; diff --git a/server/graphql/api/queries/systemobject/getSystemObjectDetails.ts b/server/graphql/api/queries/systemobject/getSystemObjectDetails.ts index 42c2ff5bf..e4fde0009 100644 --- a/server/graphql/api/queries/systemobject/getSystemObjectDetails.ts +++ b/server/graphql/api/queries/systemobject/getSystemObjectDetails.ts @@ -3,6 +3,7 @@ import { gql } from 'apollo-server-express'; const getSystemObjectDetails = gql` query getSystemObjectDetails($input: GetSystemObjectDetailsInput!) { getSystemObjectDetails(input: $input) { + idObject name retired objectType diff --git a/server/graphql/schema.graphql b/server/graphql/schema.graphql index c52ac0c2d..7e98d3ecb 100644 --- a/server/graphql/schema.graphql +++ b/server/graphql/schema.graphql @@ -810,6 +810,7 @@ type IntermediaryFile { input UpdateObjectDetailsInput { idSystemObject: Int! + idObject: Int! objectType: Int! data: UpdateObjectDetailsDataInput! } @@ -1107,6 +1108,7 @@ type RepositoryPath { } type GetSystemObjectDetailsResult { + idObject: Int! name: String! retired: Boolean! objectType: Int! diff --git a/server/graphql/schema/systemobject/mutations.graphql b/server/graphql/schema/systemobject/mutations.graphql index 1f447825e..0a4f0de78 100644 --- a/server/graphql/schema/systemobject/mutations.graphql +++ b/server/graphql/schema/systemobject/mutations.graphql @@ -6,6 +6,7 @@ type Mutation { input UpdateObjectDetailsInput { idSystemObject: Int! + idObject: Int! objectType: Int! data: UpdateObjectDetailsDataInput! } diff --git a/server/graphql/schema/systemobject/queries.graphql b/server/graphql/schema/systemobject/queries.graphql index e519d1a17..d018627e3 100644 --- a/server/graphql/schema/systemobject/queries.graphql +++ b/server/graphql/schema/systemobject/queries.graphql @@ -161,6 +161,7 @@ type RepositoryPath { } type GetSystemObjectDetailsResult { + idObject: Int! name: String! retired: Boolean! objectType: Int! diff --git a/server/types/graphql.ts b/server/types/graphql.ts index 911a694fa..15e6b5de8 100644 --- a/server/types/graphql.ts +++ b/server/types/graphql.ts @@ -51,162 +51,130 @@ export type Query = { searchIngestionSubjects: SearchIngestionSubjectsResult; }; - export type QueryAreCameraSettingsUniformArgs = { input: AreCameraSettingsUniformInput; }; - export type QueryGetAccessPolicyArgs = { input: GetAccessPolicyInput; }; - export type QueryGetAssetArgs = { input: GetAssetInput; }; - export type QueryGetAssetDetailsForSystemObjectArgs = { input: GetAssetDetailsForSystemObjectInput; }; - export type QueryGetAssetVersionsDetailsArgs = { input: GetAssetVersionsDetailsInput; }; - export type QueryGetCaptureDataArgs = { input: GetCaptureDataInput; }; - export type QueryGetCaptureDataPhotoArgs = { input: GetCaptureDataPhotoInput; }; - export type QueryGetContentsForAssetVersionsArgs = { input: GetContentsForAssetVersionsInput; }; - export type QueryGetDetailsTabDataForObjectArgs = { input: GetDetailsTabDataForObjectInput; }; - export type QueryGetIngestionItemsForSubjectsArgs = { input: GetIngestionItemsForSubjectsInput; }; - export type QueryGetIngestionProjectsForSubjectsArgs = { input: GetIngestionProjectsForSubjectsInput; }; - export type QueryGetIntermediaryFileArgs = { input: GetIntermediaryFileInput; }; - export type QueryGetItemArgs = { input: GetItemInput; }; - export type QueryGetItemsForSubjectArgs = { input: GetItemsForSubjectInput; }; - export type QueryGetLicenseArgs = { input: GetLicenseInput; }; - export type QueryGetModelArgs = { input: GetModelInput; }; - export type QueryGetObjectChildrenArgs = { input: GetObjectChildrenInput; }; - export type QueryGetObjectsForItemArgs = { input: GetObjectsForItemInput; }; - export type QueryGetProjectArgs = { input: GetProjectInput; }; - export type QueryGetProjectDocumentationArgs = { input: GetProjectDocumentationInput; }; - export type QueryGetSceneArgs = { input: GetSceneInput; }; - export type QueryGetSourceObjectIdentiferArgs = { input: GetSourceObjectIdentiferInput; }; - export type QueryGetSubjectArgs = { input: GetSubjectInput; }; - export type QueryGetSubjectsForUnitArgs = { input: GetSubjectsForUnitInput; }; - export type QueryGetSystemObjectDetailsArgs = { input: GetSystemObjectDetailsInput; }; - export type QueryGetUnitArgs = { input: GetUnitInput; }; - export type QueryGetUserArgs = { input: GetUserInput; }; - export type QueryGetVersionsForSystemObjectArgs = { input: GetVersionsForSystemObjectInput; }; - export type QueryGetVocabularyArgs = { input: GetVocabularyInput; }; - export type QueryGetVocabularyEntriesArgs = { input: GetVocabularyEntriesInput; }; - export type QueryGetWorkflowArgs = { input: GetWorkflowInput; }; - export type QuerySearchIngestionSubjectsArgs = { input: SearchIngestionSubjectsInput; }; @@ -220,7 +188,6 @@ export type GetAccessPolicyResult = { AccessPolicy?: Maybe; }; - export type AccessAction = { __typename?: 'AccessAction'; idAccessAction: Scalars['Int']; @@ -269,7 +236,6 @@ export type AccessRole = { AccessAction?: Maybe>>; }; - export type Mutation = { __typename?: 'Mutation'; createCaptureData: CreateCaptureDataResult; @@ -289,77 +255,62 @@ export type Mutation = { uploadAsset: UploadAssetResult; }; - export type MutationCreateCaptureDataArgs = { input: CreateCaptureDataInput; }; - export type MutationCreateCaptureDataPhotoArgs = { input: CreateCaptureDataPhotoInput; }; - export type MutationCreateItemArgs = { input: CreateItemInput; }; - export type MutationCreateModelArgs = { input: CreateModelInput; }; - export type MutationCreateProjectArgs = { input: CreateProjectInput; }; - export type MutationCreateSceneArgs = { input: CreateSceneInput; }; - export type MutationCreateSubjectArgs = { input: CreateSubjectInput; }; - export type MutationCreateUnitArgs = { input: CreateUnitInput; }; - export type MutationCreateUserArgs = { input: CreateUserInput; }; - export type MutationCreateVocabularyArgs = { input: CreateVocabularyInput; }; - export type MutationCreateVocabularySetArgs = { input: CreateVocabularySetInput; }; - export type MutationDiscardUploadedAssetVersionsArgs = { input: DiscardUploadedAssetVersionsInput; }; - export type MutationIngestDataArgs = { input: IngestDataInput; }; - export type MutationUpdateObjectDetailsArgs = { input: UpdateObjectDetailsInput; }; - export type MutationUploadAssetArgs = { file: Scalars['Upload']; type: Scalars['Int']; @@ -1117,6 +1068,7 @@ export type IntermediaryFile = { export type UpdateObjectDetailsInput = { idSystemObject: Scalars['Int']; + idObject: Scalars['Int']; objectType: Scalars['Int']; data: UpdateObjectDetailsDataInput; }; @@ -1431,6 +1383,7 @@ export type RepositoryPath = { export type GetSystemObjectDetailsResult = { __typename?: 'GetSystemObjectDetailsResult'; + idObject: Scalars['Int']; name: Scalars['String']; retired: Scalars['Boolean']; objectType: Scalars['Int']; From 46adba60bb0778ddacf479845890e1e2a6e73d12 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Wed, 13 Jan 2021 16:28:03 +0530 Subject: [PATCH 170/239] update object type client --- .../pages/Repository/components/DetailsView/ObjectDetails.tsx | 1 - .../systemobject/resolvers/queries/getSystemObjectDetails.ts | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/client/src/pages/Repository/components/DetailsView/ObjectDetails.tsx b/client/src/pages/Repository/components/DetailsView/ObjectDetails.tsx index 1a11c1cf4..ee759a7f1 100644 --- a/client/src/pages/Repository/components/DetailsView/ObjectDetails.tsx +++ b/client/src/pages/Repository/components/DetailsView/ObjectDetails.tsx @@ -49,7 +49,6 @@ function ObjectDetails(props: ObjectDetailsProps): React.ReactElement { - Date: Wed, 13 Jan 2021 18:11:48 +0530 Subject: [PATCH 171/239] fix server not picking up the changes --- server/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/package.json b/server/package.json index 9780bdba4..066c2f727 100644 --- a/server/package.json +++ b/server/package.json @@ -27,7 +27,7 @@ "scripts": { "start": "yarn build && concurrently 'yarn build:watch' 'yarn server:start'", "start:prod": "node build/index.js", - "server:start": "nodemon build/index.js --ignore var/ --ignore build/", + "server:start": "nodemon build/index.js --ignore var/", "build": "tsc --build && copyfiles db/**/*.* graphql/**/**.graphql build/", "build:watch": "tsc --watch", "clean": "rm -rf node_modules/ build/", From 18db8ac588f172292cf6a453eee800302a5d06ab Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Wed, 13 Jan 2021 18:14:46 +0530 Subject: [PATCH 172/239] updated object details resolver --- .../mutations/updateObjectDetails.ts | 220 +++++++++++++++++- server/utils/types.ts | 3 + 2 files changed, 211 insertions(+), 12 deletions(-) create mode 100644 server/utils/types.ts diff --git a/server/graphql/schema/systemobject/resolvers/mutations/updateObjectDetails.ts b/server/graphql/schema/systemobject/resolvers/mutations/updateObjectDetails.ts index af3808faa..7a92312e4 100644 --- a/server/graphql/schema/systemobject/resolvers/mutations/updateObjectDetails.ts +++ b/server/graphql/schema/systemobject/resolvers/mutations/updateObjectDetails.ts @@ -2,45 +2,241 @@ import { eSystemObjectType } from '../../../../../db'; import { UpdateObjectDetailsResult, MutationUpdateObjectDetailsArgs } from '../../../../../types/graphql'; import { Parent } from '../../../../../types/resolvers'; import * as LOG from '../../../../../utils'; +import * as DBAPI from '../../../../../db'; +import { maybe } from '../../../../../utils/types'; +import { isNull, isUndefined } from 'lodash'; export default async function updateObjectDetails(_: Parent, args: MutationUpdateObjectDetailsArgs): Promise { const { input } = args; - const { objectType, data } = input; + const { idSystemObject, idObject, objectType, data } = input; - // TODO: KARAN: update {data} for {idSystemObject} LOG.logger.info(JSON.stringify(data, null, 2)); + if (!data.Name || isUndefined(data.Retired) || isNull(data.Retired)) { + return { success: false }; + } + + const SO = await DBAPI.SystemObject.fetch(idSystemObject); + + if (SO) { + SO.Retired = data.Retired; + // TODO: KARAN: how to update SO? SO.update()? + } + switch (objectType) { - case eSystemObjectType.eUnit: + case eSystemObjectType.eUnit: { + if (data.Unit) { + const { Abbreviation, ARKPrefix } = data.Unit; + const Unit = await DBAPI.Unit.fetch(idObject); + + if (Unit) { + Unit.Name = data.Name; + Unit.Abbreviation = maybe(Abbreviation); + Unit.ARKPrefix = maybe(ARKPrefix); + await Unit.update(); + } + } break; - case eSystemObjectType.eProject: + } + case eSystemObjectType.eProject: { + if (data.Project) { + const { Description } = data.Project; + const Project = await DBAPI.Project.fetch(idObject); + + if (Project) { + Project.Name = data.Name; + Project.Description = maybe(Description); + await Project.update(); + } + } break; - case eSystemObjectType.eSubject: + } + case eSystemObjectType.eSubject: { + if (data.Subject) { + const { Altitude, Latitude, Longitude, R0, R1, R2, R3, TS0, TS1, TS2 } = data.Subject; + const Subject = await DBAPI.Subject.fetch(idObject); + + if (Subject) { + Subject.Name = data.Name; + + if (!Subject.idGeoLocation) { + const GeoLocationInput = { + idGeoLocation: 0, + Altitude: maybe(Altitude), + Latitude: maybe(Latitude), + Longitude: maybe(Longitude), + R0: maybe(R0), + R1: maybe(R1), + R2: maybe(R2), + R3: maybe(R3), + TS0: maybe(TS0), + TS1: maybe(TS1), + TS2: maybe(TS2) + }; + const GeoLocation = new DBAPI.GeoLocation(GeoLocationInput); + await GeoLocation.create(); + Subject.idGeoLocation = GeoLocation.idGeoLocation; + + await Subject.update(); + break; + } + + await Subject.update(); + + const GeoLocation = await DBAPI.GeoLocation.fetch(Subject.idGeoLocation); + + if (GeoLocation) { + GeoLocation.Altitude = maybe(Altitude); + GeoLocation.Latitude = maybe(Latitude); + GeoLocation.Longitude = maybe(Longitude); + GeoLocation.R0 = maybe(R0); + GeoLocation.R1 = maybe(R1); + GeoLocation.R2 = maybe(R2); + GeoLocation.R3 = maybe(R3); + GeoLocation.TS0 = maybe(TS0); + GeoLocation.TS1 = maybe(TS1); + GeoLocation.TS2 = maybe(TS2); + await GeoLocation.update(); + } + } + } break; - case eSystemObjectType.eItem: + } + case eSystemObjectType.eItem: { + if (data.Item) { + const { EntireSubject, Altitude, Latitude, Longitude, R0, R1, R2, R3, TS0, TS1, TS2 } = data.Item; + const Item = await DBAPI.Item.fetch(idObject); + + if (Item) { + Item.Name = data.Name; + if (!isNull(EntireSubject) && !isUndefined(EntireSubject)) Item.EntireSubject = EntireSubject; + + if (!Item.idGeoLocation) { + const GeoLocationInput = { + idGeoLocation: 0, + Altitude: maybe(Altitude), + Latitude: maybe(Latitude), + Longitude: maybe(Longitude), + R0: maybe(R0), + R1: maybe(R1), + R2: maybe(R2), + R3: maybe(R3), + TS0: maybe(TS0), + TS1: maybe(TS1), + TS2: maybe(TS2) + }; + const GeoLocation = new DBAPI.GeoLocation(GeoLocationInput); + await GeoLocation.create(); + Item.idGeoLocation = GeoLocation.idGeoLocation; + await Item.update(); + break; + } + + await Item.update(); + + if (Item.idGeoLocation) { + const GeoLocation = await DBAPI.GeoLocation.fetch(Item.idGeoLocation); + if (GeoLocation) { + GeoLocation.Altitude = maybe(Altitude); + GeoLocation.Latitude = maybe(Latitude); + GeoLocation.Longitude = maybe(Longitude); + GeoLocation.R0 = maybe(R0); + GeoLocation.R1 = maybe(R1); + GeoLocation.R2 = maybe(R2); + GeoLocation.R3 = maybe(R3); + GeoLocation.TS0 = maybe(TS0); + GeoLocation.TS1 = maybe(TS1); + GeoLocation.TS2 = maybe(TS2); + await GeoLocation.update(); + } + } + } + } break; + } case eSystemObjectType.eCaptureData: + // TODO: KARAN: How to update capture data? break; case eSystemObjectType.eModel: + // TODO: KARAN: How to update model? break; case eSystemObjectType.eScene: + // TODO: KARAN: How to update scene? break; case eSystemObjectType.eIntermediaryFile: break; - case eSystemObjectType.eProjectDocumentation: + case eSystemObjectType.eProjectDocumentation: { + if (data.ProjectDocumentation) { + const { Description } = data.ProjectDocumentation; + const ProjectDocumentation = await DBAPI.ProjectDocumentation.fetch(idObject); + + if (ProjectDocumentation) { + ProjectDocumentation.Name = data.Name; + if (Description) ProjectDocumentation.Description = Description; + await ProjectDocumentation.update(); + } + } break; - case eSystemObjectType.eAsset: + } + case eSystemObjectType.eAsset: { + if (data.Asset) { + const { FilePath, AssetType } = data.Asset; + const Asset = await DBAPI.Asset.fetch(idObject); + + if (Asset) { + Asset.FileName = data.Name; + if (FilePath) Asset.FilePath = FilePath; + if (AssetType) Asset.idVAssetType = AssetType; + + await Asset.update(); + } + } break; - case eSystemObjectType.eAssetVersion: + } + case eSystemObjectType.eAssetVersion: { + if (data.AssetVersion) { + const { Ingested } = data.AssetVersion; + const AssetVersion = await DBAPI.AssetVersion.fetch(idObject); + + if (AssetVersion) { + AssetVersion.FileName = data.Name; + if (Ingested) AssetVersion.Ingested = Ingested; + await AssetVersion.update(); + } + } break; - case eSystemObjectType.eActor: + } + case eSystemObjectType.eActor: { + if (data.Actor) { + const { OrganizationName } = data.Actor; + const Actor = await DBAPI.Actor.fetch(idObject); + + if (Actor) { + Actor.OrganizationName = maybe(OrganizationName); + await Actor.update(); + } + } break; - case eSystemObjectType.eStakeholder: + } + case eSystemObjectType.eStakeholder: { + if (data.Stakeholder) { + const { OrganizationName, MailingAddress, EmailAddress, PhoneNumberMobile, PhoneNumberOffice } = data.Stakeholder; + const Stakeholder = await DBAPI.Stakeholder.fetch(idObject); + + if (Stakeholder) { + if (OrganizationName) Stakeholder.OrganizationName = OrganizationName; + Stakeholder.MailingAddress = maybe(MailingAddress); + Stakeholder.EmailAddress = maybe(EmailAddress); + Stakeholder.PhoneNumberMobile = maybe(PhoneNumberMobile); + Stakeholder.PhoneNumberOffice = maybe(PhoneNumberOffice); + await Stakeholder.update(); + } + } break; + } default: break; } return { success: true }; } - diff --git a/server/utils/types.ts b/server/utils/types.ts new file mode 100644 index 000000000..92a9eb417 --- /dev/null +++ b/server/utils/types.ts @@ -0,0 +1,3 @@ +export function maybe(value: T | undefined | null): T | null { + return value ?? null; +} From ade122661704fa0392dca5171a1830802964391b Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Wed, 13 Jan 2021 18:16:19 +0530 Subject: [PATCH 173/239] updated getDetailsTabData resolver --- .../queries/getDetailsTabDataForObject.ts | 80 ++++++++++++++++--- 1 file changed, 70 insertions(+), 10 deletions(-) diff --git a/server/graphql/schema/systemobject/resolvers/queries/getDetailsTabDataForObject.ts b/server/graphql/schema/systemobject/resolvers/queries/getDetailsTabDataForObject.ts index 1d97e0c84..46ed8db76 100644 --- a/server/graphql/schema/systemobject/resolvers/queries/getDetailsTabDataForObject.ts +++ b/server/graphql/schema/systemobject/resolvers/queries/getDetailsTabDataForObject.ts @@ -1,6 +1,13 @@ import * as DBAPI from '../../../../../db'; import { eSystemObjectType } from '../../../../../db'; -import { GetDetailsTabDataForObjectResult, QueryGetDetailsTabDataForObjectArgs } from '../../../../../types/graphql'; +import { + AssetVersionDetailFields, + AssetDetailFields, + SubjectDetailFields, + ItemDetailFields, + GetDetailsTabDataForObjectResult, + QueryGetDetailsTabDataForObjectArgs +} from '../../../../../types/graphql'; import { Parent } from '../../../../../types/resolvers'; export default async function getDetailsTabDataForObject(_: Parent, args: QueryGetDetailsTabDataForObjectArgs): Promise { @@ -20,7 +27,7 @@ export default async function getDetailsTabDataForObject(_: Parent, args: QueryG Asset: null, AssetVersion: null, Actor: null, - Stakeholder: null, + Stakeholder: null }; const systemObject: DBAPI.SystemObject | null = await DBAPI.SystemObject.fetch(idSystemObject); @@ -32,16 +39,45 @@ export default async function getDetailsTabDataForObject(_: Parent, args: QueryG case eSystemObjectType.eProject: if (systemObject?.idProject) result.Project = await DBAPI.Project.fetch(systemObject.idProject); break; - case eSystemObjectType.eSubject: + case eSystemObjectType.eSubject: { + if (systemObject?.idSubject) { + let fields: SubjectDetailFields = {}; + + const Subject = await DBAPI.Subject.fetch(systemObject.idSubject); + + if (Subject?.idGeoLocation) { + const GeoLocation = await DBAPI.GeoLocation.fetch(Subject.idGeoLocation); + fields = { ...fields, ...GeoLocation }; + } + + result.Subject = fields; + } break; - case eSystemObjectType.eItem: - if (systemObject?.idItem) result.Item = await DBAPI.Item.fetch(systemObject.idItem); + } + case eSystemObjectType.eItem: { + if (systemObject?.idItem) { + let fields: ItemDetailFields = {}; + + const Item = await DBAPI.Item.fetch(systemObject.idItem); + fields = { ...Item }; + + if (Item?.idGeoLocation) { + const GeoLocation = await DBAPI.GeoLocation.fetch(Item.idGeoLocation); + fields = { ...fields, ...GeoLocation }; + } + + result.Item = fields; + } break; + } case eSystemObjectType.eCaptureData: + // TODO: KARAN: How to retrieve capture data? break; case eSystemObjectType.eModel: + // TODO: KARAN: How to retrieve model? break; case eSystemObjectType.eScene: + // TODO: KARAN: How to retrieve scene? break; case eSystemObjectType.eIntermediaryFile: if (systemObject?.idIntermediaryFile) result.IntermediaryFile = await DBAPI.IntermediaryFile.fetch(systemObject.idIntermediaryFile); @@ -49,12 +85,37 @@ export default async function getDetailsTabDataForObject(_: Parent, args: QueryG case eSystemObjectType.eProjectDocumentation: if (systemObject?.idProjectDocumentation) result.ProjectDocumentation = await DBAPI.ProjectDocumentation.fetch(systemObject.idProjectDocumentation); break; - case eSystemObjectType.eAsset: - if (systemObject?.idAsset) result.Asset = await DBAPI.Asset.fetch(systemObject.idAsset); + case eSystemObjectType.eAsset: { + if (systemObject?.idAsset) { + let fields: AssetDetailFields = {}; + + const Asset = await DBAPI.Asset.fetch(systemObject.idAsset); + fields = { ...Asset }; + + if (Asset?.idVAssetType) { + const Vocabulary = await DBAPI.Vocabulary.fetch(Asset.idVAssetType); + fields = { ...fields, AssetType: Vocabulary?.idVocabulary }; + } + result.Asset = fields; + } break; - case eSystemObjectType.eAssetVersion: - if (systemObject?.idAssetVersion) result.AssetVersion = await DBAPI.AssetVersion.fetch(systemObject.idAssetVersion); + } + case eSystemObjectType.eAssetVersion: { + if (systemObject?.idAssetVersion) { + let fields: AssetVersionDetailFields = {}; + + const AssetVersion = await DBAPI.AssetVersion.fetch(systemObject.idAssetVersion); + fields = { ...AssetVersion }; + + if (AssetVersion?.idUserCreator) { + const User = await DBAPI.User.fetch(AssetVersion.idUserCreator); + fields = { ...fields, Creator: User?.Name }; + } + result.AssetVersion = fields; + } + break; + } case eSystemObjectType.eActor: if (systemObject?.idActor) result.Actor = await DBAPI.Actor.fetch(systemObject.idActor); break; @@ -67,4 +128,3 @@ export default async function getDetailsTabDataForObject(_: Parent, args: QueryG return result; } - From d14a0a1c860c147a29893e24e8a99907fdc5afe5 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Wed, 13 Jan 2021 18:16:56 +0530 Subject: [PATCH 174/239] Fix string passed as geolocation data issue --- .../components/DetailsView/DetailsTab/ItemDetails.tsx | 10 ++++++++-- .../DetailsView/DetailsTab/SubjectDetails.tsx | 8 +++++++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/client/src/pages/Repository/components/DetailsView/DetailsTab/ItemDetails.tsx b/client/src/pages/Repository/components/DetailsView/DetailsTab/ItemDetails.tsx index d9f831d4b..a1ee91e8f 100644 --- a/client/src/pages/Repository/components/DetailsView/DetailsTab/ItemDetails.tsx +++ b/client/src/pages/Repository/components/DetailsView/DetailsTab/ItemDetails.tsx @@ -50,7 +50,13 @@ function ItemDetails(props: DetailComponentProps): React.ReactElement { const onSetField = (event: React.ChangeEvent) => { const { name, value } = event.target; - setDetails(details => ({ ...details, [name]: value })); + let numberValue: number | null = null; + + if (value) { + numberValue = Number.parseInt(value, 10); + } + + setDetails(details => ({ ...details, [name]: numberValue })); }; const setCheckboxField = ({ target }): void => { @@ -77,4 +83,4 @@ function ItemDetails(props: DetailComponentProps): React.ReactElement { ); } -export default ItemDetails; \ No newline at end of file +export default ItemDetails; diff --git a/client/src/pages/Repository/components/DetailsView/DetailsTab/SubjectDetails.tsx b/client/src/pages/Repository/components/DetailsView/DetailsTab/SubjectDetails.tsx index 3876a532d..47804faeb 100644 --- a/client/src/pages/Repository/components/DetailsView/DetailsTab/SubjectDetails.tsx +++ b/client/src/pages/Repository/components/DetailsView/DetailsTab/SubjectDetails.tsx @@ -44,7 +44,13 @@ function SubjectDetails(props: DetailComponentProps): React.ReactElement { const onSetField = (event: React.ChangeEvent) => { const { name, value } = event.target; - setDetails(details => ({ ...details, [name]: value })); + let numberValue: number | null = null; + + if (value) { + numberValue = Number.parseInt(value, 10); + } + + setDetails(details => ({ ...details, [name]: numberValue })); }; const subjectData = data.getDetailsTabDataForObject?.Subject; From 6857a72460a39fb3e36becc1b4cb26fe7fff69b2 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Wed, 13 Jan 2021 18:17:46 +0530 Subject: [PATCH 175/239] Better highlight checkbox components on update --- client/src/components/controls/CheckboxField.tsx | 5 +++-- .../Repository/components/DetailsView/ObjectDetails.tsx | 6 ++++-- client/src/utils/repository.tsx | 9 +++++++++ 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/client/src/components/controls/CheckboxField.tsx b/client/src/components/controls/CheckboxField.tsx index 3cf77536b..63b29be98 100644 --- a/client/src/components/controls/CheckboxField.tsx +++ b/client/src/components/controls/CheckboxField.tsx @@ -6,6 +6,7 @@ import { Checkbox } from '@material-ui/core'; import React from 'react'; import { ViewableProps } from '../../types/repository'; +import { getUpdatedCheckboxProps } from '../../utils/repository'; import { withDefaultValueBoolean } from '../../utils/shared'; import FieldType from '../shared/FieldType'; @@ -18,7 +19,7 @@ interface CheckboxFieldProps extends ViewableProps { } function CheckboxField(props: CheckboxFieldProps): React.ReactElement { - const { label, name, value, onChange, required = false, viewMode = false, disabled = false, updated } = props; + const { label, name, value, onChange, required = false, viewMode = false, disabled = false, updated = false } = props; const rowFieldProps = { alignItems: 'center', justifyContent: 'space-between', style: { borderRadius: 0 } }; return ( @@ -33,8 +34,8 @@ function CheckboxField(props: CheckboxFieldProps): React.ReactElement { name={name} disabled={disabled} checked={withDefaultValueBoolean(value, false)} - color={updated ? 'secondary' : 'primary'} onChange={onChange} + {...getUpdatedCheckboxProps(updated)} /> ); diff --git a/client/src/pages/Repository/components/DetailsView/ObjectDetails.tsx b/client/src/pages/Repository/components/DetailsView/ObjectDetails.tsx index ee759a7f1..c17f07448 100644 --- a/client/src/pages/Repository/components/DetailsView/ObjectDetails.tsx +++ b/client/src/pages/Repository/components/DetailsView/ObjectDetails.tsx @@ -8,7 +8,7 @@ import { makeStyles } from '@material-ui/core/styles'; import React from 'react'; import { NewTabLink } from '../../../../components'; import { GetSystemObjectDetailsResult, RepositoryPath } from '../../../../types/graphql'; -import { getDetailsUrlForObject, isFieldUpdated } from '../../../../utils/repository'; +import { getDetailsUrlForObject, getUpdatedCheckboxProps, isFieldUpdated } from '../../../../utils/repository'; import { withDefaultValueBoolean } from '../../../../utils/shared'; const useStyles = makeStyles(({ palette, typography }) => ({ @@ -42,6 +42,8 @@ interface ObjectDetailsProps { function ObjectDetails(props: ObjectDetailsProps): React.ReactElement { const { unit, project, subject, item, publishedState, retired, disabled, originalFields, onRetiredUpdate } = props; + const isRetiredUpdated: boolean = isFieldUpdated({ retired }, originalFields, 'retired'); + return ( @@ -54,8 +56,8 @@ function ObjectDetails(props: ObjectDetailsProps): React.ReactElement { name='retired' disabled={disabled} checked={withDefaultValueBoolean(retired, false)} - color={isFieldUpdated({ retired }, originalFields, 'retired') ? 'secondary' : 'primary'} onChange={onRetiredUpdate} + {...getUpdatedCheckboxProps(isRetiredUpdated)} />} /> diff --git a/client/src/utils/repository.tsx b/client/src/utils/repository.tsx index f8cd432e7..d28e2a22e 100644 --- a/client/src/utils/repository.tsx +++ b/client/src/utils/repository.tsx @@ -5,6 +5,7 @@ * * Utilities for components associated with Repository UI. */ +import { CheckboxProps } from '@material-ui/core'; import { Breakpoint } from '@material-ui/core/styles/createBreakpoints'; import lodash from 'lodash'; import * as qs from 'query-string'; @@ -14,6 +15,7 @@ import { RepositoryIcon } from '../components'; import { RepositoryFilter } from '../pages/Repository'; import { TreeViewColumn } from '../pages/Repository/components/RepositoryTreeView/MetadataView'; import { StateRelatedObject } from '../store'; +import { palette } from '../theme'; import Colors, { RepositoryColorVariant } from '../theme/colors'; import { NavigationResultEntry } from '../types/graphql'; import { eMetadata, eSystemObjectType } from '../types/server'; @@ -255,4 +257,11 @@ export function validateArray(value: T[], defaultValue: T[]): T[] { export function isFieldUpdated(updatedData: any, originalData: any, fieldName: string): boolean { return originalData?.[fieldName] !== updatedData?.[fieldName]; +} + +export function getUpdatedCheckboxProps(updated: boolean): CheckboxProps { + return { + style: { border: `1px solid ${updated ? palette.secondary.main : 'transparent'}` }, + color: updated ? 'secondary' : 'primary' + }; } \ No newline at end of file From 7c57ffeefb883a0f92189d5ea074dabbe688594f Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Wed, 13 Jan 2021 18:18:41 +0530 Subject: [PATCH 176/239] Fix idVAssetType to asset type --- client/src/types/graphql.tsx | 2576 +++++++++++++-------- server/graphql/schema.graphql | 1 + server/graphql/schema/asset/types.graphql | 1 + server/types/graphql.ts | 1 + 4 files changed, 1579 insertions(+), 1000 deletions(-) diff --git a/client/src/types/graphql.tsx b/client/src/types/graphql.tsx index 7d2cc7173..3575b824f 100644 --- a/client/src/types/graphql.tsx +++ b/client/src/types/graphql.tsx @@ -55,130 +55,162 @@ export type Query = { searchIngestionSubjects: SearchIngestionSubjectsResult; }; + export type QueryAreCameraSettingsUniformArgs = { input: AreCameraSettingsUniformInput; }; + export type QueryGetAccessPolicyArgs = { input: GetAccessPolicyInput; }; + export type QueryGetAssetArgs = { input: GetAssetInput; }; + export type QueryGetAssetDetailsForSystemObjectArgs = { input: GetAssetDetailsForSystemObjectInput; }; + export type QueryGetAssetVersionsDetailsArgs = { input: GetAssetVersionsDetailsInput; }; + export type QueryGetCaptureDataArgs = { input: GetCaptureDataInput; }; + export type QueryGetCaptureDataPhotoArgs = { input: GetCaptureDataPhotoInput; }; + export type QueryGetContentsForAssetVersionsArgs = { input: GetContentsForAssetVersionsInput; }; + export type QueryGetDetailsTabDataForObjectArgs = { input: GetDetailsTabDataForObjectInput; }; + export type QueryGetIngestionItemsForSubjectsArgs = { input: GetIngestionItemsForSubjectsInput; }; + export type QueryGetIngestionProjectsForSubjectsArgs = { input: GetIngestionProjectsForSubjectsInput; }; + export type QueryGetIntermediaryFileArgs = { input: GetIntermediaryFileInput; }; + export type QueryGetItemArgs = { input: GetItemInput; }; + export type QueryGetItemsForSubjectArgs = { input: GetItemsForSubjectInput; }; + export type QueryGetLicenseArgs = { input: GetLicenseInput; }; + export type QueryGetModelArgs = { input: GetModelInput; }; + export type QueryGetObjectChildrenArgs = { input: GetObjectChildrenInput; }; + export type QueryGetObjectsForItemArgs = { input: GetObjectsForItemInput; }; + export type QueryGetProjectArgs = { input: GetProjectInput; }; + export type QueryGetProjectDocumentationArgs = { input: GetProjectDocumentationInput; }; + export type QueryGetSceneArgs = { input: GetSceneInput; }; + export type QueryGetSourceObjectIdentiferArgs = { input: GetSourceObjectIdentiferInput; }; + export type QueryGetSubjectArgs = { input: GetSubjectInput; }; + export type QueryGetSubjectsForUnitArgs = { input: GetSubjectsForUnitInput; }; + export type QueryGetSystemObjectDetailsArgs = { input: GetSystemObjectDetailsInput; }; + export type QueryGetUnitArgs = { input: GetUnitInput; }; + export type QueryGetUserArgs = { input: GetUserInput; }; + export type QueryGetVersionsForSystemObjectArgs = { input: GetVersionsForSystemObjectInput; }; + export type QueryGetVocabularyArgs = { input: GetVocabularyInput; }; + export type QueryGetVocabularyEntriesArgs = { input: GetVocabularyEntriesInput; }; + export type QueryGetWorkflowArgs = { input: GetWorkflowInput; }; + export type QuerySearchIngestionSubjectsArgs = { input: SearchIngestionSubjectsInput; }; @@ -192,6 +224,7 @@ export type GetAccessPolicyResult = { AccessPolicy?: Maybe; }; + export type AccessAction = { __typename?: 'AccessAction'; idAccessAction: Scalars['Int']; @@ -240,6 +273,7 @@ export type AccessRole = { AccessAction?: Maybe>>; }; + export type Mutation = { __typename?: 'Mutation'; createCaptureData: CreateCaptureDataResult; @@ -259,62 +293,77 @@ export type Mutation = { uploadAsset: UploadAssetResult; }; + export type MutationCreateCaptureDataArgs = { input: CreateCaptureDataInput; }; + export type MutationCreateCaptureDataPhotoArgs = { input: CreateCaptureDataPhotoInput; }; + export type MutationCreateItemArgs = { input: CreateItemInput; }; + export type MutationCreateModelArgs = { input: CreateModelInput; }; + export type MutationCreateProjectArgs = { input: CreateProjectInput; }; + export type MutationCreateSceneArgs = { input: CreateSceneInput; }; + export type MutationCreateSubjectArgs = { input: CreateSubjectInput; }; + export type MutationCreateUnitArgs = { input: CreateUnitInput; }; + export type MutationCreateUserArgs = { input: CreateUserInput; }; + export type MutationCreateVocabularyArgs = { input: CreateVocabularyInput; }; + export type MutationCreateVocabularySetArgs = { input: CreateVocabularySetInput; }; + export type MutationDiscardUploadedAssetVersionsArgs = { input: DiscardUploadedAssetVersionsInput; }; + export type MutationIngestDataArgs = { input: IngestDataInput; }; + export type MutationUpdateObjectDetailsArgs = { input: UpdateObjectDetailsInput; }; + export type MutationUploadAssetArgs = { file: Scalars['Upload']; type: Scalars['Int']; @@ -518,6 +567,7 @@ export type Asset = { FileName: Scalars['String']; FilePath: Scalars['String']; idAssetGroup?: Maybe; + idVAssetType?: Maybe; idSystemObject?: Maybe; StorageKey?: Maybe; AssetGroup?: Maybe; @@ -1970,608 +2020,1142 @@ export type DiscardUploadedAssetVersionsMutationVariables = Exact<{ input: DiscardUploadedAssetVersionsInput; }>; -export type DiscardUploadedAssetVersionsMutation = { __typename?: 'Mutation' } & { - discardUploadedAssetVersions: { __typename?: 'DiscardUploadedAssetVersionsResult' } & Pick; -}; + +export type DiscardUploadedAssetVersionsMutation = ( + { __typename?: 'Mutation' } + & { + discardUploadedAssetVersions: ( + { __typename?: 'DiscardUploadedAssetVersionsResult' } + & Pick + ) + } +); export type UploadAssetMutationVariables = Exact<{ file: Scalars['Upload']; type: Scalars['Int']; }>; -export type UploadAssetMutation = { __typename?: 'Mutation' } & { - uploadAsset: { __typename?: 'UploadAssetResult' } & Pick; -}; + +export type UploadAssetMutation = ( + { __typename?: 'Mutation' } + & { + uploadAsset: ( + { __typename?: 'UploadAssetResult' } + & Pick + ) + } +); export type CreateCaptureDataMutationVariables = Exact<{ input: CreateCaptureDataInput; }>; -export type CreateCaptureDataMutation = { __typename?: 'Mutation' } & { - createCaptureData: { __typename?: 'CreateCaptureDataResult' } & { CaptureData?: Maybe<{ __typename?: 'CaptureData' } & Pick> }; -}; + +export type CreateCaptureDataMutation = ( + { __typename?: 'Mutation' } + & { + createCaptureData: ( + { __typename?: 'CreateCaptureDataResult' } + & { + CaptureData?: Maybe<( + { __typename?: 'CaptureData' } + & Pick + )> + } + ) + } +); export type CreateCaptureDataPhotoMutationVariables = Exact<{ input: CreateCaptureDataPhotoInput; }>; -export type CreateCaptureDataPhotoMutation = { __typename?: 'Mutation' } & { - createCaptureDataPhoto: { __typename?: 'CreateCaptureDataPhotoResult' } & { - CaptureDataPhoto?: Maybe<{ __typename?: 'CaptureDataPhoto' } & Pick>; - }; -}; + +export type CreateCaptureDataPhotoMutation = ( + { __typename?: 'Mutation' } + & { + createCaptureDataPhoto: ( + { __typename?: 'CreateCaptureDataPhotoResult' } + & { + CaptureDataPhoto?: Maybe<( + { __typename?: 'CaptureDataPhoto' } + & Pick + )> + } + ) + } +); export type IngestDataMutationVariables = Exact<{ input: IngestDataInput; }>; -export type IngestDataMutation = { __typename?: 'Mutation' } & { ingestData: { __typename?: 'IngestDataResult' } & Pick }; + +export type IngestDataMutation = ( + { __typename?: 'Mutation' } + & { + ingestData: ( + { __typename?: 'IngestDataResult' } + & Pick + ) + } +); export type CreateModelMutationVariables = Exact<{ input: CreateModelInput; }>; -export type CreateModelMutation = { __typename?: 'Mutation' } & { - createModel: { __typename?: 'CreateModelResult' } & { Model?: Maybe<{ __typename?: 'Model' } & Pick> }; -}; + +export type CreateModelMutation = ( + { __typename?: 'Mutation' } + & { + createModel: ( + { __typename?: 'CreateModelResult' } + & { + Model?: Maybe<( + { __typename?: 'Model' } + & Pick + )> + } + ) + } +); export type CreateSceneMutationVariables = Exact<{ input: CreateSceneInput; }>; -export type CreateSceneMutation = { __typename?: 'Mutation' } & { - createScene: { __typename?: 'CreateSceneResult' } & { Scene?: Maybe<{ __typename?: 'Scene' } & Pick> }; -}; + +export type CreateSceneMutation = ( + { __typename?: 'Mutation' } + & { + createScene: ( + { __typename?: 'CreateSceneResult' } + & { + Scene?: Maybe<( + { __typename?: 'Scene' } + & Pick + )> + } + ) + } +); export type UpdateObjectDetailsMutationVariables = Exact<{ input: UpdateObjectDetailsInput; }>; -export type UpdateObjectDetailsMutation = { __typename?: 'Mutation' } & { - updateObjectDetails: { __typename?: 'UpdateObjectDetailsResult' } & Pick; -}; + +export type UpdateObjectDetailsMutation = ( + { __typename?: 'Mutation' } + & { + updateObjectDetails: ( + { __typename?: 'UpdateObjectDetailsResult' } + & Pick + ) + } +); export type CreateItemMutationVariables = Exact<{ input: CreateItemInput; }>; -export type CreateItemMutation = { __typename?: 'Mutation' } & { - createItem: { __typename?: 'CreateItemResult' } & { Item?: Maybe<{ __typename?: 'Item' } & Pick> }; -}; + +export type CreateItemMutation = ( + { __typename?: 'Mutation' } + & { + createItem: ( + { __typename?: 'CreateItemResult' } + & { + Item?: Maybe<( + { __typename?: 'Item' } + & Pick + )> + } + ) + } +); export type CreateProjectMutationVariables = Exact<{ input: CreateProjectInput; }>; -export type CreateProjectMutation = { __typename?: 'Mutation' } & { - createProject: { __typename?: 'CreateProjectResult' } & { Project?: Maybe<{ __typename?: 'Project' } & Pick> }; -}; + +export type CreateProjectMutation = ( + { __typename?: 'Mutation' } + & { + createProject: ( + { __typename?: 'CreateProjectResult' } + & { + Project?: Maybe<( + { __typename?: 'Project' } + & Pick + )> + } + ) + } +); export type CreateSubjectMutationVariables = Exact<{ input: CreateSubjectInput; }>; -export type CreateSubjectMutation = { __typename?: 'Mutation' } & { - createSubject: { __typename?: 'CreateSubjectResult' } & { Subject?: Maybe<{ __typename?: 'Subject' } & Pick> }; -}; + +export type CreateSubjectMutation = ( + { __typename?: 'Mutation' } + & { + createSubject: ( + { __typename?: 'CreateSubjectResult' } + & { + Subject?: Maybe<( + { __typename?: 'Subject' } + & Pick + )> + } + ) + } +); export type CreateUnitMutationVariables = Exact<{ input: CreateUnitInput; }>; -export type CreateUnitMutation = { __typename?: 'Mutation' } & { - createUnit: { __typename?: 'CreateUnitResult' } & { Unit?: Maybe<{ __typename?: 'Unit' } & Pick> }; -}; + +export type CreateUnitMutation = ( + { __typename?: 'Mutation' } + & { + createUnit: ( + { __typename?: 'CreateUnitResult' } + & { + Unit?: Maybe<( + { __typename?: 'Unit' } + & Pick + )> + } + ) + } +); export type CreateUserMutationVariables = Exact<{ input: CreateUserInput; }>; -export type CreateUserMutation = { __typename?: 'Mutation' } & { - createUser: { __typename?: 'CreateUserResult' } & { User?: Maybe<{ __typename?: 'User' } & Pick> }; -}; + +export type CreateUserMutation = ( + { __typename?: 'Mutation' } + & { + createUser: ( + { __typename?: 'CreateUserResult' } + & { + User?: Maybe<( + { __typename?: 'User' } + & Pick + )> + } + ) + } +); export type CreateVocabularyMutationVariables = Exact<{ input: CreateVocabularyInput; }>; -export type CreateVocabularyMutation = { __typename?: 'Mutation' } & { - createVocabulary: { __typename?: 'CreateVocabularyResult' } & { Vocabulary?: Maybe<{ __typename?: 'Vocabulary' } & Pick> }; -}; + +export type CreateVocabularyMutation = ( + { __typename?: 'Mutation' } + & { + createVocabulary: ( + { __typename?: 'CreateVocabularyResult' } + & { + Vocabulary?: Maybe<( + { __typename?: 'Vocabulary' } + & Pick + )> + } + ) + } +); export type CreateVocabularySetMutationVariables = Exact<{ input: CreateVocabularySetInput; }>; -export type CreateVocabularySetMutation = { __typename?: 'Mutation' } & { - createVocabularySet: { __typename?: 'CreateVocabularySetResult' } & { VocabularySet?: Maybe<{ __typename?: 'VocabularySet' } & Pick> }; -}; + +export type CreateVocabularySetMutation = ( + { __typename?: 'Mutation' } + & { + createVocabularySet: ( + { __typename?: 'CreateVocabularySetResult' } + & { + VocabularySet?: Maybe<( + { __typename?: 'VocabularySet' } + & Pick + )> + } + ) + } +); export type GetAccessPolicyQueryVariables = Exact<{ input: GetAccessPolicyInput; }>; -export type GetAccessPolicyQuery = { __typename?: 'Query' } & { - getAccessPolicy: { __typename?: 'GetAccessPolicyResult' } & { AccessPolicy?: Maybe<{ __typename?: 'AccessPolicy' } & Pick> }; -}; + +export type GetAccessPolicyQuery = ( + { __typename?: 'Query' } + & { + getAccessPolicy: ( + { __typename?: 'GetAccessPolicyResult' } + & { + AccessPolicy?: Maybe<( + { __typename?: 'AccessPolicy' } + & Pick + )> + } + ) + } +); export type GetAssetQueryVariables = Exact<{ input: GetAssetInput; }>; -export type GetAssetQuery = { __typename?: 'Query' } & { getAsset: { __typename?: 'GetAssetResult' } & { Asset?: Maybe<{ __typename?: 'Asset' } & Pick> } }; + +export type GetAssetQuery = ( + { __typename?: 'Query' } + & { + getAsset: ( + { __typename?: 'GetAssetResult' } + & { + Asset?: Maybe<( + { __typename?: 'Asset' } + & Pick + )> + } + ) + } +); export type GetAssetVersionsDetailsQueryVariables = Exact<{ input: GetAssetVersionsDetailsInput; }>; -export type GetAssetVersionsDetailsQuery = { __typename?: 'Query' } & { - getAssetVersionsDetails: { __typename?: 'GetAssetVersionsDetailsResult' } & Pick & { - Details: Array< - { __typename?: 'GetAssetVersionDetailResult' } & Pick & { - SubjectUnitIdentifier?: Maybe< - { __typename?: 'SubjectUnitIdentifier' } & Pick< - SubjectUnitIdentifier, - 'idSubject' | 'SubjectName' | 'UnitAbbreviation' | 'IdentifierPublic' | 'IdentifierCollection' - > - >; - Project?: Maybe>>; - Item?: Maybe<{ __typename?: 'Item' } & Pick>; - CaptureDataPhoto?: Maybe< - { __typename?: 'IngestPhotogrammetry' } & Pick< - IngestPhotogrammetry, - | 'idAssetVersion' - | 'dateCaptured' - | 'datasetType' - | 'systemCreated' - | 'description' - | 'cameraSettingUniform' - | 'datasetFieldId' - | 'itemPositionType' - | 'itemPositionFieldId' - | 'itemArrangementFieldId' - | 'focusType' - | 'lightsourceType' - | 'backgroundRemovalMethod' - | 'clusterType' - | 'clusterGeometryFieldId' - | 'directory' - > & { - folders: Array<{ __typename?: 'IngestFolder' } & Pick>; - identifiers: Array<{ __typename?: 'IngestIdentifier' } & Pick>; - } - >; - Model?: Maybe< - { __typename?: 'IngestModel' } & Pick< - IngestModel, - | 'idAssetVersion' - | 'systemCreated' - | 'master' - | 'authoritative' - | 'creationMethod' - | 'modality' - | 'purpose' - | 'units' - | 'dateCaptured' - | 'modelFileType' - | 'directory' - | 'roughness' - | 'metalness' - | 'pointCount' - | 'faceCount' - | 'isWatertight' - | 'hasNormals' - | 'hasVertexColor' - | 'hasUVSpace' - | 'boundingBoxP1X' - | 'boundingBoxP1Y' - | 'boundingBoxP1Z' - | 'boundingBoxP2X' - | 'boundingBoxP2Y' - | 'boundingBoxP2Z' - > & { - identifiers: Array<{ __typename?: 'IngestIdentifier' } & Pick>; - uvMaps: Array<{ __typename?: 'IngestUVMap' } & Pick>; - } - >; - Scene?: Maybe< - { __typename?: 'IngestScene' } & Pick & { - identifiers: Array<{ __typename?: 'IngestIdentifier' } & Pick>; + +export type GetAssetVersionsDetailsQuery = ( + { __typename?: 'Query' } + & { + getAssetVersionsDetails: ( + { __typename?: 'GetAssetVersionsDetailsResult' } + & Pick + & { + Details: Array<( + { __typename?: 'GetAssetVersionDetailResult' } + & Pick + & { + SubjectUnitIdentifier?: Maybe<( + { __typename?: 'SubjectUnitIdentifier' } + & Pick + )>, Project?: Maybe + )>>, Item?: Maybe<( + { __typename?: 'Item' } + & Pick + )>, CaptureDataPhoto?: Maybe<( + { __typename?: 'IngestPhotogrammetry' } + & Pick + & { + folders: Array<( + { __typename?: 'IngestFolder' } + & Pick + )>, identifiers: Array<( + { __typename?: 'IngestIdentifier' } + & Pick + )> + } + )>, Model?: Maybe<( + { __typename?: 'IngestModel' } + & Pick + & { + identifiers: Array<( + { __typename?: 'IngestIdentifier' } + & Pick + )>, uvMaps: Array<( + { __typename?: 'IngestUVMap' } + & Pick + )> + } + )>, Scene?: Maybe<( + { __typename?: 'IngestScene' } + & Pick + & { + identifiers: Array<( + { __typename?: 'IngestIdentifier' } + & Pick + )> + } + )> + } + )> } - >; - } - >; - }; -}; + ) + } +); export type GetContentsForAssetVersionsQueryVariables = Exact<{ input: GetContentsForAssetVersionsInput; }>; -export type GetContentsForAssetVersionsQuery = { __typename?: 'Query' } & { - getContentsForAssetVersions: { __typename?: 'GetContentsForAssetVersionsResult' } & { - AssetVersionContent: Array<{ __typename?: 'AssetVersionContent' } & Pick>; - }; -}; - -export type GetUploadedAssetVersionQueryVariables = Exact<{ [key: string]: never }>; -export type GetUploadedAssetVersionQuery = { __typename?: 'Query' } & { - getUploadedAssetVersion: { __typename?: 'GetUploadedAssetVersionResult' } & { - AssetVersion: Array< - { __typename?: 'AssetVersion' } & Pick & { - Asset?: Maybe< - { __typename?: 'Asset' } & Pick & { VAssetType?: Maybe<{ __typename?: 'Vocabulary' } & Pick> } - >; - } - >; - }; -}; +export type GetContentsForAssetVersionsQuery = ( + { __typename?: 'Query' } + & { + getContentsForAssetVersions: ( + { __typename?: 'GetContentsForAssetVersionsResult' } + & { + AssetVersionContent: Array<( + { __typename?: 'AssetVersionContent' } + & Pick + )> + } + ) + } +); + +export type GetUploadedAssetVersionQueryVariables = Exact<{ [key: string]: never; }>; + + +export type GetUploadedAssetVersionQuery = ( + { __typename?: 'Query' } + & { + getUploadedAssetVersion: ( + { __typename?: 'GetUploadedAssetVersionResult' } + & { + AssetVersion: Array<( + { __typename?: 'AssetVersion' } + & Pick + & { + Asset?: Maybe<( + { __typename?: 'Asset' } + & Pick + & { + VAssetType?: Maybe<( + { __typename?: 'Vocabulary' } + & Pick + )> + } + )> + } + )> + } + ) + } +); export type GetCaptureDataQueryVariables = Exact<{ input: GetCaptureDataInput; }>; -export type GetCaptureDataQuery = { __typename?: 'Query' } & { - getCaptureData: { __typename?: 'GetCaptureDataResult' } & { CaptureData?: Maybe<{ __typename?: 'CaptureData' } & Pick> }; -}; + +export type GetCaptureDataQuery = ( + { __typename?: 'Query' } + & { + getCaptureData: ( + { __typename?: 'GetCaptureDataResult' } + & { + CaptureData?: Maybe<( + { __typename?: 'CaptureData' } + & Pick + )> + } + ) + } +); export type GetCaptureDataPhotoQueryVariables = Exact<{ input: GetCaptureDataPhotoInput; }>; -export type GetCaptureDataPhotoQuery = { __typename?: 'Query' } & { - getCaptureDataPhoto: { __typename?: 'GetCaptureDataPhotoResult' } & { - CaptureDataPhoto?: Maybe<{ __typename?: 'CaptureDataPhoto' } & Pick>; - }; -}; + +export type GetCaptureDataPhotoQuery = ( + { __typename?: 'Query' } + & { + getCaptureDataPhoto: ( + { __typename?: 'GetCaptureDataPhotoResult' } + & { + CaptureDataPhoto?: Maybe<( + { __typename?: 'CaptureDataPhoto' } + & Pick + )> + } + ) + } +); export type AreCameraSettingsUniformQueryVariables = Exact<{ input: AreCameraSettingsUniformInput; }>; -export type AreCameraSettingsUniformQuery = { __typename?: 'Query' } & { - areCameraSettingsUniform: { __typename?: 'AreCameraSettingsUniformResult' } & Pick; -}; + +export type AreCameraSettingsUniformQuery = ( + { __typename?: 'Query' } + & { + areCameraSettingsUniform: ( + { __typename?: 'AreCameraSettingsUniformResult' } + & Pick + ) + } +); export type GetLicenseQueryVariables = Exact<{ input: GetLicenseInput; }>; -export type GetLicenseQuery = { __typename?: 'Query' } & { - getLicense: { __typename?: 'GetLicenseResult' } & { License?: Maybe<{ __typename?: 'License' } & Pick> }; -}; + +export type GetLicenseQuery = ( + { __typename?: 'Query' } + & { + getLicense: ( + { __typename?: 'GetLicenseResult' } + & { + License?: Maybe<( + { __typename?: 'License' } + & Pick + )> + } + ) + } +); export type GetModelQueryVariables = Exact<{ input: GetModelInput; }>; -export type GetModelQuery = { __typename?: 'Query' } & { getModel: { __typename?: 'GetModelResult' } & { Model?: Maybe<{ __typename?: 'Model' } & Pick> } }; - -export type GetFilterViewDataQueryVariables = Exact<{ [key: string]: never }>; -export type GetFilterViewDataQuery = { __typename?: 'Query' } & { - getFilterViewData: { __typename?: 'GetFilterViewDataResult' } & { - units: Array<{ __typename?: 'Unit' } & Pick & { SystemObject?: Maybe<{ __typename?: 'SystemObject' } & Pick> }>; - projects: Array< - { __typename?: 'Project' } & Pick & { SystemObject?: Maybe<{ __typename?: 'SystemObject' } & Pick> } - >; - }; -}; +export type GetModelQuery = ( + { __typename?: 'Query' } + & { + getModel: ( + { __typename?: 'GetModelResult' } + & { + Model?: Maybe<( + { __typename?: 'Model' } + & Pick + )> + } + ) + } +); + +export type GetFilterViewDataQueryVariables = Exact<{ [key: string]: never; }>; + + +export type GetFilterViewDataQuery = ( + { __typename?: 'Query' } + & { + getFilterViewData: ( + { __typename?: 'GetFilterViewDataResult' } + & { + units: Array<( + { __typename?: 'Unit' } + & Pick + & { + SystemObject?: Maybe<( + { __typename?: 'SystemObject' } + & Pick + )> + } + )>, projects: Array<( + { __typename?: 'Project' } + & Pick + & { + SystemObject?: Maybe<( + { __typename?: 'SystemObject' } + & Pick + )> + } + )> + } + ) + } +); export type GetObjectChildrenQueryVariables = Exact<{ input: GetObjectChildrenInput; }>; -export type GetObjectChildrenQuery = { __typename?: 'Query' } & { - getObjectChildren: { __typename?: 'GetObjectChildrenResult' } & Pick & { - entries: Array<{ __typename?: 'NavigationResultEntry' } & Pick>; - }; -}; + +export type GetObjectChildrenQuery = ( + { __typename?: 'Query' } + & { + getObjectChildren: ( + { __typename?: 'GetObjectChildrenResult' } + & Pick + & { + entries: Array<( + { __typename?: 'NavigationResultEntry' } + & Pick + )> + } + ) + } +); export type GetIntermediaryFileQueryVariables = Exact<{ input: GetIntermediaryFileInput; }>; -export type GetIntermediaryFileQuery = { __typename?: 'Query' } & { - getIntermediaryFile: { __typename?: 'GetIntermediaryFileResult' } & { - IntermediaryFile?: Maybe<{ __typename?: 'IntermediaryFile' } & Pick>; - }; -}; + +export type GetIntermediaryFileQuery = ( + { __typename?: 'Query' } + & { + getIntermediaryFile: ( + { __typename?: 'GetIntermediaryFileResult' } + & { + IntermediaryFile?: Maybe<( + { __typename?: 'IntermediaryFile' } + & Pick + )> + } + ) + } +); export type GetSceneQueryVariables = Exact<{ input: GetSceneInput; }>; -export type GetSceneQuery = { __typename?: 'Query' } & { getScene: { __typename?: 'GetSceneResult' } & { Scene?: Maybe<{ __typename?: 'Scene' } & Pick> } }; + +export type GetSceneQuery = ( + { __typename?: 'Query' } + & { + getScene: ( + { __typename?: 'GetSceneResult' } + & { + Scene?: Maybe<( + { __typename?: 'Scene' } + & Pick + )> + } + ) + } +); export type GetAssetDetailsForSystemObjectQueryVariables = Exact<{ input: GetAssetDetailsForSystemObjectInput; }>; -export type GetAssetDetailsForSystemObjectQuery = { __typename?: 'Query' } & { - getAssetDetailsForSystemObject: { __typename?: 'GetAssetDetailsForSystemObjectResult' } & { - assetDetails: Array<{ __typename?: 'AssetDetail' } & Pick>; - }; -}; + +export type GetAssetDetailsForSystemObjectQuery = ( + { __typename?: 'Query' } + & { + getAssetDetailsForSystemObject: ( + { __typename?: 'GetAssetDetailsForSystemObjectResult' } + & { + assetDetails: Array<( + { __typename?: 'AssetDetail' } + & Pick + )> + } + ) + } +); export type GetDetailsTabDataForObjectQueryVariables = Exact<{ input: GetDetailsTabDataForObjectInput; }>; -export type GetDetailsTabDataForObjectQuery = { __typename?: 'Query' } & { - getDetailsTabDataForObject: { __typename?: 'GetDetailsTabDataForObjectResult' } & { - Unit?: Maybe<{ __typename?: 'UnitDetailFields' } & Pick>; - Project?: Maybe<{ __typename?: 'ProjectDetailFields' } & Pick>; - Subject?: Maybe< - { __typename?: 'SubjectDetailFields' } & Pick - >; - Item?: Maybe< - { __typename?: 'ItemDetailFields' } & Pick< - ItemDetailFields, - 'EntireSubject' | 'Altitude' | 'Latitude' | 'Longitude' | 'R0' | 'R1' | 'R2' | 'R3' | 'TS0' | 'TS1' | 'TS2' - > - >; - CaptureData?: Maybe< - { __typename?: 'CaptureDataDetailFields' } & Pick< - CaptureDataDetailFields, - | 'captureMethod' - | 'dateCaptured' - | 'datasetType' - | 'description' - | 'cameraSettingUniform' - | 'datasetFieldId' - | 'itemPositionType' - | 'itemPositionFieldId' - | 'itemArrangementFieldId' - | 'focusType' - | 'lightsourceType' - | 'backgroundRemovalMethod' - | 'clusterType' - | 'clusterGeometryFieldId' - > & { folders: Array<{ __typename?: 'IngestFolder' } & Pick> } - >; - Model?: Maybe< - { __typename?: 'ModelDetailFields' } & Pick< - ModelDetailFields, - | 'size' - | 'master' - | 'authoritative' - | 'creationMethod' - | 'modality' - | 'purpose' - | 'units' - | 'dateCaptured' - | 'modelFileType' - | 'roughness' - | 'metalness' - | 'pointCount' - | 'faceCount' - | 'isWatertight' - | 'hasNormals' - | 'hasVertexColor' - | 'hasUVSpace' - | 'boundingBoxP1X' - | 'boundingBoxP1Y' - | 'boundingBoxP1Z' - | 'boundingBoxP2X' - | 'boundingBoxP2Y' - | 'boundingBoxP2Z' - > & { uvMaps: Array<{ __typename?: 'IngestUVMap' } & Pick> } - >; - Scene?: Maybe<{ __typename?: 'SceneDetailFields' } & Pick>; - IntermediaryFile?: Maybe<{ __typename?: 'IntermediaryFileDetailFields' } & Pick>; - ProjectDocumentation?: Maybe<{ __typename?: 'ProjectDocumentationDetailFields' } & Pick>; - Asset?: Maybe<{ __typename?: 'AssetDetailFields' } & Pick>; - AssetVersion?: Maybe<{ __typename?: 'AssetVersionDetailFields' } & Pick>; - Actor?: Maybe<{ __typename?: 'ActorDetailFields' } & Pick>; - Stakeholder?: Maybe< - { __typename?: 'StakeholderDetailFields' } & Pick< - StakeholderDetailFields, - 'OrganizationName' | 'EmailAddress' | 'PhoneNumberMobile' | 'PhoneNumberOffice' | 'MailingAddress' - > - >; - }; -}; + +export type GetDetailsTabDataForObjectQuery = ( + { __typename?: 'Query' } + & { + getDetailsTabDataForObject: ( + { __typename?: 'GetDetailsTabDataForObjectResult' } + & { + Unit?: Maybe<( + { __typename?: 'UnitDetailFields' } + & Pick + )>, Project?: Maybe<( + { __typename?: 'ProjectDetailFields' } + & Pick + )>, Subject?: Maybe<( + { __typename?: 'SubjectDetailFields' } + & Pick + )>, Item?: Maybe<( + { __typename?: 'ItemDetailFields' } + & Pick + )>, CaptureData?: Maybe<( + { __typename?: 'CaptureDataDetailFields' } + & Pick + & { + folders: Array<( + { __typename?: 'IngestFolder' } + & Pick + )> + } + )>, Model?: Maybe<( + { __typename?: 'ModelDetailFields' } + & Pick + & { + uvMaps: Array<( + { __typename?: 'IngestUVMap' } + & Pick + )> + } + )>, Scene?: Maybe<( + { __typename?: 'SceneDetailFields' } + & Pick + )>, IntermediaryFile?: Maybe<( + { __typename?: 'IntermediaryFileDetailFields' } + & Pick + )>, ProjectDocumentation?: Maybe<( + { __typename?: 'ProjectDocumentationDetailFields' } + & Pick + )>, Asset?: Maybe<( + { __typename?: 'AssetDetailFields' } + & Pick + )>, AssetVersion?: Maybe<( + { __typename?: 'AssetVersionDetailFields' } + & Pick + )>, Actor?: Maybe<( + { __typename?: 'ActorDetailFields' } + & Pick + )>, Stakeholder?: Maybe<( + { __typename?: 'StakeholderDetailFields' } + & Pick + )> + } + ) + } +); export type GetSourceObjectIdentiferQueryVariables = Exact<{ input: GetSourceObjectIdentiferInput; }>; -export type GetSourceObjectIdentiferQuery = { __typename?: 'Query' } & { - getSourceObjectIdentifer: { __typename?: 'GetSourceObjectIdentiferResult' } & { - sourceObjectIdentifiers: Array<{ __typename?: 'SourceObjectIdentifier' } & Pick>; - }; -}; + +export type GetSourceObjectIdentiferQuery = ( + { __typename?: 'Query' } + & { + getSourceObjectIdentifer: ( + { __typename?: 'GetSourceObjectIdentiferResult' } + & { + sourceObjectIdentifiers: Array<( + { __typename?: 'SourceObjectIdentifier' } + & Pick + )> + } + ) + } +); export type GetSystemObjectDetailsQueryVariables = Exact<{ input: GetSystemObjectDetailsInput; }>; -export type GetSystemObjectDetailsQuery = { __typename?: 'Query' } & { - getSystemObjectDetails: { __typename?: 'GetSystemObjectDetailsResult' } & Pick< - GetSystemObjectDetailsResult, - 'idObject' | 'name' | 'retired' | 'objectType' | 'allowed' | 'publishedState' | 'thumbnail' - > & { - identifiers: Array<{ __typename?: 'IngestIdentifier' } & Pick>; - unit?: Maybe<{ __typename?: 'RepositoryPath' } & Pick>; - project?: Maybe<{ __typename?: 'RepositoryPath' } & Pick>; - subject?: Maybe<{ __typename?: 'RepositoryPath' } & Pick>; - item?: Maybe<{ __typename?: 'RepositoryPath' } & Pick>; - objectAncestors: Array>>; - sourceObjects: Array<{ __typename?: 'RelatedObject' } & Pick>; - derivedObjects: Array<{ __typename?: 'RelatedObject' } & Pick>; - }; -}; + +export type GetSystemObjectDetailsQuery = ( + { __typename?: 'Query' } + & { + getSystemObjectDetails: ( + { __typename?: 'GetSystemObjectDetailsResult' } + & Pick + & { + identifiers: Array<( + { __typename?: 'IngestIdentifier' } + & Pick + )>, unit?: Maybe<( + { __typename?: 'RepositoryPath' } + & Pick + )>, project?: Maybe<( + { __typename?: 'RepositoryPath' } + & Pick + )>, subject?: Maybe<( + { __typename?: 'RepositoryPath' } + & Pick + )>, item?: Maybe<( + { __typename?: 'RepositoryPath' } + & Pick + )>, objectAncestors: Array + )>>, sourceObjects: Array<( + { __typename?: 'RelatedObject' } + & Pick + )>, derivedObjects: Array<( + { __typename?: 'RelatedObject' } + & Pick + )> + } + ) + } +); export type GetVersionsForSystemObjectQueryVariables = Exact<{ input: GetVersionsForSystemObjectInput; }>; -export type GetVersionsForSystemObjectQuery = { __typename?: 'Query' } & { - getVersionsForSystemObject: { __typename?: 'GetVersionsForSystemObjectResult' } & { - versions: Array<{ __typename?: 'DetailVersion' } & Pick>; - }; -}; + +export type GetVersionsForSystemObjectQuery = ( + { __typename?: 'Query' } + & { + getVersionsForSystemObject: ( + { __typename?: 'GetVersionsForSystemObjectResult' } + & { + versions: Array<( + { __typename?: 'DetailVersion' } + & Pick + )> + } + ) + } +); export type GetIngestionItemsForSubjectsQueryVariables = Exact<{ input: GetIngestionItemsForSubjectsInput; }>; -export type GetIngestionItemsForSubjectsQuery = { __typename?: 'Query' } & { - getIngestionItemsForSubjects: { __typename?: 'GetIngestionItemsForSubjectsResult' } & { - Item: Array<{ __typename?: 'Item' } & Pick>; - }; -}; + +export type GetIngestionItemsForSubjectsQuery = ( + { __typename?: 'Query' } + & { + getIngestionItemsForSubjects: ( + { __typename?: 'GetIngestionItemsForSubjectsResult' } + & { + Item: Array<( + { __typename?: 'Item' } + & Pick + )> + } + ) + } +); export type GetIngestionProjectsForSubjectsQueryVariables = Exact<{ input: GetIngestionProjectsForSubjectsInput; }>; -export type GetIngestionProjectsForSubjectsQuery = { __typename?: 'Query' } & { - getIngestionProjectsForSubjects: { __typename?: 'GetIngestionProjectsForSubjectsResult' } & { - Project: Array<{ __typename?: 'Project' } & Pick>; - }; -}; + +export type GetIngestionProjectsForSubjectsQuery = ( + { __typename?: 'Query' } + & { + getIngestionProjectsForSubjects: ( + { __typename?: 'GetIngestionProjectsForSubjectsResult' } + & { + Project: Array<( + { __typename?: 'Project' } + & Pick + )> + } + ) + } +); export type GetItemQueryVariables = Exact<{ input: GetItemInput; }>; -export type GetItemQuery = { __typename?: 'Query' } & { getItem: { __typename?: 'GetItemResult' } & { Item?: Maybe<{ __typename?: 'Item' } & Pick> } }; + +export type GetItemQuery = ( + { __typename?: 'Query' } + & { + getItem: ( + { __typename?: 'GetItemResult' } + & { + Item?: Maybe<( + { __typename?: 'Item' } + & Pick + )> + } + ) + } +); export type GetItemsForSubjectQueryVariables = Exact<{ input: GetItemsForSubjectInput; }>; -export type GetItemsForSubjectQuery = { __typename?: 'Query' } & { - getItemsForSubject: { __typename?: 'GetItemsForSubjectResult' } & { Item: Array<{ __typename?: 'Item' } & Pick> }; -}; + +export type GetItemsForSubjectQuery = ( + { __typename?: 'Query' } + & { + getItemsForSubject: ( + { __typename?: 'GetItemsForSubjectResult' } + & { + Item: Array<( + { __typename?: 'Item' } + & Pick + )> + } + ) + } +); export type GetObjectsForItemQueryVariables = Exact<{ input: GetObjectsForItemInput; }>; -export type GetObjectsForItemQuery = { __typename?: 'Query' } & { - getObjectsForItem: { __typename?: 'GetObjectsForItemResult' } & { - CaptureData: Array<{ __typename?: 'CaptureData' } & Pick>; - Model: Array<{ __typename?: 'Model' } & Pick>; - Scene: Array<{ __typename?: 'Scene' } & Pick>; - IntermediaryFile: Array<{ __typename?: 'IntermediaryFile' } & Pick>; - ProjectDocumentation: Array<{ __typename?: 'ProjectDocumentation' } & Pick>; - }; -}; + +export type GetObjectsForItemQuery = ( + { __typename?: 'Query' } + & { + getObjectsForItem: ( + { __typename?: 'GetObjectsForItemResult' } + & { + CaptureData: Array<( + { __typename?: 'CaptureData' } + & Pick + )>, Model: Array<( + { __typename?: 'Model' } + & Pick + )>, Scene: Array<( + { __typename?: 'Scene' } + & Pick + )>, IntermediaryFile: Array<( + { __typename?: 'IntermediaryFile' } + & Pick + )>, ProjectDocumentation: Array<( + { __typename?: 'ProjectDocumentation' } + & Pick + )> + } + ) + } +); export type GetProjectQueryVariables = Exact<{ input: GetProjectInput; }>; -export type GetProjectQuery = { __typename?: 'Query' } & { - getProject: { __typename?: 'GetProjectResult' } & { Project?: Maybe<{ __typename?: 'Project' } & Pick> }; -}; + +export type GetProjectQuery = ( + { __typename?: 'Query' } + & { + getProject: ( + { __typename?: 'GetProjectResult' } + & { + Project?: Maybe<( + { __typename?: 'Project' } + & Pick + )> + } + ) + } +); export type GetProjectDocumentationQueryVariables = Exact<{ input: GetProjectDocumentationInput; }>; -export type GetProjectDocumentationQuery = { __typename?: 'Query' } & { - getProjectDocumentation: { __typename?: 'GetProjectDocumentationResult' } & { - ProjectDocumentation?: Maybe<{ __typename?: 'ProjectDocumentation' } & Pick>; - }; -}; + +export type GetProjectDocumentationQuery = ( + { __typename?: 'Query' } + & { + getProjectDocumentation: ( + { __typename?: 'GetProjectDocumentationResult' } + & { + ProjectDocumentation?: Maybe<( + { __typename?: 'ProjectDocumentation' } + & Pick + )> + } + ) + } +); export type GetSubjectQueryVariables = Exact<{ input: GetSubjectInput; }>; -export type GetSubjectQuery = { __typename?: 'Query' } & { - getSubject: { __typename?: 'GetSubjectResult' } & { Subject?: Maybe<{ __typename?: 'Subject' } & Pick> }; -}; + +export type GetSubjectQuery = ( + { __typename?: 'Query' } + & { + getSubject: ( + { __typename?: 'GetSubjectResult' } + & { + Subject?: Maybe<( + { __typename?: 'Subject' } + & Pick + )> + } + ) + } +); export type GetSubjectsForUnitQueryVariables = Exact<{ input: GetSubjectsForUnitInput; }>; -export type GetSubjectsForUnitQuery = { __typename?: 'Query' } & { - getSubjectsForUnit: { __typename?: 'GetSubjectsForUnitResult' } & { Subject: Array<{ __typename?: 'Subject' } & Pick> }; -}; + +export type GetSubjectsForUnitQuery = ( + { __typename?: 'Query' } + & { + getSubjectsForUnit: ( + { __typename?: 'GetSubjectsForUnitResult' } + & { + Subject: Array<( + { __typename?: 'Subject' } + & Pick + )> + } + ) + } +); export type GetUnitQueryVariables = Exact<{ input: GetUnitInput; }>; -export type GetUnitQuery = { __typename?: 'Query' } & { getUnit: { __typename?: 'GetUnitResult' } & { Unit?: Maybe<{ __typename?: 'Unit' } & Pick> } }; + +export type GetUnitQuery = ( + { __typename?: 'Query' } + & { + getUnit: ( + { __typename?: 'GetUnitResult' } + & { + Unit?: Maybe<( + { __typename?: 'Unit' } + & Pick + )> + } + ) + } +); export type SearchIngestionSubjectsQueryVariables = Exact<{ input: SearchIngestionSubjectsInput; }>; -export type SearchIngestionSubjectsQuery = { __typename?: 'Query' } & { - searchIngestionSubjects: { __typename?: 'SearchIngestionSubjectsResult' } & { - SubjectUnitIdentifier: Array< - { __typename?: 'SubjectUnitIdentifier' } & Pick - >; - }; -}; -export type GetCurrentUserQueryVariables = Exact<{ [key: string]: never }>; +export type SearchIngestionSubjectsQuery = ( + { __typename?: 'Query' } + & { + searchIngestionSubjects: ( + { __typename?: 'SearchIngestionSubjectsResult' } + & { + SubjectUnitIdentifier: Array<( + { __typename?: 'SubjectUnitIdentifier' } + & Pick + )> + } + ) + } +); -export type GetCurrentUserQuery = { __typename?: 'Query' } & { - getCurrentUser: { __typename?: 'GetCurrentUserResult' } & { - User?: Maybe< - { __typename?: 'User' } & Pick< - User, - 'idUser' | 'Name' | 'Active' | 'DateActivated' | 'DateDisabled' | 'EmailAddress' | 'EmailSettings' | 'SecurityID' | 'WorkflowNotificationTime' - > - >; - }; -}; +export type GetCurrentUserQueryVariables = Exact<{ [key: string]: never; }>; + + +export type GetCurrentUserQuery = ( + { __typename?: 'Query' } + & { + getCurrentUser: ( + { __typename?: 'GetCurrentUserResult' } + & { + User?: Maybe<( + { __typename?: 'User' } + & Pick + )> + } + ) + } +); export type GetUserQueryVariables = Exact<{ input: GetUserInput; }>; -export type GetUserQuery = { __typename?: 'Query' } & { - getUser: { __typename?: 'GetUserResult' } & { User?: Maybe<{ __typename?: 'User' } & Pick> }; -}; + +export type GetUserQuery = ( + { __typename?: 'Query' } + & { + getUser: ( + { __typename?: 'GetUserResult' } + & { + User?: Maybe<( + { __typename?: 'User' } + & Pick + )> + } + ) + } +); export type GetVocabularyQueryVariables = Exact<{ input: GetVocabularyInput; }>; -export type GetVocabularyQuery = { __typename?: 'Query' } & { - getVocabulary: { __typename?: 'GetVocabularyResult' } & { Vocabulary?: Maybe<{ __typename?: 'Vocabulary' } & Pick> }; -}; + +export type GetVocabularyQuery = ( + { __typename?: 'Query' } + & { + getVocabulary: ( + { __typename?: 'GetVocabularyResult' } + & { + Vocabulary?: Maybe<( + { __typename?: 'Vocabulary' } + & Pick + )> + } + ) + } +); export type GetVocabularyEntriesQueryVariables = Exact<{ input: GetVocabularyEntriesInput; }>; -export type GetVocabularyEntriesQuery = { __typename?: 'Query' } & { - getVocabularyEntries: { __typename?: 'GetVocabularyEntriesResult' } & { - VocabularyEntries: Array< - { __typename?: 'VocabularyEntry' } & Pick & { - Vocabulary: Array<{ __typename?: 'Vocabulary' } & Pick>; - } - >; - }; -}; + +export type GetVocabularyEntriesQuery = ( + { __typename?: 'Query' } + & { + getVocabularyEntries: ( + { __typename?: 'GetVocabularyEntriesResult' } + & { + VocabularyEntries: Array<( + { __typename?: 'VocabularyEntry' } + & Pick + & { + Vocabulary: Array<( + { __typename?: 'Vocabulary' } + & Pick + )> + } + )> + } + ) + } +); export type GetWorkflowQueryVariables = Exact<{ input: GetWorkflowInput; }>; -export type GetWorkflowQuery = { __typename?: 'Query' } & { - getWorkflow: { __typename?: 'GetWorkflowResult' } & { Workflow?: Maybe<{ __typename?: 'Workflow' } & Pick> }; -}; + +export type GetWorkflowQuery = ( + { __typename?: 'Query' } + & { + getWorkflow: ( + { __typename?: 'GetWorkflowResult' } + & { + Workflow?: Maybe<( + { __typename?: 'Workflow' } + & Pick + )> + } + ) + } +); + export const DiscardUploadedAssetVersionsDocument = gql` mutation discardUploadedAssetVersions($input: DiscardUploadedAssetVersionsInput!) { - discardUploadedAssetVersions(input: $input) { - success - } - } -`; + discardUploadedAssetVersions(input: $input) { + success + } +} + `; export type DiscardUploadedAssetVersionsMutationFn = Apollo.MutationFunction; /** @@ -2591,9 +3175,7 @@ export type DiscardUploadedAssetVersionsMutationFn = Apollo.MutationFunction -) { +export function useDiscardUploadedAssetVersionsMutation(baseOptions?: Apollo.MutationHookOptions) { return Apollo.useMutation(DiscardUploadedAssetVersionsDocument, baseOptions); } export type DiscardUploadedAssetVersionsMutationHookResult = ReturnType; @@ -2601,13 +3183,13 @@ export type DiscardUploadedAssetVersionsMutationResult = Apollo.MutationResult; export const UploadAssetDocument = gql` mutation uploadAsset($file: Upload!, $type: Int!) { - uploadAsset(file: $file, type: $type) { - status - idAssetVersions - error - } - } -`; + uploadAsset(file: $file, type: $type) { + status + idAssetVersions + error + } +} + `; export type UploadAssetMutationFn = Apollo.MutationFunction; /** @@ -2636,13 +3218,13 @@ export type UploadAssetMutationResult = Apollo.MutationResult; export const CreateCaptureDataDocument = gql` mutation createCaptureData($input: CreateCaptureDataInput!) { - createCaptureData(input: $input) { - CaptureData { - idCaptureData - } - } + createCaptureData(input: $input) { + CaptureData { + idCaptureData } -`; + } +} + `; export type CreateCaptureDataMutationFn = Apollo.MutationFunction; /** @@ -2670,13 +3252,13 @@ export type CreateCaptureDataMutationResult = Apollo.MutationResult; export const CreateCaptureDataPhotoDocument = gql` mutation createCaptureDataPhoto($input: CreateCaptureDataPhotoInput!) { - createCaptureDataPhoto(input: $input) { - CaptureDataPhoto { - idCaptureDataPhoto - } - } + createCaptureDataPhoto(input: $input) { + CaptureDataPhoto { + idCaptureDataPhoto } -`; + } +} + `; export type CreateCaptureDataPhotoMutationFn = Apollo.MutationFunction; /** @@ -2704,11 +3286,11 @@ export type CreateCaptureDataPhotoMutationResult = Apollo.MutationResult; export const IngestDataDocument = gql` mutation ingestData($input: IngestDataInput!) { - ingestData(input: $input) { - success - } - } -`; + ingestData(input: $input) { + success + } +} + `; export type IngestDataMutationFn = Apollo.MutationFunction; /** @@ -2736,13 +3318,13 @@ export type IngestDataMutationResult = Apollo.MutationResult export type IngestDataMutationOptions = Apollo.BaseMutationOptions; export const CreateModelDocument = gql` mutation createModel($input: CreateModelInput!) { - createModel(input: $input) { - Model { - idModel - } - } + createModel(input: $input) { + Model { + idModel } -`; + } +} + `; export type CreateModelMutationFn = Apollo.MutationFunction; /** @@ -2770,13 +3352,13 @@ export type CreateModelMutationResult = Apollo.MutationResult; export const CreateSceneDocument = gql` mutation createScene($input: CreateSceneInput!) { - createScene(input: $input) { - Scene { - idScene - } - } + createScene(input: $input) { + Scene { + idScene } -`; + } +} + `; export type CreateSceneMutationFn = Apollo.MutationFunction; /** @@ -2804,11 +3386,11 @@ export type CreateSceneMutationResult = Apollo.MutationResult; export const UpdateObjectDetailsDocument = gql` mutation updateObjectDetails($input: UpdateObjectDetailsInput!) { - updateObjectDetails(input: $input) { - success - } - } -`; + updateObjectDetails(input: $input) { + success + } +} + `; export type UpdateObjectDetailsMutationFn = Apollo.MutationFunction; /** @@ -2836,13 +3418,13 @@ export type UpdateObjectDetailsMutationResult = Apollo.MutationResult; export const CreateItemDocument = gql` mutation createItem($input: CreateItemInput!) { - createItem(input: $input) { - Item { - idItem - } - } + createItem(input: $input) { + Item { + idItem } -`; + } +} + `; export type CreateItemMutationFn = Apollo.MutationFunction; /** @@ -2870,13 +3452,13 @@ export type CreateItemMutationResult = Apollo.MutationResult export type CreateItemMutationOptions = Apollo.BaseMutationOptions; export const CreateProjectDocument = gql` mutation createProject($input: CreateProjectInput!) { - createProject(input: $input) { - Project { - idProject - } - } + createProject(input: $input) { + Project { + idProject } -`; + } +} + `; export type CreateProjectMutationFn = Apollo.MutationFunction; /** @@ -2904,13 +3486,13 @@ export type CreateProjectMutationResult = Apollo.MutationResult; export const CreateSubjectDocument = gql` mutation createSubject($input: CreateSubjectInput!) { - createSubject(input: $input) { - Subject { - idSubject - } - } + createSubject(input: $input) { + Subject { + idSubject } -`; + } +} + `; export type CreateSubjectMutationFn = Apollo.MutationFunction; /** @@ -2938,13 +3520,13 @@ export type CreateSubjectMutationResult = Apollo.MutationResult; export const CreateUnitDocument = gql` mutation createUnit($input: CreateUnitInput!) { - createUnit(input: $input) { - Unit { - idUnit - } - } + createUnit(input: $input) { + Unit { + idUnit } -`; + } +} + `; export type CreateUnitMutationFn = Apollo.MutationFunction; /** @@ -2972,16 +3554,16 @@ export type CreateUnitMutationResult = Apollo.MutationResult export type CreateUnitMutationOptions = Apollo.BaseMutationOptions; export const CreateUserDocument = gql` mutation createUser($input: CreateUserInput!) { - createUser(input: $input) { - User { - idUser - Name - Active - DateActivated - } - } + createUser(input: $input) { + User { + idUser + Name + Active + DateActivated } -`; + } +} + `; export type CreateUserMutationFn = Apollo.MutationFunction; /** @@ -3009,13 +3591,13 @@ export type CreateUserMutationResult = Apollo.MutationResult export type CreateUserMutationOptions = Apollo.BaseMutationOptions; export const CreateVocabularyDocument = gql` mutation createVocabulary($input: CreateVocabularyInput!) { - createVocabulary(input: $input) { - Vocabulary { - idVocabulary - } - } + createVocabulary(input: $input) { + Vocabulary { + idVocabulary } -`; + } +} + `; export type CreateVocabularyMutationFn = Apollo.MutationFunction; /** @@ -3043,13 +3625,13 @@ export type CreateVocabularyMutationResult = Apollo.MutationResult; export const CreateVocabularySetDocument = gql` mutation createVocabularySet($input: CreateVocabularySetInput!) { - createVocabularySet(input: $input) { - VocabularySet { - idVocabularySet - } - } + createVocabularySet(input: $input) { + VocabularySet { + idVocabularySet } -`; + } +} + `; export type CreateVocabularySetMutationFn = Apollo.MutationFunction; /** @@ -3077,13 +3659,13 @@ export type CreateVocabularySetMutationResult = Apollo.MutationResult; export const GetAccessPolicyDocument = gql` query getAccessPolicy($input: GetAccessPolicyInput!) { - getAccessPolicy(input: $input) { - AccessPolicy { - idAccessPolicy - } - } + getAccessPolicy(input: $input) { + AccessPolicy { + idAccessPolicy } -`; + } +} + `; /** * __useGetAccessPolicyQuery__ @@ -3112,13 +3694,13 @@ export type GetAccessPolicyLazyQueryHookResult = ReturnType; export const GetAssetDocument = gql` query getAsset($input: GetAssetInput!) { - getAsset(input: $input) { - Asset { - idAsset - } - } + getAsset(input: $input) { + Asset { + idAsset } -`; + } +} + `; /** * __useGetAssetQuery__ @@ -3147,99 +3729,99 @@ export type GetAssetLazyQueryHookResult = ReturnType; export const GetAssetVersionsDetailsDocument = gql` query getAssetVersionsDetails($input: GetAssetVersionsDetailsInput!) { - getAssetVersionsDetails(input: $input) { - valid - Details { - idAssetVersion - SubjectUnitIdentifier { - idSubject - SubjectName - UnitAbbreviation - IdentifierPublic - IdentifierCollection - } - Project { - idProject - Name - } - Item { - idItem - Name - EntireSubject - } - CaptureDataPhoto { - idAssetVersion - dateCaptured - datasetType - systemCreated - description - cameraSettingUniform - datasetFieldId - itemPositionType - itemPositionFieldId - itemArrangementFieldId - focusType - lightsourceType - backgroundRemovalMethod - clusterType - clusterGeometryFieldId - directory - folders { - name - variantType - } - identifiers { - identifier - identifierType - } - } - Model { - idAssetVersion - systemCreated - master - authoritative - creationMethod - modality - purpose - units - dateCaptured - modelFileType - directory - identifiers { - identifier - identifierType - } - uvMaps { - name - edgeLength - mapType - } - roughness - metalness - pointCount - faceCount - isWatertight - hasNormals - hasVertexColor - hasUVSpace - boundingBoxP1X - boundingBoxP1Y - boundingBoxP1Z - boundingBoxP2X - boundingBoxP2Y - boundingBoxP2Z - } - Scene { - idAssetVersion - identifiers { - identifier - identifierType - } - } - } + getAssetVersionsDetails(input: $input) { + valid + Details { + idAssetVersion + SubjectUnitIdentifier { + idSubject + SubjectName + UnitAbbreviation + IdentifierPublic + IdentifierCollection + } + Project { + idProject + Name + } + Item { + idItem + Name + EntireSubject + } + CaptureDataPhoto { + idAssetVersion + dateCaptured + datasetType + systemCreated + description + cameraSettingUniform + datasetFieldId + itemPositionType + itemPositionFieldId + itemArrangementFieldId + focusType + lightsourceType + backgroundRemovalMethod + clusterType + clusterGeometryFieldId + directory + folders { + name + variantType + } + identifiers { + identifier + identifierType } + } + Model { + idAssetVersion + systemCreated + master + authoritative + creationMethod + modality + purpose + units + dateCaptured + modelFileType + directory + identifiers { + identifier + identifierType + } + uvMaps { + name + edgeLength + mapType + } + roughness + metalness + pointCount + faceCount + isWatertight + hasNormals + hasVertexColor + hasUVSpace + boundingBoxP1X + boundingBoxP1Y + boundingBoxP1Z + boundingBoxP2X + boundingBoxP2Y + boundingBoxP2Z + } + Scene { + idAssetVersion + identifiers { + identifier + identifierType + } + } } -`; + } +} + `; /** * __useGetAssetVersionsDetailsQuery__ @@ -3268,15 +3850,15 @@ export type GetAssetVersionsDetailsLazyQueryHookResult = ReturnType; export const GetContentsForAssetVersionsDocument = gql` query getContentsForAssetVersions($input: GetContentsForAssetVersionsInput!) { - getContentsForAssetVersions(input: $input) { - AssetVersionContent { - idAssetVersion - folders - all - } - } + getContentsForAssetVersions(input: $input) { + AssetVersionContent { + idAssetVersion + folders + all } -`; + } +} + `; /** * __useGetContentsForAssetVersionsQuery__ @@ -3305,23 +3887,23 @@ export type GetContentsForAssetVersionsLazyQueryHookResult = ReturnType; export const GetUploadedAssetVersionDocument = gql` query getUploadedAssetVersion { - getUploadedAssetVersion { - AssetVersion { - idAssetVersion - StorageSize - FileName - DateCreated - Asset { - idAsset - VAssetType { - idVocabulary - Term - } - } - } + getUploadedAssetVersion { + AssetVersion { + idAssetVersion + StorageSize + FileName + DateCreated + Asset { + idAsset + VAssetType { + idVocabulary + Term } + } } -`; + } +} + `; /** * __useGetUploadedAssetVersionQuery__ @@ -3349,13 +3931,13 @@ export type GetUploadedAssetVersionLazyQueryHookResult = ReturnType; export const GetCaptureDataDocument = gql` query getCaptureData($input: GetCaptureDataInput!) { - getCaptureData(input: $input) { - CaptureData { - idCaptureData - } - } + getCaptureData(input: $input) { + CaptureData { + idCaptureData } -`; + } +} + `; /** * __useGetCaptureDataQuery__ @@ -3384,13 +3966,13 @@ export type GetCaptureDataLazyQueryHookResult = ReturnType; export const GetCaptureDataPhotoDocument = gql` query getCaptureDataPhoto($input: GetCaptureDataPhotoInput!) { - getCaptureDataPhoto(input: $input) { - CaptureDataPhoto { - idCaptureDataPhoto - } - } + getCaptureDataPhoto(input: $input) { + CaptureDataPhoto { + idCaptureDataPhoto } -`; + } +} + `; /** * __useGetCaptureDataPhotoQuery__ @@ -3419,11 +4001,11 @@ export type GetCaptureDataPhotoLazyQueryHookResult = ReturnType; export const AreCameraSettingsUniformDocument = gql` query areCameraSettingsUniform($input: AreCameraSettingsUniformInput!) { - areCameraSettingsUniform(input: $input) { - isUniform - } - } -`; + areCameraSettingsUniform(input: $input) { + isUniform + } +} + `; /** * __useAreCameraSettingsUniformQuery__ @@ -3452,13 +4034,13 @@ export type AreCameraSettingsUniformLazyQueryHookResult = ReturnType; export const GetLicenseDocument = gql` query getLicense($input: GetLicenseInput!) { - getLicense(input: $input) { - License { - idLicense - } - } + getLicense(input: $input) { + License { + idLicense } -`; + } +} + `; /** * __useGetLicenseQuery__ @@ -3487,13 +4069,13 @@ export type GetLicenseLazyQueryHookResult = ReturnType; export const GetModelDocument = gql` query getModel($input: GetModelInput!) { - getModel(input: $input) { - Model { - idModel - } - } + getModel(input: $input) { + Model { + idModel } -`; + } +} + `; /** * __useGetModelQuery__ @@ -3522,24 +4104,24 @@ export type GetModelLazyQueryHookResult = ReturnType; export const GetFilterViewDataDocument = gql` query getFilterViewData { - getFilterViewData { - units { - idUnit - Name - SystemObject { - idSystemObject - } - } - projects { - idProject - Name - SystemObject { - idSystemObject - } - } - } + getFilterViewData { + units { + idUnit + Name + SystemObject { + idSystemObject + } } -`; + projects { + idProject + Name + SystemObject { + idSystemObject + } + } + } +} + `; /** * __useGetFilterViewDataQuery__ @@ -3567,20 +4149,20 @@ export type GetFilterViewDataLazyQueryHookResult = ReturnType; export const GetObjectChildrenDocument = gql` query getObjectChildren($input: GetObjectChildrenInput!) { - getObjectChildren(input: $input) { - success - error - entries { - idSystemObject - name - objectType - idObject - metadata - } - metadataColumns - } + getObjectChildren(input: $input) { + success + error + entries { + idSystemObject + name + objectType + idObject + metadata } -`; + metadataColumns + } +} + `; /** * __useGetObjectChildrenQuery__ @@ -3609,13 +4191,13 @@ export type GetObjectChildrenLazyQueryHookResult = ReturnType; export const GetIntermediaryFileDocument = gql` query getIntermediaryFile($input: GetIntermediaryFileInput!) { - getIntermediaryFile(input: $input) { - IntermediaryFile { - idIntermediaryFile - } - } + getIntermediaryFile(input: $input) { + IntermediaryFile { + idIntermediaryFile } -`; + } +} + `; /** * __useGetIntermediaryFileQuery__ @@ -3644,13 +4226,13 @@ export type GetIntermediaryFileLazyQueryHookResult = ReturnType; export const GetSceneDocument = gql` query getScene($input: GetSceneInput!) { - getScene(input: $input) { - Scene { - idScene - } - } + getScene(input: $input) { + Scene { + idScene } -`; + } +} + `; /** * __useGetSceneQuery__ @@ -3679,19 +4261,19 @@ export type GetSceneLazyQueryHookResult = ReturnType; export const GetAssetDetailsForSystemObjectDocument = gql` query getAssetDetailsForSystemObject($input: GetAssetDetailsForSystemObjectInput!) { - getAssetDetailsForSystemObject(input: $input) { - assetDetails { - idSystemObject - name - path - assetType - version - dateCreated - size - } - } + getAssetDetailsForSystemObject(input: $input) { + assetDetails { + idSystemObject + name + path + assetType + version + dateCreated + size } -`; + } +} + `; /** * __useGetAssetDetailsForSystemObjectQuery__ @@ -3712,9 +4294,7 @@ export const GetAssetDetailsForSystemObjectDocument = gql` export function useGetAssetDetailsForSystemObjectQuery(baseOptions?: Apollo.QueryHookOptions) { return Apollo.useQuery(GetAssetDetailsForSystemObjectDocument, baseOptions); } -export function useGetAssetDetailsForSystemObjectLazyQuery( - baseOptions?: Apollo.LazyQueryHookOptions -) { +export function useGetAssetDetailsForSystemObjectLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { return Apollo.useLazyQuery(GetAssetDetailsForSystemObjectDocument, baseOptions); } export type GetAssetDetailsForSystemObjectQueryHookResult = ReturnType; @@ -3722,125 +4302,125 @@ export type GetAssetDetailsForSystemObjectLazyQueryHookResult = ReturnType; export const GetDetailsTabDataForObjectDocument = gql` query getDetailsTabDataForObject($input: GetDetailsTabDataForObjectInput!) { - getDetailsTabDataForObject(input: $input) { - Unit { - Abbreviation - ARKPrefix - } - Project { - Description - } - Subject { - Altitude - Latitude - Longitude - R0 - R1 - R2 - R3 - TS0 - TS1 - TS2 - } - Item { - EntireSubject - Altitude - Latitude - Longitude - R0 - R1 - R2 - R3 - TS0 - TS1 - TS2 - } - CaptureData { - captureMethod - dateCaptured - datasetType - description - cameraSettingUniform - datasetFieldId - itemPositionType - itemPositionFieldId - itemArrangementFieldId - focusType - lightsourceType - backgroundRemovalMethod - clusterType - clusterGeometryFieldId - folders { - name - variantType - } - } - Model { - size - master - authoritative - creationMethod - modality - purpose - units - dateCaptured - modelFileType - uvMaps { - name - edgeLength - mapType - } - roughness - metalness - pointCount - faceCount - isWatertight - hasNormals - hasVertexColor - hasUVSpace - boundingBoxP1X - boundingBoxP1Y - boundingBoxP1Z - boundingBoxP2X - boundingBoxP2Y - boundingBoxP2Z - } - Scene { - Links - AssetType - Tours - Annotation - } - IntermediaryFile { - idIntermediaryFile - } - ProjectDocumentation { - Description - } - Asset { - FilePath - AssetType - } - AssetVersion { - Creator - DateCreated - StorageSize - Ingested - Version - } - Actor { - OrganizationName - } - Stakeholder { - OrganizationName - EmailAddress - PhoneNumberMobile - PhoneNumberOffice - MailingAddress - } - } + getDetailsTabDataForObject(input: $input) { + Unit { + Abbreviation + ARKPrefix + } + Project { + Description + } + Subject { + Altitude + Latitude + Longitude + R0 + R1 + R2 + R3 + TS0 + TS1 + TS2 + } + Item { + EntireSubject + Altitude + Latitude + Longitude + R0 + R1 + R2 + R3 + TS0 + TS1 + TS2 + } + CaptureData { + captureMethod + dateCaptured + datasetType + description + cameraSettingUniform + datasetFieldId + itemPositionType + itemPositionFieldId + itemArrangementFieldId + focusType + lightsourceType + backgroundRemovalMethod + clusterType + clusterGeometryFieldId + folders { + name + variantType + } + } + Model { + size + master + authoritative + creationMethod + modality + purpose + units + dateCaptured + modelFileType + uvMaps { + name + edgeLength + mapType + } + roughness + metalness + pointCount + faceCount + isWatertight + hasNormals + hasVertexColor + hasUVSpace + boundingBoxP1X + boundingBoxP1Y + boundingBoxP1Z + boundingBoxP2X + boundingBoxP2Y + boundingBoxP2Z + } + Scene { + Links + AssetType + Tours + Annotation + } + IntermediaryFile { + idIntermediaryFile + } + ProjectDocumentation { + Description } -`; + Asset { + FilePath + AssetType + } + AssetVersion { + Creator + DateCreated + StorageSize + Ingested + Version + } + Actor { + OrganizationName + } + Stakeholder { + OrganizationName + EmailAddress + PhoneNumberMobile + PhoneNumberOffice + MailingAddress + } + } +} + `; /** * __useGetDetailsTabDataForObjectQuery__ @@ -3869,14 +4449,14 @@ export type GetDetailsTabDataForObjectLazyQueryHookResult = ReturnType; export const GetSourceObjectIdentiferDocument = gql` query getSourceObjectIdentifer($input: GetSourceObjectIdentiferInput!) { - getSourceObjectIdentifer(input: $input) { - sourceObjectIdentifiers { - idSystemObject - identifier - } - } + getSourceObjectIdentifer(input: $input) { + sourceObjectIdentifiers { + idSystemObject + identifier } -`; + } +} + `; /** * __useGetSourceObjectIdentiferQuery__ @@ -3905,58 +4485,58 @@ export type GetSourceObjectIdentiferLazyQueryHookResult = ReturnType; export const GetSystemObjectDetailsDocument = gql` query getSystemObjectDetails($input: GetSystemObjectDetailsInput!) { - getSystemObjectDetails(input: $input) { - idObject - name - retired - objectType - allowed - publishedState - thumbnail - identifiers { - identifier - identifierType - } - unit { - idSystemObject - name - objectType - } - project { - idSystemObject - name - objectType - } - subject { - idSystemObject - name - objectType - } - item { - idSystemObject - name - objectType - } - objectAncestors { - idSystemObject - name - objectType - } - sourceObjects { - idSystemObject - name - identifier - objectType - } - derivedObjects { - idSystemObject - name - identifier - objectType - } - } + getSystemObjectDetails(input: $input) { + idObject + name + retired + objectType + allowed + publishedState + thumbnail + identifiers { + identifier + identifierType + } + unit { + idSystemObject + name + objectType + } + project { + idSystemObject + name + objectType + } + subject { + idSystemObject + name + objectType + } + item { + idSystemObject + name + objectType } -`; + objectAncestors { + idSystemObject + name + objectType + } + sourceObjects { + idSystemObject + name + identifier + objectType + } + derivedObjects { + idSystemObject + name + identifier + objectType + } + } +} + `; /** * __useGetSystemObjectDetailsQuery__ @@ -3985,18 +4565,18 @@ export type GetSystemObjectDetailsLazyQueryHookResult = ReturnType; export const GetVersionsForSystemObjectDocument = gql` query getVersionsForSystemObject($input: GetVersionsForSystemObjectInput!) { - getVersionsForSystemObject(input: $input) { - versions { - idSystemObject - version - name - creator - dateCreated - size - } - } + getVersionsForSystemObject(input: $input) { + versions { + idSystemObject + version + name + creator + dateCreated + size } -`; + } +} + `; /** * __useGetVersionsForSystemObjectQuery__ @@ -4025,15 +4605,15 @@ export type GetVersionsForSystemObjectLazyQueryHookResult = ReturnType; export const GetIngestionItemsForSubjectsDocument = gql` query getIngestionItemsForSubjects($input: GetIngestionItemsForSubjectsInput!) { - getIngestionItemsForSubjects(input: $input) { - Item { - idItem - EntireSubject - Name - } - } + getIngestionItemsForSubjects(input: $input) { + Item { + idItem + EntireSubject + Name } -`; + } +} + `; /** * __useGetIngestionItemsForSubjectsQuery__ @@ -4062,14 +4642,14 @@ export type GetIngestionItemsForSubjectsLazyQueryHookResult = ReturnType; export const GetIngestionProjectsForSubjectsDocument = gql` query getIngestionProjectsForSubjects($input: GetIngestionProjectsForSubjectsInput!) { - getIngestionProjectsForSubjects(input: $input) { - Project { - idProject - Name - } - } + getIngestionProjectsForSubjects(input: $input) { + Project { + idProject + Name } -`; + } +} + `; /** * __useGetIngestionProjectsForSubjectsQuery__ @@ -4087,14 +4667,10 @@ export const GetIngestionProjectsForSubjectsDocument = gql` * }, * }); */ -export function useGetIngestionProjectsForSubjectsQuery( - baseOptions?: Apollo.QueryHookOptions -) { +export function useGetIngestionProjectsForSubjectsQuery(baseOptions?: Apollo.QueryHookOptions) { return Apollo.useQuery(GetIngestionProjectsForSubjectsDocument, baseOptions); } -export function useGetIngestionProjectsForSubjectsLazyQuery( - baseOptions?: Apollo.LazyQueryHookOptions -) { +export function useGetIngestionProjectsForSubjectsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { return Apollo.useLazyQuery(GetIngestionProjectsForSubjectsDocument, baseOptions); } export type GetIngestionProjectsForSubjectsQueryHookResult = ReturnType; @@ -4102,13 +4678,13 @@ export type GetIngestionProjectsForSubjectsLazyQueryHookResult = ReturnType; export const GetItemDocument = gql` query getItem($input: GetItemInput!) { - getItem(input: $input) { - Item { - idItem - } - } + getItem(input: $input) { + Item { + idItem } -`; + } +} + `; /** * __useGetItemQuery__ @@ -4137,14 +4713,14 @@ export type GetItemLazyQueryHookResult = ReturnType; export type GetItemQueryResult = Apollo.QueryResult; export const GetItemsForSubjectDocument = gql` query getItemsForSubject($input: GetItemsForSubjectInput!) { - getItemsForSubject(input: $input) { - Item { - idItem - Name - } - } + getItemsForSubject(input: $input) { + Item { + idItem + Name } -`; + } +} + `; /** * __useGetItemsForSubjectQuery__ @@ -4173,35 +4749,35 @@ export type GetItemsForSubjectLazyQueryHookResult = ReturnType; export const GetObjectsForItemDocument = gql` query getObjectsForItem($input: GetObjectsForItemInput!) { - getObjectsForItem(input: $input) { - CaptureData { - idCaptureData - DateCaptured - Description - } - Model { - idModel - Authoritative - DateCreated - } - Scene { - idScene - HasBeenQCd - IsOriented - Name - } - IntermediaryFile { - idIntermediaryFile - DateCreated - } - ProjectDocumentation { - idProjectDocumentation - Description - Name - } - } + getObjectsForItem(input: $input) { + CaptureData { + idCaptureData + DateCaptured + Description + } + Model { + idModel + Authoritative + DateCreated + } + Scene { + idScene + HasBeenQCd + IsOriented + Name + } + IntermediaryFile { + idIntermediaryFile + DateCreated } -`; + ProjectDocumentation { + idProjectDocumentation + Description + Name + } + } +} + `; /** * __useGetObjectsForItemQuery__ @@ -4230,13 +4806,13 @@ export type GetObjectsForItemLazyQueryHookResult = ReturnType; export const GetProjectDocument = gql` query getProject($input: GetProjectInput!) { - getProject(input: $input) { - Project { - idProject - } - } + getProject(input: $input) { + Project { + idProject } -`; + } +} + `; /** * __useGetProjectQuery__ @@ -4265,13 +4841,13 @@ export type GetProjectLazyQueryHookResult = ReturnType; export const GetProjectDocumentationDocument = gql` query getProjectDocumentation($input: GetProjectDocumentationInput!) { - getProjectDocumentation(input: $input) { - ProjectDocumentation { - idProjectDocumentation - } - } + getProjectDocumentation(input: $input) { + ProjectDocumentation { + idProjectDocumentation } -`; + } +} + `; /** * __useGetProjectDocumentationQuery__ @@ -4300,13 +4876,13 @@ export type GetProjectDocumentationLazyQueryHookResult = ReturnType; export const GetSubjectDocument = gql` query getSubject($input: GetSubjectInput!) { - getSubject(input: $input) { - Subject { - idSubject - } - } + getSubject(input: $input) { + Subject { + idSubject } -`; + } +} + `; /** * __useGetSubjectQuery__ @@ -4335,14 +4911,14 @@ export type GetSubjectLazyQueryHookResult = ReturnType; export const GetSubjectsForUnitDocument = gql` query getSubjectsForUnit($input: GetSubjectsForUnitInput!) { - getSubjectsForUnit(input: $input) { - Subject { - idSubject - Name - } - } + getSubjectsForUnit(input: $input) { + Subject { + idSubject + Name } -`; + } +} + `; /** * __useGetSubjectsForUnitQuery__ @@ -4371,13 +4947,13 @@ export type GetSubjectsForUnitLazyQueryHookResult = ReturnType; export const GetUnitDocument = gql` query getUnit($input: GetUnitInput!) { - getUnit(input: $input) { - Unit { - idUnit - } - } + getUnit(input: $input) { + Unit { + idUnit } -`; + } +} + `; /** * __useGetUnitQuery__ @@ -4406,17 +4982,17 @@ export type GetUnitLazyQueryHookResult = ReturnType; export type GetUnitQueryResult = Apollo.QueryResult; export const SearchIngestionSubjectsDocument = gql` query searchIngestionSubjects($input: SearchIngestionSubjectsInput!) { - searchIngestionSubjects(input: $input) { - SubjectUnitIdentifier { - idSubject - SubjectName - UnitAbbreviation - IdentifierPublic - IdentifierCollection - } - } + searchIngestionSubjects(input: $input) { + SubjectUnitIdentifier { + idSubject + SubjectName + UnitAbbreviation + IdentifierPublic + IdentifierCollection } -`; + } +} + `; /** * __useSearchIngestionSubjectsQuery__ @@ -4445,21 +5021,21 @@ export type SearchIngestionSubjectsLazyQueryHookResult = ReturnType; export const GetCurrentUserDocument = gql` query getCurrentUser { - getCurrentUser { - User { - idUser - Name - Active - DateActivated - DateDisabled - EmailAddress - EmailSettings - SecurityID - WorkflowNotificationTime - } - } + getCurrentUser { + User { + idUser + Name + Active + DateActivated + DateDisabled + EmailAddress + EmailSettings + SecurityID + WorkflowNotificationTime } -`; + } +} + `; /** * __useGetCurrentUserQuery__ @@ -4487,16 +5063,16 @@ export type GetCurrentUserLazyQueryHookResult = ReturnType; export const GetUserDocument = gql` query getUser($input: GetUserInput!) { - getUser(input: $input) { - User { - idUser - Name - Active - DateActivated - } - } + getUser(input: $input) { + User { + idUser + Name + Active + DateActivated } -`; + } +} + `; /** * __useGetUserQuery__ @@ -4525,13 +5101,13 @@ export type GetUserLazyQueryHookResult = ReturnType; export type GetUserQueryResult = Apollo.QueryResult; export const GetVocabularyDocument = gql` query getVocabulary($input: GetVocabularyInput!) { - getVocabulary(input: $input) { - Vocabulary { - idVocabulary - } - } + getVocabulary(input: $input) { + Vocabulary { + idVocabulary } -`; + } +} + `; /** * __useGetVocabularyQuery__ @@ -4560,17 +5136,17 @@ export type GetVocabularyLazyQueryHookResult = ReturnType; export const GetVocabularyEntriesDocument = gql` query getVocabularyEntries($input: GetVocabularyEntriesInput!) { - getVocabularyEntries(input: $input) { - VocabularyEntries { - eVocabSetID - Vocabulary { - idVocabulary - Term - } - } - } + getVocabularyEntries(input: $input) { + VocabularyEntries { + eVocabSetID + Vocabulary { + idVocabulary + Term + } } -`; + } +} + `; /** * __useGetVocabularyEntriesQuery__ @@ -4599,13 +5175,13 @@ export type GetVocabularyEntriesLazyQueryHookResult = ReturnType; export const GetWorkflowDocument = gql` query getWorkflow($input: GetWorkflowInput!) { - getWorkflow(input: $input) { - Workflow { - idWorkflow - } - } + getWorkflow(input: $input) { + Workflow { + idWorkflow } -`; + } +} + `; /** * __useGetWorkflowQuery__ @@ -4631,4 +5207,4 @@ export function useGetWorkflowLazyQuery(baseOptions?: Apollo.LazyQueryHookOption } export type GetWorkflowQueryHookResult = ReturnType; export type GetWorkflowLazyQueryHookResult = ReturnType; -export type GetWorkflowQueryResult = Apollo.QueryResult; +export type GetWorkflowQueryResult = Apollo.QueryResult; \ No newline at end of file diff --git a/server/graphql/schema.graphql b/server/graphql/schema.graphql index 7e98d3ecb..a4e434e1c 100644 --- a/server/graphql/schema.graphql +++ b/server/graphql/schema.graphql @@ -289,6 +289,7 @@ type Asset { FileName: String! FilePath: String! idAssetGroup: Int + idVAssetType: Int idSystemObject: Int StorageKey: String AssetGroup: AssetGroup diff --git a/server/graphql/schema/asset/types.graphql b/server/graphql/schema/asset/types.graphql index ea679e50a..74e2e384f 100644 --- a/server/graphql/schema/asset/types.graphql +++ b/server/graphql/schema/asset/types.graphql @@ -5,6 +5,7 @@ type Asset { FileName: String! FilePath: String! idAssetGroup: Int + idVAssetType: Int idSystemObject: Int StorageKey: String AssetGroup: AssetGroup diff --git a/server/types/graphql.ts b/server/types/graphql.ts index 15e6b5de8..c2c5a66bd 100644 --- a/server/types/graphql.ts +++ b/server/types/graphql.ts @@ -514,6 +514,7 @@ export type Asset = { FileName: Scalars['String']; FilePath: Scalars['String']; idAssetGroup?: Maybe; + idVAssetType?: Maybe; idSystemObject?: Maybe; StorageKey?: Maybe; AssetGroup?: Maybe; From 739f6b675e48d67545feecb2d5d44423de35e23c Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Thu, 14 Jan 2021 15:32:23 +0530 Subject: [PATCH 177/239] Finish up update and details resolvers for all objects!! --- .../mutations/updateObjectDetails.ts | 263 ++++++++++++++---- .../queries/getDetailsTabDataForObject.ts | 127 ++++++++- 2 files changed, 330 insertions(+), 60 deletions(-) diff --git a/server/graphql/schema/systemobject/resolvers/mutations/updateObjectDetails.ts b/server/graphql/schema/systemobject/resolvers/mutations/updateObjectDetails.ts index 7a92312e4..9971d79a9 100644 --- a/server/graphql/schema/systemobject/resolvers/mutations/updateObjectDetails.ts +++ b/server/graphql/schema/systemobject/resolvers/mutations/updateObjectDetails.ts @@ -17,37 +17,44 @@ export default async function updateObjectDetails(_: Parent, args: MutationUpdat } const SO = await DBAPI.SystemObject.fetch(idSystemObject); - + /** + * TODO: KARAN: add an error property and handle errors + */ if (SO) { - SO.Retired = data.Retired; - // TODO: KARAN: how to update SO? SO.update()? + if (data.Retired) { + await SO.retireObject(); + } else { + await SO.reinstateObject(); + } } switch (objectType) { case eSystemObjectType.eUnit: { - if (data.Unit) { - const { Abbreviation, ARKPrefix } = data.Unit; - const Unit = await DBAPI.Unit.fetch(idObject); + const Unit = await DBAPI.Unit.fetch(idObject); - if (Unit) { - Unit.Name = data.Name; + if (Unit) { + Unit.Name = data.Name; + if (data.Unit) { + const { Abbreviation, ARKPrefix } = data.Unit; Unit.Abbreviation = maybe(Abbreviation); Unit.ARKPrefix = maybe(ARKPrefix); - await Unit.update(); } + + await Unit.update(); } break; } case eSystemObjectType.eProject: { - if (data.Project) { - const { Description } = data.Project; - const Project = await DBAPI.Project.fetch(idObject); + const Project = await DBAPI.Project.fetch(idObject); - if (Project) { - Project.Name = data.Name; + if (Project) { + Project.Name = data.Name; + if (data.Project) { + const { Description } = data.Project; Project.Description = maybe(Description); - await Project.update(); } + + await Project.update(); } break; } @@ -154,83 +161,229 @@ export default async function updateObjectDetails(_: Parent, args: MutationUpdat } break; } - case eSystemObjectType.eCaptureData: - // TODO: KARAN: How to update capture data? + case eSystemObjectType.eCaptureData: { + // TODO: KARAN update/create folders, systemCreated + if (data.CaptureData) { + const CaptureData = await DBAPI.CaptureData.fetch(idObject); + + if (CaptureData) { + const { + description, + captureMethod, + dateCaptured, + cameraSettingUniform, + datasetType, + datasetFieldId, + itemPositionType, + itemPositionFieldId, + itemArrangementFieldId, + focusType, + lightsourceType, + backgroundRemovalMethod, + clusterType, + clusterGeometryFieldId, + } = data.CaptureData; + + CaptureData.DateCaptured = new Date(dateCaptured); + if (description) CaptureData.Description = description; + if (captureMethod) CaptureData.idVCaptureMethod = captureMethod; + + const CaptureDataPhoto = await DBAPI.CaptureDataPhoto.fetchFromCaptureData(CaptureData.idCaptureData); + + if (CaptureDataPhoto && CaptureDataPhoto[0]) { + const [CD] = CaptureDataPhoto; + + CD.CameraSettingsUniform = maybe(cameraSettingUniform); + if (datasetType) CD.idVCaptureDatasetType = datasetType; + CD.CaptureDatasetFieldID = maybe(datasetFieldId); + CD.idVItemPositionType = maybe(itemPositionType); + CD.idVItemPositionType = maybe(itemPositionFieldId); + CD.ItemArrangementFieldID = maybe(itemArrangementFieldId); + CD.idVFocusType = maybe(focusType); + CD.idVLightSourceType = maybe(lightsourceType); + CD.idVBackgroundRemovalMethod = maybe(backgroundRemovalMethod); + CD.idVClusterType = maybe(clusterType); + CD.ClusterGeometryFieldID = maybe(clusterGeometryFieldId); + await CD.update(); + } + await CaptureData.update(); + } + } break; - case eSystemObjectType.eModel: - // TODO: KARAN: How to update model? + } case eSystemObjectType.eModel: { + // TODO: KARAN: update/create UV Map + if (data.Model) { + const Model = await DBAPI.Model.fetch(idObject); + + if (Model) { + const { + master, + authoritative, + creationMethod, + modality, + purpose, + units, + dateCaptured, + size, + modelFileType, + roughness, + metalness, + pointCount, + faceCount, + isWatertight, + hasNormals, + hasVertexColor, + hasUVSpace, + boundingBoxP1X, + boundingBoxP1Y, + boundingBoxP1Z, + boundingBoxP2X, + boundingBoxP2Y, + boundingBoxP2Z + } = data.Model; + + if (master) Model.Master = master; + if (authoritative) Model.Authoritative = authoritative; + if (creationMethod) Model.idVCreationMethod = creationMethod; + if (modality) Model.idVModality = modality; + if (purpose) Model.idVPurpose = purpose; + if (units) Model.idVUnits = units; + Model.DateCreated = new Date(dateCaptured); + + if (Model.idAssetThumbnail) { + const AssetVersion = await DBAPI.AssetVersion.fetchFromAsset(Model.idAssetThumbnail); + if (AssetVersion && AssetVersion[0]) { + const [AV] = AssetVersion; + if (size) AV.StorageSize = size; + } + } + + const ModelGeometryFile = await DBAPI.ModelGeometryFile.fetchFromModel(Model.idModel); + if (ModelGeometryFile && ModelGeometryFile[0]) { + const [MGF] = ModelGeometryFile; + + const Asset = await DBAPI.Asset.fetch(MGF.idAsset); + if (Asset) { + Asset.FileName = data.Name; + await Asset.update(); + } + + if (modelFileType) MGF.idVModelFileType = modelFileType; + MGF.Roughness = maybe(roughness); + MGF.Metalness = maybe(metalness); + MGF.PointCount = maybe(pointCount); + MGF.FaceCount = maybe(faceCount); + MGF.IsWatertight = maybe(isWatertight); + MGF.HasNormals = maybe(hasNormals); + MGF.HasVertexColor = maybe(hasVertexColor); + MGF.HasUVSpace = maybe(hasUVSpace); + MGF.BoundingBoxP1X = maybe(boundingBoxP1X); + MGF.BoundingBoxP1Y = maybe(boundingBoxP1Y); + MGF.BoundingBoxP1Z = maybe(boundingBoxP1Z); + MGF.BoundingBoxP2X = maybe(boundingBoxP2X); + MGF.BoundingBoxP2Y = maybe(boundingBoxP2Y); + MGF.BoundingBoxP2Z = maybe(boundingBoxP2Z); + + await MGF.update(); + } + await Model.update(); + } + } break; - case eSystemObjectType.eScene: - // TODO: KARAN: How to update scene? + } case eSystemObjectType.eScene: { + const Scene = await DBAPI.Scene.fetch(idObject); + if (Scene) { + Scene.Name = data.Name; + if (data.Scene) { + // Update values here + } + + await Scene.update(); + } break; - case eSystemObjectType.eIntermediaryFile: + } case eSystemObjectType.eIntermediaryFile: { + const IntermediaryFile = await DBAPI.IntermediaryFile.fetch(idObject); + if (IntermediaryFile) { + const Asset = await DBAPI.Asset.fetch(IntermediaryFile.idAsset); + if (Asset) { + Asset.FileName = data.Name; + await Asset.update(); + } + } break; - case eSystemObjectType.eProjectDocumentation: { - if (data.ProjectDocumentation) { - const { Description } = data.ProjectDocumentation; - const ProjectDocumentation = await DBAPI.ProjectDocumentation.fetch(idObject); + } case eSystemObjectType.eProjectDocumentation: { + const ProjectDocumentation = await DBAPI.ProjectDocumentation.fetch(idObject); + + if (ProjectDocumentation) { + ProjectDocumentation.Name = data.Name; - if (ProjectDocumentation) { - ProjectDocumentation.Name = data.Name; + if (data.ProjectDocumentation) { + const { Description } = data.ProjectDocumentation; if (Description) ProjectDocumentation.Description = Description; - await ProjectDocumentation.update(); } + + await ProjectDocumentation.update(); } break; } case eSystemObjectType.eAsset: { - if (data.Asset) { - const { FilePath, AssetType } = data.Asset; - const Asset = await DBAPI.Asset.fetch(idObject); + const Asset = await DBAPI.Asset.fetch(idObject); - if (Asset) { - Asset.FileName = data.Name; + if (Asset) { + Asset.FileName = data.Name; + + if (data.Asset) { + const { FilePath, AssetType } = data.Asset; if (FilePath) Asset.FilePath = FilePath; if (AssetType) Asset.idVAssetType = AssetType; - - await Asset.update(); } + + await Asset.update(); } break; } case eSystemObjectType.eAssetVersion: { - if (data.AssetVersion) { - const { Ingested } = data.AssetVersion; - const AssetVersion = await DBAPI.AssetVersion.fetch(idObject); - - if (AssetVersion) { - AssetVersion.FileName = data.Name; - if (Ingested) AssetVersion.Ingested = Ingested; - await AssetVersion.update(); + const AssetVersion = await DBAPI.AssetVersion.fetch(idObject); + + if (AssetVersion) { + AssetVersion.FileName = data.Name; + + if (data.AssetVersion) { + const { Ingested } = data.AssetVersion; + if (!isNull(Ingested) && !isUndefined(Ingested)) AssetVersion.Ingested = Ingested; } + + await AssetVersion.update(); } break; } case eSystemObjectType.eActor: { - if (data.Actor) { - const { OrganizationName } = data.Actor; - const Actor = await DBAPI.Actor.fetch(idObject); - - if (Actor) { + const Actor = await DBAPI.Actor.fetch(idObject); + if (Actor) { + Actor.IndividualName = data.Name; + if (data.Actor) { + const { OrganizationName } = data.Actor; Actor.OrganizationName = maybe(OrganizationName); - await Actor.update(); + } + await Actor.update(); } break; } case eSystemObjectType.eStakeholder: { - if (data.Stakeholder) { - const { OrganizationName, MailingAddress, EmailAddress, PhoneNumberMobile, PhoneNumberOffice } = data.Stakeholder; - const Stakeholder = await DBAPI.Stakeholder.fetch(idObject); + const Stakeholder = await DBAPI.Stakeholder.fetch(idObject); - if (Stakeholder) { + if (Stakeholder) { + Stakeholder.IndividualName = data.Name; + if (data.Stakeholder) { + const { OrganizationName, MailingAddress, EmailAddress, PhoneNumberMobile, PhoneNumberOffice } = data.Stakeholder; if (OrganizationName) Stakeholder.OrganizationName = OrganizationName; Stakeholder.MailingAddress = maybe(MailingAddress); Stakeholder.EmailAddress = maybe(EmailAddress); Stakeholder.PhoneNumberMobile = maybe(PhoneNumberMobile); Stakeholder.PhoneNumberOffice = maybe(PhoneNumberOffice); - await Stakeholder.update(); } + await Stakeholder.update(); } break; } diff --git a/server/graphql/schema/systemobject/resolvers/queries/getDetailsTabDataForObject.ts b/server/graphql/schema/systemobject/resolvers/queries/getDetailsTabDataForObject.ts index 46ed8db76..f764d0544 100644 --- a/server/graphql/schema/systemobject/resolvers/queries/getDetailsTabDataForObject.ts +++ b/server/graphql/schema/systemobject/resolvers/queries/getDetailsTabDataForObject.ts @@ -6,7 +6,10 @@ import { SubjectDetailFields, ItemDetailFields, GetDetailsTabDataForObjectResult, - QueryGetDetailsTabDataForObjectArgs + QueryGetDetailsTabDataForObjectArgs, + CaptureDataDetailFields, + ModelDetailFields, + SceneDetailFields } from '../../../../../types/graphql'; import { Parent } from '../../../../../types/resolvers'; @@ -31,7 +34,7 @@ export default async function getDetailsTabDataForObject(_: Parent, args: QueryG }; const systemObject: DBAPI.SystemObject | null = await DBAPI.SystemObject.fetch(idSystemObject); - // TODO: KARAN: complete all object types + switch (objectType) { case eSystemObjectType.eUnit: if (systemObject?.idUnit) result.Unit = await DBAPI.Unit.fetch(systemObject.idUnit); @@ -71,13 +74,29 @@ export default async function getDetailsTabDataForObject(_: Parent, args: QueryG break; } case eSystemObjectType.eCaptureData: - // TODO: KARAN: How to retrieve capture data? + if (systemObject?.idCaptureData) { + result.CaptureData = await getCaptureDataDetailFields(systemObject.idCaptureData); + } break; case eSystemObjectType.eModel: - // TODO: KARAN: How to retrieve model? + if (systemObject?.idModel) { + result.Model = await getModelDetailFields(systemObject.idModel); + } break; case eSystemObjectType.eScene: - // TODO: KARAN: How to retrieve scene? + if (systemObject?.idScene) { + // TODO: KARAN: resolve Links, AssetType, Tours, Annotation when SceneDetailFields is finalized? + let fields: SceneDetailFields = { + Links: [] + }; + const Scene = await DBAPI.Scene.fetch(systemObject.idScene); + fields = { + ...fields, + HasBeenQCd: Scene?.HasBeenQCd, + IsOriented: Scene?.IsOriented + }; + result.Scene = fields; + } break; case eSystemObjectType.eIntermediaryFile: if (systemObject?.idIntermediaryFile) result.IntermediaryFile = await DBAPI.IntermediaryFile.fetch(systemObject.idIntermediaryFile); @@ -128,3 +147,101 @@ export default async function getDetailsTabDataForObject(_: Parent, args: QueryG return result; } + +async function getCaptureDataDetailFields(idCaptureData: number): Promise { + let fields: CaptureDataDetailFields = { + folders: [] + }; + + // TODO: KARAN resolve folders, systemCreated from where? + const CaptureData = await DBAPI.CaptureData.fetch(idCaptureData); + fields = { + ...fields, + systemCreated: true, + dateCaptured: CaptureData?.DateCaptured.toISOString(), + description: CaptureData?.Description, + captureMethod: CaptureData?.idVCaptureMethod + }; + + const CaptureDataPhoto = await DBAPI.CaptureDataPhoto.fetchFromCaptureData(idCaptureData); + + if (CaptureDataPhoto && CaptureDataPhoto[0]) { + const [CD] = CaptureDataPhoto; + + fields = { + ...fields, + cameraSettingUniform: CD.CameraSettingsUniform, + datasetType: CD.idVCaptureDatasetType, + datasetFieldId: CD.CaptureDatasetFieldID, + itemPositionType: CD.idVItemPositionType, + itemPositionFieldId: CD.idVItemPositionType, + itemArrangementFieldId: CD.ItemArrangementFieldID, + focusType: CD.idVFocusType, + lightsourceType: CD.idVLightSourceType, + backgroundRemovalMethod: CD.idVBackgroundRemovalMethod, + clusterType: CD.idVClusterType, + clusterGeometryFieldId: CD.ClusterGeometryFieldID, + }; + } + + + return fields; +} + +async function getModelDetailFields(idModel: number): Promise { + let fields: ModelDetailFields = { + uvMaps: [] + }; + + // TODO: KARAN resolve uvMaps, systemCreated? + const Model = await DBAPI.Model.fetch(idModel); + fields = { + ...fields, + master: Model?.Master, + authoritative: Model?.Authoritative, + creationMethod: Model?.idVCreationMethod, + modality: Model?.idVModality, + purpose: Model?.idVPurpose, + units: Model?.idVUnits, + dateCaptured: Model?.DateCreated.toISOString(), + }; + + + if (Model?.idAssetThumbnail) { + const AssetVersion = await DBAPI.AssetVersion.fetchFromAsset(Model.idAssetThumbnail); + if (AssetVersion && AssetVersion[0]) { + const [AV] = AssetVersion; + fields = { + ...fields, + size: AV.StorageSize + }; + } + } + + const ModelGeometryFile = await DBAPI.ModelGeometryFile.fetchFromModel(idModel); + + if (ModelGeometryFile && ModelGeometryFile[0]) { + const [MGF] = ModelGeometryFile; + + fields = { + ...fields, + modelFileType: MGF.idVModelFileType, + roughness: MGF.Roughness, + metalness: MGF.Metalness, + pointCount: MGF.PointCount, + faceCount: MGF.FaceCount, + isWatertight: MGF.IsWatertight, + hasNormals: MGF.HasNormals, + hasVertexColor: MGF.HasVertexColor, + hasUVSpace: MGF.HasUVSpace, + boundingBoxP1X: MGF.BoundingBoxP1X, + boundingBoxP1Y: MGF.BoundingBoxP1Y, + boundingBoxP1Z: MGF.BoundingBoxP1Z, + boundingBoxP2X: MGF.BoundingBoxP2X, + boundingBoxP2Y: MGF.BoundingBoxP2Y, + boundingBoxP2Z: MGF.BoundingBoxP2Z + }; + } + + return fields; +} \ No newline at end of file From 9855ec060c740be3899b708f4c11231fe4669358 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Thu, 14 Jan 2021 15:33:19 +0530 Subject: [PATCH 178/239] add missed schema in graphql types --- client/src/types/graphql.tsx | 9 ++- server/db/sql/scripts/Packrat.DATA.sql | 2 +- server/graphql/schema.graphql | 9 ++- .../graphql/schema/capturedata/types.graphql | 1 + server/types/graphql.ts | 58 ++++++++++++++++++- 5 files changed, 72 insertions(+), 7 deletions(-) diff --git a/client/src/types/graphql.tsx b/client/src/types/graphql.tsx index 3575b824f..52aad9b31 100644 --- a/client/src/types/graphql.tsx +++ b/client/src/types/graphql.tsx @@ -661,6 +661,7 @@ export type CaptureData = { VCaptureMethod?: Maybe; CaptureDataFile?: Maybe>>; CaptureDataGroup?: Maybe>>; + CaptureDataPhoto?: Maybe>>; SystemObject?: Maybe; }; @@ -1165,7 +1166,7 @@ export type ItemDetailFieldsInput = { export type CaptureDataDetailFieldsInput = { captureMethod?: Maybe; - dateCaptured?: Maybe; + dateCaptured?: Maybe; datasetType?: Maybe; systemCreated?: Maybe; description?: Maybe; @@ -1190,7 +1191,7 @@ export type ModelDetailFieldsInput = { modality?: Maybe; purpose?: Maybe; units?: Maybe; - dateCaptured?: Maybe; + dateCaptured?: Maybe; modelFileType?: Maybe; uvMaps: Array; roughness?: Maybe; @@ -1214,6 +1215,8 @@ export type SceneDetailFieldsInput = { AssetType?: Maybe; Tours?: Maybe; Annotation?: Maybe; + HasBeenQCd?: Maybe; + IsOriented?: Maybe; }; export type ProjectDocumentationDetailFieldsInput = { @@ -1366,6 +1369,8 @@ export type SceneDetailFields = { AssetType?: Maybe; Tours?: Maybe; Annotation?: Maybe; + HasBeenQCd?: Maybe; + IsOriented?: Maybe; }; export type IntermediaryFileDetailFields = { diff --git a/server/db/sql/scripts/Packrat.DATA.sql b/server/db/sql/scripts/Packrat.DATA.sql index 9b75d442c..a3b936b53 100644 --- a/server/db/sql/scripts/Packrat.DATA.sql +++ b/server/db/sql/scripts/Packrat.DATA.sql @@ -1113,7 +1113,7 @@ INSERT INTO User (Name, EmailAddress, SecurityID, Active, DateActivated) VALUES INSERT INTO User (Name, EmailAddress, SecurityID, Active, DateActivated) VALUES ('Jon Blundell', 'blundellj@si.edu', 'TBD', 1, NOW()); INSERT INTO User (Name, EmailAddress, SecurityID, Active, DateActivated) VALUES ('Vince Rossi', 'rossiv@si.edu', 'TBD', 1, NOW()); INSERT INTO User (Name, EmailAddress, SecurityID, Active, DateActivated) VALUES ('Jamie Cope', 'copeg@si.edu', 'TBD', 1, NOW()); -INSERT INTO User (Name, EmailAddress, SecurityID, Active, DateActivated) VALUES ('Karan Pratap Singh', 'karan.pratapsingh686@gmail.com', 'TBD', 1, NOW()); +INSERT INTO User (Name, EmailAddress, SecurityID, Active, DateActivated) VALUES ('Karan Pratap Singh', 'singhk@si.edu', 'TBD', 1, NOW()); INSERT INTO UnitEdan (idUnit, Abbreviation) VALUES (1, '3D_YT'); INSERT INTO UnitEdan (idUnit, Abbreviation) VALUES (4, 'AAA'); diff --git a/server/graphql/schema.graphql b/server/graphql/schema.graphql index a4e434e1c..75a4f4b61 100644 --- a/server/graphql/schema.graphql +++ b/server/graphql/schema.graphql @@ -376,6 +376,7 @@ type CaptureData { VCaptureMethod: Vocabulary CaptureDataFile: [CaptureDataFile] CaptureDataGroup: [CaptureDataGroup] + CaptureDataPhoto: [CaptureDataPhoto] SystemObject: SystemObject } @@ -854,7 +855,7 @@ input ItemDetailFieldsInput { input CaptureDataDetailFieldsInput { captureMethod: Int - dateCaptured: String + dateCaptured: DateTime datasetType: Int systemCreated: Boolean description: String @@ -879,7 +880,7 @@ input ModelDetailFieldsInput { modality: Int purpose: Int units: Int - dateCaptured: String + dateCaptured: DateTime modelFileType: Int uvMaps: [IngestUVMapInput!]! roughness: Int @@ -903,6 +904,8 @@ input SceneDetailFieldsInput { AssetType: Int Tours: Int Annotation: Int + HasBeenQCd: Boolean + IsOriented: Boolean } input ProjectDocumentationDetailFieldsInput { @@ -1047,6 +1050,8 @@ type SceneDetailFields { AssetType: Int Tours: Int Annotation: Int + HasBeenQCd: Boolean + IsOriented: Boolean } type IntermediaryFileDetailFields { diff --git a/server/graphql/schema/capturedata/types.graphql b/server/graphql/schema/capturedata/types.graphql index 3d379873a..d5e004d23 100644 --- a/server/graphql/schema/capturedata/types.graphql +++ b/server/graphql/schema/capturedata/types.graphql @@ -10,6 +10,7 @@ type CaptureData { VCaptureMethod: Vocabulary CaptureDataFile: [CaptureDataFile] CaptureDataGroup: [CaptureDataGroup] + CaptureDataPhoto: [CaptureDataPhoto] SystemObject: SystemObject } diff --git a/server/types/graphql.ts b/server/types/graphql.ts index c2c5a66bd..0de400d33 100644 --- a/server/types/graphql.ts +++ b/server/types/graphql.ts @@ -51,130 +51,162 @@ export type Query = { searchIngestionSubjects: SearchIngestionSubjectsResult; }; + export type QueryAreCameraSettingsUniformArgs = { input: AreCameraSettingsUniformInput; }; + export type QueryGetAccessPolicyArgs = { input: GetAccessPolicyInput; }; + export type QueryGetAssetArgs = { input: GetAssetInput; }; + export type QueryGetAssetDetailsForSystemObjectArgs = { input: GetAssetDetailsForSystemObjectInput; }; + export type QueryGetAssetVersionsDetailsArgs = { input: GetAssetVersionsDetailsInput; }; + export type QueryGetCaptureDataArgs = { input: GetCaptureDataInput; }; + export type QueryGetCaptureDataPhotoArgs = { input: GetCaptureDataPhotoInput; }; + export type QueryGetContentsForAssetVersionsArgs = { input: GetContentsForAssetVersionsInput; }; + export type QueryGetDetailsTabDataForObjectArgs = { input: GetDetailsTabDataForObjectInput; }; + export type QueryGetIngestionItemsForSubjectsArgs = { input: GetIngestionItemsForSubjectsInput; }; + export type QueryGetIngestionProjectsForSubjectsArgs = { input: GetIngestionProjectsForSubjectsInput; }; + export type QueryGetIntermediaryFileArgs = { input: GetIntermediaryFileInput; }; + export type QueryGetItemArgs = { input: GetItemInput; }; + export type QueryGetItemsForSubjectArgs = { input: GetItemsForSubjectInput; }; + export type QueryGetLicenseArgs = { input: GetLicenseInput; }; + export type QueryGetModelArgs = { input: GetModelInput; }; + export type QueryGetObjectChildrenArgs = { input: GetObjectChildrenInput; }; + export type QueryGetObjectsForItemArgs = { input: GetObjectsForItemInput; }; + export type QueryGetProjectArgs = { input: GetProjectInput; }; + export type QueryGetProjectDocumentationArgs = { input: GetProjectDocumentationInput; }; + export type QueryGetSceneArgs = { input: GetSceneInput; }; + export type QueryGetSourceObjectIdentiferArgs = { input: GetSourceObjectIdentiferInput; }; + export type QueryGetSubjectArgs = { input: GetSubjectInput; }; + export type QueryGetSubjectsForUnitArgs = { input: GetSubjectsForUnitInput; }; + export type QueryGetSystemObjectDetailsArgs = { input: GetSystemObjectDetailsInput; }; + export type QueryGetUnitArgs = { input: GetUnitInput; }; + export type QueryGetUserArgs = { input: GetUserInput; }; + export type QueryGetVersionsForSystemObjectArgs = { input: GetVersionsForSystemObjectInput; }; + export type QueryGetVocabularyArgs = { input: GetVocabularyInput; }; + export type QueryGetVocabularyEntriesArgs = { input: GetVocabularyEntriesInput; }; + export type QueryGetWorkflowArgs = { input: GetWorkflowInput; }; + export type QuerySearchIngestionSubjectsArgs = { input: SearchIngestionSubjectsInput; }; @@ -188,6 +220,7 @@ export type GetAccessPolicyResult = { AccessPolicy?: Maybe; }; + export type AccessAction = { __typename?: 'AccessAction'; idAccessAction: Scalars['Int']; @@ -236,6 +269,7 @@ export type AccessRole = { AccessAction?: Maybe>>; }; + export type Mutation = { __typename?: 'Mutation'; createCaptureData: CreateCaptureDataResult; @@ -255,62 +289,77 @@ export type Mutation = { uploadAsset: UploadAssetResult; }; + export type MutationCreateCaptureDataArgs = { input: CreateCaptureDataInput; }; + export type MutationCreateCaptureDataPhotoArgs = { input: CreateCaptureDataPhotoInput; }; + export type MutationCreateItemArgs = { input: CreateItemInput; }; + export type MutationCreateModelArgs = { input: CreateModelInput; }; + export type MutationCreateProjectArgs = { input: CreateProjectInput; }; + export type MutationCreateSceneArgs = { input: CreateSceneInput; }; + export type MutationCreateSubjectArgs = { input: CreateSubjectInput; }; + export type MutationCreateUnitArgs = { input: CreateUnitInput; }; + export type MutationCreateUserArgs = { input: CreateUserInput; }; + export type MutationCreateVocabularyArgs = { input: CreateVocabularyInput; }; + export type MutationCreateVocabularySetArgs = { input: CreateVocabularySetInput; }; + export type MutationDiscardUploadedAssetVersionsArgs = { input: DiscardUploadedAssetVersionsInput; }; + export type MutationIngestDataArgs = { input: IngestDataInput; }; + export type MutationUpdateObjectDetailsArgs = { input: UpdateObjectDetailsInput; }; + export type MutationUploadAssetArgs = { file: Scalars['Upload']; type: Scalars['Int']; @@ -608,6 +657,7 @@ export type CaptureData = { VCaptureMethod?: Maybe; CaptureDataFile?: Maybe>>; CaptureDataGroup?: Maybe>>; + CaptureDataPhoto?: Maybe>>; SystemObject?: Maybe; }; @@ -1112,7 +1162,7 @@ export type ItemDetailFieldsInput = { export type CaptureDataDetailFieldsInput = { captureMethod?: Maybe; - dateCaptured?: Maybe; + dateCaptured?: Maybe; datasetType?: Maybe; systemCreated?: Maybe; description?: Maybe; @@ -1137,7 +1187,7 @@ export type ModelDetailFieldsInput = { modality?: Maybe; purpose?: Maybe; units?: Maybe; - dateCaptured?: Maybe; + dateCaptured?: Maybe; modelFileType?: Maybe; uvMaps: Array; roughness?: Maybe; @@ -1161,6 +1211,8 @@ export type SceneDetailFieldsInput = { AssetType?: Maybe; Tours?: Maybe; Annotation?: Maybe; + HasBeenQCd?: Maybe; + IsOriented?: Maybe; }; export type ProjectDocumentationDetailFieldsInput = { @@ -1313,6 +1365,8 @@ export type SceneDetailFields = { AssetType?: Maybe; Tours?: Maybe; Annotation?: Maybe; + HasBeenQCd?: Maybe; + IsOriented?: Maybe; }; export type IntermediaryFileDetailFields = { From 162dcdcc5fffbeafcf30ac457c70cb899c8c14df Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Thu, 14 Jan 2021 15:33:48 +0530 Subject: [PATCH 179/239] Update string to date time fields --- server/graphql/schema/systemobject/mutations.graphql | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/server/graphql/schema/systemobject/mutations.graphql b/server/graphql/schema/systemobject/mutations.graphql index 0a4f0de78..7bc6fe5ff 100644 --- a/server/graphql/schema/systemobject/mutations.graphql +++ b/server/graphql/schema/systemobject/mutations.graphql @@ -49,7 +49,7 @@ input ItemDetailFieldsInput { input CaptureDataDetailFieldsInput { captureMethod: Int - dateCaptured: String + dateCaptured: DateTime datasetType: Int systemCreated: Boolean description: String @@ -74,7 +74,7 @@ input ModelDetailFieldsInput { modality: Int purpose: Int units: Int - dateCaptured: String + dateCaptured: DateTime modelFileType: Int uvMaps: [IngestUVMapInput!]! roughness: Int @@ -98,6 +98,8 @@ input SceneDetailFieldsInput { AssetType: Int Tours: Int Annotation: Int + HasBeenQCd: Boolean + IsOriented: Boolean } input ProjectDocumentationDetailFieldsInput { @@ -137,7 +139,7 @@ input UpdateObjectDetailsDataInput { Item: ItemDetailFieldsInput CaptureData: CaptureDataDetailFieldsInput Model: ModelDetailFieldsInput - Scene: SceneDetailFieldsInput + Scene: SceneDetailFieldsInput ProjectDocumentation: ProjectDocumentationDetailFieldsInput Asset: AssetDetailFieldsInput AssetVersion: AssetVersionDetailFieldsInput From 68c7dbc28f277c2111897a8651704849bbb5c6ab Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Thu, 14 Jan 2021 15:34:18 +0530 Subject: [PATCH 180/239] Updated capture data photo db api --- server/db/api/CaptureDataPhoto.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/server/db/api/CaptureDataPhoto.ts b/server/db/api/CaptureDataPhoto.ts index db99ebbe1..4ebf6a1c9 100644 --- a/server/db/api/CaptureDataPhoto.ts +++ b/server/db/api/CaptureDataPhoto.ts @@ -111,4 +111,15 @@ export class CaptureDataPhoto extends DBC.DBObject impleme return null; } } + + static async fetchFromCaptureData(idCaptureData: number): Promise { + if (!idCaptureData) + return null; + try { + return DBC.CopyArray(await DBC.DBConnection.prisma.captureDataPhoto.findMany({ where: { idCaptureData, }, }), CaptureDataPhoto); + } catch (error) /* istanbul ignore next */ { + LOG.logger.error('DBAPI.CaptureDataPhoto.fetchFromCaptureData', error); + return null; + } + } } \ No newline at end of file From 5d94c83861aee6d02f8b5562eef8c7ffec5c0bea Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Fri, 15 Jan 2021 15:41:31 +0530 Subject: [PATCH 181/239] Finish up scene details component --- client/src/index.tsx | 2 +- .../DetailsView/DetailsTab/SceneDetails.tsx | 108 +++++++++++++++++- .../DetailsView/DetailsTab/index.tsx | 32 +++--- client/src/types/graphql.tsx | 4 +- .../getDetailsTabDataForObject.ts | 2 + .../schema/systemobject/queries.graphql | 2 + 6 files changed, 130 insertions(+), 20 deletions(-) diff --git a/client/src/index.tsx b/client/src/index.tsx index f7978e731..82b8e623d 100644 --- a/client/src/index.tsx +++ b/client/src/index.tsx @@ -34,7 +34,7 @@ function AppRouter(): React.ReactElement { await updateVocabularyEntries(); setLoading(false); } catch { - toast.error('Error occurred while initializing'); + toast.error('Cannot connect to the server, please try again later'); } }, [initialize, updateVocabularyEntries]); diff --git a/client/src/pages/Repository/components/DetailsView/DetailsTab/SceneDetails.tsx b/client/src/pages/Repository/components/DetailsView/DetailsTab/SceneDetails.tsx index 39bd0d794..9847b5d92 100644 --- a/client/src/pages/Repository/components/DetailsView/DetailsTab/SceneDetails.tsx +++ b/client/src/pages/Repository/components/DetailsView/DetailsTab/SceneDetails.tsx @@ -1,21 +1,119 @@ +/* eslint-disable react-hooks/exhaustive-deps */ /** * SceneDetails * * This component renders details tab for Scene specific details used in DetailsTab component. */ -import React from 'react'; -import { Loader } from '../../../../../components'; +import { Box, makeStyles, Typography } from '@material-ui/core'; +import React, { useEffect, useState } from 'react'; +import { CheckboxField, FieldType, Loader } from '../../../../../components'; +import { SceneDetailFields } from '../../../../../types/graphql'; +import { isFieldUpdated } from '../../../../../utils/repository'; import { DetailComponentProps } from './index'; -// TODO: KARAN: implement SceneDetails +export const useStyles = makeStyles(({ palette }) => ({ + value: { + fontSize: '0.8em', + color: palette.primary.dark + } +})); + function SceneDetails(props: DetailComponentProps): React.ReactElement { - const { data, loading } = props; + const classes = useStyles(); + const { data, loading, onUpdateDetail, objectType } = props; + + const [details, setDetails] = useState({ + Links: [] + }); + + useEffect(() => { + if (data && !loading) { + const { Scene } = data.getDetailsTabDataForObject; + setDetails({ + Links: Scene?.Links ?? [], + AssetType: Scene?.AssetType, + Tours: Scene?.Tours, + Annotation: Scene?.Annotation, + HasBeenQCd: Scene?.HasBeenQCd, + IsOriented: Scene?.IsOriented, + }); + } + }, [data, loading]); + + useEffect(() => { + onUpdateDetail(objectType, details); + }, [details]); if (!data || loading) { return ; } - return Scene Details; + const setCheckboxField = ({ target }): void => { + const { name, checked } = target; + setDetails(details => ({ ...details, [name]: checked })); + }; + + const rowFieldProps = { alignItems: 'center', justifyContent: 'space-between', style: { borderRadius: 0 } }; + + const sceneData = data.getDetailsTabDataForObject?.Scene; + + return ( + + {details.Links.map((link: string, index: number) => ( + + {link} + + ))} + + + {details.Tours} + + + {details.Annotation} + + + + + + + ); } export default SceneDetails; \ No newline at end of file diff --git a/client/src/pages/Repository/components/DetailsView/DetailsTab/index.tsx b/client/src/pages/Repository/components/DetailsView/DetailsTab/index.tsx index f631f8614..5638b51ba 100644 --- a/client/src/pages/Repository/components/DetailsView/DetailsTab/index.tsx +++ b/client/src/pages/Repository/components/DetailsView/DetailsTab/index.tsx @@ -105,13 +105,19 @@ function DetailsTab(props: DetailsTabParams): React.ReactElement { ); + const sharedProps = { + onUpdateDetail, + objectType, + disabled + }; + switch (objectType) { case eSystemObjectType.eUnit: tabs = ['Details', 'Related']; tabPanels = ( - + {RelatedTab(1)} @@ -122,7 +128,7 @@ function DetailsTab(props: DetailsTabParams): React.ReactElement { tabPanels = ( - + {RelatedTab(1)} @@ -136,7 +142,7 @@ function DetailsTab(props: DetailsTabParams): React.ReactElement { - + {RelatedTab(2)} @@ -150,7 +156,7 @@ function DetailsTab(props: DetailsTabParams): React.ReactElement { - + {RelatedTab(2)} @@ -164,7 +170,7 @@ function DetailsTab(props: DetailsTabParams): React.ReactElement { - + {RelatedTab(2)} @@ -178,7 +184,7 @@ function DetailsTab(props: DetailsTabParams): React.ReactElement { - + {RelatedTab(2)} @@ -192,7 +198,7 @@ function DetailsTab(props: DetailsTabParams): React.ReactElement { - + {RelatedTab(2)} @@ -206,7 +212,7 @@ function DetailsTab(props: DetailsTabParams): React.ReactElement { - + {RelatedTab(2)} @@ -220,7 +226,7 @@ function DetailsTab(props: DetailsTabParams): React.ReactElement { - + {RelatedTab(2)} @@ -234,7 +240,7 @@ function DetailsTab(props: DetailsTabParams): React.ReactElement { - + {RelatedTab(2)} @@ -245,7 +251,7 @@ function DetailsTab(props: DetailsTabParams): React.ReactElement { tabPanels = ( - + {RelatedTab(1)} @@ -256,7 +262,7 @@ function DetailsTab(props: DetailsTabParams): React.ReactElement { tabPanels = ( - + {RelatedTab(1)} @@ -267,7 +273,7 @@ function DetailsTab(props: DetailsTabParams): React.ReactElement { tabPanels = ( - + {RelatedTab(1)} diff --git a/client/src/types/graphql.tsx b/client/src/types/graphql.tsx index 52aad9b31..b76912570 100644 --- a/client/src/types/graphql.tsx +++ b/client/src/types/graphql.tsx @@ -2710,7 +2710,7 @@ export type GetDetailsTabDataForObjectQuery = ( } )>, Scene?: Maybe<( { __typename?: 'SceneDetailFields' } - & Pick + & Pick )>, IntermediaryFile?: Maybe<( { __typename?: 'IntermediaryFileDetailFields' } & Pick @@ -4395,6 +4395,8 @@ export const GetDetailsTabDataForObjectDocument = gql` AssetType Tours Annotation + HasBeenQCd + IsOriented } IntermediaryFile { idIntermediaryFile diff --git a/server/graphql/api/queries/systemobject/getDetailsTabDataForObject.ts b/server/graphql/api/queries/systemobject/getDetailsTabDataForObject.ts index b55bb0c03..2e035b2c4 100644 --- a/server/graphql/api/queries/systemobject/getDetailsTabDataForObject.ts +++ b/server/graphql/api/queries/systemobject/getDetailsTabDataForObject.ts @@ -90,6 +90,8 @@ const getDetailsTabDataForObject = gql` AssetType Tours Annotation + HasBeenQCd + IsOriented } IntermediaryFile { idIntermediaryFile diff --git a/server/graphql/schema/systemobject/queries.graphql b/server/graphql/schema/systemobject/queries.graphql index d018627e3..3cf5c5f27 100644 --- a/server/graphql/schema/systemobject/queries.graphql +++ b/server/graphql/schema/systemobject/queries.graphql @@ -100,6 +100,8 @@ type SceneDetailFields { AssetType: Int Tours: Int Annotation: Int + HasBeenQCd: Boolean + IsOriented: Boolean } type IntermediaryFileDetailFields { From 4dbb6bfc09e4ad6069a04753fa22d1264d87fe8d Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Fri, 15 Jan 2021 15:44:04 +0530 Subject: [PATCH 182/239] refactor props --- .../DetailsView/DetailsTab/index.tsx | 31 +++++++++++-------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/client/src/pages/Repository/components/DetailsView/DetailsTab/index.tsx b/client/src/pages/Repository/components/DetailsView/DetailsTab/index.tsx index 5638b51ba..8f7f82afb 100644 --- a/client/src/pages/Repository/components/DetailsView/DetailsTab/index.tsx +++ b/client/src/pages/Repository/components/DetailsView/DetailsTab/index.tsx @@ -111,13 +111,18 @@ function DetailsTab(props: DetailsTabParams): React.ReactElement { disabled }; + const detailsProps = { + ...detailsQueryResult, + ...sharedProps + }; + switch (objectType) { case eSystemObjectType.eUnit: tabs = ['Details', 'Related']; tabPanels = ( - + {RelatedTab(1)} @@ -128,7 +133,7 @@ function DetailsTab(props: DetailsTabParams): React.ReactElement { tabPanels = ( - + {RelatedTab(1)} @@ -142,7 +147,7 @@ function DetailsTab(props: DetailsTabParams): React.ReactElement { - + {RelatedTab(2)} @@ -156,7 +161,7 @@ function DetailsTab(props: DetailsTabParams): React.ReactElement { - + {RelatedTab(2)} @@ -170,7 +175,7 @@ function DetailsTab(props: DetailsTabParams): React.ReactElement { - + {RelatedTab(2)} @@ -184,7 +189,7 @@ function DetailsTab(props: DetailsTabParams): React.ReactElement { - + {RelatedTab(2)} @@ -198,7 +203,7 @@ function DetailsTab(props: DetailsTabParams): React.ReactElement { - + {RelatedTab(2)} @@ -212,7 +217,7 @@ function DetailsTab(props: DetailsTabParams): React.ReactElement { - + {RelatedTab(2)} @@ -226,7 +231,7 @@ function DetailsTab(props: DetailsTabParams): React.ReactElement { - + {RelatedTab(2)} @@ -240,7 +245,7 @@ function DetailsTab(props: DetailsTabParams): React.ReactElement { - + {RelatedTab(2)} @@ -251,7 +256,7 @@ function DetailsTab(props: DetailsTabParams): React.ReactElement { tabPanels = ( - + {RelatedTab(1)} @@ -262,7 +267,7 @@ function DetailsTab(props: DetailsTabParams): React.ReactElement { tabPanels = ( - + {RelatedTab(1)} @@ -273,7 +278,7 @@ function DetailsTab(props: DetailsTabParams): React.ReactElement { tabPanels = ( - + {RelatedTab(1)} From d1db117130fe394b470727c832c77627b967b167 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Fri, 15 Jan 2021 21:16:25 +0530 Subject: [PATCH 183/239] split up env files for dev and prod --- .gitignore | 1 + docker-compose.dev.yml | 17 +++++++++++------ docker-compose.prod.yml | 17 +++++++++++------ 3 files changed, 23 insertions(+), 12 deletions(-) diff --git a/.gitignore b/.gitignore index 7fe1c5507..f3562b30b 100644 --- a/.gitignore +++ b/.gitignore @@ -71,6 +71,7 @@ typings/ # dotenv environment variables file .env .env.test +.env.* # parcel-bundler cache (https://parceljs.org/) .cache diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 979eb9e24..295694833 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -25,10 +25,11 @@ services: context: . dockerfile: ./docker/dev.Dockerfile target: client - env_file: - - .env ports: - $PACKRAT_CLIENT_PORT:3000 + environment: + - NODE_ENV=$NODE_ENV + - REACT_APP_SERVER_ENDPOINT=$REACT_APP_SERVER_ENDPOINT volumes: - ./node_modules:/app/node_modules - ./package.json:/app/package.json @@ -43,10 +44,14 @@ services: context: . dockerfile: ./docker/dev.Dockerfile target: server - env_file: - - .env ports: - $PACKRAT_SERVER_PORT:4000 + environment: + - NODE_ENV=$NODE_ENV + - CLIENT_ENDPOINT=$CLIENT_ENDPOINT + - DATABASE_URL=$DATABASE_URL + - SESSION_SECRET=$SESSION_SECRET + - EDAN_AUTH_KEY=$EDAN_AUTH_KEY volumes: - ./node_modules:/app/node_modules - ./package.json:/app/package.json @@ -65,8 +70,8 @@ services: target: db ports: - $PACKRAT_DB_PORT:3306 - env_file: - - .env + environment: + - MYSQL_ROOT_PASSWORD=$MYSQL_ROOT_PASSWORD volumes: - ./server/db/sql:/app/ networks: diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 8eccae08d..05615597e 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -23,10 +23,11 @@ services: context: . dockerfile: ./docker/prod.Dockerfile target: client - env_file: - - .env ports: - $PACKRAT_CLIENT_PORT:3000 + environment: + - NODE_ENV=$NODE_ENV + - REACT_APP_SERVER_ENDPOINT=$REACT_APP_SERVER_ENDPOINT packrat-server: container_name: packrat-server @@ -36,10 +37,14 @@ services: context: . dockerfile: ./docker/prod.Dockerfile target: server - env_file: - - .env ports: - $PACKRAT_SERVER_PORT:4000 + environment: + - NODE_ENV=$NODE_ENV + - CLIENT_ENDPOINT=$CLIENT_ENDPOINT + - DATABASE_URL=$DATABASE_URL + - SESSION_SECRET=$SESSION_SECRET + - EDAN_AUTH_KEY=$EDAN_AUTH_KEY depends_on: - db @@ -49,8 +54,8 @@ services: restart: always ports: - $PACKRAT_DB_PORT:3306 - env_file: - - .env + environment: + - MYSQL_ROOT_PASSWORD=$MYSQL_ROOT_PASSWORD networks: default: ipam: From a3d927ba704b6d7c67b8ef58d0d8d5ea18d493f1 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Fri, 15 Jan 2021 21:17:06 +0530 Subject: [PATCH 184/239] update github action for docker build --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 04337f0b8..bb7fdad48 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -104,14 +104,14 @@ jobs: # Runs commands pre-build # Ideally .env will be created from secrets ${{ secrets.DEV/PROD_ENV }} > .env - name: Prepare build - run: touch .env + run: touch .env.dev && touch env.prod - name: Build Dev images - run: docker-compose -f docker-compose.dev.yml build + run: docker-compose --env-file .env.dev -f docker-compose.dev.yml build if: contains(github.ref, 'develop') - name: Build Prod images - run: docker-compose -f docker-compose.prod.yml build + run: docker-compose --env-file .env.prod -f docker-compose.prod.yml build if: contains(github.ref, 'master') # Prepares tag for docker images From 6b94f9cba8cd1e23d9b5d8374841399ce8cc77ef Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Fri, 15 Jan 2021 21:22:26 +0530 Subject: [PATCH 185/239] update package.json dev prod commands --- package.json | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index fa2cf9c64..34a58e65b 100644 --- a/package.json +++ b/package.json @@ -30,10 +30,8 @@ "log:server": "docker logs -f packrat-server", "start:client:prod": "yarn workspace @dpo-packrat/client start:prod", "start:server:prod": "yarn workspace @dpo-packrat/server start:prod", - "dev": "docker-compose -f docker-compose.dev.yml up -d", - "prod": "docker-compose -f docker-compose.prod.yml up -d", - "dev:rebuild": "docker-compose -f docker-compose.dev.yml up --build", - "prod:rebuild": "docker-compose -f docker-compose.prod.yml up --build", + "dev": "docker-compose --env-file .env.dev -f docker-compose.dev.yml up -d", + "prod": "docker-compose --env-file .env.prod -f docker-compose.prod.yml up -d", "build": "lerna run build", "clean": "lerna run clean && rm -rf node_modules/", "clean:docker": "docker-compose -f docker-compose.dev.yml down && docker system prune -f", From 6267c16e5d0aa897e2e06f17fb725bb2a7af9602 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Fri, 15 Jan 2021 22:13:17 +0530 Subject: [PATCH 186/239] added missing ocfl env to dev --- .env.template | 13 ++++++++++--- docker-compose.dev.yml | 4 ++++ docker-compose.prod.yml | 4 ++++ 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/.env.template b/.env.template index 3a2097331..f0f7ef4ab 100644 --- a/.env.template +++ b/.env.template @@ -1,10 +1,17 @@ +# NODE NODE_ENV= + +# DB MYSQL_ROOT_PASSWORD= -DATABASE_URL= -PACKRAT_CLIENT_PORT= -PACKRAT_SERVER_PORT= PACKRAT_DB_PORT= + +# CLIENT +PACKRAT_CLIENT_PORT= REACT_APP_SERVER_ENDPOINT= + +# SERVER +PACKRAT_SERVER_PORT= +DATABASE_URL= CLIENT_ENDPOINT= SESSION_SECRET= EDAN_AUTH_KEY= diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 295694833..620d4c843 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -52,6 +52,10 @@ services: - DATABASE_URL=$DATABASE_URL - SESSION_SECRET=$SESSION_SECRET - EDAN_AUTH_KEY=$EDAN_AUTH_KEY + - EDAN_SERVER=$EDAN_SERVER + - EDAN_APPID=$EDAN_APPID + - OCFL_STORAGE_ROOT=$OCFL_STORAGE_ROOT + - OCFL_STAGING_ROOT=$OCFL_STAGING_ROOT volumes: - ./node_modules:/app/node_modules - ./package.json:/app/package.json diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 05615597e..225eda4aa 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -45,6 +45,10 @@ services: - DATABASE_URL=$DATABASE_URL - SESSION_SECRET=$SESSION_SECRET - EDAN_AUTH_KEY=$EDAN_AUTH_KEY + - EDAN_SERVER=$EDAN_SERVER + - EDAN_APPID=$EDAN_APPID + - OCFL_STORAGE_ROOT=$OCFL_STORAGE_ROOT + - OCFL_STAGING_ROOT=$OCFL_STAGING_ROOT depends_on: - db From c62b11307505bd833795b57e02a759461662e0d9 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Fri, 15 Jan 2021 22:31:15 +0530 Subject: [PATCH 187/239] added env cmd package --- client/package.json | 1 + yarn.lock | 19 ++++++++++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/client/package.json b/client/package.json index cb93e5a6e..2838dfb64 100644 --- a/client/package.json +++ b/client/package.json @@ -44,6 +44,7 @@ "clsx": "1.1.1", "customize-cra": "1.0.0", "customize-cra-react-refresh": "1.1.0", + "env-cmd": "10.1.0", "formik": "2.1.5", "formik-material-ui": "3.0.0", "framer-motion": "2.6.13", diff --git a/yarn.lock b/yarn.lock index 7a049bfbb..4bbdb66f4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5710,7 +5710,7 @@ commander@^2.11.0, commander@^2.20.0, commander@^2.20.3: resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== -commander@^4.1.1: +commander@^4.0.0, commander@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== @@ -6169,6 +6169,15 @@ cross-spawn@^6.0.0, cross-spawn@^6.0.5: shebang-command "^1.2.0" which "^1.2.9" +cross-spawn@^7.0.0: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + crypto-browserify@^3.11.0: version "3.12.0" resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec" @@ -7102,6 +7111,14 @@ entities@^2.0.0: resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.3.tgz#5c487e5742ab93c15abb5da22759b8590ec03b7f" integrity sha512-MyoZ0jgnLvB2X3Lg5HqpFmn1kybDiIfEQmKzTb5apr51Rb+T3KdmMiqa70T+bhGnyv7bQ6WMj2QMHpGMmlrUYQ== +env-cmd@10.1.0: + version "10.1.0" + resolved "https://registry.yarnpkg.com/env-cmd/-/env-cmd-10.1.0.tgz#c7f5d3b550c9519f137fdac4dd8fb6866a8c8c4b" + integrity sha512-mMdWTT9XKN7yNth/6N6g2GuKuJTsKMDHlQFUDacb/heQRRWOTIZ42t1rMHnQu4jYxU1ajdTeJM+9eEETlqToMA== + dependencies: + commander "^4.0.0" + cross-spawn "^7.0.0" + env-paths@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.0.tgz#cdca557dc009152917d6166e2febe1f039685e43" From 98188ae56edc53ddec4b1e5b735029d87b83a198 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Fri, 15 Jan 2021 23:05:42 +0530 Subject: [PATCH 188/239] split dev/prod build --- client/package.json | 3 ++- common/package.json | 5 +++-- docker/dev.Dockerfile | 4 ++-- docker/prod.Dockerfile | 4 ++-- package.json | 3 ++- server/package.json | 5 +++-- 6 files changed, 14 insertions(+), 10 deletions(-) diff --git a/client/package.json b/client/package.json index 2838dfb64..5eb2d0785 100644 --- a/client/package.json +++ b/client/package.json @@ -73,7 +73,8 @@ "scripts": { "start": "react-app-rewired start", "start:prod": "react-app-rewired start", - "build": "react-app-rewired build", + "build:dev": "yarn env-cmd -f ../.env.dev react-app-rewired build", + "build:prod": "yarn env-cmd -f ../.env.prod react-app-rewired build", "test": "react-app-rewired test --watchAll=false --passWithNoTests", "eject": "react-app-rewired eject", "clean": "rm -rf node_modules/ build/", diff --git a/common/package.json b/common/package.json index eff1099aa..01a5c468c 100644 --- a/common/package.json +++ b/common/package.json @@ -25,9 +25,10 @@ "main": "build/index.js", "typings": "build/index.d.ts", "scripts": { - "start": "yarn build && concurrently 'yarn build:watch' 'yarn server:start'", + "start": "yarn build:dev && concurrently 'yarn build:watch' 'yarn server:start'", "server:start": "nodemon -e '*.ts' --watch build 'node build/index.js'", - "build": "tsc --build", + "build:dev": "tsc --build", + "build:prod": "tsc --build", "build:watch": "tsc --watch", "clean": "rm -rf node_modules/ build/", "postinstall": "echo \"postinstall common\"", diff --git a/docker/dev.Dockerfile b/docker/dev.Dockerfile index 3c5ade6ec..0d0df39f3 100644 --- a/docker/dev.Dockerfile +++ b/docker/dev.Dockerfile @@ -13,7 +13,7 @@ RUN rm -rf server # Expose port(s) EXPOSE 3000 # Install dependencies and build -RUN yarn && yarn build +RUN yarn && yarn build:dev # Start on excecution CMD [ "yarn", "start:client" ] @@ -24,7 +24,7 @@ RUN rm -rf client # Expose port(s) EXPOSE 4000 # Install dependencies and build -RUN yarn && yarn build +RUN yarn && yarn build:dev # Start on excecution CMD [ "yarn", "start:server" ] diff --git a/docker/prod.Dockerfile b/docker/prod.Dockerfile index 058215fba..09a2e9a96 100644 --- a/docker/prod.Dockerfile +++ b/docker/prod.Dockerfile @@ -10,13 +10,13 @@ FROM base AS client-builder # Remove server from client build RUN rm -rf server # Install dependencies (production mode) and build -RUN yarn install --frozen-lockfile && yarn build +RUN yarn install --frozen-lockfile && yarn build:prod FROM base AS server-builder # Remove client from server build RUN rm -rf client # Install dependencies (production mode) and build -RUN yarn install --frozen-lockfile && yarn build +RUN yarn install --frozen-lockfile && yarn build:prod # Client's production image FROM node:12.18.4-alpine AS client diff --git a/package.json b/package.json index 34a58e65b..dcfada79c 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,8 @@ "start:server:prod": "yarn workspace @dpo-packrat/server start:prod", "dev": "docker-compose --env-file .env.dev -f docker-compose.dev.yml up -d", "prod": "docker-compose --env-file .env.prod -f docker-compose.prod.yml up -d", - "build": "lerna run build", + "build:dev": "lerna run build:dev", + "build:prod": "lerna run build:prod", "clean": "lerna run clean && rm -rf node_modules/", "clean:docker": "docker-compose -f docker-compose.dev.yml down && docker system prune -f", "generate:server:prisma": "yarn workspace @dpo-packrat/server generate:prisma", diff --git a/server/package.json b/server/package.json index 066c2f727..bdd41d443 100644 --- a/server/package.json +++ b/server/package.json @@ -25,10 +25,11 @@ "main": "build/index.js", "typings": "build/index.d.ts", "scripts": { - "start": "yarn build && concurrently 'yarn build:watch' 'yarn server:start'", + "start": "yarn build:dev && concurrently 'yarn build:watch' 'yarn server:start'", "start:prod": "node build/index.js", "server:start": "nodemon build/index.js --ignore var/", - "build": "tsc --build && copyfiles db/**/*.* graphql/**/**.graphql build/", + "build:dev": "tsc --build && copyfiles db/**/*.* graphql/**/**.graphql build/", + "build:prod": "tsc --build && copyfiles db/**/*.* graphql/**/**.graphql build/", "build:watch": "tsc --watch", "clean": "rm -rf node_modules/ build/", "generate": "graphql-codegen --config ./graphql/codegen.yml", From b3ff55842d0d3884d8fdc8743e6386a2a5aa910e Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Sun, 17 Jan 2021 14:53:25 +0530 Subject: [PATCH 189/239] added deploy scripts and yml file --- docker-compose.deploy.yml | 100 ++++++++++++++++++++++++++++++++++++++ package.json | 2 + scripts/deploy.sh | 51 +++++++++++++++++++ 3 files changed, 153 insertions(+) create mode 100644 docker-compose.deploy.yml create mode 100644 scripts/deploy.sh diff --git a/docker-compose.deploy.yml b/docker-compose.deploy.yml new file mode 100644 index 000000000..dbd555421 --- /dev/null +++ b/docker-compose.deploy.yml @@ -0,0 +1,100 @@ +version: "3.4" + +services: + packrat-client-dev: + container_name: packrat-client-dev + image: "packrat-client-dev:${IMAGE_TAG}" + restart: always + build: + context: . + dockerfile: ./docker/dev.Dockerfile + target: client + ports: + - $PACKRAT_CLIENT_PORT:3000 + environment: + - NODE_ENV=$NODE_ENV + - REACT_APP_SERVER_ENDPOINT=$REACT_APP_SERVER_ENDPOINT + + packrat-client-prod: + container_name: packrat-client-prod + image: "packrat-client-prod:${IMAGE_TAG}" + restart: always + build: + context: . + dockerfile: ./docker/prod.Dockerfile + target: client + ports: + - $PACKRAT_CLIENT_PORT:3000 + environment: + - NODE_ENV=$NODE_ENV + - REACT_APP_SERVER_ENDPOINT=$REACT_APP_SERVER_ENDPOINT + + packrat-server-dev: + container_name: packrat-server-dev + image: "packrat-server-dev:${IMAGE_TAG}" + restart: always + build: + context: . + dockerfile: ./docker/dev.Dockerfile + target: server + ports: + - $PACKRAT_SERVER_PORT:4000 + environment: + - NODE_ENV=$NODE_ENV + - CLIENT_ENDPOINT=$CLIENT_ENDPOINT + - DATABASE_URL=$DATABASE_URL + - SESSION_SECRET=$SESSION_SECRET + - EDAN_AUTH_KEY=$EDAN_AUTH_KEY + - EDAN_SERVER=$EDAN_SERVER + - EDAN_APPID=$EDAN_APPID + - OCFL_STORAGE_ROOT=$OCFL_STORAGE_ROOT + - OCFL_STAGING_ROOT=$OCFL_STAGING_ROOT + + packrat-server-prod: + container_name: packrat-server-prod + image: "packrat-server-prod:${IMAGE_TAG}" + restart: always + build: + context: . + dockerfile: ./docker/prod.Dockerfile + target: server + ports: + - $PACKRAT_SERVER_PORT:4000 + environment: + - NODE_ENV=$NODE_ENV + - CLIENT_ENDPOINT=$CLIENT_ENDPOINT + - DATABASE_URL=$DATABASE_URL + - SESSION_SECRET=$SESSION_SECRET + - EDAN_AUTH_KEY=$EDAN_AUTH_KEY + - EDAN_SERVER=$EDAN_SERVER + - EDAN_APPID=$EDAN_APPID + - OCFL_STORAGE_ROOT=$OCFL_STORAGE_ROOT + - OCFL_STAGING_ROOT=$OCFL_STAGING_ROOT + + packrat-db-dev: + container_name: packrat-db-dev + image: mariadb:10.5 + restart: always + ports: + - $PACKRAT_DB_PORT:3306 + environment: + - MYSQL_ROOT_PASSWORD=$MYSQL_ROOT_PASSWORD + volumes: + - ./server/db/sql:/app/ + + packrat-db-prod: + container_name: packrat-db-prod + image: mariadb:10.5 + restart: always + ports: + - $PACKRAT_DB_PORT:3306 + environment: + - MYSQL_ROOT_PASSWORD=$MYSQL_ROOT_PASSWORD + volumes: + - ./server/db/sql:/app/ + +networks: + default: + ipam: + config: + - subnet: 192.168.4.0/24 diff --git a/package.json b/package.json index dcfada79c..d483ce1f6 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,8 @@ "start:server:prod": "yarn workspace @dpo-packrat/server start:prod", "dev": "docker-compose --env-file .env.dev -f docker-compose.dev.yml up -d", "prod": "docker-compose --env-file .env.prod -f docker-compose.prod.yml up -d", + "deploy:dev": "sh ./scripts/deploy.sh dev", + "deploy:prod": "sh ./scripts/deploy.sh prod", "build:dev": "lerna run build:dev", "build:prod": "lerna run build:prod", "clean": "lerna run clean && rm -rf node_modules/", diff --git a/scripts/deploy.sh b/scripts/deploy.sh new file mode 100644 index 000000000..121ca2107 --- /dev/null +++ b/scripts/deploy.sh @@ -0,0 +1,51 @@ +# This script helps with deployment of Packrat system +# usage: ./deploy.sh (environment: dev | prod) + +DEV="dev" +PROD="prod" +BRANCH=$(git branch --show-current) + +# Environment given by the user +ENV=$1 +# First 7 characters of the git SHA (basicaly short version of SHA) +IMAGE_TAG=$(git rev-parse --short=7 HEAD) + +# Checks if the current branch matches the desired branch +function branch_check() { + CURRENT_BRANCH=$1 + DESIRED_BRANCH=$2 + ENV=$3 + + if [ $CURRENT_BRANCH != DESIRED_BRANCH ] + then + echo "Cannot deploy branch $CURRENT_BRANCH to $3 environment. Make sure you're on $DESIRED_BRANCH branch" + exit 1 + fi +} + +# Input validation +if [ $1 == $DEV ] +then + # check if the branch is develop + branch_check $BRANCH "develop" $1 +elif [ $1 == $PROD ] +then + # check if the branch is master + branch_check $BRANCH "master" $1 +else + echo "First argument should be either dev or prod" + exit 1 +fi + +# Export required tags and environment variables used for building the images +export IMAGE_TAG=$IMAGE_TAG +export ENV=$ENV + +echo "Deploying docker images for env $1 with tag: $IMAGE_TAG" + +# Build packrat-server and client dynamically for environment's requested +docker-compose --env-file .env.$1 -f docker-compose.deploy.yml up --build -d packrat-server-$1 packrat-client-$1 +# Don't recreate/build DB everytime automatically +docker-compose --env-file .env.$1 -f docker-compose.deploy.yml up -d packrat-db-$1 + + From be0a7a37b8e8c8996d92a022edb024167b8dc611 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Sun, 17 Jan 2021 18:11:58 +0530 Subject: [PATCH 190/239] update deploy script --- scripts/deploy.sh | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/scripts/deploy.sh b/scripts/deploy.sh index 121ca2107..51c8e506a 100644 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -24,11 +24,11 @@ function branch_check() { } # Input validation -if [ $1 == $DEV ] +if [[ $1 == $DEV ]] then # check if the branch is develop branch_check $BRANCH "develop" $1 -elif [ $1 == $PROD ] +elif [[ $1 == $PROD ]] then # check if the branch is master branch_check $BRANCH "master" $1 @@ -45,7 +45,3 @@ echo "Deploying docker images for env $1 with tag: $IMAGE_TAG" # Build packrat-server and client dynamically for environment's requested docker-compose --env-file .env.$1 -f docker-compose.deploy.yml up --build -d packrat-server-$1 packrat-client-$1 -# Don't recreate/build DB everytime automatically -docker-compose --env-file .env.$1 -f docker-compose.deploy.yml up -d packrat-db-$1 - - From 9ee5ba5400e0cdc408c268a8fb21cf05da021297 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Sun, 17 Jan 2021 18:12:11 +0530 Subject: [PATCH 191/239] added new initdbscript --- scripts/initdb.sh | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100755 scripts/initdb.sh diff --git a/scripts/initdb.sh b/scripts/initdb.sh new file mode 100755 index 000000000..49b393b21 --- /dev/null +++ b/scripts/initdb.sh @@ -0,0 +1,44 @@ +# This script helps with deployment of Packrat system's DB +# usage: ./initdb.sh (environment: dev | prod) + +# Check if the variable MYSQL_ROOT_PASSWORD is set or not +if [[ -z "${MYSQL_ROOT_PASSWORD}" ]] + then + echo "Make sure env MYSQL_ROOT_PASSWORD is set" + exit 1 +fi + +DEV="dev" +PROD="prod" +BRANCH=$(git branch --show-current) + +# Environment given by the user +ENV=$1 +# First 7 characters of the git SHA (basicaly short version of SHA) +IMAGE_TAG=$(git rev-parse --short=7 HEAD) + +# Input validation +if [[ $1 == $DEV ]] +then + echo "Environment: dev" +elif [[ $1 == $PROD ]] +then + echo "Environment: prod" +else + echo "First argument should be either dev or prod" + exit 1 +fi + +# Export required tags and environment variables used for building the images +export IMAGE_TAG=$IMAGE_TAG +export ENV=$ENV + +echo "Starting docker DB image for env $1 with tag: $IMAGE_TAG" + +# Start the databases +docker-compose --env-file .env.$1 -f docker-compose.deploy.yml up -d packrat-db-$1 + +# DB init scripts +docker exec -i packrat-db-$1 sh -c "mysql -u root -p$MYSQL_ROOT_PASSWORD < /app/scripts/Packrat.SCHEMA.sql" +docker exec -i packrat-db-$1 sh -c "mysql -u root -p$MYSQL_ROOT_PASSWORD < /app/scripts/Packrat.PROC.sql" +docker exec -i packrat-db-$1 sh -c "mysql -u root -p$MYSQL_ROOT_PASSWORD < /app/scripts/Packrat.DATA.sql" From 05448d996bdcc3d1451b771737b575f5803a0e85 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Mon, 18 Jan 2021 01:25:10 +0530 Subject: [PATCH 192/239] added proxy update script --- scripts/proxy/nginx.conf | 94 ++++++++++++++++++++++++++++++++++++++++ scripts/proxy/update.sh | 5 +++ 2 files changed, 99 insertions(+) create mode 100644 scripts/proxy/nginx.conf create mode 100755 scripts/proxy/update.sh diff --git a/scripts/proxy/nginx.conf b/scripts/proxy/nginx.conf new file mode 100644 index 000000000..050819bd3 --- /dev/null +++ b/scripts/proxy/nginx.conf @@ -0,0 +1,94 @@ +# For more information on configuration, see: +# * Official English Documentation: http://nginx.org/en/docs/ +# * Official Russian Documentation: http://nginx.org/ru/docs/ + +user nginx; +worker_processes auto; +error_log /var/log/nginx/error.log; +pid /run/nginx.pid; + +# Load dynamic modules. See /usr/share/doc/nginx/README.dynamic. +include /usr/share/nginx/modules/*.conf; + +events { + worker_connections 1024; +} + +http { + log_format main + '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 65; + types_hash_max_size 2048; + + include /etc/nginx/mime.types; + default_type application/octet-stream; + + # Load modular configuration files from the /etc/nginx/conf.d directory. + # See http://nginx.org/en/docs/ngx_core_module.html#include + # for more information. + include /etc/nginx/conf.d/*.conf; + + client_max_body_size 10240m; + + upstream server-dev { + server 127.0.0.1:4000; + } + + upstream client-dev { + server 127.0.0.1:3000; + } + + server { + listen 80; + server_name $hostname; + + location /server { + rewrite /server/(.*) /$1 break; + proxy_pass http://server-dev; + } + + location / { + proxy_pass http://client-dev; + proxy_set_header X-Forwarded-For $remote_addr; + } + } + + # Settings for a TLS enabled server. + # + # server { + # listen 443 ssl http2 default_server; + # listen [::]:443 ssl http2 default_server; + # server_name _; + # root /usr/share/nginx/html; + # + # ssl_certificate "/etc/pki/nginx/server.crt"; + # ssl_certificate_key "/etc/pki/nginx/private/server.key"; + # ssl_session_cache shared:SSL:1m; + # ssl_session_timeout 10m; + # ssl_ciphers PROFILE=SYSTEM; + # ssl_prefer_server_ciphers on; + # + # # Load configuration files for the default server block. + # include /etc/nginx/default.d/*.conf; + # + # location / { + # } + # + # error_page 404 /404.html; + # location = /40x.html { + # } + # + # error_page 500 502 503 504 /50x.html; + # location = /50x.html { + # } + # } + +} \ No newline at end of file diff --git a/scripts/proxy/update.sh b/scripts/proxy/update.sh new file mode 100755 index 000000000..718ceac01 --- /dev/null +++ b/scripts/proxy/update.sh @@ -0,0 +1,5 @@ +mv -v nginx.conf /etc/nginx/nginx.conf + +sudo nginx +sudo systemctl restart nginx +sudo systemctl status nginx \ No newline at end of file From 3a6bc3f98e8763ab98f0549b28ace24bba15d701 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Mon, 18 Jan 2021 01:56:17 +0530 Subject: [PATCH 193/239] added updated scripts --- scripts/cleanup.sh | 2 ++ scripts/proxy/nginx.conf | 4 ++++ scripts/proxy/update.sh | 11 +++++++---- 3 files changed, 13 insertions(+), 4 deletions(-) create mode 100644 scripts/cleanup.sh diff --git a/scripts/cleanup.sh b/scripts/cleanup.sh new file mode 100644 index 000000000..59abf03aa --- /dev/null +++ b/scripts/cleanup.sh @@ -0,0 +1,2 @@ +# This script is used to cleanup dangaling images after build +sudo docker rmi $(sudo docker images -f "dangling=true" -q) \ No newline at end of file diff --git a/scripts/proxy/nginx.conf b/scripts/proxy/nginx.conf index 050819bd3..63f8f0d13 100644 --- a/scripts/proxy/nginx.conf +++ b/scripts/proxy/nginx.conf @@ -53,11 +53,15 @@ http { location /server { rewrite /server/(.*) /$1 break; proxy_pass http://server-dev; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; } location / { proxy_pass http://client-dev; proxy_set_header X-Forwarded-For $remote_addr; + proxy_set_header X-Forwarded-For $remote_addr; + proxy_set_header X-Real-Port $remote_port; } } diff --git a/scripts/proxy/update.sh b/scripts/proxy/update.sh index 718ceac01..b0c8d20ac 100755 --- a/scripts/proxy/update.sh +++ b/scripts/proxy/update.sh @@ -1,5 +1,8 @@ -mv -v nginx.conf /etc/nginx/nginx.conf +# Updates the global nginx.conf on the server +sudo mv -v ./scripts/proxy/nginx.conf /etc/nginx/nginx.conf -sudo nginx -sudo systemctl restart nginx -sudo systemctl status nginx \ No newline at end of file +# restart nginx service +sudo service nginx restart + +# Use the command below to stop if you're getting nginx: [emerg] bind() to 0.0.0.0:80 failed +# sudo fuser -k 80/tcp \ No newline at end of file From 1d9979ab124b42506ce46ca61e378ce33034d133 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Mon, 18 Jan 2021 01:58:58 +0530 Subject: [PATCH 194/239] update script --- scripts/proxy/update.sh | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/scripts/proxy/update.sh b/scripts/proxy/update.sh index b0c8d20ac..491a23468 100755 --- a/scripts/proxy/update.sh +++ b/scripts/proxy/update.sh @@ -1,8 +1,11 @@ # Updates the global nginx.conf on the server -sudo mv -v ./scripts/proxy/nginx.conf /etc/nginx/nginx.conf +sudo cp -v ./scripts/proxy/nginx.conf /etc/nginx/nginx.conf -# restart nginx service +# Restart nginx service sudo service nginx restart +# Check if status is active +sudo service nginx status + # Use the command below to stop if you're getting nginx: [emerg] bind() to 0.0.0.0:80 failed # sudo fuser -k 80/tcp \ No newline at end of file From c74195175f7c49e91e9653ff6ff1c61474c4d0aa Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Mon, 18 Jan 2021 03:04:09 +0530 Subject: [PATCH 195/239] update scripts --- scripts/cleanup.sh | 1 + scripts/deploy.sh | 2 +- scripts/initdb.sh | 4 +++- scripts/proxy/{update.sh => refresh.sh} | 3 ++- 4 files changed, 7 insertions(+), 3 deletions(-) rename scripts/proxy/{update.sh => refresh.sh} (81%) diff --git a/scripts/cleanup.sh b/scripts/cleanup.sh index 59abf03aa..9946f0934 100644 --- a/scripts/cleanup.sh +++ b/scripts/cleanup.sh @@ -1,2 +1,3 @@ # This script is used to cleanup dangaling images after build +# usage: ./scripts/cleanup.sh sudo docker rmi $(sudo docker images -f "dangling=true" -q) \ No newline at end of file diff --git a/scripts/deploy.sh b/scripts/deploy.sh index 51c8e506a..44bb8e7d3 100644 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -1,5 +1,5 @@ # This script helps with deployment of Packrat system -# usage: ./deploy.sh (environment: dev | prod) +# usage: ./scripts/deploy.sh (environment: dev | prod) DEV="dev" PROD="prod" diff --git a/scripts/initdb.sh b/scripts/initdb.sh index 49b393b21..69e1727ed 100755 --- a/scripts/initdb.sh +++ b/scripts/initdb.sh @@ -1,5 +1,7 @@ # This script helps with deployment of Packrat system's DB -# usage: ./initdb.sh (environment: dev | prod) +# usage: ./scripts/initdb.sh (environment: dev | prod) +# +# example: MYSQL_ROOT_PASSWORD= ./scripts/initdb.sh dev # Check if the variable MYSQL_ROOT_PASSWORD is set or not if [[ -z "${MYSQL_ROOT_PASSWORD}" ]] diff --git a/scripts/proxy/update.sh b/scripts/proxy/refresh.sh similarity index 81% rename from scripts/proxy/update.sh rename to scripts/proxy/refresh.sh index 491a23468..13b85efa8 100755 --- a/scripts/proxy/update.sh +++ b/scripts/proxy/refresh.sh @@ -1,11 +1,12 @@ # Updates the global nginx.conf on the server sudo cp -v ./scripts/proxy/nginx.conf /etc/nginx/nginx.conf +echo "Restarting nginx service" # Restart nginx service sudo service nginx restart # Check if status is active -sudo service nginx status +sudo service nginx status --no-pager # Use the command below to stop if you're getting nginx: [emerg] bind() to 0.0.0.0:80 failed # sudo fuser -k 80/tcp \ No newline at end of file From ef00721d5702a79b7e9d50b62d866d5995f6d5fd Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Mon, 18 Jan 2021 03:28:03 +0530 Subject: [PATCH 196/239] updated readme with deployment instructions --- README.md | 74 +++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 61 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 4cede32aa..da492c119 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ # dpo-packrat Data Repository and Workflow Management for 3D data captures, models, and scenes -## Instructions: +## Setup instructions (Development): -*`.env` is required and it follows `.env.template`* +*Note: `.env.dev` is required and it follows `.env.template`* ### Development: @@ -13,25 +13,19 @@ Data Repository and Workflow Management for 3D data captures, models, and scenes yarn ``` -2. Build existing packages: - -``` -yarn build -``` - -3. Build the docker images, if they're already available then this would just start them (if you're on a mac then make sure Docker for mac is running): +2. Build the docker images, if they're already available then this would just start them (if you're on a mac then make sure Docker for mac is running): ``` yarn dev ``` -4. Now the docker containers should start in 10s-20s. The client should be reachable at `http://localhost:3000` and server should be reachable at `http://localhost:4000` or the ports you specified in `.env` following `.env.template` +3. Now the docker containers should start in 10s-20s. The client should be reachable at `http://localhost:3000` and server should be reachable at `http://localhost:4000` or the ports you specified in `.env.dev` following `.env.template` -5. If you want to follow debug logs for `client` or `server` container then just run `yarn log:client` or `yarn log:server` +4. If you want to follow debug logs for `client` or `server` container then just run `yarn log:client` or `yarn log:server` -6. If you're developing `common` package then make sure to use `yarn start:common` so that it's actively watched/compiled and made available to other packages it's imported at. The other packages should auto reload when changes are made to them. +7. If you're developing `common` package then make sure to use `yarn start:common` so that it's actively watched/compiled and made available to other packages it's imported at. The other packages should auto reload when changes are made to them. -7. If not using docker run each command in a separate terminal for the package you're developing: +6. If not using docker run each command in a separate terminal for the package you're developing: **For client:** @@ -50,3 +44,57 @@ yarn start:server ``` yarn start:common ``` + +# Deployment instructions: +*Note: Make sure before you execute any script, you're root of the repository `dpo-packrat` and if you get permission denied for any script, make sure to do `chmod 777 path/to/script`. If you encounter any error then make sure to checkout Packrat server setup instruction on confluence* + +## Docker images: +*Note: current supported environments are `dev` and `prod`* + +1. Login into SI server + +2. Pull the latest changes +``` +git pull +``` +*Note: repository is already cloned in `/home//dpo-packrat`* + +3. Switch to the branch you want to deploy. To deploy for `dev` environment you should be on `develop` branch, for `prod` environment +``` +git checkout master +``` +*Note: `.env.dev` and `.env.prod` are already available* + +4. Deploy using the `deploy.sh` script +``` +./scripts/deploy.sh prod +``` +If you get `permission denied for docker` then use +``` +sudo chmod 777 /var/run/docker.sock +``` +If you get `Error while loading shared libraries: libz.so.1` for `docker-compose` then do the following: +``` +sudo mount /tmp -o remount,exec +``` + +5. Wait for the images to be build/updated, then use `cleanup.sh` script to cleanup any residual docker images are left (optional) + +6. Make sure nginx is active using `sudo service nginx status --no-pager` + +## Start databases (Production server only): + +1. Start `dev` or `prod` databases using `scripts/initdb.sh` script +``` +MYSQL_ROOT_PASSWORD= ./scripts/initdb.sh dev +``` +*Note: `MYSQL_ROOT_PASSWORD` be same what you mentioned in the `.env.dev` or `.env.prod` file for that particular environment. Mostly would be used for `dev` environment.* + +## Update production nginx configuration (Production server only): + +1. Make the changes to production nginx configuration is located at `scripts/proxy/nginx.conf` + +2. Use `scripts/proxy/refresh.sh` script to restart/update nginx service +``` +./scripts/proxy/refresh.sh +``` From e61bde4e6b51440f50383eb66bf595be6995e4a2 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Mon, 18 Jan 2021 15:12:25 +0530 Subject: [PATCH 197/239] added reload script --- scripts/proxy/nginx.conf | 2 +- scripts/reload.sh | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 scripts/reload.sh diff --git a/scripts/proxy/nginx.conf b/scripts/proxy/nginx.conf index 63f8f0d13..cd9e32265 100644 --- a/scripts/proxy/nginx.conf +++ b/scripts/proxy/nginx.conf @@ -47,7 +47,7 @@ http { } server { - listen 80; + listen 8080; server_name $hostname; location /server { diff --git a/scripts/reload.sh b/scripts/reload.sh new file mode 100644 index 000000000..54ca2e936 --- /dev/null +++ b/scripts/reload.sh @@ -0,0 +1,34 @@ +# This script helps with updating docker images with new .env's +# usage: ./scripts/reload.sh (environment: dev | prod) + +DEV="dev" +PROD="prod" +BRANCH=$(git branch --show-current) + +# Environment given by the user +ENV=$1 +# First 7 characters of the git SHA (basicaly short version of SHA) +IMAGE_TAG=$(git rev-parse --short=7 HEAD) + +# Input validation +if [[ $1 == $DEV ]] +then + # check if the branch is develop + echo "Environment: Dev" +elif [[ $1 == $PROD ]] +then + # check if the branch is master + echo "Environment: Prod" +else + echo "First argument should be either dev or prod" + exit 1 +fi + +# Export required tags and environment variables used for building the images +export IMAGE_TAG=$IMAGE_TAG +export ENV=$ENV + +echo "Reloading docker images for env $1 with tag: $IMAGE_TAG" + +# Build packrat-server and client dynamically for environment's requested +docker-compose --env-file .env.$1 -f docker-compose.deploy.yml up -d packrat-server-$1 packrat-client-$1 From 8d6fb4d735392c45a49bff86bfbbf87f249c7f9a Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Mon, 18 Jan 2021 15:39:22 +0530 Subject: [PATCH 198/239] added test use --- e2e/utils.ts | 4 ++-- server/db/sql/scripts/Packrat.DATA.sql | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/e2e/utils.ts b/e2e/utils.ts index 292bf244b..6a1c125e1 100644 --- a/e2e/utils.ts +++ b/e2e/utils.ts @@ -40,8 +40,8 @@ class E2ETestUtils { throw new Error('E2E tests: page was not initialized'); } - const TEST_USER_EMAIL: string = 'karan.pratapsingh686@gmail.com'; - const TEST_USER_PASSWORD: string = 'karan.pratapsingh686@gmail.com'; + const TEST_USER_EMAIL: string = 'user@test.com'; + const TEST_USER_PASSWORD: string = 'user@test.com'; await this.page.type(ID(Selectors.AUTH.EMAIL_FIELD), TEST_USER_EMAIL); await this.page.type(ID(Selectors.AUTH.PASSWORD_FIELD), TEST_USER_PASSWORD); diff --git a/server/db/sql/scripts/Packrat.DATA.sql b/server/db/sql/scripts/Packrat.DATA.sql index a3b936b53..679a583fd 100644 --- a/server/db/sql/scripts/Packrat.DATA.sql +++ b/server/db/sql/scripts/Packrat.DATA.sql @@ -1114,6 +1114,7 @@ INSERT INTO User (Name, EmailAddress, SecurityID, Active, DateActivated) VALUES INSERT INTO User (Name, EmailAddress, SecurityID, Active, DateActivated) VALUES ('Vince Rossi', 'rossiv@si.edu', 'TBD', 1, NOW()); INSERT INTO User (Name, EmailAddress, SecurityID, Active, DateActivated) VALUES ('Jamie Cope', 'copeg@si.edu', 'TBD', 1, NOW()); INSERT INTO User (Name, EmailAddress, SecurityID, Active, DateActivated) VALUES ('Karan Pratap Singh', 'singhk@si.edu', 'TBD', 1, NOW()); +INSERT INTO User (Name, EmailAddress, SecurityID, Active, DateActivated) VALUES ('Test User', 'user@test.com', 'TBD', 1, NOW()); INSERT INTO UnitEdan (idUnit, Abbreviation) VALUES (1, '3D_YT'); INSERT INTO UnitEdan (idUnit, Abbreviation) VALUES (4, 'AAA'); From 5fc1dc9eb28121bf7c2251d53a8f9df5e55dd582 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Mon, 18 Jan 2021 15:45:05 +0530 Subject: [PATCH 199/239] integrate volta for ease of node version management --- README.md | 12 ++++++++++++ client/package.json | 4 ++++ common/package.json | 4 ++++ e2e/package.json | 4 ++++ package.json | 4 ++++ server/package.json | 4 ++++ 6 files changed, 32 insertions(+) diff --git a/README.md b/README.md index da492c119..d6995b182 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,18 @@ Data Repository and Workflow Management for 3D data captures, models, and scenes ### Development: +#### Prerequisites: +It is recommended to install [Volta](https://volta.sh/) which keeps node version in check. The versions can be specified in `package.json` and when switched to the directory of the project, volta automatically switches to the correct node version. + +``` +cd ~ +curl https://get.volta.sh | bash +volta install node +volta install yarn +``` +Now when you switch to the `dpo-packrat` repo, your node version would automatically pinned to the correct version by volta. + + 1. Install the dependencies: ``` diff --git a/client/package.json b/client/package.json index 5eb2d0785..6edb40edf 100644 --- a/client/package.json +++ b/client/package.json @@ -91,5 +91,9 @@ "last 1 firefox version", "last 1 safari version" ] + }, + "volta": { + "node": "12.18.4", + "yarn": "1.22.5" } } diff --git a/common/package.json b/common/package.json index 01a5c468c..158a82a43 100644 --- a/common/package.json +++ b/common/package.json @@ -33,5 +33,9 @@ "clean": "rm -rf node_modules/ build/", "postinstall": "echo \"postinstall common\"", "test": "jest --passWithNoTests" + }, + "volta": { + "node": "12.18.4", + "yarn": "1.22.5" } } \ No newline at end of file diff --git a/e2e/package.json b/e2e/package.json index fd4ead584..db7b129f4 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -29,5 +29,9 @@ "jest": "26.6.0", "playwright": "1.5.1", "ts-jest": "26.4.1" + }, + "volta": { + "node": "12.18.4", + "yarn": "1.22.5" } } diff --git a/package.json b/package.json index d483ce1f6..95eca67fe 100644 --- a/package.json +++ b/package.json @@ -79,5 +79,9 @@ "server", "common" ] + }, + "volta": { + "node": "12.18.4", + "yarn": "1.22.5" } } diff --git a/server/package.json b/server/package.json index bdd41d443..2acf802ee 100644 --- a/server/package.json +++ b/server/package.json @@ -80,5 +80,9 @@ "@types/node-fetch": "2.5.7", "jest": "24.9.0", "ts-jest": "24.3.0" + }, + "volta": { + "node": "12.18.4", + "yarn": "1.22.5" } } From 96601165cfa999853a530e8724ea96341fe737a8 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Mon, 18 Jan 2021 16:34:49 +0530 Subject: [PATCH 200/239] added dev banner to client --- client/src/components/index.ts | 4 +- client/src/components/shared/EnvBanner.tsx | 46 ++++++++++++++++++++++ client/src/index.tsx | 3 +- 3 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 client/src/components/shared/EnvBanner.tsx diff --git a/client/src/components/index.ts b/client/src/components/index.ts index e25226743..4cdb7c981 100644 --- a/client/src/components/index.ts +++ b/client/src/components/index.ts @@ -19,6 +19,7 @@ import DateInputField from './controls/DateInputField'; import BreadcrumbsView from './shared/BreadcrumbsView'; import NewTabLink from './shared/NewTabLink'; import EmptyTable from './shared/EmptyTable'; +import EnvBanner from './shared/EnvBanner'; import DebounceNumberInput from './controls/DebounceNumberInput'; import CheckboxField from './controls/CheckboxField'; @@ -40,6 +41,7 @@ export { BreadcrumbsView, NewTabLink, EmptyTable, + EnvBanner, DebounceNumberInput, - CheckboxField + CheckboxField, }; diff --git a/client/src/components/shared/EnvBanner.tsx b/client/src/components/shared/EnvBanner.tsx new file mode 100644 index 000000000..b62f21935 --- /dev/null +++ b/client/src/components/shared/EnvBanner.tsx @@ -0,0 +1,46 @@ +/** + * EnvBanner + * + * This component renders banner for the environment the app is running. + */ +import { Box, Typography } from '@material-ui/core'; +import { makeStyles } from '@material-ui/core/styles'; +import React from 'react'; + +const useStyles = makeStyles(({ palette, typography }) => ({ + container: { + display: 'flex', + position: 'absolute', + zIndex: 100, + backgroundColor: palette.primary.main, + padding: 10, + right: 0, + bottom: 0, + borderTopLeftRadius: 5, + color: palette.background.paper + }, + content: { + fontWeight: typography.fontWeightMedium + } +})); + +interface EnvBannerProps { + renderFor: string[]; +} + +function EnvBanner(props: EnvBannerProps): React.ReactElement | null { + const { renderFor } = props; + const classes = useStyles(); + + const env = process.env.NODE_ENV; + + if (!renderFor.includes(env)) return null; + + return ( + + {env.toUpperCase()} + + ); +} + +export default EnvBanner; diff --git a/client/src/index.tsx b/client/src/index.tsx index 82b8e623d..2b082895a 100644 --- a/client/src/index.tsx +++ b/client/src/index.tsx @@ -13,7 +13,7 @@ import { Helmet } from 'react-helmet'; import { BrowserRouter as Router, Switch } from 'react-router-dom'; import { Slide, toast, ToastContainer } from 'react-toastify'; import 'react-toastify/dist/ReactToastify.css'; -import { ErrorBoundary, Loader, PrivateRoute, PublicRoute } from './components'; +import { EnvBanner, ErrorBoundary, Loader, PrivateRoute, PublicRoute } from './components'; import { ROUTES } from './constants'; import './global/root.css'; import { apolloClient } from './graphql'; @@ -79,6 +79,7 @@ function App(): React.ReactElement { draggable pauseOnHover /> + ); From 54f8b23c22af2d5998142b25bc5560a2b78d12ea Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Tue, 19 Jan 2021 21:18:42 +0530 Subject: [PATCH 201/239] minor: props update --- client/src/components/shared/PrivateRoute.tsx | 6 +++--- client/src/components/shared/PublicRoute.tsx | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/client/src/components/shared/PrivateRoute.tsx b/client/src/components/shared/PrivateRoute.tsx index 96c034398..64157a7ae 100644 --- a/client/src/components/shared/PrivateRoute.tsx +++ b/client/src/components/shared/PrivateRoute.tsx @@ -8,12 +8,12 @@ import { Redirect, Route, RouteProps } from 'react-router-dom'; import { ROUTES } from '../../constants'; import { useUserStore } from '../../store'; -interface PrivateRouteProps { +interface PrivateRouteProps extends RouteProps { component?: React.ComponentType; - children?: unknown; + children?: React.ReactNode; } -function PrivateRoute({ component: Component, children, ...rest }: PrivateRouteProps & RouteProps): React.ReactElement { +function PrivateRoute({ component: Component, children, ...rest }: PrivateRouteProps): React.ReactElement { const user = useUserStore(state => state.user); const render = props => { diff --git a/client/src/components/shared/PublicRoute.tsx b/client/src/components/shared/PublicRoute.tsx index b526db2cf..4e4ca73a8 100644 --- a/client/src/components/shared/PublicRoute.tsx +++ b/client/src/components/shared/PublicRoute.tsx @@ -8,12 +8,12 @@ import { Redirect, Route, RouteProps } from 'react-router-dom'; import { ROUTES } from '../../constants'; import { useUserStore } from '../../store'; -interface PublicRouteProps { +interface PublicRouteProps extends RouteProps { restricted?: boolean; component: React.ComponentType; } -function PublicRoute({ component: Component, restricted = false, ...rest }: PublicRouteProps & RouteProps): React.ReactElement { +function PublicRoute({ component: Component, restricted = false, ...rest }: PublicRouteProps): React.ReactElement { const user = useUserStore(state => state.user); const render = props => ( From 213ab85f1745b4f920359de4dd7127fc04f2e876 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Tue, 19 Jan 2021 22:47:50 +0530 Subject: [PATCH 202/239] fix deploy script --- scripts/deploy.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/deploy.sh b/scripts/deploy.sh index 44bb8e7d3..92247e9b7 100644 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -16,7 +16,7 @@ function branch_check() { DESIRED_BRANCH=$2 ENV=$3 - if [ $CURRENT_BRANCH != DESIRED_BRANCH ] + if [ $CURRENT_BRANCH != $DESIRED_BRANCH ] then echo "Cannot deploy branch $CURRENT_BRANCH to $3 environment. Make sure you're on $DESIRED_BRANCH branch" exit 1 From 7122b3f2286a63d98099849ec0aa930b87223e8b Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Sat, 23 Jan 2021 03:39:38 +0530 Subject: [PATCH 203/239] align external link content --- .../RepositoryTreeView/MetadataView.tsx | 4 ++- .../RepositoryTreeView/TreeLabel.tsx | 26 ++++++++++++++----- server/cache/VocabularyCache.ts | 6 ++++- 3 files changed, 27 insertions(+), 9 deletions(-) diff --git a/client/src/pages/Repository/components/RepositoryTreeView/MetadataView.tsx b/client/src/pages/Repository/components/RepositoryTreeView/MetadataView.tsx index 9fe9c944d..c91429c95 100644 --- a/client/src/pages/Repository/components/RepositoryTreeView/MetadataView.tsx +++ b/client/src/pages/Repository/components/RepositoryTreeView/MetadataView.tsx @@ -43,10 +43,11 @@ const useStyles = makeStyles(({ palette, typography, breakpoints }) => ({ interface MetadataViewProps { header: boolean; treeColumns: TreeViewColumn[]; + options?: React.ReactNode; } function MetadataView(props: MetadataViewProps): React.ReactElement { - const { header, treeColumns } = props; + const { header, treeColumns, options = null } = props; const classes = useStyles(props); const width = computeMetadataViewWidth(treeColumns); @@ -67,6 +68,7 @@ function MetadataView(props: MetadataViewProps): React.ReactElement { return (
+ {options} {renderTreeColumns(treeColumns)}
); diff --git a/client/src/pages/Repository/components/RepositoryTreeView/TreeLabel.tsx b/client/src/pages/Repository/components/RepositoryTreeView/TreeLabel.tsx index 91a3c63f5..8f6dbfa50 100644 --- a/client/src/pages/Repository/components/RepositoryTreeView/TreeLabel.tsx +++ b/client/src/pages/Repository/components/RepositoryTreeView/TreeLabel.tsx @@ -22,7 +22,7 @@ const useStyles = makeStyles(({ breakpoints }) => ({ }, label: { display: 'flex', - flex: 1, + flex: 0.9, alignItems: 'center', position: 'sticky', left: 45, @@ -36,14 +36,22 @@ const useStyles = makeStyles(({ breakpoints }) => ({ justifyContent: 'space-between', width: '60%', fontSize: '0.8em', - background: ({ color }: TreeLabelProps) => color, + backgroundColor: ({ color }: TreeLabelProps) => color, zIndex: 10, [breakpoints.down('lg')]: { fontSize: '0.9em', } }, options: { - display: 'flex' + display: 'flex', + alignItems: 'center', + position: 'sticky', + left: 0, + width: 120 + }, + option: { + display: 'flex', + alignItems: 'center' } })); @@ -75,12 +83,16 @@ function TreeLabel(props: TreeLabelProps): React.ReactElement { )}
{label} - - -
- + + + + + + } + /> ); } diff --git a/server/cache/VocabularyCache.ts b/server/cache/VocabularyCache.ts index 52200eb9d..21d41d910 100644 --- a/server/cache/VocabularyCache.ts +++ b/server/cache/VocabularyCache.ts @@ -2,7 +2,11 @@ import * as LOG from '../utils/logger'; import { CacheControl } from './CacheControl'; import { Vocabulary, VocabularySet } from '../db'; -/** enum used to provide declarative, programmatic access to sorted vocabulary for system-generated vocabulary sets */ +/** + * enum used to provide declarative, programmatic access to sorted vocabulary for system-generated vocabulary sets + * + * Note: these types are also used at client/src/types/server.ts, make sure to update the enum's there as well + * */ export enum eVocabularySetID { eCaptureDataCaptureMethod, eCaptureDataDatasetType, From aef52b47fdb2f361de272ed7d55ad44d53fe4a12 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Wed, 27 Jan 2021 16:53:47 +0530 Subject: [PATCH 204/239] added failed to fetch user condition --- client/src/store/user.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/client/src/store/user.ts b/client/src/store/user.ts index cfa05455d..4e41880f7 100644 --- a/client/src/store/user.ts +++ b/client/src/store/user.ts @@ -39,7 +39,14 @@ export const useUserStore = create((set: SetState, get: Ge }; } - const user = await getAuthenticatedUser(); + const user: User | null = await getAuthenticatedUser(); + + if (!user) { + return { + success: false, + message: 'Failed to fetch user info' + }; + } set({ user }); return authResponse; From 2630e6841b4a8601bf75fb2b18868f9065dd4a3a Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Wed, 27 Jan 2021 17:28:59 +0530 Subject: [PATCH 205/239] fix description is required issue --- client/src/store/metadata/metadata.defaults.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/store/metadata/metadata.defaults.ts b/client/src/store/metadata/metadata.defaults.ts index e89c689e2..d586dffd9 100644 --- a/client/src/store/metadata/metadata.defaults.ts +++ b/client/src/store/metadata/metadata.defaults.ts @@ -75,7 +75,7 @@ export const photogrammetryFieldsSchema = yup.object().shape({ clusterType: yup.number().nullable(true), clusterGeometryFieldId: yup.number().nullable(true), cameraSettingUniform: yup.boolean().required(), - directory: yup.string().required() + directory: yup.string() }); const uvMapSchema = yup.object().shape({ From 00fedc602648fb5a0c2b50d5d34a43b1eeb24967 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Wed, 27 Jan 2021 17:29:11 +0530 Subject: [PATCH 206/239] fix bounding box always in updated state --- .../components/Metadata/Model/BoundingBoxInput.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/client/src/pages/Ingestion/components/Metadata/Model/BoundingBoxInput.tsx b/client/src/pages/Ingestion/components/Metadata/Model/BoundingBoxInput.tsx index c0e87d7e7..4a4758aa0 100644 --- a/client/src/pages/Ingestion/components/Metadata/Model/BoundingBoxInput.tsx +++ b/client/src/pages/Ingestion/components/Metadata/Model/BoundingBoxInput.tsx @@ -47,21 +47,21 @@ function BoundingBoxInput(props: BoundingBoxInputProps): React.ReactElement { Date: Wed, 27 Jan 2021 21:35:09 +0530 Subject: [PATCH 207/239] fix yarn version --- client/package.json | 2 +- common/package.json | 2 +- e2e/package.json | 2 +- server/package.json | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/client/package.json b/client/package.json index 6edb40edf..edbce17f8 100644 --- a/client/package.json +++ b/client/package.json @@ -94,6 +94,6 @@ }, "volta": { "node": "12.18.4", - "yarn": "1.22.5" + "yarn": "1.22.4" } } diff --git a/common/package.json b/common/package.json index 158a82a43..2281a5e8e 100644 --- a/common/package.json +++ b/common/package.json @@ -36,6 +36,6 @@ }, "volta": { "node": "12.18.4", - "yarn": "1.22.5" + "yarn": "1.22.4" } } \ No newline at end of file diff --git a/e2e/package.json b/e2e/package.json index db7b129f4..d32808bd5 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -32,6 +32,6 @@ }, "volta": { "node": "12.18.4", - "yarn": "1.22.5" + "yarn": "1.22.4" } } diff --git a/server/package.json b/server/package.json index 2acf802ee..799c2ea0e 100644 --- a/server/package.json +++ b/server/package.json @@ -83,6 +83,6 @@ }, "volta": { "node": "12.18.4", - "yarn": "1.22.5" + "yarn": "1.22.4" } } From 9414d28c2c80d0dace6775205d0a98f24948f5b9 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Wed, 27 Jan 2021 21:35:47 +0530 Subject: [PATCH 208/239] devbox approach for development --- README.md | 18 ++++++++++++++++++ package.json | 7 ++++++- scripts/devbox/db.sh | 14 ++++++++++++++ scripts/devbox/network.sh | 9 +++++++++ scripts/devbox/up.sh | 6 ++++++ 5 files changed, 53 insertions(+), 1 deletion(-) create mode 100755 scripts/devbox/db.sh create mode 100755 scripts/devbox/network.sh create mode 100755 scripts/devbox/up.sh diff --git a/README.md b/README.md index d6995b182..84ad2abba 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,24 @@ yarn start:server yarn start:common ``` +# Alternative docker workflow: + +``` +# Creates Devbox for packrat +yarn devbox:up +# Creates DB for devbox +yarn devbox:db +# Create and Connects db and devbox to the same network +yarn devbox:network +# Drops you into shell inside the image +yarn devbox:start +``` + +*Note: if you get permission denied during the execution make sure to give it permission using:* +``` +chmod 777 ./scripts/devbox/*.sh +``` + # Deployment instructions: *Note: Make sure before you execute any script, you're root of the repository `dpo-packrat` and if you get permission denied for any script, make sure to do `chmod 777 path/to/script`. If you encounter any error then make sure to checkout Packrat server setup instruction on confluence* diff --git a/package.json b/package.json index 95eca67fe..753f380e1 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,11 @@ "log:server": "docker logs -f packrat-server", "start:client:prod": "yarn workspace @dpo-packrat/client start:prod", "start:server:prod": "yarn workspace @dpo-packrat/server start:prod", + "devbox:up": "./scripts/devbox/up.sh", + "devbox:db": "./scripts/devbox/db.sh", + "devbox:network": "./scripts/devbox/network.sh", + "devbox:start": "docker exec -it packrat-devbox bash", + "devbox:down": "docker container rm packrat-devbox --force", "dev": "docker-compose --env-file .env.dev -f docker-compose.dev.yml up -d", "prod": "docker-compose --env-file .env.prod -f docker-compose.prod.yml up -d", "deploy:dev": "sh ./scripts/deploy.sh dev", @@ -82,6 +87,6 @@ }, "volta": { "node": "12.18.4", - "yarn": "1.22.5" + "yarn": "1.22.4" } } diff --git a/scripts/devbox/db.sh b/scripts/devbox/db.sh new file mode 100755 index 000000000..dee90d474 --- /dev/null +++ b/scripts/devbox/db.sh @@ -0,0 +1,14 @@ +# Creates and seeds the packrat db + +IMAGE=mariadb:10.5 +PACKRAT_WORKDIR=/app + +docker run --name packrat-db -p 3306:3306 -v ${PWD}/server/db/sql:${PACKRAT_WORKDIR} --env-file ./.env.dev -itd $IMAGE + +SQL_PASSWORD=$(grep MYSQL_ROOT_PASSWORD .env.dev | cut -d '=' -f2) + +docker exec -i packrat-db sh -c "mysql -u root -p$SQL_PASSWORD < /app/scripts/Packrat.SCHEMA.sql" +docker exec -i packrat-db sh -c "mysql -u root -p$SQL_PASSWORD < /app/scripts/Packrat.PROC.sql" +docker exec -i packrat-db sh -c "mysql -u root -p$SQL_PASSWORD < /app/scripts/Packrat.DATA.sql" + +echo "Successful" \ No newline at end of file diff --git a/scripts/devbox/network.sh b/scripts/devbox/network.sh new file mode 100755 index 000000000..ac5b05689 --- /dev/null +++ b/scripts/devbox/network.sh @@ -0,0 +1,9 @@ +# Creates a docker network and connect devbox and db + +NETWORK_NAME=packrat-devbox-network + +docker network create $NETWORK_NAME +docker network connect $NETWORK_NAME packrat-devbox +docker network connect $NETWORK_NAME packrat-db + +echo "Successful" \ No newline at end of file diff --git a/scripts/devbox/up.sh b/scripts/devbox/up.sh new file mode 100755 index 000000000..84ae8c7c2 --- /dev/null +++ b/scripts/devbox/up.sh @@ -0,0 +1,6 @@ +# Devbox for Packrat + +IMAGE=node:12.18.4 +PACKRAT_WORKDIR=/app +# Run node docker image and map port 3000, 4000 for access to client and server +docker run --name packrat-devbox -p 3000:3000 -p 4000:4000 -v ${PWD}:${PACKRAT_WORKDIR} --env-file ./.env.dev -itd $IMAGE From 9c78005457ab31ef273af84073b17a3d00b9eb8f Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Wed, 27 Jan 2021 21:36:41 +0530 Subject: [PATCH 209/239] switch to event based upload (fixes multi upload issue) --- .../Ingestion/components/Uploads/index.tsx | 57 ++++++++++-- client/src/store/upload.ts | 90 +++++++++++++------ client/src/utils/events.ts | 49 ++++++++++ 3 files changed, 163 insertions(+), 33 deletions(-) create mode 100644 client/src/utils/events.ts diff --git a/client/src/pages/Ingestion/components/Uploads/index.tsx b/client/src/pages/Ingestion/components/Uploads/index.tsx index 31caead4c..88e7ac9ca 100644 --- a/client/src/pages/Ingestion/components/Uploads/index.tsx +++ b/client/src/pages/Ingestion/components/Uploads/index.tsx @@ -6,7 +6,7 @@ */ import { Box } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; -import React, { useState } from 'react'; +import React, { useEffect, useState } from 'react'; import KeepAlive from 'react-activation'; import { useHistory } from 'react-router'; import { toast } from 'react-toastify'; @@ -14,6 +14,7 @@ import { SidebarBottomNavigator } from '../../../../components'; import { HOME_ROUTES, INGESTION_ROUTE, resolveSubRoute } from '../../../../constants'; import { useMetadataStore, useUploadStore } from '../../../../store'; import { Colors } from '../../../../theme'; +import { UploadCompleteEvent, UploadEvents, UploadEventType, UploadFailedEvent, UploadProgressEvent, UploadSetCancelEvent } from '../../../../utils/events'; import UploadCompleteList from './UploadCompleteList'; import UploadFilesPicker from './UploadFilesPicker'; import UploadList from './UploadList'; @@ -65,7 +66,7 @@ function Uploads(): React.ReactElement { const history = useHistory(); const [gettingAssetDetails, setGettingAssetDetails] = useState(false); const [discardingFiles, setDiscardingFiles] = useState(false); - const [completed, discardFiles] = useUploadStore(state => [state.completed,state.discardFiles]); + const [completed, discardFiles] = useUploadStore(state => [state.completed, state.discardFiles]); const updateMetadataSteps = useMetadataStore(state => state.updateMetadataSteps); const onIngest = async (): Promise => { @@ -110,11 +111,7 @@ function Uploads(): React.ReactElement { - - - - - + [state.onProgressEvent, state.onSetCancelledEvent, state.onFailedEvent, state.onCompleteEvent]); + + useEffect(() => { + const onProgress = data => { + const eventData: UploadProgressEvent = data.detail; + onProgressEvent(eventData); + }; + + UploadEvents.subscribe(UploadEventType.PROGRESS, onProgress); + + const onSetCancelled = data => { + const eventData: UploadSetCancelEvent = data.detail; + onSetCancelledEvent(eventData); + }; + + UploadEvents.subscribe(UploadEventType.SET_CANCELLED, onSetCancelled); + + const onFailed = data => { + const eventData: UploadFailedEvent = data.detail; + onFailedEvent(eventData); + }; + + UploadEvents.subscribe(UploadEventType.FAILED, onFailed); + + const onComplete = data => { + const eventData: UploadCompleteEvent = data.detail; + onCompleteEvent(eventData); + }; + + UploadEvents.subscribe(UploadEventType.COMPLETE, onComplete); + + return () => { + console.log('Thread closed'); + }; + }, []); + + return ( + + + + + + ); +} + export default Uploads; \ No newline at end of file diff --git a/client/src/store/upload.ts b/client/src/store/upload.ts index 5cf059522..b8dc9b2c7 100644 --- a/client/src/store/upload.ts +++ b/client/src/store/upload.ts @@ -13,6 +13,7 @@ import { apolloClient, apolloUploader } from '../graphql'; import { DiscardUploadedAssetVersionsDocument, DiscardUploadedAssetVersionsMutation, UploadAssetDocument, UploadAssetMutation, UploadStatus } from '../types/graphql'; import { FetchResult } from '@apollo/client'; import { parseFileId } from './utils'; +import { UploadEvents, UploadEventType, UploadCompleteEvent, UploadProgressEvent, UploadSetCancelEvent, UploadFailedEvent } from '../utils/events'; export type FileId = string; @@ -52,6 +53,10 @@ type UploadStore = { changeAssetType: (id: FileId, assetType: number) => void; discardFiles: () => Promise; removeSelectedUploads: () => void; + onProgressEvent: (data: UploadProgressEvent) => void; + onSetCancelledEvent: (data: UploadSetCancelEvent) => void; + onFailedEvent: (data: UploadFailedEvent) => void; + onCompleteEvent: (data: UploadCompleteEvent) => void; reset: () => void; }; @@ -180,22 +185,20 @@ export const useUploadStore = create((set: SetState, g const updateProgress = !(progress % 5); if (updateProgress) { - const updatedPendingProgress = lodash.forEach(pending, file => { - if (file.id === id) { - lodash.set(file, 'progress', progress); - } - }); - set({ pending: updatedPendingProgress }); + const progressEvent: UploadProgressEvent = { + id, + progress + }; + UploadEvents.dispatch(UploadEventType.PROGRESS, progressEvent); } }; const onCancel = (cancel: () => void) => { - const updatedPendingProgress = lodash.forEach(pending, file => { - if (file.id === id) { - lodash.set(file, 'cancel', cancel); - } - }); - set({ pending: updatedPendingProgress }); + const setCancelEvent: UploadSetCancelEvent = { + id, + cancel + }; + UploadEvents.dispatch(UploadEventType.SET_CANCELLED, setCancelEvent); }; const { data } = await apolloUploader({ @@ -213,18 +216,16 @@ export const useUploadStore = create((set: SetState, g const { status, error } = uploadAsset; if (status === UploadStatus.Complete) { - const updatedPending = pending.filter(file => file.id !== id); - set({ pending: updatedPending }); + const uploadEvent: UploadCompleteEvent = { id }; + UploadEvents.dispatch(UploadEventType.COMPLETE, uploadEvent); + toast.success(`Upload finished for ${file.name}`); } else if (status === UploadStatus.Failed) { + const failedEvent: UploadFailedEvent = { id }; + UploadEvents.dispatch(UploadEventType.FAILED, failedEvent); + const errorMessage = error || `Upload failed for ${file.name}`; toast.error(errorMessage); - const updatedPending = lodash.forEach(pending, file => { - if (file.id === id) { - lodash.set(file, 'status', FileUploadStatus.FAILED); - } - }); - set({ pending: updatedPending }); } } } catch ({ message }) { @@ -232,12 +233,8 @@ export const useUploadStore = create((set: SetState, g if (file) { if (file.status !== FileUploadStatus.CANCELLED) { - const updatedPending = lodash.forEach(pending, file => { - if (file.id === id) { - lodash.set(file, 'status', FileUploadStatus.FAILED); - } - }); - set({ pending: updatedPending }); + const failedEvent: UploadFailedEvent = { id }; + UploadEvents.dispatch(UploadEventType.FAILED, failedEvent); } } } @@ -302,6 +299,47 @@ export const useUploadStore = create((set: SetState, g const updatedCompleted = completed.filter(({ selected }) => !selected); set({ completed: updatedCompleted }); }, + onProgressEvent: (eventData: UploadProgressEvent): void => { + const { pending } = get(); + const { id, progress } = eventData; + + const updatedPendingProgress = lodash.forEach(pending, file => { + if (file.id === id) { + lodash.set(file, 'progress', progress); + } + }); + set({ pending: updatedPendingProgress }); + }, + onSetCancelledEvent: (eventData: UploadSetCancelEvent): void => { + const { pending } = get(); + const { id, cancel } = eventData; + + const updateSetCancel = lodash.forEach(pending, file => { + if (file.id === id) { + lodash.set(file, 'cancel', cancel); + } + }); + set({ pending: updateSetCancel }); + }, + onFailedEvent: (eventData: UploadFailedEvent): void => { + const { pending } = get(); + const { id } = eventData; + + const updatedFailedPending = lodash.forEach(pending, file => { + if (file.id === id) { + lodash.set(file, 'status', FileUploadStatus.FAILED); + } + }); + + set({ pending: updatedFailedPending }); + }, + onCompleteEvent: (eventData: UploadCompleteEvent): void => { + const { pending } = get(); + const { id } = eventData; + const updatedComplete = pending.filter(file => file.id !== id); + + set({ pending: updatedComplete }); + }, reset: (): void => { const { completed } = get(); const unselectFiles = (file: IngestionFile): IngestionFile => ({ diff --git a/client/src/utils/events.ts b/client/src/utils/events.ts new file mode 100644 index 000000000..59bdb8ac1 --- /dev/null +++ b/client/src/utils/events.ts @@ -0,0 +1,49 @@ +import { FileId } from '../store'; + +export enum UploadEventType { + PROGRESS = 'PROGRESS', + SET_CANCELLED = 'SET_CANCELLED', + FAILED = 'FAILED', + COMPLETE = 'COMPLETE', +} + +export type UploadProgressEvent = { + id: FileId; + progress: number; +}; + +export type UploadSetCancelEvent = { + id: FileId; + cancel: () => void; +}; + +export type UploadFailedEvent = { + id: FileId; +}; + +export type UploadCompleteEvent = { + id: FileId; +}; + + +export type UploadEventData = UploadProgressEvent | UploadFailedEvent; + +class UploadEvents { + static subscribe(event: UploadEventType, listener: EventListenerOrEventListenerObject): void { + window.addEventListener(event, listener); + } + + static dispatch(event: UploadEventType, data: UploadEventData): void { + const customEvent = new CustomEvent(event, { + detail: data + }); + + window.dispatchEvent(customEvent); + } + + static unsubscribe(event: UploadEventType, listener: EventListenerOrEventListenerObject): void { + window.removeEventListener(event, listener); + } +} + +export { UploadEvents }; From 447a2c7c38e99fa6476dc9914ff7c7c0b70a1626 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Sat, 30 Jan 2021 13:36:29 +0530 Subject: [PATCH 210/239] script update --- scripts/devbox/db.sh | 2 +- scripts/devbox/network.sh | 2 +- scripts/devbox/up.sh | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/scripts/devbox/db.sh b/scripts/devbox/db.sh index dee90d474..32e9f6cbc 100755 --- a/scripts/devbox/db.sh +++ b/scripts/devbox/db.sh @@ -11,4 +11,4 @@ docker exec -i packrat-db sh -c "mysql -u root -p$SQL_PASSWORD < /app/scripts/Pa docker exec -i packrat-db sh -c "mysql -u root -p$SQL_PASSWORD < /app/scripts/Packrat.PROC.sql" docker exec -i packrat-db sh -c "mysql -u root -p$SQL_PASSWORD < /app/scripts/Packrat.DATA.sql" -echo "Successful" \ No newline at end of file +echo "Done" \ No newline at end of file diff --git a/scripts/devbox/network.sh b/scripts/devbox/network.sh index ac5b05689..2667e9b8d 100755 --- a/scripts/devbox/network.sh +++ b/scripts/devbox/network.sh @@ -6,4 +6,4 @@ docker network create $NETWORK_NAME docker network connect $NETWORK_NAME packrat-devbox docker network connect $NETWORK_NAME packrat-db -echo "Successful" \ No newline at end of file +echo "Done" \ No newline at end of file diff --git a/scripts/devbox/up.sh b/scripts/devbox/up.sh index 84ae8c7c2..a48180165 100755 --- a/scripts/devbox/up.sh +++ b/scripts/devbox/up.sh @@ -4,3 +4,5 @@ IMAGE=node:12.18.4 PACKRAT_WORKDIR=/app # Run node docker image and map port 3000, 4000 for access to client and server docker run --name packrat-devbox -p 3000:3000 -p 4000:4000 -v ${PWD}:${PACKRAT_WORKDIR} --env-file ./.env.dev -itd $IMAGE + +echo "Done" \ No newline at end of file From 015892c3263f7332a10aecada155dfb7fdb65c47 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Sat, 30 Jan 2021 13:56:35 +0530 Subject: [PATCH 211/239] removed "common" package --- README.md | 10 +--------- client/package.json | 1 - common/index.ts | 7 ------- common/package.json | 41 ----------------------------------------- common/tsconfig.json | 14 -------------- docker-compose.dev.yml | 2 -- lerna.json | 3 +-- package.json | 7 +++---- server/package.json | 1 - 9 files changed, 5 insertions(+), 81 deletions(-) delete mode 100644 common/index.ts delete mode 100644 common/package.json delete mode 100644 common/tsconfig.json diff --git a/README.md b/README.md index 84ad2abba..a820f9c06 100644 --- a/README.md +++ b/README.md @@ -35,9 +35,7 @@ yarn dev 4. If you want to follow debug logs for `client` or `server` container then just run `yarn log:client` or `yarn log:server` -7. If you're developing `common` package then make sure to use `yarn start:common` so that it's actively watched/compiled and made available to other packages it's imported at. The other packages should auto reload when changes are made to them. - -6. If not using docker run each command in a separate terminal for the package you're developing: +5. If not using docker run each command in a separate terminal for the package you're developing: **For client:** @@ -51,12 +49,6 @@ yarn start:client yarn start:server ``` -**For common:** - -``` -yarn start:common -``` - # Alternative docker workflow: ``` diff --git a/client/package.json b/client/package.json index edbce17f8..71ad07bb2 100644 --- a/client/package.json +++ b/client/package.json @@ -24,7 +24,6 @@ "dependencies": { "@apollo/client": "3.1.5", "@date-io/date-fns": "1.3.13", - "@dpo-packrat/common": "0.4.0", "@material-ui/core": "4.11.0", "@material-ui/icons": "4.9.1", "@material-ui/lab": "4.0.0-alpha.56", diff --git a/common/index.ts b/common/index.ts deleted file mode 100644 index 1107f6aa4..000000000 --- a/common/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -/** - * DPO-PACKRAT/Common - * - * Entry file for common files - */ - -console.log('Common started'); diff --git a/common/package.json b/common/package.json deleted file mode 100644 index 2281a5e8e..000000000 --- a/common/package.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "name": "@dpo-packrat/common", - "version": "0.4.0", - "private": true, - "license": "Apache-2.0", - "description": "Common files for Packrat", - "homepage": "https://github.com/Smithsonian/dpo-packrat#readme", - "repository": { - "type": "git", - "url": "git+https://github.com/Smithsonian/dpo-packrat.git" - }, - "bugs": { - "url": "https://github.com/Smithsonian/dpo-packrat/issues" - }, - "contributors": [ - { - "name": "Jon Tyson", - "url": "https://github.com/jahjedtieson" - }, - { - "name": "Karan Pratap Singh", - "url": "https://github.com/karanpratapsingh" - } - ], - "main": "build/index.js", - "typings": "build/index.d.ts", - "scripts": { - "start": "yarn build:dev && concurrently 'yarn build:watch' 'yarn server:start'", - "server:start": "nodemon -e '*.ts' --watch build 'node build/index.js'", - "build:dev": "tsc --build", - "build:prod": "tsc --build", - "build:watch": "tsc --watch", - "clean": "rm -rf node_modules/ build/", - "postinstall": "echo \"postinstall common\"", - "test": "jest --passWithNoTests" - }, - "volta": { - "node": "12.18.4", - "yarn": "1.22.4" - } -} \ No newline at end of file diff --git a/common/tsconfig.json b/common/tsconfig.json deleted file mode 100644 index e91063baa..000000000 --- a/common/tsconfig.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "extends": "../tsconfig.json", - "compilerOptions": { - "target": "es6", - "module": "commonjs", - "moduleResolution": "node", - "rootDir": ".", - "outDir": "build", - "declaration": true, - }, - "include": [ - "**/*" - ], -} \ No newline at end of file diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 620d4c843..955fa466f 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -34,7 +34,6 @@ services: - ./node_modules:/app/node_modules - ./package.json:/app/package.json - ./client:/app/client - - ./common:/app/common packrat-server: container_name: packrat-server @@ -60,7 +59,6 @@ services: - ./node_modules:/app/node_modules - ./package.json:/app/package.json - ./server:/app/server - - ./common:/app/common depends_on: - db diff --git a/lerna.json b/lerna.json index 80d27772b..125743586 100644 --- a/lerna.json +++ b/lerna.json @@ -2,8 +2,7 @@ "lerna": "3.22.0", "packages": [ "client", - "server", - "common" + "server" ], "version": "0.4.0", "npmClient": "yarn", diff --git a/package.json b/package.json index 753f380e1..a0335808d 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,6 @@ "scripts": { "start:client": "yarn workspace @dpo-packrat/client start", "start:server": "yarn workspace @dpo-packrat/server start", - "start:common": "yarn workspace @dpo-packrat/common start", "log:client": "docker logs -f packrat-client", "log:server": "docker logs -f packrat-server", "start:client:prod": "yarn workspace @dpo-packrat/client start:prod", @@ -44,7 +43,8 @@ "clean": "lerna run clean && rm -rf node_modules/", "clean:docker": "docker-compose -f docker-compose.dev.yml down && docker system prune -f", "generate:server:prisma": "yarn workspace @dpo-packrat/server generate:prisma", - "postinstall": "lerna run postinstall", + "postinstall": "lerna run postinstall && yarn install:e2e", + "install:e2e": "cd e2e && yarn && cd ..", "lint": "yarn eslint --ext .js,.jsx,.ts,.tsx .", "lint:fix": "yarn prettier --write . && yarn lint --fix", "test": "yarn workspaces run test", @@ -81,8 +81,7 @@ ], "packages": [ "client", - "server", - "common" + "server" ] }, "volta": { diff --git a/server/package.json b/server/package.json index 799c2ea0e..d0c7a8a52 100644 --- a/server/package.json +++ b/server/package.json @@ -42,7 +42,6 @@ "dropdb": "sh -c \"exec mysql -u root -p$MYSQL_ROOT_PASSWORD -e 'DROP DATABASE IF EXISTS `Packrat`'\"" }, "dependencies": { - "@dpo-packrat/common": "0.4.0", "@prisma/client": "2.4.1", "@types/express": "4.17.8", "@types/lodash": "4.14.161", From 03ece1fe1f0d4979259c77c9d5f3d021cc864af2 Mon Sep 17 00:00:00 2001 From: Karan Pratap Singh Date: Sat, 30 Jan 2021 14:08:38 +0530 Subject: [PATCH 212/239] fix repeated server restart issue with ts-node, update on graphql changes as well! --- server/package.json | 5 ++--- yarn.lock | 36 ++++++++++++++++++++++++++++++++++-- 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/server/package.json b/server/package.json index d0c7a8a52..ad8257c6e 100644 --- a/server/package.json +++ b/server/package.json @@ -25,12 +25,10 @@ "main": "build/index.js", "typings": "build/index.d.ts", "scripts": { - "start": "yarn build:dev && concurrently 'yarn build:watch' 'yarn server:start'", + "start": "nodemon --ignore var/ --exec yarn ts-node index.ts -e ts,js,json,graphql", "start:prod": "node build/index.js", - "server:start": "nodemon build/index.js --ignore var/", "build:dev": "tsc --build && copyfiles db/**/*.* graphql/**/**.graphql build/", "build:prod": "tsc --build && copyfiles db/**/*.* graphql/**/**.graphql build/", - "build:watch": "tsc --watch", "clean": "rm -rf node_modules/ build/", "generate": "graphql-codegen --config ./graphql/codegen.yml", "generate:prisma": "npx prisma generate --schema=./db/prisma/schema.prisma", @@ -65,6 +63,7 @@ "passport": "0.4.1", "passport-local": "1.0.0", "supertest": "4.0.2", + "ts-node": "9.1.1", "uuid": "8.3.0", "winston": "3.3.3" }, diff --git a/yarn.lock b/yarn.lock index 4bbdb66f4..83c740e1d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4365,6 +4365,11 @@ are-we-there-yet@~1.1.2: delegates "^1.0.0" readable-stream "^2.0.6" +arg@^4.1.0: + version "4.1.3" + resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" + integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== + argparse@^1.0.7: version "1.0.10" resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" @@ -6135,6 +6140,11 @@ create-react-context@^0.3.0: gud "^1.0.0" warning "^4.0.3" +create-require@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" + integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== + cross-fetch@3.0.5: version "3.0.5" resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.0.5.tgz#2739d2981892e7ab488a7ad03b92df2816e03f4c" @@ -6807,6 +6817,11 @@ diff-sequences@^25.2.6: resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-25.2.6.tgz#5f467c00edd35352b7bca46d7927d60e687a76dd" integrity sha512-Hq8o7+6GaZeoFjtpgvRBUknSXNeJiCx7V9Fr94ZMljNiCr9n9L8H8aJqgWOQiDDGdyn29fRNcDdRVJ5fdyihfg== +diff@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" + integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== + diffie-hellman@^5.0.0: version "5.0.3" resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875" @@ -11172,7 +11187,7 @@ make-dir@^3.0.0, make-dir@^3.0.2: dependencies: semver "^6.0.0" -make-error@1.x: +make-error@1.x, make-error@^1.1.1: version "1.3.6" resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== @@ -15260,7 +15275,7 @@ source-map-resolve@^0.6.0: atob "^2.1.2" decode-uri-component "^0.2.0" -source-map-support@^0.5.6, source-map-support@~0.5.12: +source-map-support@^0.5.17, source-map-support@^0.5.6, source-map-support@~0.5.12: version "0.5.19" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61" integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw== @@ -16157,6 +16172,18 @@ ts-log@^2.1.4: resolved "https://registry.yarnpkg.com/ts-log/-/ts-log-2.1.4.tgz#063c5ad1cbab5d49d258d18015963489fb6fb59a" integrity sha512-P1EJSoyV+N3bR/IWFeAqXzKPZwHpnLY6j7j58mAvewHRipo+BQM2Y1f9Y9BjEQznKwgqqZm7H8iuixmssU7tYQ== +ts-node@9.1.1: + version "9.1.1" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-9.1.1.tgz#51a9a450a3e959401bda5f004a72d54b936d376d" + integrity sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg== + dependencies: + arg "^4.1.0" + create-require "^1.1.0" + diff "^4.0.1" + make-error "^1.1.1" + source-map-support "^0.5.17" + yn "3.1.1" + ts-pnp@1.1.5: version "1.1.5" resolved "https://registry.yarnpkg.com/ts-pnp/-/ts-pnp-1.1.5.tgz#840e0739c89fce5f3abd9037bb091dbff16d9dec" @@ -17400,6 +17427,11 @@ yargs@^15.3.1, yargs@^15.4.1: y18n "^4.0.0" yargs-parser "^18.1.2" +yn@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" + integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== + yup@0.29.3: version "0.29.3" resolved "https://registry.yarnpkg.com/yup/-/yup-0.29.3.tgz#69a30fd3f1c19f5d9e31b1cf1c2b851ce8045fea" From 41abf27d7f175ae224c57b8df2d9982e2f5af7e5 Mon Sep 17 00:00:00 2001 From: Jon Tyson <6943745+jahjedtieson@users.noreply.github.com> Date: Sat, 30 Jan 2021 11:56:02 -0800 Subject: [PATCH 213/239] Apache Solr: WIP checkin as I move systems * Added SystemObjectCache convenience methods for looking up SystemObjects from "derived" objects * Solr Schema: added fields needed to support hierarchical filters / queries * Began implementation of ObjectGraphDatabase and ObjectGraphEntry, helper classes for extracting hierarchical relationships from the object graph * Added fetchAll() methods to SystemObject dervied classes --- server/cache/SystemObjectCache.ts | 54 ++++++++++++- .../config/solr/data/packrat/conf/schema.xml | 8 +- server/db/api/Actor.ts | 10 +++ server/db/api/Asset.ts | 10 +++ server/db/api/AssetVersion.ts | 10 +++ server/db/api/IntermediaryFile.ts | 10 +++ server/db/api/Model.ts | 10 +++ server/db/api/ProjectDocumentation.ts | 10 +++ server/db/api/Scene.ts | 10 +++ server/db/api/Stakeholder.ts | 10 +++ server/db/api/composite/ObjectGraph.ts | 18 ++++- .../db/api/composite/ObjectGraphDataEntry.ts | 19 +++++ .../db/api/composite/ObjectGraphDatabase.ts | 25 ++++++ server/db/index.ts | 2 + .../impl/NavigationSolr/ReindexSolr.ts | 4 +- server/tests/db/dbcreation.test.ts | 79 +++++++++++++++++++ server/tests/jest.config.js | 1 + 17 files changed, 285 insertions(+), 5 deletions(-) create mode 100644 server/db/api/composite/ObjectGraphDataEntry.ts create mode 100644 server/db/api/composite/ObjectGraphDatabase.ts diff --git a/server/cache/SystemObjectCache.ts b/server/cache/SystemObjectCache.ts index 0875bee01..5b7627e38 100644 --- a/server/cache/SystemObjectCache.ts +++ b/server/cache/SystemObjectCache.ts @@ -1,5 +1,5 @@ import * as LOG from '../utils/logger'; -// import * as DBAPI from '../db'; +import * as DBAPI from '../db'; import { CacheControl } from './CacheControl'; import { SystemObject, eSystemObjectType } from '../db'; @@ -129,6 +129,58 @@ export class SystemObjectCache { return await (await this.getInstance()).getSystemFromObjectIDInternal(oID); } + static async getSystemFromUnit(unit: DBAPI.Unit): Promise { + return await (await this.getInstance()).getSystemFromObjectIDInternal({ idObject: unit.idUnit, eObjectType: DBAPI.eSystemObjectType.eUnit }); + } + + static async getSystemFromProject(project: DBAPI.Project): Promise { + return await (await this.getInstance()).getSystemFromObjectIDInternal({ idObject: project.idProject, eObjectType: DBAPI.eSystemObjectType.eProject }); + } + + static async getSystemFromSubject(subject: DBAPI.Subject): Promise { + return await (await this.getInstance()).getSystemFromObjectIDInternal({ idObject: subject.idSubject, eObjectType: DBAPI.eSystemObjectType.eSubject }); + } + + static async getSystemFromItem(item: DBAPI.Item): Promise { + return await (await this.getInstance()).getSystemFromObjectIDInternal({ idObject: item.idItem, eObjectType: DBAPI.eSystemObjectType.eItem }); + } + + static async getSystemFromCaptureData(captureData: DBAPI.CaptureData): Promise { + return await (await this.getInstance()).getSystemFromObjectIDInternal({ idObject: captureData.idCaptureData, eObjectType: DBAPI.eSystemObjectType.eCaptureData }); + } + + static async getSystemFromModel(model: DBAPI.Model): Promise { + return await (await this.getInstance()).getSystemFromObjectIDInternal({ idObject: model.idModel, eObjectType: DBAPI.eSystemObjectType.eModel }); + } + + static async getSystemFromScene(scene: DBAPI.Scene): Promise { + return await (await this.getInstance()).getSystemFromObjectIDInternal({ idObject: scene.idScene, eObjectType: DBAPI.eSystemObjectType.eScene }); + } + + static async getSystemFromIntermediaryFile(intermediaryFile: DBAPI.IntermediaryFile): Promise { + return await (await this.getInstance()).getSystemFromObjectIDInternal({ idObject: intermediaryFile.idIntermediaryFile, eObjectType: DBAPI.eSystemObjectType.eIntermediaryFile }); + } + + static async getSystemFromProjectDocumentation(projectDocumentation: DBAPI.ProjectDocumentation): Promise { + return await (await this.getInstance()).getSystemFromObjectIDInternal({ idObject: projectDocumentation.idProjectDocumentation, eObjectType: DBAPI.eSystemObjectType.eProjectDocumentation }); + } + + static async getSystemFromAsset(asset: DBAPI.Asset): Promise { + return await (await this.getInstance()).getSystemFromObjectIDInternal({ idObject: asset.idAsset, eObjectType: DBAPI.eSystemObjectType.eAsset }); + } + + static async getSystemFromAssetVersion(assetVersion: DBAPI.AssetVersion): Promise { + return await (await this.getInstance()).getSystemFromObjectIDInternal({ idObject: assetVersion.idAssetVersion, eObjectType: DBAPI.eSystemObjectType.eAssetVersion }); + } + + static async getSystemFromActor(actor: DBAPI.Actor): Promise { + return await (await this.getInstance()).getSystemFromObjectIDInternal({ idObject: actor.idActor, eObjectType: DBAPI.eSystemObjectType.eActor }); + } + + static async getSystemFromStakeholder(stakeholder: DBAPI.Stakeholder): Promise { + return await (await this.getInstance()).getSystemFromObjectIDInternal({ idObject: stakeholder.idStakeholder, eObjectType: DBAPI.eSystemObjectType.eStakeholder }); + } + /** * Fetches object ID and object type for the specified SystemObject.idSystemObject * @param idSystemObject SystemObject.idSystemObject to query diff --git a/server/config/solr/data/packrat/conf/schema.xml b/server/config/solr/data/packrat/conf/schema.xml index bd520f9ea..10d14ec79 100644 --- a/server/config/solr/data/packrat/conf/schema.xml +++ b/server/config/solr/data/packrat/conf/schema.xml @@ -7,10 +7,10 @@ + - @@ -115,6 +115,12 @@ + + + + + + diff --git a/server/db/api/Actor.ts b/server/db/api/Actor.ts index 8c866374d..f9541e28e 100644 --- a/server/db/api/Actor.ts +++ b/server/db/api/Actor.ts @@ -80,6 +80,16 @@ export class Actor extends DBC.DBObject implements ActorBase, SystemO } } + static async fetchAll(): Promise { + try { + return DBC.CopyArray( + await DBC.DBConnection.prisma.actor.findMany(), Actor); + } catch (error) /* istanbul ignore next */ { + LOG.logger.error('DBAPI.Actor.fetchAll', error); + return null; + } + } + static async fetchFromUnit(idUnit: number): Promise { if (!idUnit) return null; diff --git a/server/db/api/Asset.ts b/server/db/api/Asset.ts index c2610d556..91a274e84 100644 --- a/server/db/api/Asset.ts +++ b/server/db/api/Asset.ts @@ -94,6 +94,16 @@ export class Asset extends DBC.DBObject implements AssetBase, SystemO } } + static async fetchAll(): Promise { + try { + return DBC.CopyArray( + await DBC.DBConnection.prisma.asset.findMany(), Asset); + } catch (error) /* istanbul ignore next */ { + LOG.logger.error('DBAPI.Asset.fetchAll', error); + return null; + } + } + static async fetchByStorageKey(StorageKey: string): Promise { if (!StorageKey) return null; diff --git a/server/db/api/AssetVersion.ts b/server/db/api/AssetVersion.ts index 9ca5a58a2..f0e64586a 100644 --- a/server/db/api/AssetVersion.ts +++ b/server/db/api/AssetVersion.ts @@ -158,6 +158,16 @@ export class AssetVersion extends DBC.DBObject implements Asse } } + static async fetchAll(): Promise { + try { + return DBC.CopyArray( + await DBC.DBConnection.prisma.assetVersion.findMany(), AssetVersion); + } catch (error) /* istanbul ignore next */ { + LOG.logger.error('DBAPI.AssetVersion.fetchAll', error); + return null; + } + } + static async fetchByAssetAndVersion(idAsset: number, Version: number): Promise { if (!idAsset) return null; diff --git a/server/db/api/IntermediaryFile.ts b/server/db/api/IntermediaryFile.ts index d44db34db..6558bcdff 100644 --- a/server/db/api/IntermediaryFile.ts +++ b/server/db/api/IntermediaryFile.ts @@ -72,6 +72,16 @@ export class IntermediaryFile extends DBC.DBObject impleme } } + static async fetchAll(): Promise { + try { + return DBC.CopyArray( + await DBC.DBConnection.prisma.intermediaryFile.findMany(), IntermediaryFile); + } catch (error) /* istanbul ignore next */ { + LOG.logger.error('DBAPI.IntermediaryFile.fetchAll', error); + return null; + } + } + /** * Computes the array of IntermediaryFiles that are connected to any of the specified items. * IntermediaryFiles are connected to system objects; we examine those system objects which are in a *derived* relationship diff --git a/server/db/api/Model.ts b/server/db/api/Model.ts index 537a478ff..cf6d6c037 100644 --- a/server/db/api/Model.ts +++ b/server/db/api/Model.ts @@ -98,6 +98,16 @@ export class Model extends DBC.DBObject implements ModelBase, SystemO } } + static async fetchAll(): Promise { + try { + return DBC.CopyArray( + await DBC.DBConnection.prisma.model.findMany(), Model); + } catch (error) /* istanbul ignore next */ { + LOG.logger.error('DBAPI.Model.fetchAll', error); + return null; + } + } + static async fetchFromXref(idScene: number): Promise { if (!idScene) return null; diff --git a/server/db/api/ProjectDocumentation.ts b/server/db/api/ProjectDocumentation.ts index 319f63167..1ad1d0fd9 100644 --- a/server/db/api/ProjectDocumentation.ts +++ b/server/db/api/ProjectDocumentation.ts @@ -76,6 +76,16 @@ export class ProjectDocumentation extends DBC.DBObject } } + static async fetchAll(): Promise { + try { + return DBC.CopyArray( + await DBC.DBConnection.prisma.projectDocumentation.findMany(), ProjectDocumentation); + } catch (error) /* istanbul ignore next */ { + LOG.logger.error('DBAPI.ProjectDocumentation.fetchAll', error); + return null; + } + } + static async fetchFromProject(idProject: number): Promise { if (!idProject) return null; diff --git a/server/db/api/Scene.ts b/server/db/api/Scene.ts index e7f929cdd..d6bc749b7 100644 --- a/server/db/api/Scene.ts +++ b/server/db/api/Scene.ts @@ -84,6 +84,16 @@ export class Scene extends DBC.DBObject implements SceneBase, SystemO } } + static async fetchAll(): Promise { + try { + return DBC.CopyArray( + await DBC.DBConnection.prisma.scene.findMany(), Scene); + } catch (error) /* istanbul ignore next */ { + LOG.logger.error('DBAPI.Scene.fetchAll', error); + return null; + } + } + static async fetchFromXref(idModel: number): Promise { if (!idModel) return null; diff --git a/server/db/api/Stakeholder.ts b/server/db/api/Stakeholder.ts index fcd0d6577..889240095 100644 --- a/server/db/api/Stakeholder.ts +++ b/server/db/api/Stakeholder.ts @@ -86,6 +86,16 @@ export class Stakeholder extends DBC.DBObject implements Stakeh } } + static async fetchAll(): Promise { + try { + return DBC.CopyArray( + await DBC.DBConnection.prisma.stakeholder.findMany(), Stakeholder); + } catch (error) /* istanbul ignore next */ { + LOG.logger.error('DBAPI.Stakeholder.fetchAll', error); + return null; + } + } + /** * Computes the array of Stakeholders that are connected to any of the specified projects. * Stakeholders are connected to system objects; we examine those system objects which are in a *derived* relationship diff --git a/server/db/api/composite/ObjectGraph.ts b/server/db/api/composite/ObjectGraph.ts index 92654a4af..f5ea546dd 100644 --- a/server/db/api/composite/ObjectGraph.ts +++ b/server/db/api/composite/ObjectGraph.ts @@ -3,8 +3,9 @@ import { Actor, Asset, AssetVersion, CaptureData, IntermediaryFile, Item, Model, SystemObjectPairs, Unit, eSystemObjectType } from '../..'; import * as LOG from '../../../utils/logger'; import * as L from 'lodash'; +import { ObjectGraphDatabase } from './ObjectGraphDatabase'; -type SystemObjectIDType = { +export type SystemObjectIDType = { idSystemObject: number, idObject: number, eType: eSystemObjectType @@ -40,14 +41,17 @@ export class ObjectGraph { pushCount: number = 0; maxPushCount: number = 500; depth: number = 32; + systemObjectList: number[] = []; // array of idSystemObjects to be processed systemObjectProcessed: Map = new Map(); // map from idSystemObject -> { idSystemObject, id of database object, type of database object} systemObjectAdded: Map = new Map(); // map from idSystemObject -> { idSystemObject, id of database object, type of database object} + objectGraphDatabase: ObjectGraphDatabase | null = null; // non-null means we'll record relationship and hierarchy data in this object - constructor(idSystemObject: number, eMode: eObjectGraphMode, depth: number = 32) { + constructor(idSystemObject: number, eMode: eObjectGraphMode, depth: number = 32, objectGraphDatabase: ObjectGraphDatabase | null = null) { this.idSystemObject = idSystemObject; this.eMode = eMode; this.depth = depth; + this.objectGraphDatabase = objectGraphDatabase; } async fetch(): Promise { @@ -156,6 +160,16 @@ export class ObjectGraph { this.systemObjectProcessed.set(idSystemObject, sourceType); this.systemObjectAdded.set(idSystemObject, sourceType); + // record relationship + if (this.objectGraphDatabase) { + if (relatedType) { + if (eMode == eObjectGraphMode.eAncestors) + this.objectGraphDatabase.recordRelationship(sourceType, relatedType); + else + this.objectGraphDatabase.recordRelationship(relatedType, sourceType); + } + } + // gather using master/derived systemobjectxref's const SORelated: SystemObject[] | null = (eMode == eObjectGraphMode.eAncestors) ? await SystemObject.fetchMasterFromXref(idSystemObject) diff --git a/server/db/api/composite/ObjectGraphDataEntry.ts b/server/db/api/composite/ObjectGraphDataEntry.ts new file mode 100644 index 000000000..d4ec65737 --- /dev/null +++ b/server/db/api/composite/ObjectGraphDataEntry.ts @@ -0,0 +1,19 @@ +// import { Actor, Asset, AssetVersion, CaptureData, IntermediaryFile, Model, ProjectDocumentation, Scene, Stakeholder, SystemObject } from '../..'; +import { Unit, Project, Subject, Item, SystemObjectIDType, eSystemObjectType } from '../..'; +// import * as LOG from '../../../utils/logger'; +// import * as CACHE from '../../../cache'; + +export class ObjectGraphDataEntry { + // The object whom this graph data entry describes + objectIDAndType: SystemObjectIDType = { idSystemObject: 0, idObject: 0, eType: eSystemObjectType.eUnknown }; + + // Derived data + childMap: Map = new Map(); // map of child objects + parentMap: Map = new Map(); // map of parent objects + + unitMap: Map = new Map(); // map of Units associted with this object + projectMap: Map = new Map(); // map of Projects associted with this object + subjectMap: Map = new Map(); // map of Subjects associted with this object + itemMap: Map = new Map(); // map of Items associted with this object + +} \ No newline at end of file diff --git a/server/db/api/composite/ObjectGraphDatabase.ts b/server/db/api/composite/ObjectGraphDatabase.ts new file mode 100644 index 000000000..839d775e0 --- /dev/null +++ b/server/db/api/composite/ObjectGraphDatabase.ts @@ -0,0 +1,25 @@ +// import { Actor, Asset, AssetVersion, CaptureData, IntermediaryFile, Model, ProjectDocumentation, Scene, Stakeholder, SystemObject, eSystemObjectType } from '../..'; +import { Unit, Project, Subject, Item, SystemObjectIDType } from '../..'; +// import * as LOG from '../../../utils/logger'; + +export class ObjectGraphDatabase { + // database + childMap: Map> = new Map>(); // map from parent object to map of children + parentMap: Map> = new Map>(); // map from child object to map of parents + + // state-based data + unit: Unit | null = null; + project: Project | null = null; + subject: Subject | null = null; + item: Item | null = null; + + recordRelationship(parent: SystemObjectIDType, child: SystemObjectIDType): void { + const childMap: Map = this.childMap.get(parent) || new Map(); + childMap.set(child.idSystemObject, child); + + const parentList: Map = this.parentMap.get(child) || new Map(); + parentList.set(parent.idSystemObject, parent); + } + + +} \ No newline at end of file diff --git a/server/db/index.ts b/server/db/index.ts index 0ee6df60d..e8f4d3c38 100644 --- a/server/db/index.ts +++ b/server/db/index.ts @@ -57,4 +57,6 @@ export * from './api/WorkflowStepSystemObjectXref'; export * from './api/WorkflowTemplate'; export * from './api/composite/ObjectGraph'; +export * from './api/composite/ObjectGraphDatabase'; +export * from './api/composite/ObjectGraphDataEntry'; export * from './api/composite/SubjectUnitIdentifier'; diff --git a/server/navigation/impl/NavigationSolr/ReindexSolr.ts b/server/navigation/impl/NavigationSolr/ReindexSolr.ts index 46f0e3508..14611d50f 100644 --- a/server/navigation/impl/NavigationSolr/ReindexSolr.ts +++ b/server/navigation/impl/NavigationSolr/ReindexSolr.ts @@ -29,6 +29,8 @@ export class ReindexSolr { const solrClient: SolrClient = new SolrClient(null, null, null); solrClient._client.autoCommit = true; + // await this.computeGraphDataFromUnits(); + solrClient._client.add(await this.computeUnits(), function(err, obj) { if (err) LOG.logger.error('ReindexSolr.FullIndex -> computeUnits()', err); else obj; }); solrClient._client.add(await this.computeProjects(), function(err, obj) { if (err) LOG.logger.error('ReindexSolr.FullIndex -> computeProjects()', err); else obj; }); solrClient._client.add(await this.computeSubjects(), function(err, obj) { if (err) LOG.logger.error('ReindexSolr.FullIndex -> computeSubjects()', err); else obj; }); @@ -53,7 +55,7 @@ export class ReindexSolr { const oID: CACHE.ObjectIDAndType = { idObject: unit.idUnit, eObjectType: eSystemObjectType.eUnit }; const sID: CACHE.SystemObjectInfo | undefined = await CACHE.SystemObjectCache.getSystemFromObjectID(oID); /* istanbul ignore if */ if (!sID) { - LOG.logger.error(`NavigationDB.getRoot unable to compute idSystemObject for ${JSON.stringify(oID)}`); + LOG.logger.error(`ReindexSolr.computeUnits unable to compute idSystemObject for ${JSON.stringify(oID)}`); continue; } diff --git a/server/tests/db/dbcreation.test.ts b/server/tests/db/dbcreation.test.ts index 51d3ddc8a..92256759e 100644 --- a/server/tests/db/dbcreation.test.ts +++ b/server/tests/db/dbcreation.test.ts @@ -3480,6 +3480,16 @@ describe('DB Fetch Xref Test Suite', () => { // DB Fetch Special Test Suite // ******************************************************************* describe('DB Fetch Special Test Suite', () => { + test('DB Fetch Special: Actor.fetchAll', async () => { + let actorFetch: DBAPI.Actor[] | null = null; + if (actorWithUnit && actorWithOutUnit) { + actorFetch = await DBAPI.Actor.fetchAll(); + if (actorFetch) + expect(actorFetch).toEqual(expect.arrayContaining([actorWithUnit, actorWithOutUnit])); + } + expect(actorFetch).toBeTruthy(); + }); + test('DB Fetch Special: Asset.assetType undefined', async() => { let eVocabID: eVocabularyID | undefined = undefined; if (assetThumbnail) @@ -3513,6 +3523,15 @@ describe('DB Fetch Special Test Suite', () => { } }); + test('DB Fetch Special: Asset.fetchAll', async () => { + let assetFetch: DBAPI.Asset[] | null = null; + if (assetThumbnail && assetWithoutAG && assetBulkIngest) { + assetFetch = await DBAPI.Asset.fetchAll(); + if (assetFetch) + expect(assetFetch).toEqual(expect.arrayContaining([assetThumbnail, assetWithoutAG, assetBulkIngest])); + } + expect(assetFetch).toBeTruthy(); + }); test('DB Fetch Special: Asset.fetchSourceSystemObject 1', async() => { let SOAsset: DBAPI.SystemObject | null = null; @@ -3529,6 +3548,16 @@ describe('DB Fetch Special Test Suite', () => { expect(SOAssetSource).toEqual(systemObjectSubject); }); + test('DB Fetch Special: AssetVersion.fetchAll', async () => { + let assetVersionFetch: DBAPI.AssetVersion[] | null = null; + if (assetVersion && assetVersion2 && assetVersionNotIngested) { + assetVersionFetch = await DBAPI.AssetVersion.fetchAll(); + if (assetVersionFetch) + expect(assetVersionFetch).toEqual(expect.arrayContaining([assetVersion, assetVersion2, assetVersionNotIngested])); + } + expect(assetVersionFetch).toBeTruthy(); + }); + test('DB Fetch Special: CaptureData.fetchFromCaptureDataPhoto', async () => { let captureDataFetch: DBAPI.CaptureData | null = null; if (captureDataPhoto) @@ -3590,6 +3619,16 @@ describe('DB Fetch Special Test Suite', () => { expect(identifierFetch).toBeTruthy(); }); + test('DB Fetch Special: IntermediaryFile.fetchAll', async () => { + let ifFetch: DBAPI.IntermediaryFile[] | null = null; + if (intermediaryFile) { + ifFetch = await DBAPI.IntermediaryFile.fetchAll(); + if (ifFetch) + expect(ifFetch).toEqual(expect.arrayContaining([intermediaryFile])); + } + expect(ifFetch).toBeTruthy(); + }); + test('DB Fetch Special: IntermediaryFile.fetchDerivedFromItems', async () => { let intermediaryFileFetch: DBAPI.IntermediaryFile[] | null = null; if (item && itemNulls) { @@ -3683,6 +3722,16 @@ describe('DB Fetch Special Test Suite', () => { expect(itemFetch).toBeTruthy(); }); + test('DB Fetch Special: Model.fetchAll', async () => { + let modelFetch: DBAPI.Model[] | null = null; + if (model && modelNulls) { + modelFetch = await DBAPI.Model.fetchAll(); + if (modelFetch) + expect(modelFetch).toEqual(expect.arrayContaining([model, modelNulls])); + } + expect(modelFetch).toBeTruthy(); + }); + test('DB Fetch Special: Model.fetchDerivedFromItems', async () => { let modelFetch: DBAPI.Model[] | null = null; if (item && itemNulls) { @@ -3747,6 +3796,16 @@ describe('DB Fetch Special Test Suite', () => { expect(projectFetch).toBeTruthy(); }); + test('DB Fetch Special: ProjectDocumentation.fetchAll', async () => { + let pdFetch: DBAPI.ProjectDocumentation[] | null = null; + if (projectDocumentation) { + pdFetch = await DBAPI.ProjectDocumentation.fetchAll(); + if (pdFetch) + expect(pdFetch).toEqual(expect.arrayContaining([projectDocumentation])); + } + expect(pdFetch).toBeTruthy(); + }); + test('DB Fetch Special: ProjectDocumentation.fetchDerivedFromProjects', async () => { let projectDocumentationFetch: DBAPI.ProjectDocumentation[] | null = null; if (project && project2) { @@ -3757,6 +3816,16 @@ describe('DB Fetch Special Test Suite', () => { expect(projectDocumentationFetch).toBeTruthy(); }); + test('DB Fetch Special: Scene.fetchAll', async () => { + let sceneFetch: DBAPI.Scene[] | null = null; + if (scene && sceneNulls) { + sceneFetch = await DBAPI.Scene.fetchAll(); + if (sceneFetch) + expect(sceneFetch).toEqual(expect.arrayContaining([scene, sceneNulls])); + } + expect(sceneFetch).toBeTruthy(); + }); + test('DB Fetch Special: Scene.fetchDerivedFromItems', async () => { let sceneFetch: DBAPI.Scene[] | null = null; if (item && itemNulls) { @@ -3771,6 +3840,16 @@ describe('DB Fetch Special Test Suite', () => { expect(sceneFetch).toBeTruthy(); }); + test('DB Fetch Special: Stakeholder.fetchAll', async () => { + let stakeholderFetch: DBAPI.Stakeholder[] | null = null; + if (stakeholder) { + stakeholderFetch = await DBAPI.Stakeholder.fetchAll(); + if (stakeholderFetch) + expect(stakeholderFetch).toEqual(expect.arrayContaining([stakeholder])); + } + expect(stakeholderFetch).toBeTruthy(); + }); + test('DB Fetch Special: Stakeholder.fetchDerivedFromProjects', async () => { let stakeholderFetch: DBAPI.Stakeholder[] | null = null; if (project && project2) { diff --git a/server/tests/jest.config.js b/server/tests/jest.config.js index 9ec1a2711..45b964f76 100644 --- a/server/tests/jest.config.js +++ b/server/tests/jest.config.js @@ -24,6 +24,7 @@ module.exports = { // '**/tests/collections/EdanCollection.test.ts', // '**/tests/db/dbcreation.test.ts', // '**/tests/db/composite/ObjectGraph.test.ts', + // '**/tests/db/composite/SubjectUnitIdentifier.test.ts', // '**/tests/navigation/impl/NavigationDB.test.ts', // '**/tests/storage/interface/AssetStorageAdapter.test.ts', // '**/tests/storage/impl/LocalStorage/OCFL.test.ts', From dd2037a7b97cec5e906891fc37efb43cc7276daf Mon Sep 17 00:00:00 2001 From: Jon Tyson <6943745+jahjedtieson@users.noreply.github.com> Date: Thu, 4 Feb 2021 11:54:57 -0800 Subject: [PATCH 214/239] DBAPI.User: Implemented fetchUserList * Added business logic to maintain DateActivated and DateDisabled upon User creation and updating. These fields are maintained by the database API layer; business logic values supplied here at creation or updating are ignored. * Added test code for new User API, as well as for updated business logic to maintain DateActivated and DateDisabled --- server/db/api/User.ts | 62 ++++++++- server/tests/db/dbcreation.test.ts | 211 ++++++++++++++++++++--------- 2 files changed, 204 insertions(+), 69 deletions(-) diff --git a/server/db/api/User.ts b/server/db/api/User.ts index 3bf0439f2..3891b775a 100644 --- a/server/db/api/User.ts +++ b/server/db/api/User.ts @@ -3,6 +3,12 @@ import { User as UserBase } from '@prisma/client'; import * as DBC from '../connection'; import * as LOG from '../../utils/logger'; +export enum eUserStatus { + eAll, + eActive, + eInactive +} + export class User extends DBC.DBObject implements UserBase { idUser!: number; Active!: boolean; @@ -14,15 +20,23 @@ export class User extends DBC.DBObject implements UserBase { SecurityID!: string; WorkflowNotificationTime!: Date | null; + private ActiveOrig!: boolean; + constructor(input: UserBase) { super(input); } - protected updateCachedValues(): void { } + protected updateCachedValues(): void { + this.ActiveOrig = this.Active; + } protected async createWorker(): Promise { try { - const { Name, EmailAddress, SecurityID, Active, DateActivated, DateDisabled, WorkflowNotificationTime, EmailSettings } = this; + let { DateActivated, DateDisabled } = this; + const { Name, EmailAddress, SecurityID, Active, WorkflowNotificationTime, EmailSettings } = this; + DateActivated = new Date(); + DateDisabled = (!Active) ? DateActivated : null; + ({ idUser: this.idUser, Name: this.Name, EmailAddress: this.EmailAddress, SecurityID: this.SecurityID, Active: this.Active, DateActivated: this.DateActivated, DateDisabled: this.DateDisabled, WorkflowNotificationTime: this.WorkflowNotificationTime, EmailSettings: this.EmailSettings } = @@ -47,7 +61,19 @@ export class User extends DBC.DBObject implements UserBase { protected async updateWorker(): Promise { try { - const { idUser, Name, EmailAddress, SecurityID, Active, DateActivated, DateDisabled, WorkflowNotificationTime, EmailSettings } = this; + let { DateActivated, DateDisabled } = this; + const { idUser, Name, EmailAddress, SecurityID, Active, WorkflowNotificationTime, EmailSettings, ActiveOrig } = this; + + // handle disabling and activating by detecting a change in the Active state: + if (Active != ActiveOrig) { + if (ActiveOrig) // we are disabling + DateDisabled = new Date(); + else { // we are activating + DateActivated = new Date(); + DateDisabled = null; + } + } + return await DBC.DBConnection.prisma.user.update({ where: { idUser, }, data: { @@ -87,4 +113,34 @@ export class User extends DBC.DBObject implements UserBase { return null; } } + + static async fetchUserList(search: string, eStatus: eUserStatus): Promise { + try { + const Active: boolean = (eStatus == eUserStatus.eActive); + + switch (eStatus) { + case eUserStatus.eAll: + return DBC.CopyArray(await DBC.DBConnection.prisma.user.findMany({ where: { + OR: [ + { EmailAddress: { contains: search }, }, + { Name: { contains: search }, }, + ], + }, }), User); + + default: + return DBC.CopyArray(await DBC.DBConnection.prisma.user.findMany({ where: { + AND: [ + { OR: [ + { EmailAddress: { contains: search }, }, + { Name: { contains: search }, }, + ] }, + { Active }, + ], + }, }), User); + } + } catch (error) /* istanbul ignore next */ { + LOG.logger.error('DBAPI.User.fetchUserList', error); + return null; + } + } } diff --git a/server/tests/db/dbcreation.test.ts b/server/tests/db/dbcreation.test.ts index b9c9187e8..585a81fb1 100644 --- a/server/tests/db/dbcreation.test.ts +++ b/server/tests/db/dbcreation.test.ts @@ -102,7 +102,8 @@ let unit: DBAPI.Unit | null; let unit2: DBAPI.Unit | null; let unitEdan: DBAPI.UnitEdan | null; let unitEdan2: DBAPI.UnitEdan | null; -let user: DBAPI.User | null; +let userActive: DBAPI.User | null; +let userInactive: DBAPI.User | null; let userPersonalizationSystemObject: DBAPI.UserPersonalizationSystemObject | null; let userPersonalizationUrl: DBAPI.UserPersonalizationUrl | null; let vocabulary: DBAPI.Vocabulary | null; @@ -262,9 +263,9 @@ describe('DB Creation Test Suite', () => { }); test('DB Creation: User', async () => { - user = await UTIL.createUserTest({ - Name: 'Test User', - EmailAddress: 'test@si.edu', + userActive = await UTIL.createUserTest({ + Name: 'Test User 1', + EmailAddress: UTIL.randomStorageKey('test') + '@si.edu', SecurityID: 'SECURITY_ID', Active: true, DateActivated: UTIL.nowCleansed(), @@ -273,7 +274,23 @@ describe('DB Creation Test Suite', () => { EmailSettings: 0, idUser: 0 }); - expect(user).toBeTruthy(); + expect(userActive).toBeTruthy(); + expect(userActive.DateDisabled).toBeFalsy(); + + userInactive = await UTIL.createUserTest({ + Name: 'Test User 2', + EmailAddress: UTIL.randomStorageKey('test') + '@si.edu', + SecurityID: 'SECURITY_ID', + Active: false, + DateActivated: UTIL.nowCleansed(), + DateDisabled: null, + WorkflowNotificationTime: UTIL.nowCleansed(), + EmailSettings: 0, + idUser: 0 + }); + expect(userInactive).toBeTruthy(); + expect(userInactive.DateDisabled).toBeTruthy(); + expect(userInactive.DateDisabled).toEqual(userInactive.DateActivated); }); test('DB Creation: Identifier Subject Hookup', async () => { @@ -424,9 +441,9 @@ describe('DB Creation Test Suite', () => { }); test('DB Creation: AccessPolicy', async () => { - if (user && accessRole && accessContext) { + if (userActive && accessRole && accessContext) { accessPolicy = new DBAPI.AccessPolicy({ - idUser: user.idUser, + idUser: userActive.idUser, idAccessRole: accessRole.idAccessRole, idAccessContext: accessContext.idAccessContext, idAccessPolicy: 0 @@ -516,12 +533,12 @@ describe('DB Creation Test Suite', () => { }); test('DB Creation: AssetVersion', async () => { - if (assetThumbnail && user) + if (assetThumbnail && userActive) assetVersion = await UTIL.createAssetVersionTest({ idAsset: assetThumbnail.idAsset, Version: 0, FileName: assetThumbnail.FilePath, - idUserCreator: user.idUser, + idUserCreator: userActive.idUser, DateCreated: UTIL.nowCleansed(), StorageHash: 'Asset Checksum', StorageSize: 50, @@ -534,12 +551,12 @@ describe('DB Creation Test Suite', () => { }); test('DB Creation: AssetVersion Not Ingested', async () => { - if (assetThumbnail && user) + if (assetThumbnail && userActive) assetVersionNotIngested = await UTIL.createAssetVersionTest({ idAsset: assetThumbnail.idAsset, Version: 0, FileName: assetThumbnail.FilePath, - idUserCreator: user.idUser, + idUserCreator: userActive.idUser, DateCreated: UTIL.nowCleansed(), StorageHash: 'Asset Checksum', StorageSize: 50, @@ -790,13 +807,13 @@ describe('DB Creation Test Suite', () => { }); test('DB Creation: Metadata', async () => { - if (assetThumbnail && user && vocabulary && systemObjectScene) + if (assetThumbnail && userActive && vocabulary && systemObjectScene) metadata = new DBAPI.Metadata({ Name: 'Test Metadata', ValueShort: 'Test Value Short', ValueExtended: 'Test Value Ext', idAssetValue: assetThumbnail.idAsset, - idUser: user.idUser, + idUser: userActive.idUser, idVMetadataSource: vocabulary.idVocabulary, idSystemObject: systemObjectScene.idSystemObject, idMetadata: 0, @@ -1127,10 +1144,10 @@ describe('DB Creation Test Suite', () => { }); test('DB Creation: LicenseAssignment', async () => { - if (license && user && systemObjectSubject) + if (license && userActive && systemObjectSubject) licenseAssignment = new DBAPI.LicenseAssignment({ idLicense: license.idLicense, - idUserCreator: user.idUser, + idUserCreator: userActive.idUser, DateStart: UTIL.nowCleansed(), DateEnd: UTIL.nowCleansed(), idSystemObject: systemObjectSubject.idSystemObject, @@ -1189,9 +1206,9 @@ describe('DB Creation Test Suite', () => { }); test('DB Creation: UserPersonalizationSystemObject', async () => { - if (systemObjectSubject && user) + if (systemObjectSubject && userActive) userPersonalizationSystemObject = new DBAPI.UserPersonalizationSystemObject({ - idUser: user.idUser, + idUser: userActive.idUser, idSystemObject: systemObjectSubject.idSystemObject, Personalization: 'Test Personalization', idUserPersonalizationSystemObject: 0 @@ -1204,9 +1221,9 @@ describe('DB Creation Test Suite', () => { }); test('DB Creation: UserPersonalizationUrl', async () => { - if (user) + if (userActive) userPersonalizationUrl = new DBAPI.UserPersonalizationUrl({ - idUser: user.idUser, + idUser: userActive.idUser, URL: '/test/personalization/Url', Personalization: 'Test Personalization', idUserPersonalizationUrl: 0 @@ -1219,11 +1236,11 @@ describe('DB Creation Test Suite', () => { }); test('DB Creation: Workflow', async () => { - if (workflowTemplate && project && user) + if (workflowTemplate && project && userActive) workflow = await UTIL.createWorkflowTest({ idWorkflowTemplate: workflowTemplate.idWorkflowTemplate, idProject: project.idProject, - idUserInitiator: user.idUser, + idUserInitiator: userActive.idUser, DateInitiated: UTIL.nowCleansed(), DateUpdated: UTIL.nowCleansed(), idWorkflow: 0 @@ -1253,10 +1270,10 @@ describe('DB Creation Test Suite', () => { }); test('DB Creation: WorkflowStep', async () => { - if (workflow && user && vocabulary) + if (workflow && userActive && vocabulary) workflowStep = await UTIL.createWorkflowStepTest({ idWorkflow: workflow.idWorkflow, - idUserOwner: user.idUser, + idUserOwner: userActive.idUser, idVWorkflowStepType: vocabulary.idVocabulary, State: 0, DateCreated: UTIL.nowCleansed(), @@ -1444,8 +1461,8 @@ describe('DB Fetch By ID Test Suite', () => { test('DB Fetch By ID: AccessPolicy.fetchFromUser', async () => { let accessPolicyFetch: DBAPI.AccessPolicy[] | null = null; - if (user) { - accessPolicyFetch = await DBAPI.AccessPolicy.fetchFromUser(user.idUser); + if (userActive) { + accessPolicyFetch = await DBAPI.AccessPolicy.fetchFromUser(userActive.idUser); if (accessPolicyFetch) { expect(accessPolicyFetch).toEqual(expect.arrayContaining([accessPolicy])); } @@ -1601,12 +1618,12 @@ describe('DB Fetch By ID Test Suite', () => { }); test('DB Creation: AssetVersion 2', async () => { - if (assetThumbnail && user) + if (assetThumbnail && userActive) assetVersion2 = await UTIL.createAssetVersionTest({ idAsset: assetThumbnail.idAsset, Version: 0, FileName: assetThumbnail.FileName, - idUserCreator: user.idUser, + idUserCreator: userActive.idUser, DateCreated: UTIL.nowCleansed(), StorageHash: 'Asset Checksum', StorageSize: 50, @@ -1644,8 +1661,8 @@ describe('DB Fetch By ID Test Suite', () => { test('DB Fetch AssetVersion: AssetVersion.fetchFromUser', async () => { let assetVersionFetch: DBAPI.AssetVersion[] | null = null; - if (user) { - assetVersionFetch = await DBAPI.AssetVersion.fetchFromUser(user.idUser); + if (userActive) { + assetVersionFetch = await DBAPI.AssetVersion.fetchFromUser(userActive.idUser); if (assetVersionFetch) { expect(assetVersionFetch).toEqual(expect.arrayContaining([assetVersion])); } @@ -1655,8 +1672,8 @@ describe('DB Fetch By ID Test Suite', () => { test('DB Fetch AssetVersion: AssetVersion.fetchFromUserByIngested Ingested', async () => { let assetVersionFetch: DBAPI.AssetVersion[] | null = null; - if (user) { - assetVersionFetch = await DBAPI.AssetVersion.fetchFromUserByIngested(user.idUser, true); + if (userActive) { + assetVersionFetch = await DBAPI.AssetVersion.fetchFromUserByIngested(userActive.idUser, true); if (assetVersionFetch) { expect(assetVersionFetch).toEqual(expect.arrayContaining([assetVersion])); } @@ -1666,8 +1683,8 @@ describe('DB Fetch By ID Test Suite', () => { test('DB Fetch AssetVersion: AssetVersion.fetchFromUserByIngested Not Ingested', async () => { let assetVersionFetch: DBAPI.AssetVersion[] | null = null; - if (user) { - assetVersionFetch = await DBAPI.AssetVersion.fetchFromUserByIngested(user.idUser, false); + if (userActive) { + assetVersionFetch = await DBAPI.AssetVersion.fetchFromUserByIngested(userActive.idUser, false); if (assetVersionFetch) { expect(assetVersionFetch).toEqual(expect.arrayContaining([assetVersionNotIngested])); } @@ -1677,7 +1694,7 @@ describe('DB Fetch By ID Test Suite', () => { test('DB Fetch AssetVersion: AssetVersion.fetchByIngested Ingested', async () => { let assetVersionFetch: DBAPI.AssetVersion[] | null = null; - if (user) { + if (userActive) { assetVersionFetch = await DBAPI.AssetVersion.fetchByIngested(true); if (assetVersionFetch) { expect(assetVersionFetch).toEqual(expect.arrayContaining([assetVersion])); @@ -1688,7 +1705,7 @@ describe('DB Fetch By ID Test Suite', () => { test('DB Fetch AssetVersion: AssetVersion.fetchByIngested Not Ingested', async () => { let assetVersionFetch: DBAPI.AssetVersion[] | null = null; - if (user) { + if (userActive) { assetVersionFetch = await DBAPI.AssetVersion.fetchByIngested(false); if (assetVersionFetch) { expect(assetVersionFetch).toEqual(expect.arrayContaining([assetVersionNotIngested])); @@ -1894,8 +1911,8 @@ describe('DB Fetch By ID Test Suite', () => { test('DB Fetch LicenseAssignment: LicenseAssignment.fetchFromUser', async () => { let licenseAssignmentFetch: DBAPI.LicenseAssignment[] | null = null; - if (user) { - licenseAssignmentFetch = await DBAPI.LicenseAssignment.fetchFromUser(user.idUser); + if (userActive) { + licenseAssignmentFetch = await DBAPI.LicenseAssignment.fetchFromUser(userActive.idUser); if (licenseAssignmentFetch) { expect(licenseAssignmentFetch).toEqual(expect.arrayContaining([licenseAssignment])); } @@ -1928,8 +1945,8 @@ describe('DB Fetch By ID Test Suite', () => { test('DB Fetch Metadata: Metadata.fetchFromUser', async () => { let metadataFetch: DBAPI.Metadata[] | null = null; - if (user) { - metadataFetch = await DBAPI.Metadata.fetchFromUser(user.idUser); + if (userActive) { + metadataFetch = await DBAPI.Metadata.fetchFromUser(userActive.idUser); if (metadataFetch) { expect(metadataFetch).toEqual(expect.arrayContaining([metadata])); } @@ -2282,11 +2299,11 @@ describe('DB Fetch By ID Test Suite', () => { test('DB Fetch By ID: User', async () => { let userFetch: DBAPI.User | null = null; - if (user) { - userFetch = await DBAPI.User.fetch(user.idUser); + if (userActive) { + userFetch = await DBAPI.User.fetch(userActive.idUser); if (userFetch) { - expect(userFetch).toMatchObject(user); - expect(user).toMatchObject(userFetch); + expect(userFetch).toMatchObject(userActive); + expect(userActive).toMatchObject(userFetch); } } expect(userFetch).toBeTruthy(); @@ -2294,10 +2311,10 @@ describe('DB Fetch By ID Test Suite', () => { test('DB Fetch By EmailAddress: User', async () => { let userFetchArray: DBAPI.User[] | null = null; - if (user) { - userFetchArray = await DBAPI.User.fetchByEmail(user.EmailAddress); + if (userActive) { + userFetchArray = await DBAPI.User.fetchByEmail(userActive.EmailAddress); if (userFetchArray) - expect(userFetchArray).toEqual(expect.arrayContaining([user])); + expect(userFetchArray).toEqual(expect.arrayContaining([userActive])); } expect(userFetchArray).toBeTruthy(); }); @@ -2316,8 +2333,8 @@ describe('DB Fetch By ID Test Suite', () => { test('DB Fetch UserPersonalizationSystemObject: UserPersonalizationSystemObject.fetchFromUser', async () => { let userPersonalizationSystemObjectFetch: DBAPI.UserPersonalizationSystemObject[] | null = null; - if (user) { - userPersonalizationSystemObjectFetch = await DBAPI.UserPersonalizationSystemObject.fetchFromUser(user.idUser); + if (userActive) { + userPersonalizationSystemObjectFetch = await DBAPI.UserPersonalizationSystemObject.fetchFromUser(userActive.idUser); if (userPersonalizationSystemObjectFetch) { expect(userPersonalizationSystemObjectFetch).toEqual(expect.arrayContaining([userPersonalizationSystemObject])); } @@ -2350,8 +2367,8 @@ describe('DB Fetch By ID Test Suite', () => { test('DB Fetch UserPersonalizationUrl: UserPersonalizationUrl.fetchFromUser', async () => { let userPersonalizationUrlFetch: DBAPI.UserPersonalizationUrl[] | null = null; - if (user) { - userPersonalizationUrlFetch = await DBAPI.UserPersonalizationUrl.fetchFromUser(user.idUser); + if (userActive) { + userPersonalizationUrlFetch = await DBAPI.UserPersonalizationUrl.fetchFromUser(userActive.idUser); if (userPersonalizationUrlFetch) { expect(userPersonalizationUrlFetch).toEqual(expect.arrayContaining([userPersonalizationUrl])); } @@ -2440,8 +2457,8 @@ describe('DB Fetch By ID Test Suite', () => { test('DB Fetch Workflow: Workflow.fetchFromUser', async () => { let workflowFetch: DBAPI.Workflow[] | null = null; - if (user) { - workflowFetch = await DBAPI.Workflow.fetchFromUser(user.idUser); + if (userActive) { + workflowFetch = await DBAPI.Workflow.fetchFromUser(userActive.idUser); if (workflowFetch) { expect(workflowFetch).toEqual(expect.arrayContaining([workflow])); } @@ -2476,8 +2493,8 @@ describe('DB Fetch By ID Test Suite', () => { test('DB Fetch WorkflowStep: WorkflowStep.fetchFromUser', async () => { let workflowStepFetch: DBAPI.WorkflowStep[] | null = null; - if (user) { - workflowStepFetch = await DBAPI.WorkflowStep.fetchFromUser(user.idUser); + if (userActive) { + workflowStepFetch = await DBAPI.WorkflowStep.fetchFromUser(userActive.idUser); if (workflowStepFetch) { expect(workflowStepFetch).toEqual(expect.arrayContaining([workflowStep])); } @@ -3828,6 +3845,31 @@ describe('DB Fetch Special Test Suite', () => { } expect(unitEdanFetch).toBeTruthy(); }); + + test('DB Fetch Special: User.fetchUserList', async () => { + let userFetchArray: DBAPI.User[] | null = null; + if (userActive && userInactive) { + userFetchArray = await DBAPI.User.fetchUserList('test', DBAPI.eUserStatus.eAll); + expect(userFetchArray).toBeTruthy(); + if (userFetchArray) + expect(userFetchArray).toEqual(expect.arrayContaining([userActive, userInactive])); + + userFetchArray = await DBAPI.User.fetchUserList('test', DBAPI.eUserStatus.eActive); + expect(userFetchArray).toBeTruthy(); + if (userFetchArray) + expect(userFetchArray).toEqual(expect.arrayContaining([userActive])); + + userFetchArray = await DBAPI.User.fetchUserList('test', DBAPI.eUserStatus.eInactive); + expect(userFetchArray).toBeTruthy(); + if (userFetchArray) + expect(userFetchArray).toEqual(expect.arrayContaining([userInactive])); + + userFetchArray = await DBAPI.User.fetchUserList('NOMATCH', DBAPI.eUserStatus.eAll); + expect(userFetchArray).toBeTruthy(); + if (userFetchArray) + expect(userFetchArray.length).toEqual(0); + } + }); }); // ******************************************************************* @@ -4085,12 +4127,12 @@ describe('DB Update Test Suite', () => { test('DB Creation: AssetVersion.delete', async () => { let assetVersion3: DBAPI.AssetVersion | null = null; - if (assetThumbnail && user) { + if (assetThumbnail && userActive) { assetVersion3 = await UTIL.createAssetVersionTest({ idAsset: assetThumbnail.idAsset, Version: 0, FileName: assetThumbnail.FileName, - idUserCreator: user.idUser, + idUserCreator: userActive.idUser, DateCreated: UTIL.nowCleansed(), StorageHash: 'Asset Checksum', StorageSize: 50, @@ -4488,14 +4530,14 @@ describe('DB Update Test Suite', () => { test('DB Update: LicenseAssignment.update', async () => { let bUpdated: boolean = false; - if (licenseAssignmentNull && user) { - licenseAssignmentNull.idUserCreator = user.idUser; + if (licenseAssignmentNull && userActive) { + licenseAssignmentNull.idUserCreator = userActive.idUser; bUpdated = await licenseAssignmentNull.update(); const licenseAssignmentFetch: DBAPI.LicenseAssignment | null = await DBAPI.LicenseAssignment.fetch(licenseAssignmentNull.idLicenseAssignment); expect(licenseAssignmentFetch).toBeTruthy(); if (licenseAssignmentFetch) - expect(licenseAssignmentFetch.idUserCreator).toBe(user.idUser); + expect(licenseAssignmentFetch.idUserCreator).toBe(userActive.idUser); } expect(bUpdated).toBeTruthy(); }); @@ -4531,16 +4573,16 @@ describe('DB Update Test Suite', () => { test('DB Update: Metadata.update', async () => { let bUpdated: boolean = false; - if (metadataNull && assetThumbnail && user) { + if (metadataNull && assetThumbnail && userActive) { metadataNull.idAssetValue = assetThumbnail.idAsset; - metadataNull.idUser = user.idUser; + metadataNull.idUser = userActive.idUser; bUpdated = await metadataNull.update(); const metadataFetch: DBAPI.Metadata | null = await DBAPI.Metadata.fetch(metadataNull.idMetadata); expect(metadataFetch).toBeTruthy(); if (metadataFetch) { expect(metadataFetch.idAssetValue).toBe(assetThumbnail.idAsset); - expect(metadataFetch.idUser).toEqual(user.idUser); + expect(metadataFetch.idUser).toEqual(userActive.idUser); } } expect(bUpdated).toBeTruthy(); @@ -5090,12 +5132,12 @@ describe('DB Update Test Suite', () => { test('DB Update: User.update', async () => { let bUpdated: boolean = false; - if (user) { + if (userActive) { const updatedName: string = 'Updated Test User'; - user.Name = updatedName; - bUpdated = await user.update(); + userActive.Name = updatedName; + bUpdated = await userActive.update(); - const userFetch: DBAPI.User | null = await DBAPI.User.fetch(user.idUser); + const userFetch: DBAPI.User | null = await DBAPI.User.fetch(userActive.idUser); expect(userFetch).toBeTruthy(); if (userFetch) expect(userFetch.Name).toBe(updatedName); @@ -5103,6 +5145,43 @@ describe('DB Update Test Suite', () => { expect(bUpdated).toBeTruthy(); }); + test('DB Update: User.update make inactive', async () => { + let bUpdated: boolean = false; + if (userActive) { + const now = UTIL.nowCleansed(); + userActive.Active = false; + bUpdated = await userActive.update(); + + const userFetch: DBAPI.User | null = await DBAPI.User.fetch(userActive.idUser); + expect(userFetch).toBeTruthy(); + if (userFetch) { + expect(userFetch.Active).toBe(false); + expect(userFetch.DateDisabled).toBeTruthy(); + if (userFetch.DateDisabled) + expect(userFetch.DateDisabled.getTime()).toBeGreaterThanOrEqual(now.getTime()); + } + } + expect(bUpdated).toBeTruthy(); + }); + + test('DB Update: User.update make active', async () => { + let bUpdated: boolean = false; + if (userActive) { + const now = UTIL.nowCleansed(); + userActive.Active = true; + bUpdated = await userActive.update(); + + const userFetch: DBAPI.User | null = await DBAPI.User.fetch(userActive.idUser); + expect(userFetch).toBeTruthy(); + if (userFetch) { + expect(userFetch.Active).toBe(true); + expect(userFetch.DateDisabled).toBeNull(); + expect(userFetch.DateActivated.getTime()).toBeGreaterThanOrEqual(now.getTime()); + } + } + expect(bUpdated).toBeTruthy(); + }); + test('DB Update: UserPersonalizationSystemObject.update', async () => { let bUpdated: boolean = false; if (userPersonalizationSystemObject) { From a6d68b32a5cebf7622450b5caa3ced7843d403b4 Mon Sep 17 00:00:00 2001 From: Jon Tyson <6943745+jahjedtieson@users.noreply.github.com> Date: Mon, 8 Feb 2021 16:10:41 -0800 Subject: [PATCH 215/239] Solr: * Updated Solr schema with children IDs as well as proper multiValue settings for 3D model-specific fields * Implemented Solr indexing logic for all system object types * Make use of new algorithms in ObjectGraphDatabase and ObjectGraphDataEntry, which collects ancestor and descendent properties for use in full population of Solr schema * Added Model.Name to data model, DB API, GraphQL API, test code, etc. * Added DBAPI.ModelConstellation, an object that can fetch all model-specific data for a given model --- client/src/types/graphql.tsx | 2653 +++++++---------- server/cache/SystemObjectCache.ts | 8 +- .../config/solr/data/packrat/conf/schema.xml | 34 +- server/db/api/Model.ts | 337 ++- server/db/api/ModelUVMapChannel.ts | 21 +- server/db/api/ModelUVMapFile.ts | 21 +- server/db/api/SystemObjectPairs.ts | 982 +++--- server/db/api/composite/ObjectGraph.ts | 134 +- .../db/api/composite/ObjectGraphDataEntry.ts | 176 +- .../db/api/composite/ObjectGraphDatabase.ts | 346 ++- server/db/prisma/schema.prisma | 1 + server/db/sql/models/Packrat.mwb | Bin 122803 -> 123572 bytes server/db/sql/scripts/Packrat.SCHEMA.sql | 1 + server/graphql/schema.graphql | 2072 ++++++------- server/graphql/schema/model/mutations.graphql | 1 + .../model/resolvers/mutations/createModel.ts | 3 +- server/graphql/schema/model/types.graphql | 1 + server/index.ts | 2 +- .../impl/NavigationSolr/ReindexSolr.ts | 887 +++--- server/navigation/interface/INavigation.ts | 44 +- .../tests/db/composite/ObjectGraph.setup.ts | 8 +- server/tests/db/dbcreation.test.ts | 2 + server/tests/graphql/utils/index.ts | 1 + server/types/graphql.ts | 2 + server/utils/helpers.ts | 18 +- 25 files changed, 3960 insertions(+), 3795 deletions(-) diff --git a/client/src/types/graphql.tsx b/client/src/types/graphql.tsx index 0382bf652..2f11ee103 100644 --- a/client/src/types/graphql.tsx +++ b/client/src/types/graphql.tsx @@ -55,162 +55,130 @@ export type Query = { searchIngestionSubjects: SearchIngestionSubjectsResult; }; - export type QueryAreCameraSettingsUniformArgs = { input: AreCameraSettingsUniformInput; }; - export type QueryGetAccessPolicyArgs = { input: GetAccessPolicyInput; }; - export type QueryGetAssetArgs = { input: GetAssetInput; }; - export type QueryGetAssetDetailsForSystemObjectArgs = { input: GetAssetDetailsForSystemObjectInput; }; - export type QueryGetAssetVersionsDetailsArgs = { input: GetAssetVersionsDetailsInput; }; - export type QueryGetCaptureDataArgs = { input: GetCaptureDataInput; }; - export type QueryGetCaptureDataPhotoArgs = { input: GetCaptureDataPhotoInput; }; - export type QueryGetContentsForAssetVersionsArgs = { input: GetContentsForAssetVersionsInput; }; - export type QueryGetDetailsTabDataForObjectArgs = { input: GetDetailsTabDataForObjectInput; }; - export type QueryGetIngestionItemsForSubjectsArgs = { input: GetIngestionItemsForSubjectsInput; }; - export type QueryGetIngestionProjectsForSubjectsArgs = { input: GetIngestionProjectsForSubjectsInput; }; - export type QueryGetIntermediaryFileArgs = { input: GetIntermediaryFileInput; }; - export type QueryGetItemArgs = { input: GetItemInput; }; - export type QueryGetItemsForSubjectArgs = { input: GetItemsForSubjectInput; }; - export type QueryGetLicenseArgs = { input: GetLicenseInput; }; - export type QueryGetModelArgs = { input: GetModelInput; }; - export type QueryGetObjectChildrenArgs = { input: GetObjectChildrenInput; }; - export type QueryGetObjectsForItemArgs = { input: GetObjectsForItemInput; }; - export type QueryGetProjectArgs = { input: GetProjectInput; }; - export type QueryGetProjectDocumentationArgs = { input: GetProjectDocumentationInput; }; - export type QueryGetSceneArgs = { input: GetSceneInput; }; - export type QueryGetSourceObjectIdentiferArgs = { input: GetSourceObjectIdentiferInput; }; - export type QueryGetSubjectArgs = { input: GetSubjectInput; }; - export type QueryGetSubjectsForUnitArgs = { input: GetSubjectsForUnitInput; }; - export type QueryGetSystemObjectDetailsArgs = { input: GetSystemObjectDetailsInput; }; - export type QueryGetUnitArgs = { input: GetUnitInput; }; - export type QueryGetUserArgs = { input: GetUserInput; }; - export type QueryGetVersionsForSystemObjectArgs = { input: GetVersionsForSystemObjectInput; }; - export type QueryGetVocabularyArgs = { input: GetVocabularyInput; }; - export type QueryGetVocabularyEntriesArgs = { input: GetVocabularyEntriesInput; }; - export type QueryGetWorkflowArgs = { input: GetWorkflowInput; }; - export type QuerySearchIngestionSubjectsArgs = { input: SearchIngestionSubjectsInput; }; @@ -224,7 +192,6 @@ export type GetAccessPolicyResult = { AccessPolicy?: Maybe; }; - export type AccessAction = { __typename?: 'AccessAction'; idAccessAction: Scalars['Int']; @@ -273,7 +240,6 @@ export type AccessRole = { AccessAction?: Maybe>>; }; - export type Mutation = { __typename?: 'Mutation'; createCaptureData: CreateCaptureDataResult; @@ -293,77 +259,62 @@ export type Mutation = { uploadAsset: UploadAssetResult; }; - export type MutationCreateCaptureDataArgs = { input: CreateCaptureDataInput; }; - export type MutationCreateCaptureDataPhotoArgs = { input: CreateCaptureDataPhotoInput; }; - export type MutationCreateItemArgs = { input: CreateItemInput; }; - export type MutationCreateModelArgs = { input: CreateModelInput; }; - export type MutationCreateProjectArgs = { input: CreateProjectInput; }; - export type MutationCreateSceneArgs = { input: CreateSceneInput; }; - export type MutationCreateSubjectArgs = { input: CreateSubjectInput; }; - export type MutationCreateUnitArgs = { input: CreateUnitInput; }; - export type MutationCreateUserArgs = { input: CreateUserInput; }; - export type MutationCreateVocabularyArgs = { input: CreateVocabularyInput; }; - export type MutationCreateVocabularySetArgs = { input: CreateVocabularySetInput; }; - export type MutationDiscardUploadedAssetVersionsArgs = { input: DiscardUploadedAssetVersionsInput; }; - export type MutationIngestDataArgs = { input: IngestDataInput; }; - export type MutationUpdateObjectDetailsArgs = { input: UpdateObjectDetailsInput; }; - export type MutationUploadAssetArgs = { file: Scalars['Upload']; type: Scalars['Int']; @@ -883,6 +834,7 @@ export type LicenseAssignment = { }; export type CreateModelInput = { + Name: Scalars['String']; Authoritative: Scalars['Boolean']; idVCreationMethod: Scalars['Int']; idVModality: Scalars['Int']; @@ -909,6 +861,7 @@ export type GetModelResult = { export type Model = { __typename?: 'Model'; idModel: Scalars['Int']; + Name: Scalars['String']; Authoritative: Scalars['Boolean']; DateCreated: Scalars['DateTime']; idAssetThumbnail?: Maybe; @@ -2026,1142 +1979,682 @@ export type DiscardUploadedAssetVersionsMutationVariables = Exact<{ input: DiscardUploadedAssetVersionsInput; }>; - -export type DiscardUploadedAssetVersionsMutation = ( - { __typename?: 'Mutation' } - & { - discardUploadedAssetVersions: ( - { __typename?: 'DiscardUploadedAssetVersionsResult' } - & Pick - ) - } -); +export type DiscardUploadedAssetVersionsMutation = { __typename?: 'Mutation' } & { + discardUploadedAssetVersions: { __typename?: 'DiscardUploadedAssetVersionsResult' } & Pick; +}; export type UploadAssetMutationVariables = Exact<{ file: Scalars['Upload']; type: Scalars['Int']; }>; - -export type UploadAssetMutation = ( - { __typename?: 'Mutation' } - & { - uploadAsset: ( - { __typename?: 'UploadAssetResult' } - & Pick - ) - } -); +export type UploadAssetMutation = { __typename?: 'Mutation' } & { + uploadAsset: { __typename?: 'UploadAssetResult' } & Pick; +}; export type CreateCaptureDataMutationVariables = Exact<{ input: CreateCaptureDataInput; }>; - -export type CreateCaptureDataMutation = ( - { __typename?: 'Mutation' } - & { - createCaptureData: ( - { __typename?: 'CreateCaptureDataResult' } - & { - CaptureData?: Maybe<( - { __typename?: 'CaptureData' } - & Pick - )> - } - ) - } -); +export type CreateCaptureDataMutation = { __typename?: 'Mutation' } & { + createCaptureData: { __typename?: 'CreateCaptureDataResult' } & { + CaptureData?: Maybe<{ __typename?: 'CaptureData' } & Pick>; + }; +}; export type CreateCaptureDataPhotoMutationVariables = Exact<{ input: CreateCaptureDataPhotoInput; }>; - -export type CreateCaptureDataPhotoMutation = ( - { __typename?: 'Mutation' } - & { - createCaptureDataPhoto: ( - { __typename?: 'CreateCaptureDataPhotoResult' } - & { - CaptureDataPhoto?: Maybe<( - { __typename?: 'CaptureDataPhoto' } - & Pick - )> - } - ) - } -); +export type CreateCaptureDataPhotoMutation = { __typename?: 'Mutation' } & { + createCaptureDataPhoto: { __typename?: 'CreateCaptureDataPhotoResult' } & { + CaptureDataPhoto?: Maybe<{ __typename?: 'CaptureDataPhoto' } & Pick>; + }; +}; export type IngestDataMutationVariables = Exact<{ input: IngestDataInput; }>; - -export type IngestDataMutation = ( - { __typename?: 'Mutation' } - & { - ingestData: ( - { __typename?: 'IngestDataResult' } - & Pick - ) - } -); +export type IngestDataMutation = { __typename?: 'Mutation' } & { + ingestData: { __typename?: 'IngestDataResult' } & Pick; +}; export type CreateModelMutationVariables = Exact<{ input: CreateModelInput; }>; - -export type CreateModelMutation = ( - { __typename?: 'Mutation' } - & { - createModel: ( - { __typename?: 'CreateModelResult' } - & { - Model?: Maybe<( - { __typename?: 'Model' } - & Pick - )> - } - ) - } -); +export type CreateModelMutation = { __typename?: 'Mutation' } & { + createModel: { __typename?: 'CreateModelResult' } & { + Model?: Maybe<{ __typename?: 'Model' } & Pick>; + }; +}; export type CreateSceneMutationVariables = Exact<{ input: CreateSceneInput; }>; - -export type CreateSceneMutation = ( - { __typename?: 'Mutation' } - & { - createScene: ( - { __typename?: 'CreateSceneResult' } - & { - Scene?: Maybe<( - { __typename?: 'Scene' } - & Pick - )> - } - ) - } -); +export type CreateSceneMutation = { __typename?: 'Mutation' } & { + createScene: { __typename?: 'CreateSceneResult' } & { + Scene?: Maybe<{ __typename?: 'Scene' } & Pick>; + }; +}; export type UpdateObjectDetailsMutationVariables = Exact<{ input: UpdateObjectDetailsInput; }>; - -export type UpdateObjectDetailsMutation = ( - { __typename?: 'Mutation' } - & { - updateObjectDetails: ( - { __typename?: 'UpdateObjectDetailsResult' } - & Pick - ) - } -); +export type UpdateObjectDetailsMutation = { __typename?: 'Mutation' } & { + updateObjectDetails: { __typename?: 'UpdateObjectDetailsResult' } & Pick; +}; export type CreateItemMutationVariables = Exact<{ input: CreateItemInput; }>; - -export type CreateItemMutation = ( - { __typename?: 'Mutation' } - & { - createItem: ( - { __typename?: 'CreateItemResult' } - & { - Item?: Maybe<( - { __typename?: 'Item' } - & Pick - )> - } - ) - } -); +export type CreateItemMutation = { __typename?: 'Mutation' } & { + createItem: { __typename?: 'CreateItemResult' } & { + Item?: Maybe<{ __typename?: 'Item' } & Pick>; + }; +}; export type CreateProjectMutationVariables = Exact<{ input: CreateProjectInput; }>; - -export type CreateProjectMutation = ( - { __typename?: 'Mutation' } - & { - createProject: ( - { __typename?: 'CreateProjectResult' } - & { - Project?: Maybe<( - { __typename?: 'Project' } - & Pick - )> - } - ) - } -); +export type CreateProjectMutation = { __typename?: 'Mutation' } & { + createProject: { __typename?: 'CreateProjectResult' } & { + Project?: Maybe<{ __typename?: 'Project' } & Pick>; + }; +}; export type CreateSubjectMutationVariables = Exact<{ input: CreateSubjectInput; }>; - -export type CreateSubjectMutation = ( - { __typename?: 'Mutation' } - & { - createSubject: ( - { __typename?: 'CreateSubjectResult' } - & { - Subject?: Maybe<( - { __typename?: 'Subject' } - & Pick - )> - } - ) - } -); +export type CreateSubjectMutation = { __typename?: 'Mutation' } & { + createSubject: { __typename?: 'CreateSubjectResult' } & { + Subject?: Maybe<{ __typename?: 'Subject' } & Pick>; + }; +}; export type CreateUnitMutationVariables = Exact<{ input: CreateUnitInput; }>; - -export type CreateUnitMutation = ( - { __typename?: 'Mutation' } - & { - createUnit: ( - { __typename?: 'CreateUnitResult' } - & { - Unit?: Maybe<( - { __typename?: 'Unit' } - & Pick - )> - } - ) - } -); +export type CreateUnitMutation = { __typename?: 'Mutation' } & { + createUnit: { __typename?: 'CreateUnitResult' } & { + Unit?: Maybe<{ __typename?: 'Unit' } & Pick>; + }; +}; export type CreateUserMutationVariables = Exact<{ input: CreateUserInput; }>; - -export type CreateUserMutation = ( - { __typename?: 'Mutation' } - & { - createUser: ( - { __typename?: 'CreateUserResult' } - & { - User?: Maybe<( - { __typename?: 'User' } - & Pick - )> - } - ) - } -); +export type CreateUserMutation = { __typename?: 'Mutation' } & { + createUser: { __typename?: 'CreateUserResult' } & { + User?: Maybe<{ __typename?: 'User' } & Pick>; + }; +}; export type CreateVocabularyMutationVariables = Exact<{ input: CreateVocabularyInput; }>; - -export type CreateVocabularyMutation = ( - { __typename?: 'Mutation' } - & { - createVocabulary: ( - { __typename?: 'CreateVocabularyResult' } - & { - Vocabulary?: Maybe<( - { __typename?: 'Vocabulary' } - & Pick - )> - } - ) - } -); +export type CreateVocabularyMutation = { __typename?: 'Mutation' } & { + createVocabulary: { __typename?: 'CreateVocabularyResult' } & { + Vocabulary?: Maybe<{ __typename?: 'Vocabulary' } & Pick>; + }; +}; export type CreateVocabularySetMutationVariables = Exact<{ input: CreateVocabularySetInput; }>; - -export type CreateVocabularySetMutation = ( - { __typename?: 'Mutation' } - & { - createVocabularySet: ( - { __typename?: 'CreateVocabularySetResult' } - & { - VocabularySet?: Maybe<( - { __typename?: 'VocabularySet' } - & Pick - )> - } - ) - } -); +export type CreateVocabularySetMutation = { __typename?: 'Mutation' } & { + createVocabularySet: { __typename?: 'CreateVocabularySetResult' } & { + VocabularySet?: Maybe<{ __typename?: 'VocabularySet' } & Pick>; + }; +}; export type GetAccessPolicyQueryVariables = Exact<{ input: GetAccessPolicyInput; }>; - -export type GetAccessPolicyQuery = ( - { __typename?: 'Query' } - & { - getAccessPolicy: ( - { __typename?: 'GetAccessPolicyResult' } - & { - AccessPolicy?: Maybe<( - { __typename?: 'AccessPolicy' } - & Pick - )> - } - ) - } -); +export type GetAccessPolicyQuery = { __typename?: 'Query' } & { + getAccessPolicy: { __typename?: 'GetAccessPolicyResult' } & { + AccessPolicy?: Maybe<{ __typename?: 'AccessPolicy' } & Pick>; + }; +}; export type GetAssetQueryVariables = Exact<{ input: GetAssetInput; }>; - -export type GetAssetQuery = ( - { __typename?: 'Query' } - & { - getAsset: ( - { __typename?: 'GetAssetResult' } - & { - Asset?: Maybe<( - { __typename?: 'Asset' } - & Pick - )> - } - ) - } -); +export type GetAssetQuery = { __typename?: 'Query' } & { + getAsset: { __typename?: 'GetAssetResult' } & { + Asset?: Maybe<{ __typename?: 'Asset' } & Pick>; + }; +}; export type GetAssetVersionsDetailsQueryVariables = Exact<{ input: GetAssetVersionsDetailsInput; }>; - -export type GetAssetVersionsDetailsQuery = ( - { __typename?: 'Query' } - & { - getAssetVersionsDetails: ( - { __typename?: 'GetAssetVersionsDetailsResult' } - & Pick - & { - Details: Array<( - { __typename?: 'GetAssetVersionDetailResult' } - & Pick - & { - SubjectUnitIdentifier?: Maybe<( - { __typename?: 'SubjectUnitIdentifier' } - & Pick - )>, Project?: Maybe - )>>, Item?: Maybe<( - { __typename?: 'Item' } - & Pick - )>, CaptureDataPhoto?: Maybe<( - { __typename?: 'IngestPhotogrammetry' } - & Pick - & { - folders: Array<( - { __typename?: 'IngestFolder' } - & Pick - )>, identifiers: Array<( - { __typename?: 'IngestIdentifier' } - & Pick - )> - } - )>, Model?: Maybe<( - { __typename?: 'IngestModel' } - & Pick - & { - identifiers: Array<( - { __typename?: 'IngestIdentifier' } - & Pick - )>, uvMaps: Array<( - { __typename?: 'IngestUVMap' } - & Pick - )> - } - )>, Scene?: Maybe<( - { __typename?: 'IngestScene' } - & Pick - & { - identifiers: Array<( - { __typename?: 'IngestIdentifier' } - & Pick - )> - } - )> - } - )> +export type GetAssetVersionsDetailsQuery = { __typename?: 'Query' } & { + getAssetVersionsDetails: { __typename?: 'GetAssetVersionsDetailsResult' } & Pick & { + Details: Array< + { __typename?: 'GetAssetVersionDetailResult' } & Pick & { + SubjectUnitIdentifier?: Maybe< + { __typename?: 'SubjectUnitIdentifier' } & Pick< + SubjectUnitIdentifier, + 'idSubject' | 'SubjectName' | 'UnitAbbreviation' | 'IdentifierPublic' | 'IdentifierCollection' + > + >; + Project?: Maybe>>; + Item?: Maybe<{ __typename?: 'Item' } & Pick>; + CaptureDataPhoto?: Maybe< + { __typename?: 'IngestPhotogrammetry' } & Pick< + IngestPhotogrammetry, + | 'idAssetVersion' + | 'dateCaptured' + | 'datasetType' + | 'systemCreated' + | 'description' + | 'cameraSettingUniform' + | 'datasetFieldId' + | 'itemPositionType' + | 'itemPositionFieldId' + | 'itemArrangementFieldId' + | 'focusType' + | 'lightsourceType' + | 'backgroundRemovalMethod' + | 'clusterType' + | 'clusterGeometryFieldId' + | 'directory' + > & { + folders: Array<{ __typename?: 'IngestFolder' } & Pick>; + identifiers: Array<{ __typename?: 'IngestIdentifier' } & Pick>; } - ) - } -); + >; + Model?: Maybe< + { __typename?: 'IngestModel' } & Pick< + IngestModel, + | 'idAssetVersion' + | 'systemCreated' + | 'master' + | 'authoritative' + | 'creationMethod' + | 'modality' + | 'purpose' + | 'units' + | 'dateCaptured' + | 'modelFileType' + | 'directory' + | 'roughness' + | 'metalness' + | 'pointCount' + | 'faceCount' + | 'isWatertight' + | 'hasNormals' + | 'hasVertexColor' + | 'hasUVSpace' + | 'boundingBoxP1X' + | 'boundingBoxP1Y' + | 'boundingBoxP1Z' + | 'boundingBoxP2X' + | 'boundingBoxP2Y' + | 'boundingBoxP2Z' + > & { + identifiers: Array<{ __typename?: 'IngestIdentifier' } & Pick>; + uvMaps: Array<{ __typename?: 'IngestUVMap' } & Pick>; + } + >; + Scene?: Maybe< + { __typename?: 'IngestScene' } & Pick & { + identifiers: Array<{ __typename?: 'IngestIdentifier' } & Pick>; + } + >; + } + >; + }; +}; export type GetContentsForAssetVersionsQueryVariables = Exact<{ input: GetContentsForAssetVersionsInput; }>; +export type GetContentsForAssetVersionsQuery = { __typename?: 'Query' } & { + getContentsForAssetVersions: { __typename?: 'GetContentsForAssetVersionsResult' } & { + AssetVersionContent: Array<{ __typename?: 'AssetVersionContent' } & Pick>; + }; +}; -export type GetContentsForAssetVersionsQuery = ( - { __typename?: 'Query' } - & { - getContentsForAssetVersions: ( - { __typename?: 'GetContentsForAssetVersionsResult' } - & { - AssetVersionContent: Array<( - { __typename?: 'AssetVersionContent' } - & Pick - )> - } - ) - } -); - -export type GetUploadedAssetVersionQueryVariables = Exact<{ [key: string]: never; }>; - - -export type GetUploadedAssetVersionQuery = ( - { __typename?: 'Query' } - & { - getUploadedAssetVersion: ( - { __typename?: 'GetUploadedAssetVersionResult' } - & { - AssetVersion: Array<( - { __typename?: 'AssetVersion' } - & Pick - & { - Asset?: Maybe<( - { __typename?: 'Asset' } - & Pick - & { - VAssetType?: Maybe<( - { __typename?: 'Vocabulary' } - & Pick - )> - } - )> - } - )> +export type GetUploadedAssetVersionQueryVariables = Exact<{ [key: string]: never }>; + +export type GetUploadedAssetVersionQuery = { __typename?: 'Query' } & { + getUploadedAssetVersion: { __typename?: 'GetUploadedAssetVersionResult' } & { + AssetVersion: Array< + { __typename?: 'AssetVersion' } & Pick & { + Asset?: Maybe< + { __typename?: 'Asset' } & Pick & { + VAssetType?: Maybe<{ __typename?: 'Vocabulary' } & Pick>; } - ) - } -); + >; + } + >; + }; +}; export type GetCaptureDataQueryVariables = Exact<{ input: GetCaptureDataInput; }>; - -export type GetCaptureDataQuery = ( - { __typename?: 'Query' } - & { - getCaptureData: ( - { __typename?: 'GetCaptureDataResult' } - & { - CaptureData?: Maybe<( - { __typename?: 'CaptureData' } - & Pick - )> - } - ) - } -); +export type GetCaptureDataQuery = { __typename?: 'Query' } & { + getCaptureData: { __typename?: 'GetCaptureDataResult' } & { + CaptureData?: Maybe<{ __typename?: 'CaptureData' } & Pick>; + }; +}; export type GetCaptureDataPhotoQueryVariables = Exact<{ input: GetCaptureDataPhotoInput; }>; - -export type GetCaptureDataPhotoQuery = ( - { __typename?: 'Query' } - & { - getCaptureDataPhoto: ( - { __typename?: 'GetCaptureDataPhotoResult' } - & { - CaptureDataPhoto?: Maybe<( - { __typename?: 'CaptureDataPhoto' } - & Pick - )> - } - ) - } -); +export type GetCaptureDataPhotoQuery = { __typename?: 'Query' } & { + getCaptureDataPhoto: { __typename?: 'GetCaptureDataPhotoResult' } & { + CaptureDataPhoto?: Maybe<{ __typename?: 'CaptureDataPhoto' } & Pick>; + }; +}; export type AreCameraSettingsUniformQueryVariables = Exact<{ input: AreCameraSettingsUniformInput; }>; - -export type AreCameraSettingsUniformQuery = ( - { __typename?: 'Query' } - & { - areCameraSettingsUniform: ( - { __typename?: 'AreCameraSettingsUniformResult' } - & Pick - ) - } -); +export type AreCameraSettingsUniformQuery = { __typename?: 'Query' } & { + areCameraSettingsUniform: { __typename?: 'AreCameraSettingsUniformResult' } & Pick; +}; export type GetLicenseQueryVariables = Exact<{ input: GetLicenseInput; }>; - -export type GetLicenseQuery = ( - { __typename?: 'Query' } - & { - getLicense: ( - { __typename?: 'GetLicenseResult' } - & { - License?: Maybe<( - { __typename?: 'License' } - & Pick - )> - } - ) - } -); +export type GetLicenseQuery = { __typename?: 'Query' } & { + getLicense: { __typename?: 'GetLicenseResult' } & { + License?: Maybe<{ __typename?: 'License' } & Pick>; + }; +}; export type GetModelQueryVariables = Exact<{ input: GetModelInput; }>; +export type GetModelQuery = { __typename?: 'Query' } & { + getModel: { __typename?: 'GetModelResult' } & { + Model?: Maybe<{ __typename?: 'Model' } & Pick>; + }; +}; + +export type GetFilterViewDataQueryVariables = Exact<{ [key: string]: never }>; -export type GetModelQuery = ( - { __typename?: 'Query' } - & { - getModel: ( - { __typename?: 'GetModelResult' } - & { - Model?: Maybe<( - { __typename?: 'Model' } - & Pick - )> - } - ) - } -); - -export type GetFilterViewDataQueryVariables = Exact<{ [key: string]: never; }>; - - -export type GetFilterViewDataQuery = ( - { __typename?: 'Query' } - & { - getFilterViewData: ( - { __typename?: 'GetFilterViewDataResult' } - & { - units: Array<( - { __typename?: 'Unit' } - & Pick - & { - SystemObject?: Maybe<( - { __typename?: 'SystemObject' } - & Pick - )> - } - )>, projects: Array<( - { __typename?: 'Project' } - & Pick - & { - SystemObject?: Maybe<( - { __typename?: 'SystemObject' } - & Pick - )> - } - )> - } - ) - } -); +export type GetFilterViewDataQuery = { __typename?: 'Query' } & { + getFilterViewData: { __typename?: 'GetFilterViewDataResult' } & { + units: Array< + { __typename?: 'Unit' } & Pick & { + SystemObject?: Maybe<{ __typename?: 'SystemObject' } & Pick>; + } + >; + projects: Array< + { __typename?: 'Project' } & Pick & { + SystemObject?: Maybe<{ __typename?: 'SystemObject' } & Pick>; + } + >; + }; +}; export type GetObjectChildrenQueryVariables = Exact<{ input: GetObjectChildrenInput; }>; - -export type GetObjectChildrenQuery = ( - { __typename?: 'Query' } - & { - getObjectChildren: ( - { __typename?: 'GetObjectChildrenResult' } - & Pick - & { - entries: Array<( - { __typename?: 'NavigationResultEntry' } - & Pick - )> - } - ) - } -); +export type GetObjectChildrenQuery = { __typename?: 'Query' } & { + getObjectChildren: { __typename?: 'GetObjectChildrenResult' } & Pick & { + entries: Array<{ __typename?: 'NavigationResultEntry' } & Pick>; + }; +}; export type GetIntermediaryFileQueryVariables = Exact<{ input: GetIntermediaryFileInput; }>; - -export type GetIntermediaryFileQuery = ( - { __typename?: 'Query' } - & { - getIntermediaryFile: ( - { __typename?: 'GetIntermediaryFileResult' } - & { - IntermediaryFile?: Maybe<( - { __typename?: 'IntermediaryFile' } - & Pick - )> - } - ) - } -); +export type GetIntermediaryFileQuery = { __typename?: 'Query' } & { + getIntermediaryFile: { __typename?: 'GetIntermediaryFileResult' } & { + IntermediaryFile?: Maybe<{ __typename?: 'IntermediaryFile' } & Pick>; + }; +}; export type GetSceneQueryVariables = Exact<{ input: GetSceneInput; }>; - -export type GetSceneQuery = ( - { __typename?: 'Query' } - & { - getScene: ( - { __typename?: 'GetSceneResult' } - & { - Scene?: Maybe<( - { __typename?: 'Scene' } - & Pick - )> - } - ) - } -); +export type GetSceneQuery = { __typename?: 'Query' } & { + getScene: { __typename?: 'GetSceneResult' } & { + Scene?: Maybe<{ __typename?: 'Scene' } & Pick>; + }; +}; export type GetAssetDetailsForSystemObjectQueryVariables = Exact<{ input: GetAssetDetailsForSystemObjectInput; }>; - -export type GetAssetDetailsForSystemObjectQuery = ( - { __typename?: 'Query' } - & { - getAssetDetailsForSystemObject: ( - { __typename?: 'GetAssetDetailsForSystemObjectResult' } - & { - assetDetails: Array<( - { __typename?: 'AssetDetail' } - & Pick - )> - } - ) - } -); +export type GetAssetDetailsForSystemObjectQuery = { __typename?: 'Query' } & { + getAssetDetailsForSystemObject: { __typename?: 'GetAssetDetailsForSystemObjectResult' } & { + assetDetails: Array<{ __typename?: 'AssetDetail' } & Pick>; + }; +}; export type GetDetailsTabDataForObjectQueryVariables = Exact<{ input: GetDetailsTabDataForObjectInput; }>; - -export type GetDetailsTabDataForObjectQuery = ( - { __typename?: 'Query' } - & { - getDetailsTabDataForObject: ( - { __typename?: 'GetDetailsTabDataForObjectResult' } - & { - Unit?: Maybe<( - { __typename?: 'UnitDetailFields' } - & Pick - )>, Project?: Maybe<( - { __typename?: 'ProjectDetailFields' } - & Pick - )>, Subject?: Maybe<( - { __typename?: 'SubjectDetailFields' } - & Pick - )>, Item?: Maybe<( - { __typename?: 'ItemDetailFields' } - & Pick - )>, CaptureData?: Maybe<( - { __typename?: 'CaptureDataDetailFields' } - & Pick - & { - folders: Array<( - { __typename?: 'IngestFolder' } - & Pick - )> - } - )>, Model?: Maybe<( - { __typename?: 'ModelDetailFields' } - & Pick - & { - uvMaps: Array<( - { __typename?: 'IngestUVMap' } - & Pick - )> - } - )>, Scene?: Maybe<( - { __typename?: 'SceneDetailFields' } - & Pick - )>, IntermediaryFile?: Maybe<( - { __typename?: 'IntermediaryFileDetailFields' } - & Pick - )>, ProjectDocumentation?: Maybe<( - { __typename?: 'ProjectDocumentationDetailFields' } - & Pick - )>, Asset?: Maybe<( - { __typename?: 'AssetDetailFields' } - & Pick - )>, AssetVersion?: Maybe<( - { __typename?: 'AssetVersionDetailFields' } - & Pick - )>, Actor?: Maybe<( - { __typename?: 'ActorDetailFields' } - & Pick - )>, Stakeholder?: Maybe<( - { __typename?: 'StakeholderDetailFields' } - & Pick - )> - } - ) - } -); +export type GetDetailsTabDataForObjectQuery = { __typename?: 'Query' } & { + getDetailsTabDataForObject: { __typename?: 'GetDetailsTabDataForObjectResult' } & { + Unit?: Maybe<{ __typename?: 'UnitDetailFields' } & Pick>; + Project?: Maybe<{ __typename?: 'ProjectDetailFields' } & Pick>; + Subject?: Maybe< + { __typename?: 'SubjectDetailFields' } & Pick + >; + Item?: Maybe< + { __typename?: 'ItemDetailFields' } & Pick< + ItemDetailFields, + 'EntireSubject' | 'Altitude' | 'Latitude' | 'Longitude' | 'R0' | 'R1' | 'R2' | 'R3' | 'TS0' | 'TS1' | 'TS2' + > + >; + CaptureData?: Maybe< + { __typename?: 'CaptureDataDetailFields' } & Pick< + CaptureDataDetailFields, + | 'captureMethod' + | 'dateCaptured' + | 'datasetType' + | 'description' + | 'cameraSettingUniform' + | 'datasetFieldId' + | 'itemPositionType' + | 'itemPositionFieldId' + | 'itemArrangementFieldId' + | 'focusType' + | 'lightsourceType' + | 'backgroundRemovalMethod' + | 'clusterType' + | 'clusterGeometryFieldId' + > & { + folders: Array<{ __typename?: 'IngestFolder' } & Pick>; + } + >; + Model?: Maybe< + { __typename?: 'ModelDetailFields' } & Pick< + ModelDetailFields, + | 'size' + | 'master' + | 'authoritative' + | 'creationMethod' + | 'modality' + | 'purpose' + | 'units' + | 'dateCaptured' + | 'modelFileType' + | 'roughness' + | 'metalness' + | 'pointCount' + | 'faceCount' + | 'isWatertight' + | 'hasNormals' + | 'hasVertexColor' + | 'hasUVSpace' + | 'boundingBoxP1X' + | 'boundingBoxP1Y' + | 'boundingBoxP1Z' + | 'boundingBoxP2X' + | 'boundingBoxP2Y' + | 'boundingBoxP2Z' + > & { + uvMaps: Array<{ __typename?: 'IngestUVMap' } & Pick>; + } + >; + Scene?: Maybe<{ __typename?: 'SceneDetailFields' } & Pick>; + IntermediaryFile?: Maybe<{ __typename?: 'IntermediaryFileDetailFields' } & Pick>; + ProjectDocumentation?: Maybe<{ __typename?: 'ProjectDocumentationDetailFields' } & Pick>; + Asset?: Maybe<{ __typename?: 'AssetDetailFields' } & Pick>; + AssetVersion?: Maybe<{ __typename?: 'AssetVersionDetailFields' } & Pick>; + Actor?: Maybe<{ __typename?: 'ActorDetailFields' } & Pick>; + Stakeholder?: Maybe< + { __typename?: 'StakeholderDetailFields' } & Pick< + StakeholderDetailFields, + 'OrganizationName' | 'EmailAddress' | 'PhoneNumberMobile' | 'PhoneNumberOffice' | 'MailingAddress' + > + >; + }; +}; export type GetSourceObjectIdentiferQueryVariables = Exact<{ input: GetSourceObjectIdentiferInput; }>; - -export type GetSourceObjectIdentiferQuery = ( - { __typename?: 'Query' } - & { - getSourceObjectIdentifer: ( - { __typename?: 'GetSourceObjectIdentiferResult' } - & { - sourceObjectIdentifiers: Array<( - { __typename?: 'SourceObjectIdentifier' } - & Pick - )> - } - ) - } -); +export type GetSourceObjectIdentiferQuery = { __typename?: 'Query' } & { + getSourceObjectIdentifer: { __typename?: 'GetSourceObjectIdentiferResult' } & { + sourceObjectIdentifiers: Array<{ __typename?: 'SourceObjectIdentifier' } & Pick>; + }; +}; export type GetSystemObjectDetailsQueryVariables = Exact<{ input: GetSystemObjectDetailsInput; }>; - -export type GetSystemObjectDetailsQuery = ( - { __typename?: 'Query' } - & { - getSystemObjectDetails: ( - { __typename?: 'GetSystemObjectDetailsResult' } - & Pick - & { - identifiers: Array<( - { __typename?: 'IngestIdentifier' } - & Pick - )>, unit?: Maybe<( - { __typename?: 'RepositoryPath' } - & Pick - )>, project?: Maybe<( - { __typename?: 'RepositoryPath' } - & Pick - )>, subject?: Maybe<( - { __typename?: 'RepositoryPath' } - & Pick - )>, item?: Maybe<( - { __typename?: 'RepositoryPath' } - & Pick - )>, objectAncestors: Array - )>>, sourceObjects: Array<( - { __typename?: 'RelatedObject' } - & Pick - )>, derivedObjects: Array<( - { __typename?: 'RelatedObject' } - & Pick - )> - } - ) - } -); +export type GetSystemObjectDetailsQuery = { __typename?: 'Query' } & { + getSystemObjectDetails: { __typename?: 'GetSystemObjectDetailsResult' } & Pick< + GetSystemObjectDetailsResult, + 'idObject' | 'name' | 'retired' | 'objectType' | 'allowed' | 'publishedState' | 'thumbnail' + > & { + identifiers: Array<{ __typename?: 'IngestIdentifier' } & Pick>; + unit?: Maybe<{ __typename?: 'RepositoryPath' } & Pick>; + project?: Maybe<{ __typename?: 'RepositoryPath' } & Pick>; + subject?: Maybe<{ __typename?: 'RepositoryPath' } & Pick>; + item?: Maybe<{ __typename?: 'RepositoryPath' } & Pick>; + objectAncestors: Array>>; + sourceObjects: Array<{ __typename?: 'RelatedObject' } & Pick>; + derivedObjects: Array<{ __typename?: 'RelatedObject' } & Pick>; + }; +}; export type GetVersionsForSystemObjectQueryVariables = Exact<{ input: GetVersionsForSystemObjectInput; }>; - -export type GetVersionsForSystemObjectQuery = ( - { __typename?: 'Query' } - & { - getVersionsForSystemObject: ( - { __typename?: 'GetVersionsForSystemObjectResult' } - & { - versions: Array<( - { __typename?: 'DetailVersion' } - & Pick - )> - } - ) - } -); +export type GetVersionsForSystemObjectQuery = { __typename?: 'Query' } & { + getVersionsForSystemObject: { __typename?: 'GetVersionsForSystemObjectResult' } & { + versions: Array<{ __typename?: 'DetailVersion' } & Pick>; + }; +}; export type GetIngestionItemsForSubjectsQueryVariables = Exact<{ input: GetIngestionItemsForSubjectsInput; }>; - -export type GetIngestionItemsForSubjectsQuery = ( - { __typename?: 'Query' } - & { - getIngestionItemsForSubjects: ( - { __typename?: 'GetIngestionItemsForSubjectsResult' } - & { - Item: Array<( - { __typename?: 'Item' } - & Pick - )> - } - ) - } -); +export type GetIngestionItemsForSubjectsQuery = { __typename?: 'Query' } & { + getIngestionItemsForSubjects: { __typename?: 'GetIngestionItemsForSubjectsResult' } & { + Item: Array<{ __typename?: 'Item' } & Pick>; + }; +}; export type GetIngestionProjectsForSubjectsQueryVariables = Exact<{ input: GetIngestionProjectsForSubjectsInput; }>; - -export type GetIngestionProjectsForSubjectsQuery = ( - { __typename?: 'Query' } - & { - getIngestionProjectsForSubjects: ( - { __typename?: 'GetIngestionProjectsForSubjectsResult' } - & { - Project: Array<( - { __typename?: 'Project' } - & Pick - )> - } - ) - } -); +export type GetIngestionProjectsForSubjectsQuery = { __typename?: 'Query' } & { + getIngestionProjectsForSubjects: { __typename?: 'GetIngestionProjectsForSubjectsResult' } & { + Project: Array<{ __typename?: 'Project' } & Pick>; + }; +}; export type GetItemQueryVariables = Exact<{ input: GetItemInput; }>; - -export type GetItemQuery = ( - { __typename?: 'Query' } - & { - getItem: ( - { __typename?: 'GetItemResult' } - & { - Item?: Maybe<( - { __typename?: 'Item' } - & Pick - )> - } - ) - } -); +export type GetItemQuery = { __typename?: 'Query' } & { + getItem: { __typename?: 'GetItemResult' } & { + Item?: Maybe<{ __typename?: 'Item' } & Pick>; + }; +}; export type GetItemsForSubjectQueryVariables = Exact<{ input: GetItemsForSubjectInput; }>; - -export type GetItemsForSubjectQuery = ( - { __typename?: 'Query' } - & { - getItemsForSubject: ( - { __typename?: 'GetItemsForSubjectResult' } - & { - Item: Array<( - { __typename?: 'Item' } - & Pick - )> - } - ) - } -); +export type GetItemsForSubjectQuery = { __typename?: 'Query' } & { + getItemsForSubject: { __typename?: 'GetItemsForSubjectResult' } & { + Item: Array<{ __typename?: 'Item' } & Pick>; + }; +}; export type GetObjectsForItemQueryVariables = Exact<{ input: GetObjectsForItemInput; }>; - -export type GetObjectsForItemQuery = ( - { __typename?: 'Query' } - & { - getObjectsForItem: ( - { __typename?: 'GetObjectsForItemResult' } - & { - CaptureData: Array<( - { __typename?: 'CaptureData' } - & Pick - )>, Model: Array<( - { __typename?: 'Model' } - & Pick - )>, Scene: Array<( - { __typename?: 'Scene' } - & Pick - )>, IntermediaryFile: Array<( - { __typename?: 'IntermediaryFile' } - & Pick - )>, ProjectDocumentation: Array<( - { __typename?: 'ProjectDocumentation' } - & Pick - )> - } - ) - } -); +export type GetObjectsForItemQuery = { __typename?: 'Query' } & { + getObjectsForItem: { __typename?: 'GetObjectsForItemResult' } & { + CaptureData: Array<{ __typename?: 'CaptureData' } & Pick>; + Model: Array<{ __typename?: 'Model' } & Pick>; + Scene: Array<{ __typename?: 'Scene' } & Pick>; + IntermediaryFile: Array<{ __typename?: 'IntermediaryFile' } & Pick>; + ProjectDocumentation: Array<{ __typename?: 'ProjectDocumentation' } & Pick>; + }; +}; export type GetProjectQueryVariables = Exact<{ input: GetProjectInput; }>; - -export type GetProjectQuery = ( - { __typename?: 'Query' } - & { - getProject: ( - { __typename?: 'GetProjectResult' } - & { - Project?: Maybe<( - { __typename?: 'Project' } - & Pick - )> - } - ) - } -); +export type GetProjectQuery = { __typename?: 'Query' } & { + getProject: { __typename?: 'GetProjectResult' } & { + Project?: Maybe<{ __typename?: 'Project' } & Pick>; + }; +}; export type GetProjectDocumentationQueryVariables = Exact<{ input: GetProjectDocumentationInput; }>; - -export type GetProjectDocumentationQuery = ( - { __typename?: 'Query' } - & { - getProjectDocumentation: ( - { __typename?: 'GetProjectDocumentationResult' } - & { - ProjectDocumentation?: Maybe<( - { __typename?: 'ProjectDocumentation' } - & Pick - )> - } - ) - } -); +export type GetProjectDocumentationQuery = { __typename?: 'Query' } & { + getProjectDocumentation: { __typename?: 'GetProjectDocumentationResult' } & { + ProjectDocumentation?: Maybe<{ __typename?: 'ProjectDocumentation' } & Pick>; + }; +}; export type GetSubjectQueryVariables = Exact<{ input: GetSubjectInput; }>; - -export type GetSubjectQuery = ( - { __typename?: 'Query' } - & { - getSubject: ( - { __typename?: 'GetSubjectResult' } - & { - Subject?: Maybe<( - { __typename?: 'Subject' } - & Pick - )> - } - ) - } -); +export type GetSubjectQuery = { __typename?: 'Query' } & { + getSubject: { __typename?: 'GetSubjectResult' } & { + Subject?: Maybe<{ __typename?: 'Subject' } & Pick>; + }; +}; export type GetSubjectsForUnitQueryVariables = Exact<{ input: GetSubjectsForUnitInput; }>; - -export type GetSubjectsForUnitQuery = ( - { __typename?: 'Query' } - & { - getSubjectsForUnit: ( - { __typename?: 'GetSubjectsForUnitResult' } - & { - Subject: Array<( - { __typename?: 'Subject' } - & Pick - )> - } - ) - } -); +export type GetSubjectsForUnitQuery = { __typename?: 'Query' } & { + getSubjectsForUnit: { __typename?: 'GetSubjectsForUnitResult' } & { + Subject: Array<{ __typename?: 'Subject' } & Pick>; + }; +}; export type GetUnitQueryVariables = Exact<{ input: GetUnitInput; }>; - -export type GetUnitQuery = ( - { __typename?: 'Query' } - & { - getUnit: ( - { __typename?: 'GetUnitResult' } - & { - Unit?: Maybe<( - { __typename?: 'Unit' } - & Pick - )> - } - ) - } -); +export type GetUnitQuery = { __typename?: 'Query' } & { + getUnit: { __typename?: 'GetUnitResult' } & { + Unit?: Maybe<{ __typename?: 'Unit' } & Pick>; + }; +}; export type SearchIngestionSubjectsQueryVariables = Exact<{ input: SearchIngestionSubjectsInput; }>; +export type SearchIngestionSubjectsQuery = { __typename?: 'Query' } & { + searchIngestionSubjects: { __typename?: 'SearchIngestionSubjectsResult' } & { + SubjectUnitIdentifier: Array< + { __typename?: 'SubjectUnitIdentifier' } & Pick + >; + }; +}; -export type SearchIngestionSubjectsQuery = ( - { __typename?: 'Query' } - & { - searchIngestionSubjects: ( - { __typename?: 'SearchIngestionSubjectsResult' } - & { - SubjectUnitIdentifier: Array<( - { __typename?: 'SubjectUnitIdentifier' } - & Pick - )> - } - ) - } -); - -export type GetCurrentUserQueryVariables = Exact<{ [key: string]: never; }>; - +export type GetCurrentUserQueryVariables = Exact<{ [key: string]: never }>; -export type GetCurrentUserQuery = ( - { __typename?: 'Query' } - & { - getCurrentUser: ( - { __typename?: 'GetCurrentUserResult' } - & { - User?: Maybe<( - { __typename?: 'User' } - & Pick - )> - } - ) - } -); +export type GetCurrentUserQuery = { __typename?: 'Query' } & { + getCurrentUser: { __typename?: 'GetCurrentUserResult' } & { + User?: Maybe< + { __typename?: 'User' } & Pick< + User, + 'idUser' | 'Name' | 'Active' | 'DateActivated' | 'DateDisabled' | 'EmailAddress' | 'EmailSettings' | 'SecurityID' | 'WorkflowNotificationTime' + > + >; + }; +}; export type GetUserQueryVariables = Exact<{ input: GetUserInput; }>; - -export type GetUserQuery = ( - { __typename?: 'Query' } - & { - getUser: ( - { __typename?: 'GetUserResult' } - & { - User?: Maybe<( - { __typename?: 'User' } - & Pick - )> - } - ) - } -); +export type GetUserQuery = { __typename?: 'Query' } & { + getUser: { __typename?: 'GetUserResult' } & { + User?: Maybe<{ __typename?: 'User' } & Pick>; + }; +}; export type GetVocabularyQueryVariables = Exact<{ input: GetVocabularyInput; }>; - -export type GetVocabularyQuery = ( - { __typename?: 'Query' } - & { - getVocabulary: ( - { __typename?: 'GetVocabularyResult' } - & { - Vocabulary?: Maybe<( - { __typename?: 'Vocabulary' } - & Pick - )> - } - ) - } -); +export type GetVocabularyQuery = { __typename?: 'Query' } & { + getVocabulary: { __typename?: 'GetVocabularyResult' } & { + Vocabulary?: Maybe<{ __typename?: 'Vocabulary' } & Pick>; + }; +}; export type GetVocabularyEntriesQueryVariables = Exact<{ input: GetVocabularyEntriesInput; }>; - -export type GetVocabularyEntriesQuery = ( - { __typename?: 'Query' } - & { - getVocabularyEntries: ( - { __typename?: 'GetVocabularyEntriesResult' } - & { - VocabularyEntries: Array<( - { __typename?: 'VocabularyEntry' } - & Pick - & { - Vocabulary: Array<( - { __typename?: 'Vocabulary' } - & Pick - )> - } - )> - } - ) - } -); +export type GetVocabularyEntriesQuery = { __typename?: 'Query' } & { + getVocabularyEntries: { __typename?: 'GetVocabularyEntriesResult' } & { + VocabularyEntries: Array< + { __typename?: 'VocabularyEntry' } & Pick & { + Vocabulary: Array<{ __typename?: 'Vocabulary' } & Pick>; + } + >; + }; +}; export type GetWorkflowQueryVariables = Exact<{ input: GetWorkflowInput; }>; - -export type GetWorkflowQuery = ( - { __typename?: 'Query' } - & { - getWorkflow: ( - { __typename?: 'GetWorkflowResult' } - & { - Workflow?: Maybe<( - { __typename?: 'Workflow' } - & Pick - )> - } - ) - } -); - +export type GetWorkflowQuery = { __typename?: 'Query' } & { + getWorkflow: { __typename?: 'GetWorkflowResult' } & { + Workflow?: Maybe<{ __typename?: 'Workflow' } & Pick>; + }; +}; export const DiscardUploadedAssetVersionsDocument = gql` mutation discardUploadedAssetVersions($input: DiscardUploadedAssetVersionsInput!) { - discardUploadedAssetVersions(input: $input) { - success - } -} - `; + discardUploadedAssetVersions(input: $input) { + success + } + } +`; export type DiscardUploadedAssetVersionsMutationFn = Apollo.MutationFunction; /** @@ -3181,7 +2674,9 @@ export type DiscardUploadedAssetVersionsMutationFn = Apollo.MutationFunction) { +export function useDiscardUploadedAssetVersionsMutation( + baseOptions?: Apollo.MutationHookOptions +) { return Apollo.useMutation(DiscardUploadedAssetVersionsDocument, baseOptions); } export type DiscardUploadedAssetVersionsMutationHookResult = ReturnType; @@ -3189,13 +2684,13 @@ export type DiscardUploadedAssetVersionsMutationResult = Apollo.MutationResult; export const UploadAssetDocument = gql` mutation uploadAsset($file: Upload!, $type: Int!) { - uploadAsset(file: $file, type: $type) { - status - idAssetVersions - error - } -} - `; + uploadAsset(file: $file, type: $type) { + status + idAssetVersions + error + } + } +`; export type UploadAssetMutationFn = Apollo.MutationFunction; /** @@ -3224,13 +2719,13 @@ export type UploadAssetMutationResult = Apollo.MutationResult; export const CreateCaptureDataDocument = gql` mutation createCaptureData($input: CreateCaptureDataInput!) { - createCaptureData(input: $input) { - CaptureData { - idCaptureData + createCaptureData(input: $input) { + CaptureData { + idCaptureData + } + } } - } -} - `; +`; export type CreateCaptureDataMutationFn = Apollo.MutationFunction; /** @@ -3258,13 +2753,13 @@ export type CreateCaptureDataMutationResult = Apollo.MutationResult; export const CreateCaptureDataPhotoDocument = gql` mutation createCaptureDataPhoto($input: CreateCaptureDataPhotoInput!) { - createCaptureDataPhoto(input: $input) { - CaptureDataPhoto { - idCaptureDataPhoto + createCaptureDataPhoto(input: $input) { + CaptureDataPhoto { + idCaptureDataPhoto + } + } } - } -} - `; +`; export type CreateCaptureDataPhotoMutationFn = Apollo.MutationFunction; /** @@ -3292,11 +2787,11 @@ export type CreateCaptureDataPhotoMutationResult = Apollo.MutationResult; export const IngestDataDocument = gql` mutation ingestData($input: IngestDataInput!) { - ingestData(input: $input) { - success - } -} - `; + ingestData(input: $input) { + success + } + } +`; export type IngestDataMutationFn = Apollo.MutationFunction; /** @@ -3324,13 +2819,13 @@ export type IngestDataMutationResult = Apollo.MutationResult export type IngestDataMutationOptions = Apollo.BaseMutationOptions; export const CreateModelDocument = gql` mutation createModel($input: CreateModelInput!) { - createModel(input: $input) { - Model { - idModel + createModel(input: $input) { + Model { + idModel + } + } } - } -} - `; +`; export type CreateModelMutationFn = Apollo.MutationFunction; /** @@ -3358,13 +2853,13 @@ export type CreateModelMutationResult = Apollo.MutationResult; export const CreateSceneDocument = gql` mutation createScene($input: CreateSceneInput!) { - createScene(input: $input) { - Scene { - idScene + createScene(input: $input) { + Scene { + idScene + } + } } - } -} - `; +`; export type CreateSceneMutationFn = Apollo.MutationFunction; /** @@ -3392,11 +2887,11 @@ export type CreateSceneMutationResult = Apollo.MutationResult; export const UpdateObjectDetailsDocument = gql` mutation updateObjectDetails($input: UpdateObjectDetailsInput!) { - updateObjectDetails(input: $input) { - success - } -} - `; + updateObjectDetails(input: $input) { + success + } + } +`; export type UpdateObjectDetailsMutationFn = Apollo.MutationFunction; /** @@ -3424,13 +2919,13 @@ export type UpdateObjectDetailsMutationResult = Apollo.MutationResult; export const CreateItemDocument = gql` mutation createItem($input: CreateItemInput!) { - createItem(input: $input) { - Item { - idItem + createItem(input: $input) { + Item { + idItem + } + } } - } -} - `; +`; export type CreateItemMutationFn = Apollo.MutationFunction; /** @@ -3458,13 +2953,13 @@ export type CreateItemMutationResult = Apollo.MutationResult export type CreateItemMutationOptions = Apollo.BaseMutationOptions; export const CreateProjectDocument = gql` mutation createProject($input: CreateProjectInput!) { - createProject(input: $input) { - Project { - idProject + createProject(input: $input) { + Project { + idProject + } + } } - } -} - `; +`; export type CreateProjectMutationFn = Apollo.MutationFunction; /** @@ -3492,13 +2987,13 @@ export type CreateProjectMutationResult = Apollo.MutationResult; export const CreateSubjectDocument = gql` mutation createSubject($input: CreateSubjectInput!) { - createSubject(input: $input) { - Subject { - idSubject + createSubject(input: $input) { + Subject { + idSubject + } + } } - } -} - `; +`; export type CreateSubjectMutationFn = Apollo.MutationFunction; /** @@ -3526,13 +3021,13 @@ export type CreateSubjectMutationResult = Apollo.MutationResult; export const CreateUnitDocument = gql` mutation createUnit($input: CreateUnitInput!) { - createUnit(input: $input) { - Unit { - idUnit + createUnit(input: $input) { + Unit { + idUnit + } + } } - } -} - `; +`; export type CreateUnitMutationFn = Apollo.MutationFunction; /** @@ -3560,16 +3055,16 @@ export type CreateUnitMutationResult = Apollo.MutationResult export type CreateUnitMutationOptions = Apollo.BaseMutationOptions; export const CreateUserDocument = gql` mutation createUser($input: CreateUserInput!) { - createUser(input: $input) { - User { - idUser - Name - Active - DateActivated + createUser(input: $input) { + User { + idUser + Name + Active + DateActivated + } + } } - } -} - `; +`; export type CreateUserMutationFn = Apollo.MutationFunction; /** @@ -3597,13 +3092,13 @@ export type CreateUserMutationResult = Apollo.MutationResult export type CreateUserMutationOptions = Apollo.BaseMutationOptions; export const CreateVocabularyDocument = gql` mutation createVocabulary($input: CreateVocabularyInput!) { - createVocabulary(input: $input) { - Vocabulary { - idVocabulary + createVocabulary(input: $input) { + Vocabulary { + idVocabulary + } + } } - } -} - `; +`; export type CreateVocabularyMutationFn = Apollo.MutationFunction; /** @@ -3631,13 +3126,13 @@ export type CreateVocabularyMutationResult = Apollo.MutationResult; export const CreateVocabularySetDocument = gql` mutation createVocabularySet($input: CreateVocabularySetInput!) { - createVocabularySet(input: $input) { - VocabularySet { - idVocabularySet + createVocabularySet(input: $input) { + VocabularySet { + idVocabularySet + } + } } - } -} - `; +`; export type CreateVocabularySetMutationFn = Apollo.MutationFunction; /** @@ -3665,13 +3160,13 @@ export type CreateVocabularySetMutationResult = Apollo.MutationResult; export const GetAccessPolicyDocument = gql` query getAccessPolicy($input: GetAccessPolicyInput!) { - getAccessPolicy(input: $input) { - AccessPolicy { - idAccessPolicy + getAccessPolicy(input: $input) { + AccessPolicy { + idAccessPolicy + } + } } - } -} - `; +`; /** * __useGetAccessPolicyQuery__ @@ -3700,13 +3195,13 @@ export type GetAccessPolicyLazyQueryHookResult = ReturnType; export const GetAssetDocument = gql` query getAsset($input: GetAssetInput!) { - getAsset(input: $input) { - Asset { - idAsset + getAsset(input: $input) { + Asset { + idAsset + } + } } - } -} - `; +`; /** * __useGetAssetQuery__ @@ -3735,99 +3230,99 @@ export type GetAssetLazyQueryHookResult = ReturnType; export const GetAssetVersionsDetailsDocument = gql` query getAssetVersionsDetails($input: GetAssetVersionsDetailsInput!) { - getAssetVersionsDetails(input: $input) { - valid - Details { - idAssetVersion - SubjectUnitIdentifier { - idSubject - SubjectName - UnitAbbreviation - IdentifierPublic - IdentifierCollection - } - Project { - idProject - Name - } - Item { - idItem - Name - EntireSubject - } - CaptureDataPhoto { - idAssetVersion - dateCaptured - datasetType - systemCreated - description - cameraSettingUniform - datasetFieldId - itemPositionType - itemPositionFieldId - itemArrangementFieldId - focusType - lightsourceType - backgroundRemovalMethod - clusterType - clusterGeometryFieldId - directory - folders { - name - variantType - } - identifiers { - identifier - identifierType - } - } - Model { - idAssetVersion - systemCreated - master - authoritative - creationMethod - modality - purpose - units - dateCaptured - modelFileType - directory - identifiers { - identifier - identifierType - } - uvMaps { - name - edgeLength - mapType - } - roughness - metalness - pointCount - faceCount - isWatertight - hasNormals - hasVertexColor - hasUVSpace - boundingBoxP1X - boundingBoxP1Y - boundingBoxP1Z - boundingBoxP2X - boundingBoxP2Y - boundingBoxP2Z - } - Scene { - idAssetVersion - identifiers { - identifier - identifierType + getAssetVersionsDetails(input: $input) { + valid + Details { + idAssetVersion + SubjectUnitIdentifier { + idSubject + SubjectName + UnitAbbreviation + IdentifierPublic + IdentifierCollection + } + Project { + idProject + Name + } + Item { + idItem + Name + EntireSubject + } + CaptureDataPhoto { + idAssetVersion + dateCaptured + datasetType + systemCreated + description + cameraSettingUniform + datasetFieldId + itemPositionType + itemPositionFieldId + itemArrangementFieldId + focusType + lightsourceType + backgroundRemovalMethod + clusterType + clusterGeometryFieldId + directory + folders { + name + variantType + } + identifiers { + identifier + identifierType + } + } + Model { + idAssetVersion + systemCreated + master + authoritative + creationMethod + modality + purpose + units + dateCaptured + modelFileType + directory + identifiers { + identifier + identifierType + } + uvMaps { + name + edgeLength + mapType + } + roughness + metalness + pointCount + faceCount + isWatertight + hasNormals + hasVertexColor + hasUVSpace + boundingBoxP1X + boundingBoxP1Y + boundingBoxP1Z + boundingBoxP2X + boundingBoxP2Y + boundingBoxP2Z + } + Scene { + idAssetVersion + identifiers { + identifier + identifierType + } + } + } } - } } - } -} - `; +`; /** * __useGetAssetVersionsDetailsQuery__ @@ -3856,15 +3351,15 @@ export type GetAssetVersionsDetailsLazyQueryHookResult = ReturnType; export const GetContentsForAssetVersionsDocument = gql` query getContentsForAssetVersions($input: GetContentsForAssetVersionsInput!) { - getContentsForAssetVersions(input: $input) { - AssetVersionContent { - idAssetVersion - folders - all + getContentsForAssetVersions(input: $input) { + AssetVersionContent { + idAssetVersion + folders + all + } + } } - } -} - `; +`; /** * __useGetContentsForAssetVersionsQuery__ @@ -3893,23 +3388,23 @@ export type GetContentsForAssetVersionsLazyQueryHookResult = ReturnType; export const GetUploadedAssetVersionDocument = gql` query getUploadedAssetVersion { - getUploadedAssetVersion { - AssetVersion { - idAssetVersion - StorageSize - FileName - DateCreated - Asset { - idAsset - VAssetType { - idVocabulary - Term + getUploadedAssetVersion { + AssetVersion { + idAssetVersion + StorageSize + FileName + DateCreated + Asset { + idAsset + VAssetType { + idVocabulary + Term + } + } + } } - } } - } -} - `; +`; /** * __useGetUploadedAssetVersionQuery__ @@ -3937,13 +3432,13 @@ export type GetUploadedAssetVersionLazyQueryHookResult = ReturnType; export const GetCaptureDataDocument = gql` query getCaptureData($input: GetCaptureDataInput!) { - getCaptureData(input: $input) { - CaptureData { - idCaptureData + getCaptureData(input: $input) { + CaptureData { + idCaptureData + } + } } - } -} - `; +`; /** * __useGetCaptureDataQuery__ @@ -3972,13 +3467,13 @@ export type GetCaptureDataLazyQueryHookResult = ReturnType; export const GetCaptureDataPhotoDocument = gql` query getCaptureDataPhoto($input: GetCaptureDataPhotoInput!) { - getCaptureDataPhoto(input: $input) { - CaptureDataPhoto { - idCaptureDataPhoto + getCaptureDataPhoto(input: $input) { + CaptureDataPhoto { + idCaptureDataPhoto + } + } } - } -} - `; +`; /** * __useGetCaptureDataPhotoQuery__ @@ -4007,11 +3502,11 @@ export type GetCaptureDataPhotoLazyQueryHookResult = ReturnType; export const AreCameraSettingsUniformDocument = gql` query areCameraSettingsUniform($input: AreCameraSettingsUniformInput!) { - areCameraSettingsUniform(input: $input) { - isUniform - } -} - `; + areCameraSettingsUniform(input: $input) { + isUniform + } + } +`; /** * __useAreCameraSettingsUniformQuery__ @@ -4040,13 +3535,13 @@ export type AreCameraSettingsUniformLazyQueryHookResult = ReturnType; export const GetLicenseDocument = gql` query getLicense($input: GetLicenseInput!) { - getLicense(input: $input) { - License { - idLicense + getLicense(input: $input) { + License { + idLicense + } + } } - } -} - `; +`; /** * __useGetLicenseQuery__ @@ -4075,13 +3570,13 @@ export type GetLicenseLazyQueryHookResult = ReturnType; export const GetModelDocument = gql` query getModel($input: GetModelInput!) { - getModel(input: $input) { - Model { - idModel + getModel(input: $input) { + Model { + idModel + } + } } - } -} - `; +`; /** * __useGetModelQuery__ @@ -4110,24 +3605,24 @@ export type GetModelLazyQueryHookResult = ReturnType; export const GetFilterViewDataDocument = gql` query getFilterViewData { - getFilterViewData { - units { - idUnit - Name - SystemObject { - idSystemObject - } - } - projects { - idProject - Name - SystemObject { - idSystemObject - } + getFilterViewData { + units { + idUnit + Name + SystemObject { + idSystemObject + } + } + projects { + idProject + Name + SystemObject { + idSystemObject + } + } + } } - } -} - `; +`; /** * __useGetFilterViewDataQuery__ @@ -4155,20 +3650,20 @@ export type GetFilterViewDataLazyQueryHookResult = ReturnType; export const GetObjectChildrenDocument = gql` query getObjectChildren($input: GetObjectChildrenInput!) { - getObjectChildren(input: $input) { - success - error - entries { - idSystemObject - name - objectType - idObject - metadata + getObjectChildren(input: $input) { + success + error + entries { + idSystemObject + name + objectType + idObject + metadata + } + metadataColumns + } } - metadataColumns - } -} - `; +`; /** * __useGetObjectChildrenQuery__ @@ -4197,13 +3692,13 @@ export type GetObjectChildrenLazyQueryHookResult = ReturnType; export const GetIntermediaryFileDocument = gql` query getIntermediaryFile($input: GetIntermediaryFileInput!) { - getIntermediaryFile(input: $input) { - IntermediaryFile { - idIntermediaryFile + getIntermediaryFile(input: $input) { + IntermediaryFile { + idIntermediaryFile + } + } } - } -} - `; +`; /** * __useGetIntermediaryFileQuery__ @@ -4232,13 +3727,13 @@ export type GetIntermediaryFileLazyQueryHookResult = ReturnType; export const GetSceneDocument = gql` query getScene($input: GetSceneInput!) { - getScene(input: $input) { - Scene { - idScene + getScene(input: $input) { + Scene { + idScene + } + } } - } -} - `; +`; /** * __useGetSceneQuery__ @@ -4267,19 +3762,19 @@ export type GetSceneLazyQueryHookResult = ReturnType; export const GetAssetDetailsForSystemObjectDocument = gql` query getAssetDetailsForSystemObject($input: GetAssetDetailsForSystemObjectInput!) { - getAssetDetailsForSystemObject(input: $input) { - assetDetails { - idSystemObject - name - path - assetType - version - dateCreated - size + getAssetDetailsForSystemObject(input: $input) { + assetDetails { + idSystemObject + name + path + assetType + version + dateCreated + size + } + } } - } -} - `; +`; /** * __useGetAssetDetailsForSystemObjectQuery__ @@ -4300,7 +3795,9 @@ export const GetAssetDetailsForSystemObjectDocument = gql` export function useGetAssetDetailsForSystemObjectQuery(baseOptions?: Apollo.QueryHookOptions) { return Apollo.useQuery(GetAssetDetailsForSystemObjectDocument, baseOptions); } -export function useGetAssetDetailsForSystemObjectLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { +export function useGetAssetDetailsForSystemObjectLazyQuery( + baseOptions?: Apollo.LazyQueryHookOptions +) { return Apollo.useLazyQuery(GetAssetDetailsForSystemObjectDocument, baseOptions); } export type GetAssetDetailsForSystemObjectQueryHookResult = ReturnType; @@ -4308,127 +3805,127 @@ export type GetAssetDetailsForSystemObjectLazyQueryHookResult = ReturnType; export const GetDetailsTabDataForObjectDocument = gql` query getDetailsTabDataForObject($input: GetDetailsTabDataForObjectInput!) { - getDetailsTabDataForObject(input: $input) { - Unit { - Abbreviation - ARKPrefix - } - Project { - Description - } - Subject { - Altitude - Latitude - Longitude - R0 - R1 - R2 - R3 - TS0 - TS1 - TS2 - } - Item { - EntireSubject - Altitude - Latitude - Longitude - R0 - R1 - R2 - R3 - TS0 - TS1 - TS2 - } - CaptureData { - captureMethod - dateCaptured - datasetType - description - cameraSettingUniform - datasetFieldId - itemPositionType - itemPositionFieldId - itemArrangementFieldId - focusType - lightsourceType - backgroundRemovalMethod - clusterType - clusterGeometryFieldId - folders { - name - variantType - } - } - Model { - size - master - authoritative - creationMethod - modality - purpose - units - dateCaptured - modelFileType - uvMaps { - name - edgeLength - mapType - } - roughness - metalness - pointCount - faceCount - isWatertight - hasNormals - hasVertexColor - hasUVSpace - boundingBoxP1X - boundingBoxP1Y - boundingBoxP1Z - boundingBoxP2X - boundingBoxP2Y - boundingBoxP2Z - } - Scene { - Links - AssetType - Tours - Annotation - HasBeenQCd - IsOriented - } - IntermediaryFile { - idIntermediaryFile - } - ProjectDocumentation { - Description - } - Asset { - FilePath - AssetType - } - AssetVersion { - Creator - DateCreated - StorageSize - Ingested - Version - } - Actor { - OrganizationName - } - Stakeholder { - OrganizationName - EmailAddress - PhoneNumberMobile - PhoneNumberOffice - MailingAddress + getDetailsTabDataForObject(input: $input) { + Unit { + Abbreviation + ARKPrefix + } + Project { + Description + } + Subject { + Altitude + Latitude + Longitude + R0 + R1 + R2 + R3 + TS0 + TS1 + TS2 + } + Item { + EntireSubject + Altitude + Latitude + Longitude + R0 + R1 + R2 + R3 + TS0 + TS1 + TS2 + } + CaptureData { + captureMethod + dateCaptured + datasetType + description + cameraSettingUniform + datasetFieldId + itemPositionType + itemPositionFieldId + itemArrangementFieldId + focusType + lightsourceType + backgroundRemovalMethod + clusterType + clusterGeometryFieldId + folders { + name + variantType + } + } + Model { + size + master + authoritative + creationMethod + modality + purpose + units + dateCaptured + modelFileType + uvMaps { + name + edgeLength + mapType + } + roughness + metalness + pointCount + faceCount + isWatertight + hasNormals + hasVertexColor + hasUVSpace + boundingBoxP1X + boundingBoxP1Y + boundingBoxP1Z + boundingBoxP2X + boundingBoxP2Y + boundingBoxP2Z + } + Scene { + Links + AssetType + Tours + Annotation + HasBeenQCd + IsOriented + } + IntermediaryFile { + idIntermediaryFile + } + ProjectDocumentation { + Description + } + Asset { + FilePath + AssetType + } + AssetVersion { + Creator + DateCreated + StorageSize + Ingested + Version + } + Actor { + OrganizationName + } + Stakeholder { + OrganizationName + EmailAddress + PhoneNumberMobile + PhoneNumberOffice + MailingAddress + } + } } - } -} - `; +`; /** * __useGetDetailsTabDataForObjectQuery__ @@ -4457,14 +3954,14 @@ export type GetDetailsTabDataForObjectLazyQueryHookResult = ReturnType; export const GetSourceObjectIdentiferDocument = gql` query getSourceObjectIdentifer($input: GetSourceObjectIdentiferInput!) { - getSourceObjectIdentifer(input: $input) { - sourceObjectIdentifiers { - idSystemObject - identifier + getSourceObjectIdentifer(input: $input) { + sourceObjectIdentifiers { + idSystemObject + identifier + } + } } - } -} - `; +`; /** * __useGetSourceObjectIdentiferQuery__ @@ -4493,58 +3990,58 @@ export type GetSourceObjectIdentiferLazyQueryHookResult = ReturnType; export const GetSystemObjectDetailsDocument = gql` query getSystemObjectDetails($input: GetSystemObjectDetailsInput!) { - getSystemObjectDetails(input: $input) { - idObject - name - retired - objectType - allowed - publishedState - thumbnail - identifiers { - identifier - identifierType - } - unit { - idSystemObject - name - objectType - } - project { - idSystemObject - name - objectType - } - subject { - idSystemObject - name - objectType - } - item { - idSystemObject - name - objectType - } - objectAncestors { - idSystemObject - name - objectType - } - sourceObjects { - idSystemObject - name - identifier - objectType - } - derivedObjects { - idSystemObject - name - identifier - objectType + getSystemObjectDetails(input: $input) { + idObject + name + retired + objectType + allowed + publishedState + thumbnail + identifiers { + identifier + identifierType + } + unit { + idSystemObject + name + objectType + } + project { + idSystemObject + name + objectType + } + subject { + idSystemObject + name + objectType + } + item { + idSystemObject + name + objectType + } + objectAncestors { + idSystemObject + name + objectType + } + sourceObjects { + idSystemObject + name + identifier + objectType + } + derivedObjects { + idSystemObject + name + identifier + objectType + } + } } - } -} - `; +`; /** * __useGetSystemObjectDetailsQuery__ @@ -4573,18 +4070,18 @@ export type GetSystemObjectDetailsLazyQueryHookResult = ReturnType; export const GetVersionsForSystemObjectDocument = gql` query getVersionsForSystemObject($input: GetVersionsForSystemObjectInput!) { - getVersionsForSystemObject(input: $input) { - versions { - idSystemObject - version - name - creator - dateCreated - size + getVersionsForSystemObject(input: $input) { + versions { + idSystemObject + version + name + creator + dateCreated + size + } + } } - } -} - `; +`; /** * __useGetVersionsForSystemObjectQuery__ @@ -4613,15 +4110,15 @@ export type GetVersionsForSystemObjectLazyQueryHookResult = ReturnType; export const GetIngestionItemsForSubjectsDocument = gql` query getIngestionItemsForSubjects($input: GetIngestionItemsForSubjectsInput!) { - getIngestionItemsForSubjects(input: $input) { - Item { - idItem - EntireSubject - Name + getIngestionItemsForSubjects(input: $input) { + Item { + idItem + EntireSubject + Name + } + } } - } -} - `; +`; /** * __useGetIngestionItemsForSubjectsQuery__ @@ -4650,14 +4147,14 @@ export type GetIngestionItemsForSubjectsLazyQueryHookResult = ReturnType; export const GetIngestionProjectsForSubjectsDocument = gql` query getIngestionProjectsForSubjects($input: GetIngestionProjectsForSubjectsInput!) { - getIngestionProjectsForSubjects(input: $input) { - Project { - idProject - Name + getIngestionProjectsForSubjects(input: $input) { + Project { + idProject + Name + } + } } - } -} - `; +`; /** * __useGetIngestionProjectsForSubjectsQuery__ @@ -4675,10 +4172,14 @@ export const GetIngestionProjectsForSubjectsDocument = gql` * }, * }); */ -export function useGetIngestionProjectsForSubjectsQuery(baseOptions?: Apollo.QueryHookOptions) { +export function useGetIngestionProjectsForSubjectsQuery( + baseOptions?: Apollo.QueryHookOptions +) { return Apollo.useQuery(GetIngestionProjectsForSubjectsDocument, baseOptions); } -export function useGetIngestionProjectsForSubjectsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { +export function useGetIngestionProjectsForSubjectsLazyQuery( + baseOptions?: Apollo.LazyQueryHookOptions +) { return Apollo.useLazyQuery(GetIngestionProjectsForSubjectsDocument, baseOptions); } export type GetIngestionProjectsForSubjectsQueryHookResult = ReturnType; @@ -4686,13 +4187,13 @@ export type GetIngestionProjectsForSubjectsLazyQueryHookResult = ReturnType; export const GetItemDocument = gql` query getItem($input: GetItemInput!) { - getItem(input: $input) { - Item { - idItem + getItem(input: $input) { + Item { + idItem + } + } } - } -} - `; +`; /** * __useGetItemQuery__ @@ -4721,14 +4222,14 @@ export type GetItemLazyQueryHookResult = ReturnType; export type GetItemQueryResult = Apollo.QueryResult; export const GetItemsForSubjectDocument = gql` query getItemsForSubject($input: GetItemsForSubjectInput!) { - getItemsForSubject(input: $input) { - Item { - idItem - Name + getItemsForSubject(input: $input) { + Item { + idItem + Name + } + } } - } -} - `; +`; /** * __useGetItemsForSubjectQuery__ @@ -4757,35 +4258,35 @@ export type GetItemsForSubjectLazyQueryHookResult = ReturnType; export const GetObjectsForItemDocument = gql` query getObjectsForItem($input: GetObjectsForItemInput!) { - getObjectsForItem(input: $input) { - CaptureData { - idCaptureData - DateCaptured - Description - } - Model { - idModel - Authoritative - DateCreated - } - Scene { - idScene - HasBeenQCd - IsOriented - Name - } - IntermediaryFile { - idIntermediaryFile - DateCreated - } - ProjectDocumentation { - idProjectDocumentation - Description - Name + getObjectsForItem(input: $input) { + CaptureData { + idCaptureData + DateCaptured + Description + } + Model { + idModel + Authoritative + DateCreated + } + Scene { + idScene + HasBeenQCd + IsOriented + Name + } + IntermediaryFile { + idIntermediaryFile + DateCreated + } + ProjectDocumentation { + idProjectDocumentation + Description + Name + } + } } - } -} - `; +`; /** * __useGetObjectsForItemQuery__ @@ -4814,13 +4315,13 @@ export type GetObjectsForItemLazyQueryHookResult = ReturnType; export const GetProjectDocument = gql` query getProject($input: GetProjectInput!) { - getProject(input: $input) { - Project { - idProject + getProject(input: $input) { + Project { + idProject + } + } } - } -} - `; +`; /** * __useGetProjectQuery__ @@ -4849,13 +4350,13 @@ export type GetProjectLazyQueryHookResult = ReturnType; export const GetProjectDocumentationDocument = gql` query getProjectDocumentation($input: GetProjectDocumentationInput!) { - getProjectDocumentation(input: $input) { - ProjectDocumentation { - idProjectDocumentation + getProjectDocumentation(input: $input) { + ProjectDocumentation { + idProjectDocumentation + } + } } - } -} - `; +`; /** * __useGetProjectDocumentationQuery__ @@ -4884,13 +4385,13 @@ export type GetProjectDocumentationLazyQueryHookResult = ReturnType; export const GetSubjectDocument = gql` query getSubject($input: GetSubjectInput!) { - getSubject(input: $input) { - Subject { - idSubject + getSubject(input: $input) { + Subject { + idSubject + } + } } - } -} - `; +`; /** * __useGetSubjectQuery__ @@ -4919,14 +4420,14 @@ export type GetSubjectLazyQueryHookResult = ReturnType; export const GetSubjectsForUnitDocument = gql` query getSubjectsForUnit($input: GetSubjectsForUnitInput!) { - getSubjectsForUnit(input: $input) { - Subject { - idSubject - Name + getSubjectsForUnit(input: $input) { + Subject { + idSubject + Name + } + } } - } -} - `; +`; /** * __useGetSubjectsForUnitQuery__ @@ -4955,13 +4456,13 @@ export type GetSubjectsForUnitLazyQueryHookResult = ReturnType; export const GetUnitDocument = gql` query getUnit($input: GetUnitInput!) { - getUnit(input: $input) { - Unit { - idUnit + getUnit(input: $input) { + Unit { + idUnit + } + } } - } -} - `; +`; /** * __useGetUnitQuery__ @@ -4990,17 +4491,17 @@ export type GetUnitLazyQueryHookResult = ReturnType; export type GetUnitQueryResult = Apollo.QueryResult; export const SearchIngestionSubjectsDocument = gql` query searchIngestionSubjects($input: SearchIngestionSubjectsInput!) { - searchIngestionSubjects(input: $input) { - SubjectUnitIdentifier { - idSubject - SubjectName - UnitAbbreviation - IdentifierPublic - IdentifierCollection + searchIngestionSubjects(input: $input) { + SubjectUnitIdentifier { + idSubject + SubjectName + UnitAbbreviation + IdentifierPublic + IdentifierCollection + } + } } - } -} - `; +`; /** * __useSearchIngestionSubjectsQuery__ @@ -5029,21 +4530,21 @@ export type SearchIngestionSubjectsLazyQueryHookResult = ReturnType; export const GetCurrentUserDocument = gql` query getCurrentUser { - getCurrentUser { - User { - idUser - Name - Active - DateActivated - DateDisabled - EmailAddress - EmailSettings - SecurityID - WorkflowNotificationTime + getCurrentUser { + User { + idUser + Name + Active + DateActivated + DateDisabled + EmailAddress + EmailSettings + SecurityID + WorkflowNotificationTime + } + } } - } -} - `; +`; /** * __useGetCurrentUserQuery__ @@ -5071,16 +4572,16 @@ export type GetCurrentUserLazyQueryHookResult = ReturnType; export const GetUserDocument = gql` query getUser($input: GetUserInput!) { - getUser(input: $input) { - User { - idUser - Name - Active - DateActivated + getUser(input: $input) { + User { + idUser + Name + Active + DateActivated + } + } } - } -} - `; +`; /** * __useGetUserQuery__ @@ -5109,13 +4610,13 @@ export type GetUserLazyQueryHookResult = ReturnType; export type GetUserQueryResult = Apollo.QueryResult; export const GetVocabularyDocument = gql` query getVocabulary($input: GetVocabularyInput!) { - getVocabulary(input: $input) { - Vocabulary { - idVocabulary + getVocabulary(input: $input) { + Vocabulary { + idVocabulary + } + } } - } -} - `; +`; /** * __useGetVocabularyQuery__ @@ -5144,17 +4645,17 @@ export type GetVocabularyLazyQueryHookResult = ReturnType; export const GetVocabularyEntriesDocument = gql` query getVocabularyEntries($input: GetVocabularyEntriesInput!) { - getVocabularyEntries(input: $input) { - VocabularyEntries { - eVocabSetID - Vocabulary { - idVocabulary - Term - } + getVocabularyEntries(input: $input) { + VocabularyEntries { + eVocabSetID + Vocabulary { + idVocabulary + Term + } + } + } } - } -} - `; +`; /** * __useGetVocabularyEntriesQuery__ @@ -5183,13 +4684,13 @@ export type GetVocabularyEntriesLazyQueryHookResult = ReturnType; export const GetWorkflowDocument = gql` query getWorkflow($input: GetWorkflowInput!) { - getWorkflow(input: $input) { - Workflow { - idWorkflow + getWorkflow(input: $input) { + Workflow { + idWorkflow + } + } } - } -} - `; +`; /** * __useGetWorkflowQuery__ @@ -5215,4 +4716,4 @@ export function useGetWorkflowLazyQuery(baseOptions?: Apollo.LazyQueryHookOption } export type GetWorkflowQueryHookResult = ReturnType; export type GetWorkflowLazyQueryHookResult = ReturnType; -export type GetWorkflowQueryResult = Apollo.QueryResult; \ No newline at end of file +export type GetWorkflowQueryResult = Apollo.QueryResult; diff --git a/server/cache/SystemObjectCache.ts b/server/cache/SystemObjectCache.ts index 5b7627e38..6bbfe2859 100644 --- a/server/cache/SystemObjectCache.ts +++ b/server/cache/SystemObjectCache.ts @@ -4,13 +4,13 @@ import { CacheControl } from './CacheControl'; import { SystemObject, eSystemObjectType } from '../db'; export type ObjectIDAndType = { - idObject: number, - eObjectType: eSystemObjectType, + idObject: number; + eObjectType: eSystemObjectType; }; export type SystemObjectInfo = { - idSystemObject: number, - Retired: boolean, + idSystemObject: number; + Retired: boolean; }; export class SystemObjectCache { diff --git a/server/config/solr/data/packrat/conf/schema.xml b/server/config/solr/data/packrat/conf/schema.xml index 10d14ec79..5a1d3ebcc 100644 --- a/server/config/solr/data/packrat/conf/schema.xml +++ b/server/config/solr/data/packrat/conf/schema.xml @@ -56,21 +56,21 @@ - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + @@ -105,6 +105,9 @@ + + + @@ -113,7 +116,6 @@ - diff --git a/server/db/api/Model.ts b/server/db/api/Model.ts index cf6d6c037..4f31a6c12 100644 --- a/server/db/api/Model.ts +++ b/server/db/api/Model.ts @@ -1,146 +1,191 @@ -/* eslint-disable camelcase */ -import { Model as ModelBase, SystemObject as SystemObjectBase, join } from '@prisma/client'; -import { SystemObject, SystemObjectBased } from '..'; -import * as DBC from '../connection'; -import * as LOG from '../../utils/logger'; - -export class Model extends DBC.DBObject implements ModelBase, SystemObjectBased { - idModel!: number; - Authoritative!: boolean; - DateCreated!: Date; - idAssetThumbnail!: number | null; - idVCreationMethod!: number; - idVModality!: number; - idVPurpose!: number; - idVUnits!: number; - Master!: boolean; - - private idAssetThumbnailOrig!: number | null; - - constructor(input: ModelBase) { - super(input); - } - - protected updateCachedValues(): void { - this.idAssetThumbnailOrig = this.idAssetThumbnail; - } - - protected async createWorker(): Promise { - try { - const { DateCreated, idVCreationMethod, Master, Authoritative, idVModality, idVUnits, idVPurpose, idAssetThumbnail } = this; - ({ idModel: this.idModel, DateCreated: this.DateCreated, idVCreationMethod: this.idVCreationMethod, - Master: this.Master, Authoritative: this.Authoritative, idVModality: this.idVModality, - idVUnits: this.idVUnits, idVPurpose: this.idVPurpose, idAssetThumbnail: this.idAssetThumbnail } = - await DBC.DBConnection.prisma.model.create({ - data: { - DateCreated, - Vocabulary_Model_idVCreationMethodToVocabulary: { connect: { idVocabulary: idVCreationMethod }, }, - Master, - Authoritative, - Vocabulary_Model_idVModalityToVocabulary: { connect: { idVocabulary: idVModality }, }, - Vocabulary_Model_idVUnitsToVocabulary: { connect: { idVocabulary: idVUnits }, }, - Vocabulary_Model_idVPurposeToVocabulary: { connect: { idVocabulary: idVPurpose }, }, - Asset: idAssetThumbnail ? { connect: { idAsset: idAssetThumbnail }, } : undefined, - SystemObject: { create: { Retired: false }, }, - }, - })); - return true; - } catch (error) /* istanbul ignore next */ { - LOG.logger.error('DBAPI.Model.create', error); - return false; - } - } - - protected async updateWorker(): Promise { - try { - const { idModel, DateCreated, idVCreationMethod, Master, Authoritative, idVModality, idVUnits, - idVPurpose, idAssetThumbnail, idAssetThumbnailOrig } = this; - const retValue: boolean = await DBC.DBConnection.prisma.model.update({ - where: { idModel, }, - data: { - DateCreated, - Vocabulary_Model_idVCreationMethodToVocabulary: { connect: { idVocabulary: idVCreationMethod }, }, - Master, - Authoritative, - Vocabulary_Model_idVModalityToVocabulary: { connect: { idVocabulary: idVModality }, }, - Vocabulary_Model_idVUnitsToVocabulary: { connect: { idVocabulary: idVUnits }, }, - Vocabulary_Model_idVPurposeToVocabulary: { connect: { idVocabulary: idVPurpose }, }, - Asset: idAssetThumbnail ? { connect: { idAsset: idAssetThumbnail }, } : idAssetThumbnailOrig ? { disconnect: true, } : undefined, - }, - }) ? true : /* istanbul ignore next */ false; - return retValue; - } catch (error) /* istanbul ignore next */ { - LOG.logger.error('DBAPI.Model.update', error); - return false; - } - } - - async fetchSystemObject(): Promise { - try { - const { idModel } = this; - return DBC.CopyObject( - await DBC.DBConnection.prisma.systemObject.findOne({ where: { idModel, }, }), SystemObject); - } catch (error) /* istanbul ignore next */ { - LOG.logger.error('DBAPI.model.fetchSystemObject', error); - return null; - } - } - - static async fetch(idModel: number): Promise { - if (!idModel) - return null; - try { - return DBC.CopyObject( - await DBC.DBConnection.prisma.model.findOne({ where: { idModel, }, }), Model); - } catch (error) /* istanbul ignore next */ { - LOG.logger.error('DBAPI.Model.fetch', error); - return null; - } - } - - static async fetchAll(): Promise { - try { - return DBC.CopyArray( - await DBC.DBConnection.prisma.model.findMany(), Model); - } catch (error) /* istanbul ignore next */ { - LOG.logger.error('DBAPI.Model.fetchAll', error); - return null; - } - } - - static async fetchFromXref(idScene: number): Promise { - if (!idScene) - return null; - try { - return DBC.CopyArray( - await DBC.DBConnection.prisma.model.findMany({ where: { ModelSceneXref: { some: { idScene }, }, }, }), Model); - } catch (error) /* istanbul ignore next */ { - LOG.logger.error('DBAPI.fetchModelFromXref', error); - return null; - } - } - - /** - * Computes the array of Models that are connected to any of the specified items. - * Models are connected to system objects; we examine those system objects which are in a *derived* relationship - * to system objects connected to any of the specified items. - * @param idItem Array of Item.idItem - */ - static async fetchDerivedFromItems(idItem: number[]): Promise { - if (!idItem || idItem.length == 0) - return null; - try { - return DBC.CopyArray( - await DBC.DBConnection.prisma.$queryRaw` - SELECT DISTINCT M.* - FROM Model AS M - JOIN SystemObject AS SOM ON (M.idModel = SOM.idModel) - JOIN SystemObjectXref AS SOX ON (SOM.idSystemObject = SOX.idSystemObjectDerived) - JOIN SystemObject AS SOI ON (SOX.idSystemObjectMaster = SOI.idSystemObject) - WHERE SOI.idItem IN (${join(idItem)})`, Model); - } catch (error) /* istanbul ignore next */ { - LOG.logger.error('DBAPI.Model.fetchDerivedFromItems', error); - return null; - } - } -} +/* eslint-disable camelcase */ +import { Model as ModelBase, SystemObject as SystemObjectBase, join } from '@prisma/client'; +import { ModelGeometryFile, ModelUVMapFile, ModelUVMapChannel, SystemObject, SystemObjectBased } from '..'; +import * as DBC from '../connection'; +import * as LOG from '../../utils/logger'; + +export class ModelConstellation { + model: Model; + modelGeometryFiles: ModelGeometryFile[]; + modelUVMapFiles: ModelUVMapFile[]; + modelUVMapChannels: ModelUVMapChannel[]; + + constructor(model: Model, modelGeometryFiles: ModelGeometryFile[], modelUVMapFiles: ModelUVMapFile[], modelUVMapChannels: ModelUVMapChannel[]) { + this.model = model; + this.modelGeometryFiles = modelGeometryFiles; + this.modelUVMapFiles = modelUVMapFiles; + this.modelUVMapChannels = modelUVMapChannels; + } + + static async fetch(idModel: number): Promise { + const model: Model | null = await Model.fetch(idModel); + if (!model) { + LOG.logger.error(`ModelConstellation.fetch() unable to compute model from ${idModel}`); + return null; + } + + const modelGeometryFiles: ModelGeometryFile[] | null = await ModelGeometryFile.fetchFromModel(idModel); + if (!modelGeometryFiles) { + LOG.logger.error(`ModelConstellation.fetch() unable to compute model geometry files from ${idModel}`); + return null; + } + + const modelUVMapFiles: ModelUVMapFile[] | null = await ModelUVMapFile.fetchFromModelGeometryFiles(modelGeometryFiles); + if (!modelUVMapFiles) { + LOG.logger.error(`ModelConstellation.fetch() unable to compute model uv map files from ${idModel}`); + return null; + } + + const modelUVMapChannels: ModelUVMapChannel[] | null = await ModelUVMapChannel.fetchFromModelUVMapFiles(modelUVMapFiles); + if (!modelUVMapChannels) { + LOG.logger.error(`ModelConstellation.fetch() unable to compute model uv map files from ${idModel}`); + return null; + } + + return new ModelConstellation(model, modelGeometryFiles, modelUVMapFiles, modelUVMapChannels); + } +} + +export class Model extends DBC.DBObject implements ModelBase, SystemObjectBased { + idModel!: number; + Name!: string; + Authoritative!: boolean; + DateCreated!: Date; + idAssetThumbnail!: number | null; + idVCreationMethod!: number; + idVModality!: number; + idVPurpose!: number; + idVUnits!: number; + Master!: boolean; + + private idAssetThumbnailOrig!: number | null; + + constructor(input: ModelBase) { + super(input); + } + + protected updateCachedValues(): void { + this.idAssetThumbnailOrig = this.idAssetThumbnail; + } + + protected async createWorker(): Promise { + try { + const { Name, DateCreated, idVCreationMethod, Master, Authoritative, idVModality, idVUnits, idVPurpose, idAssetThumbnail } = this; + ({ idModel: this.idModel, Name: this.Name, DateCreated: this.DateCreated, idVCreationMethod: this.idVCreationMethod, + Master: this.Master, Authoritative: this.Authoritative, idVModality: this.idVModality, + idVUnits: this.idVUnits, idVPurpose: this.idVPurpose, idAssetThumbnail: this.idAssetThumbnail } = + await DBC.DBConnection.prisma.model.create({ + data: { + Name, + DateCreated, + Vocabulary_Model_idVCreationMethodToVocabulary: { connect: { idVocabulary: idVCreationMethod }, }, + Master, + Authoritative, + Vocabulary_Model_idVModalityToVocabulary: { connect: { idVocabulary: idVModality }, }, + Vocabulary_Model_idVUnitsToVocabulary: { connect: { idVocabulary: idVUnits }, }, + Vocabulary_Model_idVPurposeToVocabulary: { connect: { idVocabulary: idVPurpose }, }, + Asset: idAssetThumbnail ? { connect: { idAsset: idAssetThumbnail }, } : undefined, + SystemObject: { create: { Retired: false }, }, + }, + })); + return true; + } catch (error) /* istanbul ignore next */ { + LOG.logger.error('DBAPI.Model.create', error); + return false; + } + } + + protected async updateWorker(): Promise { + try { + const { idModel, Name, DateCreated, idVCreationMethod, Master, Authoritative, idVModality, idVUnits, + idVPurpose, idAssetThumbnail, idAssetThumbnailOrig } = this; + const retValue: boolean = await DBC.DBConnection.prisma.model.update({ + where: { idModel, }, + data: { + Name, + DateCreated, + Vocabulary_Model_idVCreationMethodToVocabulary: { connect: { idVocabulary: idVCreationMethod }, }, + Master, + Authoritative, + Vocabulary_Model_idVModalityToVocabulary: { connect: { idVocabulary: idVModality }, }, + Vocabulary_Model_idVUnitsToVocabulary: { connect: { idVocabulary: idVUnits }, }, + Vocabulary_Model_idVPurposeToVocabulary: { connect: { idVocabulary: idVPurpose }, }, + Asset: idAssetThumbnail ? { connect: { idAsset: idAssetThumbnail }, } : idAssetThumbnailOrig ? { disconnect: true, } : undefined, + }, + }) ? true : /* istanbul ignore next */ false; + return retValue; + } catch (error) /* istanbul ignore next */ { + LOG.logger.error('DBAPI.Model.update', error); + return false; + } + } + + async fetchSystemObject(): Promise { + try { + const { idModel } = this; + return DBC.CopyObject( + await DBC.DBConnection.prisma.systemObject.findOne({ where: { idModel, }, }), SystemObject); + } catch (error) /* istanbul ignore next */ { + LOG.logger.error('DBAPI.model.fetchSystemObject', error); + return null; + } + } + + static async fetch(idModel: number): Promise { + if (!idModel) + return null; + try { + return DBC.CopyObject( + await DBC.DBConnection.prisma.model.findOne({ where: { idModel, }, }), Model); + } catch (error) /* istanbul ignore next */ { + LOG.logger.error('DBAPI.Model.fetch', error); + return null; + } + } + + static async fetchAll(): Promise { + try { + return DBC.CopyArray( + await DBC.DBConnection.prisma.model.findMany(), Model); + } catch (error) /* istanbul ignore next */ { + LOG.logger.error('DBAPI.Model.fetchAll', error); + return null; + } + } + + static async fetchFromXref(idScene: number): Promise { + if (!idScene) + return null; + try { + return DBC.CopyArray( + await DBC.DBConnection.prisma.model.findMany({ where: { ModelSceneXref: { some: { idScene }, }, }, }), Model); + } catch (error) /* istanbul ignore next */ { + LOG.logger.error('DBAPI.fetchModelFromXref', error); + return null; + } + } + + /** + * Computes the array of Models that are connected to any of the specified items. + * Models are connected to system objects; we examine those system objects which are in a *derived* relationship + * to system objects connected to any of the specified items. + * @param idItem Array of Item.idItem + */ + static async fetchDerivedFromItems(idItem: number[]): Promise { + if (!idItem || idItem.length == 0) + return null; + try { + return DBC.CopyArray( + await DBC.DBConnection.prisma.$queryRaw` + SELECT DISTINCT M.* + FROM Model AS M + JOIN SystemObject AS SOM ON (M.idModel = SOM.idModel) + JOIN SystemObjectXref AS SOX ON (SOM.idSystemObject = SOX.idSystemObjectDerived) + JOIN SystemObject AS SOI ON (SOX.idSystemObjectMaster = SOI.idSystemObject) + WHERE SOI.idItem IN (${join(idItem)})`, Model); + } catch (error) /* istanbul ignore next */ { + LOG.logger.error('DBAPI.Model.fetchDerivedFromItems', error); + return null; + } + } +} diff --git a/server/db/api/ModelUVMapChannel.ts b/server/db/api/ModelUVMapChannel.ts index 5c1bbc90c..e0cb952d0 100644 --- a/server/db/api/ModelUVMapChannel.ts +++ b/server/db/api/ModelUVMapChannel.ts @@ -1,5 +1,6 @@ /* eslint-disable camelcase */ -import { ModelUVMapChannel as ModelUVMapChannelBase } from '@prisma/client'; +import { ModelUVMapChannel as ModelUVMapChannelBase, join } from '@prisma/client'; +import { ModelUVMapFile } from '..'; import * as DBC from '../connection'; import * as LOG from '../../utils/logger'; @@ -75,4 +76,22 @@ export class ModelUVMapChannel extends DBC.DBObject imple return null; } } + + static async fetchFromModelUVMapFiles(modelUVMapFiles: ModelUVMapFile[]): Promise { + try { + const idModelUVMapFiles: number[] = []; + for (const modelUVMapFile of modelUVMapFiles) idModelUVMapFiles.push(modelUVMapFile.idModelUVMapFile); + + return DBC.CopyArray( + await DBC.DBConnection.prisma.$queryRaw` + SELECT DISTINCT * + FROM ModelUVMapChannel + WHERE idModelUVMapFile IN (${join(idModelUVMapFiles)})`, + ModelUVMapChannel + ); + } catch (error) /* istanbul ignore next */ { + LOG.logger.error('DBAPI.ModelUVMapChannel.fetchFromModelUVMapFiles', error); + return null; + } + } } diff --git a/server/db/api/ModelUVMapFile.ts b/server/db/api/ModelUVMapFile.ts index 72682bc3c..003646ced 100644 --- a/server/db/api/ModelUVMapFile.ts +++ b/server/db/api/ModelUVMapFile.ts @@ -1,5 +1,6 @@ /* eslint-disable camelcase */ -import { ModelUVMapFile as ModelUVMapFileBase } from '@prisma/client'; +import { ModelUVMapFile as ModelUVMapFileBase, join } from '@prisma/client'; +import { ModelGeometryFile } from '..'; import * as DBC from '../connection'; import * as LOG from '../../utils/logger'; @@ -74,4 +75,22 @@ export class ModelUVMapFile extends DBC.DBObject implements return null; } } + + static async fetchFromModelGeometryFiles(modelGeometryFiles: ModelGeometryFile[]): Promise { + try { + const idModelGeometryFiles: number[] = []; + for (const modelGeometryFile of modelGeometryFiles) idModelGeometryFiles.push(modelGeometryFile.idModelGeometryFile); + + return DBC.CopyArray( + await DBC.DBConnection.prisma.$queryRaw` + SELECT DISTINCT * + FROM ModelUVMapFile + WHERE idModelGeometryFile IN (${join(idModelGeometryFiles)})`, + ModelUVMapFile + ); + } catch (error) /* istanbul ignore next */ { + LOG.logger.error('DBAPI.ModelUVMapFile.fetchFromModelGeometryFile', error); + return null; + } + } } diff --git a/server/db/api/SystemObjectPairs.ts b/server/db/api/SystemObjectPairs.ts index 38487f10e..562b456a2 100644 --- a/server/db/api/SystemObjectPairs.ts +++ b/server/db/api/SystemObjectPairs.ts @@ -1,481 +1,501 @@ -/* eslint-disable camelcase */ -import * as P from '@prisma/client'; -import { Actor, Asset, AssetVersion, CaptureData, IntermediaryFile, Item, Model, - Project, ProjectDocumentation, Scene, Stakeholder, SystemObject, Subject, Unit } from '..'; -import * as DBC from '../connection'; -import * as LOG from '../../utils/logger'; - -type SystemObjectPairsBase = P.SystemObject -& { Actor: P.Actor | null} -& { Asset_AssetToSystemObject_idAsset: P.Asset | null} -& { AssetVersion: P.AssetVersion | null} -& { CaptureData: P.CaptureData | null} -& { IntermediaryFile: P.IntermediaryFile | null} -& { Item: P.Item | null} -& { Model: P.Model | null} -& { Project: P.Project | null} -& { ProjectDocumentation: P.ProjectDocumentation | null} -& { Scene: P.Scene | null} -& { Stakeholder: P.Stakeholder | null} -& { Subject: P.Subject | null} -& { Unit: P.Unit | null}; - -type SystemObjectActorBase = P.SystemObject & { Actor: P.Actor | null}; -type SystemObjectAssetBase = P.SystemObject & { Asset_AssetToSystemObject_idAsset: P.Asset | null}; -type SystemObjectAssetVersionBase = P.SystemObject & { AssetVersion: P.AssetVersion | null}; -type SystemObjectCaptureDataBase = P.SystemObject & { CaptureData: P.CaptureData | null}; -type SystemObjectIntermediaryFileBase = P.SystemObject & { IntermediaryFile: P.IntermediaryFile | null}; -type SystemObjectItemBase = P.SystemObject & { Item: P.Item | null}; -type SystemObjectModelBase = P.SystemObject & { Model: P.Model | null}; -type SystemObjectProjectBase = P.SystemObject & { Project: P.Project | null}; -type SystemObjectProjectDocumentationBase = P.SystemObject & { ProjectDocumentation: P.ProjectDocumentation | null}; -type SystemObjectSceneBase = P.SystemObject & { Scene: P.Scene | null}; -type SystemObjectStakeholderBase = P.SystemObject & { Stakeholder: P.Stakeholder | null}; -type SystemObjectSubjectBase = P.SystemObject & { Subject: P.Subject | null}; -type SystemObjectUnitBase = P.SystemObject & { Unit: P.Unit | null}; - -export class SystemObjectActor extends SystemObject implements SystemObjectActorBase { - Actor: Actor | null; - - constructor(input: SystemObjectActorBase) { - super(input); - this.Actor = (input.Actor) ? new Actor(input.Actor) : /* istanbul ignore next */ null; - } - - static async fetch(idActor: number): Promise { - if (!idActor) - return null; - try { - const SOPair: SystemObjectActorBase | null = - await DBC.DBConnection.prisma.systemObject.findOne({ where: { idActor, }, include: { Actor: true, }, }); - return SOPair ? new SystemObjectActor(SOPair) : null; - } catch (error) /* istanbul ignore next */ { - LOG.logger.error('DBAPI.SystemObjectActor.fetch', error); - return null; - } - } -} - -export class SystemObjectAsset extends SystemObject implements SystemObjectAssetBase { - Asset_AssetToSystemObject_idAsset: Asset | null; // wacky name produced by prisma - get Asset(): Asset | null { - return this.Asset_AssetToSystemObject_idAsset; - } - set Asset(value: Asset | null) { - this.Asset_AssetToSystemObject_idAsset = value; - } - - constructor(input: SystemObjectAssetBase) { - super(input); - this.Asset_AssetToSystemObject_idAsset = (input.Asset_AssetToSystemObject_idAsset) - ? new Asset(input.Asset_AssetToSystemObject_idAsset) : /* istanbul ignore next */ null; - } - - static async fetch(idAsset: number): Promise { - if (!idAsset) - return null; - try { - const SOPair: SystemObjectAssetBase | null = - await DBC.DBConnection.prisma.systemObject.findOne({ where: { idAsset, }, include: { Asset_AssetToSystemObject_idAsset: true, }, }); - return SOPair ? new SystemObjectAsset(SOPair) : null; - } catch (error) /* istanbul ignore next */ { - LOG.logger.error('DBAPI.SystemObjectAsset.fetch', error); - return null; - } - } -} - -export class SystemObjectAssetVersion extends SystemObject implements SystemObjectAssetVersionBase { - AssetVersion: AssetVersion | null; - - constructor(input: SystemObjectAssetVersionBase) { - super(input); - this.AssetVersion = (input.AssetVersion) ? new AssetVersion(input.AssetVersion) : /* istanbul ignore next */ null; - } - - static async fetch(idAssetVersion: number): Promise { - if (!idAssetVersion) - return null; - try { - const SOPair: SystemObjectAssetVersionBase | null = - await DBC.DBConnection.prisma.systemObject.findOne({ where: { idAssetVersion, }, include: { AssetVersion: true, }, }); - return SOPair ? new SystemObjectAssetVersion(SOPair) : null; - } catch (error) /* istanbul ignore next */ { - LOG.logger.error('DBAPI.SystemObjectAssetVersion.fetch', error); - return null; - } - } -} - -export class SystemObjectCaptureData extends SystemObject implements SystemObjectCaptureDataBase { - CaptureData: CaptureData | null; - - constructor(input: SystemObjectCaptureDataBase) { - super(input); - this.CaptureData = (input.CaptureData) ? new CaptureData(input.CaptureData) : /* istanbul ignore next */ null; - } - - static async fetch(idCaptureData: number): Promise { - if (!idCaptureData) - return null; - try { - const SOPair: SystemObjectCaptureDataBase | null = - await DBC.DBConnection.prisma.systemObject.findOne({ where: { idCaptureData, }, include: { CaptureData: true, }, }); - return SOPair ? new SystemObjectCaptureData(SOPair) : null; - } catch (error) /* istanbul ignore next */ { - LOG.logger.error('DBAPI.SystemObjectCaptureData.fetch', error); - return null; - } - } -} - -export class SystemObjectIntermediaryFile extends SystemObject implements SystemObjectIntermediaryFileBase { - IntermediaryFile: IntermediaryFile | null; - - constructor(input: SystemObjectIntermediaryFileBase) { - super(input); - this.IntermediaryFile = (input.IntermediaryFile) ? new IntermediaryFile(input.IntermediaryFile) : /* istanbul ignore next */ null; - } - - static async fetch(idIntermediaryFile: number): Promise { - if (!idIntermediaryFile) - return null; - try { - const SOPair: SystemObjectIntermediaryFileBase | null = - await DBC.DBConnection.prisma.systemObject.findOne({ where: { idIntermediaryFile, }, include: { IntermediaryFile: true, }, }); - return SOPair ? new SystemObjectIntermediaryFile(SOPair) : null; - } catch (error) /* istanbul ignore next */ { - LOG.logger.error('DBAPI.SystemObjectIntermediaryFile.fetch', error); - return null; - } - } -} - -export class SystemObjectItem extends SystemObject implements SystemObjectItemBase { - Item: Item | null; - - constructor(input: SystemObjectItemBase) { - super(input); - this.Item = (input.Item) ? new Item(input.Item) : /* istanbul ignore next */ null; - } - - static async fetch(idItem: number): Promise { - if (!idItem) - return null; - try { - const SOPair: SystemObjectItemBase | null = - await DBC.DBConnection.prisma.systemObject.findOne({ where: { idItem, }, include: { Item: true, }, }); - return SOPair ? new SystemObjectItem(SOPair) : null; - } catch (error) /* istanbul ignore next */ { - LOG.logger.error('DBAPI.SystemObjectItem.fetch', error); - return null; - } - } -} - -export class SystemObjectModel extends SystemObject implements SystemObjectModelBase { - Model: Model | null; - - constructor(input: SystemObjectModelBase) { - super(input); - this.Model = (input.Model) ? new Model(input.Model) : /* istanbul ignore next */ null; - } - - static async fetch(idModel: number): Promise { - if (!idModel) - return null; - try { - const SOPair: SystemObjectModelBase | null = - await DBC.DBConnection.prisma.systemObject.findOne({ where: { idModel, }, include: { Model: true, }, }); - return SOPair ? new SystemObjectModel(SOPair) : null; - } catch (error) /* istanbul ignore next */ { - LOG.logger.error('DBAPI.SystemObjectModel.fetch', error); - return null; - } - } -} - -export class SystemObjectProject extends SystemObject implements SystemObjectProjectBase { - Project: Project | null; - - constructor(input: SystemObjectProjectBase) { - super(input); - this.Project = (input.Project) ? new Project(input.Project) : /* istanbul ignore next */ null; - } - - static async fetch(idProject: number): Promise { - if (!idProject) - return null; - try { - const SOPair: SystemObjectProjectBase | null = - await DBC.DBConnection.prisma.systemObject.findOne({ where: { idProject, }, include: { Project: true, }, }); - return SOPair ? new SystemObjectProject(SOPair) : null; - } catch (error) /* istanbul ignore next */ { - LOG.logger.error('DBAPI.SystemObjectProject.fetch', error); - return null; - } - } -} - -export class SystemObjectProjectDocumentation extends SystemObject implements SystemObjectProjectDocumentationBase { - ProjectDocumentation: ProjectDocumentation | null; - - constructor(input: SystemObjectProjectDocumentationBase) { - super(input); - this.ProjectDocumentation = (input.ProjectDocumentation) ? new ProjectDocumentation(input.ProjectDocumentation) : /* istanbul ignore next */ null; - } - - static async fetch(idProjectDocumentation: number): Promise { - if (!idProjectDocumentation) - return null; - try { - const SOPair: SystemObjectProjectDocumentationBase | null = - await DBC.DBConnection.prisma.systemObject.findOne({ where: { idProjectDocumentation, }, include: { ProjectDocumentation: true, }, }); - return SOPair ? new SystemObjectProjectDocumentation(SOPair) : null; - } catch (error) /* istanbul ignore next */ { - LOG.logger.error('DBAPI.SystemObjectProjectDocumentation.fetch', error); - return null; - } - } -} - -export class SystemObjectScene extends SystemObject implements SystemObjectSceneBase { - Scene: Scene | null; - - constructor(input: SystemObjectSceneBase) { - super(input); - this.Scene = (input.Scene) ? new Scene(input.Scene) : /* istanbul ignore next */ null; - } - - static async fetch(idScene: number): Promise { - if (!idScene) - return null; - try { - const SOPair: SystemObjectSceneBase | null = - await DBC.DBConnection.prisma.systemObject.findOne({ where: { idScene, }, include: { Scene: true, }, }); - return SOPair ? new SystemObjectScene(SOPair) : null; - } catch (error) /* istanbul ignore next */ { - LOG.logger.error('DBAPI.SystemObjectScene.fetch', error); - return null; - } - } -} - -export class SystemObjectStakeholder extends SystemObject implements SystemObjectStakeholderBase { - Stakeholder: Stakeholder | null; - - constructor(input: SystemObjectStakeholderBase) { - super(input); - this.Stakeholder = (input.Stakeholder) ? new Stakeholder(input.Stakeholder) : /* istanbul ignore next */ null; - } - - static async fetch(idStakeholder: number): Promise { - if (!idStakeholder) - return null; - try { - const SOPair: SystemObjectStakeholderBase | null = - await DBC.DBConnection.prisma.systemObject.findOne({ where: { idStakeholder, }, include: { Stakeholder: true, }, }); - return SOPair ? new SystemObjectStakeholder(SOPair) : null; - } catch (error) /* istanbul ignore next */ { - LOG.logger.error('DBAPI.SystemObjectStakeholder.fetch', error); - return null; - } - } -} - -export class SystemObjectSubject extends SystemObject implements SystemObjectSubjectBase { - Subject: Subject | null; - - constructor(input: SystemObjectSubjectBase) { - super(input); - this.Subject = (input.Subject) ? new Subject(input.Subject) : /* istanbul ignore next */ null; - } - - static async fetch(idSubject: number): Promise { - if (!idSubject) - return null; - try { - const SOPair: SystemObjectSubjectBase | null = - await DBC.DBConnection.prisma.systemObject.findOne({ where: { idSubject, }, include: { Subject: true, }, }); - return SOPair ? new SystemObjectSubject(SOPair) : null; - } catch (error) /* istanbul ignore next */ { - LOG.logger.error('DBAPI.SystemObjectSubject.fetch', error); - return null; - } - } -} - -export class SystemObjectUnit extends SystemObject implements SystemObjectUnitBase { - Unit: Unit | null; - - constructor(input: SystemObjectUnitBase) { - super(input); - this.Unit = (input.Unit) ? new Unit(input.Unit) : /* istanbul ignore next */ null; - } - - static async fetch(idUnit: number): Promise { - if (!idUnit) - return null; - try { - const SOPair: SystemObjectUnitBase | null = - await DBC.DBConnection.prisma.systemObject.findOne({ where: { idUnit, }, include: { Unit: true, }, }); - return SOPair ? new SystemObjectUnit(SOPair) : null; - } catch (error) /* istanbul ignore next */ { - LOG.logger.error('DBAPI.SystemObjectUnit.fetch', error); - return null; - } - } -} - -export enum eSystemObjectType { - eUnit, - eProject, - eSubject, - eItem, - eCaptureData, - eModel, - eScene, - eIntermediaryFile, - eProjectDocumentation, - eAsset, - eAssetVersion, - eActor, - eStakeholder, - eUnknown -} - -export class SystemObjectPairs extends SystemObject implements SystemObjectPairsBase { - Actor: Actor | null = null; - Asset_AssetToSystemObject_idAsset: Asset | null = null; - AssetVersion: AssetVersion | null = null; - CaptureData: CaptureData | null = null; - IntermediaryFile: IntermediaryFile | null = null; - Item: Item | null = null; - Model: Model | null = null; - Project: Project | null = null; - ProjectDocumentation: ProjectDocumentation | null = null; - Scene: Scene | null = null; - Stakeholder: Stakeholder | null = null; - Subject: Subject | null = null; - Unit: Unit | null = null; - - get Asset(): Asset | null { - return this.Asset_AssetToSystemObject_idAsset; - } - set Asset(value: Asset | null) { - this.Asset_AssetToSystemObject_idAsset = value; - } - - constructor(input: SystemObjectPairsBase) { - super(input); - if (input.Actor) this.Actor = new Actor(input.Actor); - if (input.Asset_AssetToSystemObject_idAsset) this.Asset_AssetToSystemObject_idAsset = new Asset(input.Asset_AssetToSystemObject_idAsset); - if (input.AssetVersion) this.AssetVersion = new AssetVersion(input.AssetVersion); - if (input.CaptureData) this.CaptureData = new CaptureData(input.CaptureData); - if (input.IntermediaryFile) this.IntermediaryFile = new IntermediaryFile(input.IntermediaryFile); - if (input.Item) this.Item = new Item(input.Item); - if (input.Model) this.Model = new Model(input.Model); - if (input.Project) this.Project = new Project(input.Project); - if (input.ProjectDocumentation) this.ProjectDocumentation = new ProjectDocumentation(input.ProjectDocumentation); - if (input.Scene) this.Scene = new Scene(input.Scene); - if (input.Subject) this.Subject = new Subject(input.Subject); - if (input.Stakeholder) this.Stakeholder = new Stakeholder(input.Stakeholder); - if (input.Unit) this.Unit = new Unit(input.Unit); - } - - static async fetch(idSystemObject: number): Promise { - if (!idSystemObject) - return null; - try { - const SOAPB: SystemObjectPairsBase | null = - await DBC.DBConnection.prisma.systemObject.findOne({ - where: { idSystemObject, }, - include: { - Actor: true, - Asset_AssetToSystemObject_idAsset: true, - AssetVersion: true, - CaptureData: true, - IntermediaryFile: true, - Item: true, - Model: true, - Project: true, - ProjectDocumentation: true, - Scene: true, - Stakeholder: true, - Subject: true, - Unit: true - }, - }); - return (SOAPB ? new SystemObjectPairs(SOAPB) : null); - } catch (error) /* istanbul ignore next */ { - LOG.logger.error('DBAPI.SystemObjectAndPairs.fetch', error); - return null; - } - } - - static async fetchDerivedFromXref(idSystemObjectMaster: number): Promise { - if (!idSystemObjectMaster) - return null; - try { - return DBC.CopyArray( - await DBC.DBConnection.prisma.systemObject.findMany({ - where: { - SystemObjectXref_SystemObjectToSystemObjectXref_idSystemObjectDerived: { - some: { idSystemObjectMaster }, - }, - }, - include: { - Actor: true, - Asset_AssetToSystemObject_idAsset: true, - AssetVersion: true, - CaptureData: true, - IntermediaryFile: true, - Item: true, - Model: true, - Project: true, - ProjectDocumentation: true, - Scene: true, - Stakeholder: true, - Subject: true, - Unit: true - }, - }), SystemObjectPairs); - } catch (error) /* istanbul ignore next */ { - LOG.logger.error('DBAPI.SystemObjectAndPairs.fetchDerivedFromXref', error); - return null; - } - } - - static async fetchMasterFromXref(idSystemObjectDerived: number): Promise { - if (!idSystemObjectDerived) - return null; - try { - return DBC.CopyArray( - await DBC.DBConnection.prisma.systemObject.findMany({ - where: { - SystemObjectXref_SystemObjectToSystemObjectXref_idSystemObjectMaster: { - some: { idSystemObjectDerived }, - }, - }, - include: { - Actor: true, - Asset_AssetToSystemObject_idAsset: true, - AssetVersion: true, - CaptureData: true, - IntermediaryFile: true, - Item: true, - Model: true, - Project: true, - ProjectDocumentation: true, - Scene: true, - Stakeholder: true, - Subject: true, - Unit: true - }, - }), SystemObjectPairs); - } catch (error) /* istanbul ignore next */ { - LOG.logger.error('DBAPI.SystemObjectAndPairs.fetchMasterFromXref', error); - return null; - } - } -} +/* eslint-disable camelcase */ +import * as P from '@prisma/client'; +import { Actor, Asset, AssetVersion, CaptureData, IntermediaryFile, Item, Model, + Project, ProjectDocumentation, Scene, Stakeholder, SystemObject, Subject, Unit } from '..'; +import * as DBC from '../connection'; +import * as LOG from '../../utils/logger'; + +type SystemObjectPairsBase = P.SystemObject +& { Actor: P.Actor | null} +& { Asset_AssetToSystemObject_idAsset: P.Asset | null} +& { AssetVersion: P.AssetVersion | null} +& { CaptureData: P.CaptureData | null} +& { IntermediaryFile: P.IntermediaryFile | null} +& { Item: P.Item | null} +& { Model: P.Model | null} +& { Project: P.Project | null} +& { ProjectDocumentation: P.ProjectDocumentation | null} +& { Scene: P.Scene | null} +& { Stakeholder: P.Stakeholder | null} +& { Subject: P.Subject | null} +& { Unit: P.Unit | null}; + +type SystemObjectActorBase = P.SystemObject & { Actor: P.Actor | null}; +type SystemObjectAssetBase = P.SystemObject & { Asset_AssetToSystemObject_idAsset: P.Asset | null}; +type SystemObjectAssetVersionBase = P.SystemObject & { AssetVersion: P.AssetVersion | null}; +type SystemObjectCaptureDataBase = P.SystemObject & { CaptureData: P.CaptureData | null}; +type SystemObjectIntermediaryFileBase = P.SystemObject & { IntermediaryFile: P.IntermediaryFile | null}; +type SystemObjectItemBase = P.SystemObject & { Item: P.Item | null}; +type SystemObjectModelBase = P.SystemObject & { Model: P.Model | null}; +type SystemObjectProjectBase = P.SystemObject & { Project: P.Project | null}; +type SystemObjectProjectDocumentationBase = P.SystemObject & { ProjectDocumentation: P.ProjectDocumentation | null}; +type SystemObjectSceneBase = P.SystemObject & { Scene: P.Scene | null}; +type SystemObjectStakeholderBase = P.SystemObject & { Stakeholder: P.Stakeholder | null}; +type SystemObjectSubjectBase = P.SystemObject & { Subject: P.Subject | null}; +type SystemObjectUnitBase = P.SystemObject & { Unit: P.Unit | null}; + +export class SystemObjectActor extends SystemObject implements SystemObjectActorBase { + Actor: Actor | null; + + constructor(input: SystemObjectActorBase) { + super(input); + this.Actor = (input.Actor) ? new Actor(input.Actor) : /* istanbul ignore next */ null; + } + + static async fetch(idActor: number): Promise { + if (!idActor) + return null; + try { + const SOPair: SystemObjectActorBase | null = + await DBC.DBConnection.prisma.systemObject.findOne({ where: { idActor, }, include: { Actor: true, }, }); + return SOPair ? new SystemObjectActor(SOPair) : null; + } catch (error) /* istanbul ignore next */ { + LOG.logger.error('DBAPI.SystemObjectActor.fetch', error); + return null; + } + } +} + +export class SystemObjectAsset extends SystemObject implements SystemObjectAssetBase { + Asset_AssetToSystemObject_idAsset: Asset | null; // wacky name produced by prisma + get Asset(): Asset | null { + return this.Asset_AssetToSystemObject_idAsset; + } + set Asset(value: Asset | null) { + this.Asset_AssetToSystemObject_idAsset = value; + } + + constructor(input: SystemObjectAssetBase) { + super(input); + this.Asset_AssetToSystemObject_idAsset = (input.Asset_AssetToSystemObject_idAsset) + ? new Asset(input.Asset_AssetToSystemObject_idAsset) : /* istanbul ignore next */ null; + } + + static async fetch(idAsset: number): Promise { + if (!idAsset) + return null; + try { + const SOPair: SystemObjectAssetBase | null = + await DBC.DBConnection.prisma.systemObject.findOne({ where: { idAsset, }, include: { Asset_AssetToSystemObject_idAsset: true, }, }); + return SOPair ? new SystemObjectAsset(SOPair) : null; + } catch (error) /* istanbul ignore next */ { + LOG.logger.error('DBAPI.SystemObjectAsset.fetch', error); + return null; + } + } +} + +export class SystemObjectAssetVersion extends SystemObject implements SystemObjectAssetVersionBase { + AssetVersion: AssetVersion | null; + + constructor(input: SystemObjectAssetVersionBase) { + super(input); + this.AssetVersion = (input.AssetVersion) ? new AssetVersion(input.AssetVersion) : /* istanbul ignore next */ null; + } + + static async fetch(idAssetVersion: number): Promise { + if (!idAssetVersion) + return null; + try { + const SOPair: SystemObjectAssetVersionBase | null = + await DBC.DBConnection.prisma.systemObject.findOne({ where: { idAssetVersion, }, include: { AssetVersion: true, }, }); + return SOPair ? new SystemObjectAssetVersion(SOPair) : null; + } catch (error) /* istanbul ignore next */ { + LOG.logger.error('DBAPI.SystemObjectAssetVersion.fetch', error); + return null; + } + } +} + +export class SystemObjectCaptureData extends SystemObject implements SystemObjectCaptureDataBase { + CaptureData: CaptureData | null; + + constructor(input: SystemObjectCaptureDataBase) { + super(input); + this.CaptureData = (input.CaptureData) ? new CaptureData(input.CaptureData) : /* istanbul ignore next */ null; + } + + static async fetch(idCaptureData: number): Promise { + if (!idCaptureData) + return null; + try { + const SOPair: SystemObjectCaptureDataBase | null = + await DBC.DBConnection.prisma.systemObject.findOne({ where: { idCaptureData, }, include: { CaptureData: true, }, }); + return SOPair ? new SystemObjectCaptureData(SOPair) : null; + } catch (error) /* istanbul ignore next */ { + LOG.logger.error('DBAPI.SystemObjectCaptureData.fetch', error); + return null; + } + } +} + +export class SystemObjectIntermediaryFile extends SystemObject implements SystemObjectIntermediaryFileBase { + IntermediaryFile: IntermediaryFile | null; + + constructor(input: SystemObjectIntermediaryFileBase) { + super(input); + this.IntermediaryFile = (input.IntermediaryFile) ? new IntermediaryFile(input.IntermediaryFile) : /* istanbul ignore next */ null; + } + + static async fetch(idIntermediaryFile: number): Promise { + if (!idIntermediaryFile) + return null; + try { + const SOPair: SystemObjectIntermediaryFileBase | null = + await DBC.DBConnection.prisma.systemObject.findOne({ where: { idIntermediaryFile, }, include: { IntermediaryFile: true, }, }); + return SOPair ? new SystemObjectIntermediaryFile(SOPair) : null; + } catch (error) /* istanbul ignore next */ { + LOG.logger.error('DBAPI.SystemObjectIntermediaryFile.fetch', error); + return null; + } + } +} + +export class SystemObjectItem extends SystemObject implements SystemObjectItemBase { + Item: Item | null; + + constructor(input: SystemObjectItemBase) { + super(input); + this.Item = (input.Item) ? new Item(input.Item) : /* istanbul ignore next */ null; + } + + static async fetch(idItem: number): Promise { + if (!idItem) + return null; + try { + const SOPair: SystemObjectItemBase | null = + await DBC.DBConnection.prisma.systemObject.findOne({ where: { idItem, }, include: { Item: true, }, }); + return SOPair ? new SystemObjectItem(SOPair) : null; + } catch (error) /* istanbul ignore next */ { + LOG.logger.error('DBAPI.SystemObjectItem.fetch', error); + return null; + } + } +} + +export class SystemObjectModel extends SystemObject implements SystemObjectModelBase { + Model: Model | null; + + constructor(input: SystemObjectModelBase) { + super(input); + this.Model = (input.Model) ? new Model(input.Model) : /* istanbul ignore next */ null; + } + + static async fetch(idModel: number): Promise { + if (!idModel) + return null; + try { + const SOPair: SystemObjectModelBase | null = + await DBC.DBConnection.prisma.systemObject.findOne({ where: { idModel, }, include: { Model: true, }, }); + return SOPair ? new SystemObjectModel(SOPair) : null; + } catch (error) /* istanbul ignore next */ { + LOG.logger.error('DBAPI.SystemObjectModel.fetch', error); + return null; + } + } +} + +export class SystemObjectProject extends SystemObject implements SystemObjectProjectBase { + Project: Project | null; + + constructor(input: SystemObjectProjectBase) { + super(input); + this.Project = (input.Project) ? new Project(input.Project) : /* istanbul ignore next */ null; + } + + static async fetch(idProject: number): Promise { + if (!idProject) + return null; + try { + const SOPair: SystemObjectProjectBase | null = + await DBC.DBConnection.prisma.systemObject.findOne({ where: { idProject, }, include: { Project: true, }, }); + return SOPair ? new SystemObjectProject(SOPair) : null; + } catch (error) /* istanbul ignore next */ { + LOG.logger.error('DBAPI.SystemObjectProject.fetch', error); + return null; + } + } +} + +export class SystemObjectProjectDocumentation extends SystemObject implements SystemObjectProjectDocumentationBase { + ProjectDocumentation: ProjectDocumentation | null; + + constructor(input: SystemObjectProjectDocumentationBase) { + super(input); + this.ProjectDocumentation = (input.ProjectDocumentation) ? new ProjectDocumentation(input.ProjectDocumentation) : /* istanbul ignore next */ null; + } + + static async fetch(idProjectDocumentation: number): Promise { + if (!idProjectDocumentation) + return null; + try { + const SOPair: SystemObjectProjectDocumentationBase | null = + await DBC.DBConnection.prisma.systemObject.findOne({ where: { idProjectDocumentation, }, include: { ProjectDocumentation: true, }, }); + return SOPair ? new SystemObjectProjectDocumentation(SOPair) : null; + } catch (error) /* istanbul ignore next */ { + LOG.logger.error('DBAPI.SystemObjectProjectDocumentation.fetch', error); + return null; + } + } +} + +export class SystemObjectScene extends SystemObject implements SystemObjectSceneBase { + Scene: Scene | null; + + constructor(input: SystemObjectSceneBase) { + super(input); + this.Scene = (input.Scene) ? new Scene(input.Scene) : /* istanbul ignore next */ null; + } + + static async fetch(idScene: number): Promise { + if (!idScene) + return null; + try { + const SOPair: SystemObjectSceneBase | null = + await DBC.DBConnection.prisma.systemObject.findOne({ where: { idScene, }, include: { Scene: true, }, }); + return SOPair ? new SystemObjectScene(SOPair) : null; + } catch (error) /* istanbul ignore next */ { + LOG.logger.error('DBAPI.SystemObjectScene.fetch', error); + return null; + } + } +} + +export class SystemObjectStakeholder extends SystemObject implements SystemObjectStakeholderBase { + Stakeholder: Stakeholder | null; + + constructor(input: SystemObjectStakeholderBase) { + super(input); + this.Stakeholder = (input.Stakeholder) ? new Stakeholder(input.Stakeholder) : /* istanbul ignore next */ null; + } + + static async fetch(idStakeholder: number): Promise { + if (!idStakeholder) + return null; + try { + const SOPair: SystemObjectStakeholderBase | null = + await DBC.DBConnection.prisma.systemObject.findOne({ where: { idStakeholder, }, include: { Stakeholder: true, }, }); + return SOPair ? new SystemObjectStakeholder(SOPair) : null; + } catch (error) /* istanbul ignore next */ { + LOG.logger.error('DBAPI.SystemObjectStakeholder.fetch', error); + return null; + } + } +} + +export class SystemObjectSubject extends SystemObject implements SystemObjectSubjectBase { + Subject: Subject | null; + + constructor(input: SystemObjectSubjectBase) { + super(input); + this.Subject = (input.Subject) ? new Subject(input.Subject) : /* istanbul ignore next */ null; + } + + static async fetch(idSubject: number): Promise { + if (!idSubject) + return null; + try { + const SOPair: SystemObjectSubjectBase | null = + await DBC.DBConnection.prisma.systemObject.findOne({ where: { idSubject, }, include: { Subject: true, }, }); + return SOPair ? new SystemObjectSubject(SOPair) : null; + } catch (error) /* istanbul ignore next */ { + LOG.logger.error('DBAPI.SystemObjectSubject.fetch', error); + return null; + } + } +} + +export class SystemObjectUnit extends SystemObject implements SystemObjectUnitBase { + Unit: Unit | null; + + constructor(input: SystemObjectUnitBase) { + super(input); + this.Unit = (input.Unit) ? new Unit(input.Unit) : /* istanbul ignore next */ null; + } + + static async fetch(idUnit: number): Promise { + if (!idUnit) + return null; + try { + const SOPair: SystemObjectUnitBase | null = + await DBC.DBConnection.prisma.systemObject.findOne({ where: { idUnit, }, include: { Unit: true, }, }); + return SOPair ? new SystemObjectUnit(SOPair) : null; + } catch (error) /* istanbul ignore next */ { + LOG.logger.error('DBAPI.SystemObjectUnit.fetch', error); + return null; + } + } +} + +export enum eSystemObjectType { + eUnit, + eProject, + eSubject, + eItem, + eCaptureData, + eModel, + eScene, + eIntermediaryFile, + eProjectDocumentation, + eAsset, + eAssetVersion, + eActor, + eStakeholder, + eUnknown +} + +export function SystemObjectTypeToName(eObjectType: eSystemObjectType | null): string { + switch (eObjectType) { + case eSystemObjectType.eUnit: return 'Unit'; + case eSystemObjectType.eProject: return 'Project'; + case eSystemObjectType.eSubject: return 'Subject'; + case eSystemObjectType.eItem: return 'Item'; + case eSystemObjectType.eCaptureData: return 'Capture Data'; + case eSystemObjectType.eModel: return 'Model'; + case eSystemObjectType.eScene: return 'Scene'; + case eSystemObjectType.eIntermediaryFile: return 'Intermediary File'; + case eSystemObjectType.eProjectDocumentation: return 'Project Documentation'; + case eSystemObjectType.eAsset: return 'Asset'; + case eSystemObjectType.eAssetVersion: return 'Asset Version'; + case eSystemObjectType.eActor: return 'Actor'; + case eSystemObjectType.eStakeholder: return 'Stakeholder'; + case eSystemObjectType.eUnknown: return 'Unknown'; + default: return 'Unknown'; + } +} + +export class SystemObjectPairs extends SystemObject implements SystemObjectPairsBase { + Actor: Actor | null = null; + Asset_AssetToSystemObject_idAsset: Asset | null = null; + AssetVersion: AssetVersion | null = null; + CaptureData: CaptureData | null = null; + IntermediaryFile: IntermediaryFile | null = null; + Item: Item | null = null; + Model: Model | null = null; + Project: Project | null = null; + ProjectDocumentation: ProjectDocumentation | null = null; + Scene: Scene | null = null; + Stakeholder: Stakeholder | null = null; + Subject: Subject | null = null; + Unit: Unit | null = null; + + get Asset(): Asset | null { + return this.Asset_AssetToSystemObject_idAsset; + } + set Asset(value: Asset | null) { + this.Asset_AssetToSystemObject_idAsset = value; + } + + constructor(input: SystemObjectPairsBase) { + super(input); + if (input.Actor) this.Actor = new Actor(input.Actor); + if (input.Asset_AssetToSystemObject_idAsset) this.Asset_AssetToSystemObject_idAsset = new Asset(input.Asset_AssetToSystemObject_idAsset); + if (input.AssetVersion) this.AssetVersion = new AssetVersion(input.AssetVersion); + if (input.CaptureData) this.CaptureData = new CaptureData(input.CaptureData); + if (input.IntermediaryFile) this.IntermediaryFile = new IntermediaryFile(input.IntermediaryFile); + if (input.Item) this.Item = new Item(input.Item); + if (input.Model) this.Model = new Model(input.Model); + if (input.Project) this.Project = new Project(input.Project); + if (input.ProjectDocumentation) this.ProjectDocumentation = new ProjectDocumentation(input.ProjectDocumentation); + if (input.Scene) this.Scene = new Scene(input.Scene); + if (input.Subject) this.Subject = new Subject(input.Subject); + if (input.Stakeholder) this.Stakeholder = new Stakeholder(input.Stakeholder); + if (input.Unit) this.Unit = new Unit(input.Unit); + } + + static async fetch(idSystemObject: number): Promise { + if (!idSystemObject) + return null; + try { + const SOAPB: SystemObjectPairsBase | null = + await DBC.DBConnection.prisma.systemObject.findOne({ + where: { idSystemObject, }, + include: { + Actor: true, + Asset_AssetToSystemObject_idAsset: true, + AssetVersion: true, + CaptureData: true, + IntermediaryFile: true, + Item: true, + Model: true, + Project: true, + ProjectDocumentation: true, + Scene: true, + Stakeholder: true, + Subject: true, + Unit: true + }, + }); + return (SOAPB ? new SystemObjectPairs(SOAPB) : null); + } catch (error) /* istanbul ignore next */ { + LOG.logger.error('DBAPI.SystemObjectAndPairs.fetch', error); + return null; + } + } + + static async fetchDerivedFromXref(idSystemObjectMaster: number): Promise { + if (!idSystemObjectMaster) + return null; + try { + return DBC.CopyArray( + await DBC.DBConnection.prisma.systemObject.findMany({ + where: { + SystemObjectXref_SystemObjectToSystemObjectXref_idSystemObjectDerived: { + some: { idSystemObjectMaster }, + }, + }, + include: { + Actor: true, + Asset_AssetToSystemObject_idAsset: true, + AssetVersion: true, + CaptureData: true, + IntermediaryFile: true, + Item: true, + Model: true, + Project: true, + ProjectDocumentation: true, + Scene: true, + Stakeholder: true, + Subject: true, + Unit: true + }, + }), SystemObjectPairs); + } catch (error) /* istanbul ignore next */ { + LOG.logger.error('DBAPI.SystemObjectAndPairs.fetchDerivedFromXref', error); + return null; + } + } + + static async fetchMasterFromXref(idSystemObjectDerived: number): Promise { + if (!idSystemObjectDerived) + return null; + try { + return DBC.CopyArray( + await DBC.DBConnection.prisma.systemObject.findMany({ + where: { + SystemObjectXref_SystemObjectToSystemObjectXref_idSystemObjectMaster: { + some: { idSystemObjectDerived }, + }, + }, + include: { + Actor: true, + Asset_AssetToSystemObject_idAsset: true, + AssetVersion: true, + CaptureData: true, + IntermediaryFile: true, + Item: true, + Model: true, + Project: true, + ProjectDocumentation: true, + Scene: true, + Stakeholder: true, + Subject: true, + Unit: true + }, + }), SystemObjectPairs); + } catch (error) /* istanbul ignore next */ { + LOG.logger.error('DBAPI.SystemObjectAndPairs.fetchMasterFromXref', error); + return null; + } + } +} diff --git a/server/db/api/composite/ObjectGraph.ts b/server/db/api/composite/ObjectGraph.ts index f5ea546dd..9b1239061 100644 --- a/server/db/api/composite/ObjectGraph.ts +++ b/server/db/api/composite/ObjectGraph.ts @@ -6,9 +6,9 @@ import * as L from 'lodash'; import { ObjectGraphDatabase } from './ObjectGraphDatabase'; export type SystemObjectIDType = { - idSystemObject: number, - idObject: number, - eType: eSystemObjectType + idSystemObject: number; + idObject: number; + eObjectType: eSystemObjectType; }; export enum eObjectGraphMode { @@ -105,7 +105,7 @@ export class ObjectGraph { const sourceType: SystemObjectIDType = { idSystemObject, idObject: 0, - eType: eSystemObjectType.eUnknown + eObjectType: eSystemObjectType.eUnknown }; const SOP: SystemObjectPairs | null = await SystemObjectPairs.fetch(idSystemObject); @@ -144,7 +144,7 @@ export class ObjectGraph { } /* istanbul ignore if */ - if (sourceType.eType == eSystemObjectType.eUnknown) + if (sourceType.eObjectType == eSystemObjectType.eUnknown) LOG.logger.error(`DBAPI.ObjectGraph.fetchWorker Unidentified SystemObject type ${JSON.stringify(SOP)}`); /* const valid: string = (this.validHierarchy ? '' : ' INVALID HIERARCHY') + (this.noCycles ? '' : ' CYCLE'); @@ -218,17 +218,17 @@ export class ObjectGraph { private async pushActor(actor: Actor, sourceType: SystemObjectIDType, relatedType: SystemObjectIDType | null, eMode: eObjectGraphMode): Promise { sourceType.idObject = actor.idActor; - sourceType.eType = eSystemObjectType.eActor; + sourceType.eObjectType = eSystemObjectType.eActor; if (eMode == eObjectGraphMode.eAncestors) { // allowable children if (relatedType) this.validHierarchy = false; } else { // if (eMode == eObjectGraphMode.eDescendents) { // allowable parents if (relatedType && - (relatedType.eType != eSystemObjectType.eCaptureData && - relatedType.eType != eSystemObjectType.eModel && - relatedType.eType != eSystemObjectType.eScene && - relatedType.eType != eSystemObjectType.eIntermediaryFile && - relatedType.eType != eSystemObjectType.eUnit)) + (relatedType.eObjectType != eSystemObjectType.eCaptureData && + relatedType.eObjectType != eSystemObjectType.eModel && + relatedType.eObjectType != eSystemObjectType.eScene && + relatedType.eObjectType != eSystemObjectType.eIntermediaryFile && + relatedType.eObjectType != eSystemObjectType.eUnit)) this.validHierarchy = false; } @@ -259,9 +259,9 @@ export class ObjectGraph { private async pushAsset(asset: Asset, sourceType: SystemObjectIDType, relatedType: SystemObjectIDType | null, eMode: eObjectGraphMode): Promise { sourceType.idObject = asset.idAsset; - sourceType.eType = eSystemObjectType.eAsset; + sourceType.eObjectType = eSystemObjectType.eAsset; if (eMode == eObjectGraphMode.eAncestors) { // allowable children - if (relatedType && relatedType.eType != eSystemObjectType.eAssetVersion) + if (relatedType && relatedType.eObjectType != eSystemObjectType.eAssetVersion) this.validHierarchy = false; } // allowable parents -- any system object can have an asset @@ -300,12 +300,12 @@ export class ObjectGraph { private async pushAssetVersion(assetVersion: AssetVersion, sourceType: SystemObjectIDType, relatedType: SystemObjectIDType | null, eMode: eObjectGraphMode): Promise { sourceType.idObject = assetVersion.idAsset; - sourceType.eType = eSystemObjectType.eAssetVersion; + sourceType.eObjectType = eSystemObjectType.eAssetVersion; if (eMode == eObjectGraphMode.eAncestors) { // allowable children if (relatedType) this.validHierarchy = false; } else { // if (eMode == eObjectGraphMode.eDescendents) { // allowable parents - if (relatedType && relatedType.eType != eSystemObjectType.eAsset) + if (relatedType && relatedType.eObjectType != eSystemObjectType.eAsset) this.validHierarchy = false; } @@ -335,15 +335,15 @@ export class ObjectGraph { private async pushCaptureData(captureData: CaptureData, sourceType: SystemObjectIDType, relatedType: SystemObjectIDType | null, eMode: eObjectGraphMode): Promise { sourceType.idObject = captureData.idCaptureData; - sourceType.eType = eSystemObjectType.eCaptureData; + sourceType.eObjectType = eSystemObjectType.eCaptureData; if (eMode == eObjectGraphMode.eAncestors) { // allowable children if (relatedType && - (relatedType.eType != eSystemObjectType.eAsset && - relatedType.eType != eSystemObjectType.eModel && - relatedType.eType != eSystemObjectType.eActor)) + (relatedType.eObjectType != eSystemObjectType.eAsset && + relatedType.eObjectType != eSystemObjectType.eModel && + relatedType.eObjectType != eSystemObjectType.eActor)) this.validHierarchy = false; } else { // if (eMode == eObjectGraphMode.eDescendents) { // allowable parents - if (relatedType && relatedType.eType != eSystemObjectType.eItem) + if (relatedType && relatedType.eObjectType != eSystemObjectType.eItem) this.validHierarchy = false; } @@ -376,14 +376,14 @@ export class ObjectGraph { private async pushIntermediaryFile(intermediaryFile: IntermediaryFile, sourceType: SystemObjectIDType, relatedType: SystemObjectIDType | null, eMode: eObjectGraphMode): Promise { sourceType.idObject = intermediaryFile.idIntermediaryFile; - sourceType.eType = eSystemObjectType.eIntermediaryFile; + sourceType.eObjectType = eSystemObjectType.eIntermediaryFile; if (eMode == eObjectGraphMode.eAncestors) { // allowable children if (relatedType && - (relatedType.eType != eSystemObjectType.eAsset && - relatedType.eType != eSystemObjectType.eActor)) + (relatedType.eObjectType != eSystemObjectType.eAsset && + relatedType.eObjectType != eSystemObjectType.eActor)) this.validHierarchy = false; } else { // if (eMode == eObjectGraphMode.eDescendents) { // allowable parents - if (relatedType && relatedType.eType != eSystemObjectType.eItem) + if (relatedType && relatedType.eObjectType != eSystemObjectType.eItem) this.validHierarchy = false; } @@ -405,17 +405,17 @@ export class ObjectGraph { private async pushItem(item: Item, sourceType: SystemObjectIDType, relatedType: SystemObjectIDType | null, eMode: eObjectGraphMode): Promise { sourceType.idObject = item.idItem; - sourceType.eType = eSystemObjectType.eItem; + sourceType.eObjectType = eSystemObjectType.eItem; if (eMode == eObjectGraphMode.eAncestors) { // allowable children if (relatedType && - (relatedType.eType != eSystemObjectType.eAsset && - relatedType.eType != eSystemObjectType.eCaptureData && - relatedType.eType != eSystemObjectType.eModel && - relatedType.eType != eSystemObjectType.eScene && - relatedType.eType != eSystemObjectType.eIntermediaryFile)) + (relatedType.eObjectType != eSystemObjectType.eAsset && + relatedType.eObjectType != eSystemObjectType.eCaptureData && + relatedType.eObjectType != eSystemObjectType.eModel && + relatedType.eObjectType != eSystemObjectType.eScene && + relatedType.eObjectType != eSystemObjectType.eIntermediaryFile)) this.validHierarchy = false; } else { // if (eMode == eObjectGraphMode.eDescendents) { // allowable parents - if (relatedType && relatedType.eType != eSystemObjectType.eSubject) + if (relatedType && relatedType.eObjectType != eSystemObjectType.eSubject) this.validHierarchy = false; } @@ -449,20 +449,20 @@ export class ObjectGraph { private async pushModel(model: Model, sourceType: SystemObjectIDType, relatedType: SystemObjectIDType | null, eMode: eObjectGraphMode): Promise { sourceType.idObject = model.idModel; - sourceType.eType = eSystemObjectType.eModel; + sourceType.eObjectType = eSystemObjectType.eModel; if (eMode == eObjectGraphMode.eAncestors) { // allowable children if (relatedType && - (relatedType.eType != eSystemObjectType.eAsset && - relatedType.eType != eSystemObjectType.eScene && - relatedType.eType != eSystemObjectType.eModel && - relatedType.eType != eSystemObjectType.eActor)) + (relatedType.eObjectType != eSystemObjectType.eAsset && + relatedType.eObjectType != eSystemObjectType.eScene && + relatedType.eObjectType != eSystemObjectType.eModel && + relatedType.eObjectType != eSystemObjectType.eActor)) this.validHierarchy = false; } else { // if (eMode == eObjectGraphMode.eDescendents) { // allowable parents if (relatedType && - (relatedType.eType != eSystemObjectType.eItem && - relatedType.eType != eSystemObjectType.eCaptureData && - relatedType.eType != eSystemObjectType.eModel && - relatedType.eType != eSystemObjectType.eScene)) + (relatedType.eObjectType != eSystemObjectType.eItem && + relatedType.eObjectType != eSystemObjectType.eCaptureData && + relatedType.eObjectType != eSystemObjectType.eModel && + relatedType.eObjectType != eSystemObjectType.eScene)) this.validHierarchy = false; } @@ -495,15 +495,15 @@ export class ObjectGraph { private async pushProject(project: Project, sourceType: SystemObjectIDType, relatedType: SystemObjectIDType | null, eMode: eObjectGraphMode): Promise { sourceType.idObject = project.idProject; - sourceType.eType = eSystemObjectType.eProject; + sourceType.eObjectType = eSystemObjectType.eProject; if (eMode == eObjectGraphMode.eAncestors) { // allowable children if (relatedType && - (relatedType.eType != eSystemObjectType.eSubject && - relatedType.eType != eSystemObjectType.eProjectDocumentation && - relatedType.eType != eSystemObjectType.eStakeholder)) + (relatedType.eObjectType != eSystemObjectType.eSubject && + relatedType.eObjectType != eSystemObjectType.eProjectDocumentation && + relatedType.eObjectType != eSystemObjectType.eStakeholder)) this.validHierarchy = false; } else { // if (eMode == eObjectGraphMode.eDescendents) { // allowable parents - if (relatedType && relatedType.eType != eSystemObjectType.eUnit) + if (relatedType && relatedType.eObjectType != eSystemObjectType.eUnit) this.validHierarchy = false; } @@ -540,12 +540,12 @@ export class ObjectGraph { private async pushProjectDocumentation(projectDocumentation: ProjectDocumentation, sourceType: SystemObjectIDType, relatedType: SystemObjectIDType | null, eMode: eObjectGraphMode): Promise { sourceType.idObject = projectDocumentation.idProjectDocumentation; - sourceType.eType = eSystemObjectType.eProjectDocumentation; + sourceType.eObjectType = eSystemObjectType.eProjectDocumentation; if (eMode == eObjectGraphMode.eAncestors) { // allowable children - if (relatedType && relatedType.eType != eSystemObjectType.eAsset) + if (relatedType && relatedType.eObjectType != eSystemObjectType.eAsset) this.validHierarchy = false; } else { // if (eMode == eObjectGraphMode.eDescendents) { // allowable parents - if (relatedType && relatedType.eType != eSystemObjectType.eProject) + if (relatedType && relatedType.eObjectType != eSystemObjectType.eProject) this.validHierarchy = false; } @@ -575,17 +575,17 @@ export class ObjectGraph { private async pushScene(scene: Scene, sourceType: SystemObjectIDType, relatedType: SystemObjectIDType | null, eMode: eObjectGraphMode): Promise { sourceType.idObject = scene.idScene; - sourceType.eType = eSystemObjectType.eScene; + sourceType.eObjectType = eSystemObjectType.eScene; if (eMode == eObjectGraphMode.eAncestors) { // allowable children if (relatedType && - (relatedType.eType != eSystemObjectType.eAsset && - relatedType.eType != eSystemObjectType.eModel && - relatedType.eType != eSystemObjectType.eActor)) + (relatedType.eObjectType != eSystemObjectType.eAsset && + relatedType.eObjectType != eSystemObjectType.eModel && + relatedType.eObjectType != eSystemObjectType.eActor)) this.validHierarchy = false; } else { // if (eMode == eObjectGraphMode.eDescendents) { // allowable parents if (relatedType && - (relatedType.eType != eSystemObjectType.eItem && - relatedType.eType != eSystemObjectType.eModel)) + (relatedType.eObjectType != eSystemObjectType.eItem && + relatedType.eObjectType != eSystemObjectType.eModel)) this.validHierarchy = false; } @@ -620,14 +620,14 @@ export class ObjectGraph { private async pushStakeholder(stakeholder: Stakeholder, sourceType: SystemObjectIDType, relatedType: SystemObjectIDType | null, eMode: eObjectGraphMode): Promise { sourceType.idObject = stakeholder.idStakeholder; - sourceType.eType = eSystemObjectType.eStakeholder; + sourceType.eObjectType = eSystemObjectType.eStakeholder; if (eMode == eObjectGraphMode.eAncestors) { // allowable children if (relatedType) this.validHierarchy = false; } else { // if (eMode == eObjectGraphMode.eDescendents) { // allowable parents if (relatedType && - (relatedType.eType != eSystemObjectType.eUnit && - relatedType.eType != eSystemObjectType.eProject)) + (relatedType.eObjectType != eSystemObjectType.eUnit && + relatedType.eObjectType != eSystemObjectType.eProject)) this.validHierarchy = false; } @@ -649,16 +649,16 @@ export class ObjectGraph { private async pushSubject(subject: Subject, sourceType: SystemObjectIDType, relatedType: SystemObjectIDType | null, eMode: eObjectGraphMode): Promise { sourceType.idObject = subject.idSubject; - sourceType.eType = eSystemObjectType.eSubject; + sourceType.eObjectType = eSystemObjectType.eSubject; if (eMode == eObjectGraphMode.eAncestors) { // allowable children if (relatedType && - (relatedType.eType != eSystemObjectType.eAsset && - relatedType.eType != eSystemObjectType.eItem)) + (relatedType.eObjectType != eSystemObjectType.eAsset && + relatedType.eObjectType != eSystemObjectType.eItem)) this.validHierarchy = false; } else { // if (eMode == eObjectGraphMode.eDescendents) { // allowable parents if (relatedType && - (relatedType.eType != eSystemObjectType.eUnit && - relatedType.eType != eSystemObjectType.eProject)) + (relatedType.eObjectType != eSystemObjectType.eUnit && + relatedType.eObjectType != eSystemObjectType.eProject)) this.validHierarchy = false; } @@ -696,13 +696,13 @@ export class ObjectGraph { private async pushUnit(unit: Unit, sourceType: SystemObjectIDType, relatedType: SystemObjectIDType | null, eMode: eObjectGraphMode): Promise { sourceType.idObject = unit.idUnit; - sourceType.eType = eSystemObjectType.eUnit; + sourceType.eObjectType = eSystemObjectType.eUnit; if (eMode == eObjectGraphMode.eAncestors) { // allowable children if (relatedType && - (relatedType.eType != eSystemObjectType.eSubject && - relatedType.eType != eSystemObjectType.eProject && - relatedType.eType != eSystemObjectType.eActor && - relatedType.eType != eSystemObjectType.eStakeholder)) + (relatedType.eObjectType != eSystemObjectType.eSubject && + relatedType.eObjectType != eSystemObjectType.eProject && + relatedType.eObjectType != eSystemObjectType.eActor && + relatedType.eObjectType != eSystemObjectType.eStakeholder)) this.validHierarchy = false; } else { // if (eMode == eObjectGraphMode.eDescendents) { // allowable parents if (relatedType) diff --git a/server/db/api/composite/ObjectGraphDataEntry.ts b/server/db/api/composite/ObjectGraphDataEntry.ts index d4ec65737..8e798bbd2 100644 --- a/server/db/api/composite/ObjectGraphDataEntry.ts +++ b/server/db/api/composite/ObjectGraphDataEntry.ts @@ -1,19 +1,157 @@ -// import { Actor, Asset, AssetVersion, CaptureData, IntermediaryFile, Model, ProjectDocumentation, Scene, Stakeholder, SystemObject } from '../..'; -import { Unit, Project, Subject, Item, SystemObjectIDType, eSystemObjectType } from '../..'; -// import * as LOG from '../../../utils/logger'; -// import * as CACHE from '../../../cache'; - -export class ObjectGraphDataEntry { - // The object whom this graph data entry describes - objectIDAndType: SystemObjectIDType = { idSystemObject: 0, idObject: 0, eType: eSystemObjectType.eUnknown }; - - // Derived data - childMap: Map = new Map(); // map of child objects - parentMap: Map = new Map(); // map of parent objects - - unitMap: Map = new Map(); // map of Units associted with this object - projectMap: Map = new Map(); // map of Projects associted with this object - subjectMap: Map = new Map(); // map of Subjects associted with this object - itemMap: Map = new Map(); // map of Items associted with this object - -} \ No newline at end of file +// import { Unit, Project, Subject, Item, Actor, Asset, AssetVersion, CaptureData, IntermediaryFile, Model, ProjectDocumentation, Scene, Stakeholder, SystemObject } from '../..'; +import { SystemObjectIDType, eSystemObjectType } from '../..'; +// import * as LOG from '../../../utils/logger'; +// import * as CACHE from '../../../cache'; + +export enum eApplyGraphStateDirection { + eSelf, + eChild, + eParent +} + +export class ObjectGraphState { + eType: eSystemObjectType | null = null; + ancestorObject: SystemObjectIDType | null = null; + captureMethod: number | null = null; + variantTypes: Map | null = null; + modelPurpose: number | null = null; + modelFileTypes: Map | null = null; +} + +export class ObjectGraphDataEntryHierarchy { + idSystemObject: number = 0; + retired: boolean = false; + eObjectType: eSystemObjectType | null = null; + idObject: number = 0; + + parents: SystemObjectIDType[] = []; + children: SystemObjectIDType[] = []; + + units: SystemObjectIDType[] = []; + projects: SystemObjectIDType[] = []; + subjects: SystemObjectIDType[] = []; + items: SystemObjectIDType[] = []; + + childrenObjectTypes: eSystemObjectType[] = []; + childrenCaptureMethods: number[] = []; + childrenVariantTypes: number[] = []; + childrenModelPurposes: number[] = []; + childrenModelFileTypes: number[] = []; +} + +export class ObjectGraphDataEntry { + // The object whom this graph data entry describes + systemObjectIDType: SystemObjectIDType; + retired: boolean = false; + + // Derived data + childMap: Map = new Map(); // map of child objects + parentMap: Map = new Map(); // map of parent objects + ancestorObjectMap: Map = new Map(); // map of ancestor objects of significance (unit, project, subject, item) + + // Child data types + childrenObjectTypes: Map = new Map(); + childrenCaptureMethods: Map = new Map(); // map of idVocabulary of Capture Methods associated with this object + childrenVariantTypes: Map = new Map(); // map of idVocabulary of Capture Variant Types associated with this object + childrenModelPurposes: Map = new Map(); // map of idVocabulary of Model Purposes associated with this object + childrenModelFileTypes: Map = new Map(); // map of idVocabulary of Model File Types associated with this object + + constructor(sID: SystemObjectIDType, retired: boolean) { + this.systemObjectIDType = sID; + this.retired = retired; + } + + recordChild(child: SystemObjectIDType): void { + this.childMap.set(child, true); + } + + recordParent(parent: SystemObjectIDType): void { + this.parentMap.set(parent, true); + } + + // Returns true if applying objectGraphState updated the state of this ObjectGraphDataEntry + applyGraphState(objectGraphState: ObjectGraphState, eDirection: eApplyGraphStateDirection): boolean { + let retValue: boolean = false; + switch (eDirection) { + case eApplyGraphStateDirection.eSelf: + case eApplyGraphStateDirection.eChild: + if (objectGraphState.ancestorObject) { + if (!this.ancestorObjectMap.has(objectGraphState.ancestorObject)) { + this.ancestorObjectMap.set(objectGraphState.ancestorObject, true); + retValue = true; + } + } + break; + + case eApplyGraphStateDirection.eParent: + if (objectGraphState.eType) { + if (!this.childrenObjectTypes.has(objectGraphState.eType)) { + this.childrenObjectTypes.set(objectGraphState.eType, true); + retValue = true; + } + } + + if (objectGraphState.captureMethod) { + if (!this.childrenCaptureMethods.has(objectGraphState.captureMethod)) { + this.childrenCaptureMethods.set(objectGraphState.captureMethod, true); + retValue = true; + } + } + + if (objectGraphState.variantTypes) { + for (const variantType of objectGraphState.variantTypes.keys()) { + if (!this.childrenVariantTypes.has(variantType)) { + this.childrenVariantTypes.set(variantType, true); + retValue = true; + } + } + } + + if (objectGraphState.modelPurpose) { + if (!this.childrenModelPurposes.has(objectGraphState.modelPurpose)) { + this.childrenModelPurposes.set(objectGraphState.modelPurpose, true); + retValue = true; + } + } + + if (objectGraphState.modelFileTypes) { + for (const modelFileType of objectGraphState.modelFileTypes.keys()) { + if (!this.childrenModelFileTypes.has(modelFileType)) { + this.childrenModelFileTypes.set(modelFileType, true); + retValue = true; + } + } + } + break; + } + return retValue; + } + + extractHierarchy(): ObjectGraphDataEntryHierarchy { + const objectGraphDataEntryHierarchy: ObjectGraphDataEntryHierarchy = new ObjectGraphDataEntryHierarchy(); + + objectGraphDataEntryHierarchy.idSystemObject = this.systemObjectIDType.idSystemObject; + objectGraphDataEntryHierarchy.retired = this.retired; + objectGraphDataEntryHierarchy.eObjectType = this.systemObjectIDType.eObjectType; + objectGraphDataEntryHierarchy.idObject = this.systemObjectIDType.idObject; + + objectGraphDataEntryHierarchy.parents = [...this.parentMap.keys()]; + objectGraphDataEntryHierarchy.children = [...this.childMap.keys()]; + for (const systemObjectIDType of this.ancestorObjectMap.keys()) { + switch (systemObjectIDType.eObjectType) { + case eSystemObjectType.eUnit: objectGraphDataEntryHierarchy.units.push(systemObjectIDType); break; + case eSystemObjectType.eProject: objectGraphDataEntryHierarchy.projects.push(systemObjectIDType); break; + case eSystemObjectType.eSubject: objectGraphDataEntryHierarchy.subjects.push(systemObjectIDType); break; + case eSystemObjectType.eItem: objectGraphDataEntryHierarchy.items.push(systemObjectIDType); break; + } + } + + objectGraphDataEntryHierarchy.childrenObjectTypes = [...this.childrenObjectTypes.keys()]; + objectGraphDataEntryHierarchy.childrenCaptureMethods = [...this.childrenCaptureMethods.keys()]; + objectGraphDataEntryHierarchy.childrenVariantTypes = [...this.childrenVariantTypes.keys()]; + objectGraphDataEntryHierarchy.childrenModelPurposes = [...this.childrenModelPurposes.keys()]; + objectGraphDataEntryHierarchy.childrenModelFileTypes = [...this.childrenModelFileTypes.keys()]; + + return objectGraphDataEntryHierarchy; + } +} diff --git a/server/db/api/composite/ObjectGraphDatabase.ts b/server/db/api/composite/ObjectGraphDatabase.ts index 839d775e0..413d2fbf4 100644 --- a/server/db/api/composite/ObjectGraphDatabase.ts +++ b/server/db/api/composite/ObjectGraphDatabase.ts @@ -1,25 +1,321 @@ -// import { Actor, Asset, AssetVersion, CaptureData, IntermediaryFile, Model, ProjectDocumentation, Scene, Stakeholder, SystemObject, eSystemObjectType } from '../..'; -import { Unit, Project, Subject, Item, SystemObjectIDType } from '../..'; -// import * as LOG from '../../../utils/logger'; - -export class ObjectGraphDatabase { - // database - childMap: Map> = new Map>(); // map from parent object to map of children - parentMap: Map> = new Map>(); // map from child object to map of parents - - // state-based data - unit: Unit | null = null; - project: Project | null = null; - subject: Subject | null = null; - item: Item | null = null; - - recordRelationship(parent: SystemObjectIDType, child: SystemObjectIDType): void { - const childMap: Map = this.childMap.get(parent) || new Map(); - childMap.set(child.idSystemObject, child); - - const parentList: Map = this.parentMap.get(child) || new Map(); - parentList.set(parent.idSystemObject, parent); - } - - -} \ No newline at end of file +import { Unit, Project, Subject, Item, SystemObjectIDType, Actor, Asset, AssetVersion, CaptureData, CaptureDataFile, IntermediaryFile, + Model, ModelGeometryFile, ProjectDocumentation, Scene, Stakeholder, eSystemObjectType } from '../..'; +import { ObjectGraphDataEntry, eApplyGraphStateDirection, ObjectGraphState } from './ObjectGraphDataEntry'; +import { ObjectGraph, eObjectGraphMode } from './ObjectGraph'; +import * as CACHE from '../../../cache'; +import * as LOG from '../../../utils/logger'; + +export class ObjectGraphDatabase { + objectMap: Map = new Map(); // map from object to graph entry details + + // used by ObjectGraph + async recordRelationship(parent: SystemObjectIDType, child: SystemObjectIDType): Promise { + let parentData: ObjectGraphDataEntry | undefined = this.objectMap.get(parent); + let childData: ObjectGraphDataEntry | undefined = this.objectMap.get(child); + + if (!parentData) { + const sID: CACHE.SystemObjectInfo | undefined = await CACHE.SystemObjectCache.getSystemFromObjectID(parent); /* istanbul ignore if */ + if (!sID) + LOG.logger.error(`ObjectGraphDatabase.recordRelationship unable to compute idSystemObject for ${JSON.stringify(parent)}`); + + parentData = new ObjectGraphDataEntry(parent, sID ? sID.Retired : false); + this.objectMap.set(parent, parentData); + } + if (!childData) { + const sID: CACHE.SystemObjectInfo | undefined = await CACHE.SystemObjectCache.getSystemFromObjectID(child); /* istanbul ignore if */ + if (!sID) + LOG.logger.error(`ObjectGraphDatabase.recordRelationship unable to compute idSystemObject for ${JSON.stringify(child)}`); + + childData = new ObjectGraphDataEntry(child, sID ? sID.Retired : false); + this.objectMap.set(child, childData); + } + + parentData.recordChild(child); + childData.recordParent(parent); + } + + async fetch(): Promise { + if (!await this.computeGraphDataFromUnits()) return false; + if (!await this.computeGraphDataFromProjects()) return false; + if (!await this.computeGraphDataFromSubjects()) return false; + if (!await this.computeGraphDataFromItems()) return false; + if (!await this.computeGraphDataFromCaptureDatas()) return false; + if (!await this.computeGraphDataFromModels()) return false; + if (!await this.computeGraphDataFromScenes()) return false; + if (!await this.computeGraphDataFromIntermediaryFiles()) return false; + if (!await this.computeGraphDataFromProjectDocumentations()) return false; + if (!await this.computeGraphDataFromAssets()) return false; + if (!await this.computeGraphDataFromAssetVersions()) return false; + if (!await this.computeGraphDataFromActors()) return false; + if (!await this.computeGraphDataFromStakeholders()) return false; + + if (!(await this.applyGraphData())) return false; + return true; + } + + getObjectGraphDataEntry(oID: CACHE.ObjectIDAndType, sID: CACHE.SystemObjectInfo): ObjectGraphDataEntry | undefined { + const systemObjectIDType: SystemObjectIDType = { idSystemObject: sID.idSystemObject, idObject: oID.idObject, eObjectType: oID.eObjectType }; + return this.objectMap.get(systemObjectIDType); + } + + /* #region Compute Graph Data */ + private async computeGraphDataFromObject(idObject: number, eObjectType: eSystemObjectType, functionName: string): Promise { + const oID: CACHE.ObjectIDAndType = { idObject, eObjectType }; + const sID: CACHE.SystemObjectInfo | undefined = await CACHE.SystemObjectCache.getSystemFromObjectID(oID); /* istanbul ignore if */ + if (!sID) { + LOG.logger.error(`GraphDatabase.${functionName} unable to compute idSystemObject for ${JSON.stringify(oID)}`); + return false; + } + + const OG: ObjectGraph = new ObjectGraph(sID.idSystemObject, eObjectGraphMode.eDescendents, 32, this); // this -> gather relationships for all objects! + if (!await OG.fetch()) { + LOG.logger.error(`GraphDatabase.${functionName} unable to compute ObjectGraph for ${JSON.stringify(oID)}`); + return false; + } + + const objectIDAndType: SystemObjectIDType = { idSystemObject: sID.idSystemObject, idObject, eObjectType }; + if (!this.objectMap.has(objectIDAndType)) this.objectMap.set(objectIDAndType, new ObjectGraphDataEntry(objectIDAndType, sID.Retired)); + return true; + } + + private async computeGraphDataFromUnits(): Promise { + // iterate across all Units; for each, compute ObjectGraph; extract ObjectGraph data into a "database" + const units: Unit[] | null = await Unit.fetchAll(); /* istanbul ignore if */ + if (!units) return false; + for (const unit of units) { + if (!await this.computeGraphDataFromObject(unit.idUnit, eSystemObjectType.eUnit, 'computeGraphDataFromUnits')) + continue; + } + return true; + } + + private async computeGraphDataFromProjects(): Promise { + // iterate across all Projects; for each, compute ObjectGraph; extract ObjectGraph data into a "database" + const projects: Project[] | null = await Project.fetchAll(); /* istanbul ignore if */ + if (!projects) return false; + for (const project of projects) { + if (!await this.computeGraphDataFromObject(project.idProject, eSystemObjectType.eProject, 'computeGraphDataFromProjects')) + continue; + } + return true; + } + + private async computeGraphDataFromSubjects(): Promise { + // iterate across all Subjects; for each, compute ObjectGraph; extract ObjectGraph data into a "database" + const Subjects: Subject[] | null = await Subject.fetchAll(); /* istanbul ignore if */ + if (!Subjects) return false; + for (const Subject of Subjects) { + if (!await this.computeGraphDataFromObject(Subject.idSubject, eSystemObjectType.eSubject, 'computeGraphDataFromSubjects')) + continue; + } + return true; + } + + private async computeGraphDataFromItems(): Promise { + // iterate across all Items; for each, compute ObjectGraph; extract ObjectGraph data into a "database" + const Items: Item[] | null = await Item.fetchAll(); /* istanbul ignore if */ + if (!Items) return false; + for (const Item of Items) { + if (!await this.computeGraphDataFromObject(Item.idItem, eSystemObjectType.eItem, 'computeGraphDataFromItems')) + continue; + } + return true; + } + + private async computeGraphDataFromCaptureDatas(): Promise { + // iterate across all CaptureDatas; for each, compute ObjectGraph; extract ObjectGraph data into a "database" + const CaptureDatas: CaptureData[] | null = await CaptureData.fetchAll(); /* istanbul ignore if */ + if (!CaptureDatas) return false; + for (const CaptureData of CaptureDatas) { + if (!await this.computeGraphDataFromObject(CaptureData.idCaptureData, eSystemObjectType.eCaptureData, 'computeGraphDataFromCaptureDatas')) + continue; + } + return true; + } + + private async computeGraphDataFromModels(): Promise { + // iterate across all Models; for each, compute ObjectGraph; extract ObjectGraph data into a "database" + const Models: Model[] | null = await Model.fetchAll(); /* istanbul ignore if */ + if (!Models) return false; + for (const Model of Models) { + if (!await this.computeGraphDataFromObject(Model.idModel, eSystemObjectType.eModel, 'computeGraphDataFromModels')) + continue; + } + return true; + } + + private async computeGraphDataFromScenes(): Promise { + // iterate across all Scenes; for each, compute ObjectGraph; extract ObjectGraph data into a "database" + const Scenes: Scene[] | null = await Scene.fetchAll(); /* istanbul ignore if */ + if (!Scenes) return false; + for (const Scene of Scenes) { + if (!await this.computeGraphDataFromObject(Scene.idScene, eSystemObjectType.eScene, 'computeGraphDataFromScenes')) + continue; + } + return true; + } + + private async computeGraphDataFromIntermediaryFiles(): Promise { + // iterate across all IntermediaryFiles; for each, compute ObjectGraph; extract ObjectGraph data into a "database" + const IntermediaryFiles: IntermediaryFile[] | null = await IntermediaryFile.fetchAll(); /* istanbul ignore if */ + if (!IntermediaryFiles) return false; + for (const IntermediaryFile of IntermediaryFiles) { + if (!await this.computeGraphDataFromObject(IntermediaryFile.idIntermediaryFile, eSystemObjectType.eIntermediaryFile, 'computeGraphDataFromIntermediaryFiles')) + continue; + } + return true; + } + + private async computeGraphDataFromProjectDocumentations(): Promise { + // iterate across all ProjectDocumentations; for each, compute ObjectGraph; extract ObjectGraph data into a "database" + const ProjectDocumentations: ProjectDocumentation[] | null = await ProjectDocumentation.fetchAll(); /* istanbul ignore if */ + if (!ProjectDocumentations) return false; + for (const ProjectDocumentation of ProjectDocumentations) { + if (!await this.computeGraphDataFromObject(ProjectDocumentation.idProjectDocumentation, + eSystemObjectType.eProjectDocumentation, 'computeGraphDataFromProjectDocumentations')) + continue; + } + return true; + } + + private async computeGraphDataFromAssets(): Promise { + // iterate across all Assets; for each, compute ObjectGraph; extract ObjectGraph data into a "database" + const Assets: Asset[] | null = await Asset.fetchAll(); /* istanbul ignore if */ + if (!Assets) return false; + for (const Asset of Assets) { + if (!await this.computeGraphDataFromObject(Asset.idAsset, eSystemObjectType.eAsset, 'computeGraphDataFromAssets')) + continue; + } + return true; + } + + private async computeGraphDataFromAssetVersions(): Promise { + // iterate across all AssetVersions; for each, compute ObjectGraph; extract ObjectGraph data into a "database" + const AssetVersions: AssetVersion[] | null = await AssetVersion.fetchAll(); /* istanbul ignore if */ + if (!AssetVersions) return false; + for (const AssetVersion of AssetVersions) { + if (!await this.computeGraphDataFromObject(AssetVersion.idAssetVersion, eSystemObjectType.eAssetVersion, 'computeGraphDataFromAssetVersions')) + continue; + } + return true; + } + + private async computeGraphDataFromActors(): Promise { + // iterate across all Actors; for each, compute ObjectGraph; extract ObjectGraph data into a "database" + const Actors: Actor[] | null = await Actor.fetchAll(); /* istanbul ignore if */ + if (!Actors) return false; + for (const Actor of Actors) { + if (!await this.computeGraphDataFromObject(Actor.idActor, eSystemObjectType.eActor, 'computeGraphDataFromActors')) + continue; + } + return true; + } + + private async computeGraphDataFromStakeholders(): Promise { + // iterate across all Stakeholders; for each, compute ObjectGraph; extract ObjectGraph data into a "database" + const Stakeholders: Stakeholder[] | null = await Stakeholder.fetchAll(); /* istanbul ignore if */ + if (!Stakeholders) return false; + for (const Stakeholder of Stakeholders) { + if (!await this.computeGraphDataFromObject(Stakeholder.idStakeholder, eSystemObjectType.eStakeholder, 'computeGraphDataFromStakeholders')) + continue; + } + return true; + } + /* #endregion */ + + /* #region Apply Graph Data */ + private async applyGraphData(): Promise { + // walk across all entries + // for each entry, extract state: compute unit, project, subject, item, capture method, variant type, model purpose, and model file type + // walk all children + // determine if applicable computed values (unit, project, subject, item) have been applied to child; if not: + // apply computed value + // descend into children (extract state, apply, descend) + // walk all parents + // determine if applicable computed values (capture method, variant type, model purpose, and model file type) have been applied to parent; if not: + // apply computed value + // ascend into parents (extract state, apply, ascend) + + let retValue: boolean = true; + for (const [systemObjectIDType, objectGraphDataEntry] of this.objectMap) { + const objectGraphState = await this.extractState(systemObjectIDType); + retValue = this.applyGraphState(objectGraphDataEntry, objectGraphState) && retValue; + } + return retValue; + } + + private async applyGraphState(objectGraphDataEntry: ObjectGraphDataEntry, objectGraphState: ObjectGraphState): Promise { + // First, apply extracted state to the current object. If this does not result in changes to the state of the current object, + // then we're done -- there's no need to walk the children and parent objects, as they already have received this information + if (!objectGraphDataEntry.applyGraphState(objectGraphState, eApplyGraphStateDirection.eSelf)) + return true; + let retValue: boolean = true; + retValue = (await this.applyGraphStateRecursive(objectGraphDataEntry, objectGraphState, eApplyGraphStateDirection.eChild, 1)) && retValue; + retValue = (await this.applyGraphStateRecursive(objectGraphDataEntry, objectGraphState, eApplyGraphStateDirection.eParent, 1)) && retValue; + return retValue; + } + + private async applyGraphStateRecursive(objectGraphDataEntry: ObjectGraphDataEntry, objectGraphState: ObjectGraphState, + eDirection: eApplyGraphStateDirection, depth: number): Promise { + if (eDirection == eApplyGraphStateDirection.eSelf) + return false; + if (depth >= 32) + return false; + + const relationMap: Map | undefined = + eDirection == eApplyGraphStateDirection.eChild ? objectGraphDataEntry.childMap : objectGraphDataEntry.parentMap; + if (!relationMap) + return true; + + for (const systemObjectIDType of relationMap.keys()) { + const relationEntry: ObjectGraphDataEntry | undefined = this.objectMap.get(systemObjectIDType); + if (relationEntry) { + if (relationEntry.applyGraphState(objectGraphState, eDirection)) + // if applying this state changes things, then recurse: + await this.applyGraphStateRecursive(relationEntry, objectGraphState, eDirection, depth + 1); + } + } + return true; + } + + private async extractState(systemObjectIDType: SystemObjectIDType): Promise { + const objectGraphState = new ObjectGraphState(); + + objectGraphState.eType = systemObjectIDType.eObjectType; + switch (systemObjectIDType.eObjectType) { + case eSystemObjectType.eUnit: + case eSystemObjectType.eProject: + case eSystemObjectType.eSubject: + case eSystemObjectType.eItem: + objectGraphState.ancestorObject = systemObjectIDType; + break; + + case eSystemObjectType.eCaptureData:{ + const captureData: CaptureData | null = await CaptureData.fetch(systemObjectIDType.idObject); + if (captureData) { + objectGraphState.captureMethod = captureData.idVCaptureMethod; + const captureDataFiles: CaptureDataFile[] | null = await CaptureDataFile.fetchFromCaptureData(captureData.idCaptureData); + if (captureDataFiles) { + objectGraphState.variantTypes = new Map(); + for (const captureDataFile of captureDataFiles) objectGraphState.variantTypes.set(captureDataFile.idVVariantType, true); + } else LOG.logger.error(`ObjectGraphDatabase.applyGraphData() Unable to load CaptureDataFiles from ${systemObjectIDType}`); + } else LOG.logger.error(`ObjectGraphDatabase.applyGraphData() Unable to load CaptureData from ${systemObjectIDType}`); + } break; + + case eSystemObjectType.eModel: { + const model: Model | null = await Model.fetch(systemObjectIDType.idObject); + if (model) { + objectGraphState.modelPurpose = model.idVPurpose; + const modelGeometryFiles: ModelGeometryFile[] | null = await ModelGeometryFile.fetchFromModel(model.idModel); + if (modelGeometryFiles && modelGeometryFiles.length > 0) { + objectGraphState.modelFileTypes = new Map(); + for (const modelGeometryFile of modelGeometryFiles) objectGraphState.modelFileTypes.set(modelGeometryFile.idVModelFileType, true); + } else LOG.logger.error(`ObjectGraphDatabase.applyGraphData() Unable to load ModelGeometryFiles from ${systemObjectIDType}`); + } else LOG.logger.error(`ObjectGraphDatabase.applyGraphData() Unable to load Model from ${systemObjectIDType}`); + } break; + } + + return objectGraphState; + } + /* #endregion */ +} diff --git a/server/db/prisma/schema.prisma b/server/db/prisma/schema.prisma index 89a4e4836..5f4bd5b30 100644 --- a/server/db/prisma/schema.prisma +++ b/server/db/prisma/schema.prisma @@ -356,6 +356,7 @@ model Metadata { model Model { idModel Int @default(autoincrement()) @id + Name String DateCreated DateTime idVCreationMethod Int Master Boolean diff --git a/server/db/sql/models/Packrat.mwb b/server/db/sql/models/Packrat.mwb index ae217f23736440dc1e1c0d98703a6f2ab9c88e05..9bec5e1179c141f6038b34f3d21ff648477e33dd 100644 GIT binary patch delta 120288 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

xc-c*_djA=S_)0= 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�a}6&u2DQAG9>2U&DeSkJ;QoT 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�O;;%tS>B_lz$e9LPwH}R1RGfl?b?`TeXmCx)#{!63a2-EMnBg^-z1jf;12efeejo;A7q2i z)#M9=W3w#}yl)b$=9=K=B+w2aYsFO%{hhv0-m2BI)A9!6lgHbju;K#n{h#qL@ugrn z9;ZS{2h?n4{0nW1n4M?4S5OO*r=a}xBuW!~FQP73b4DspKgf5!k^av>qT{}ga58fe zwSg-AMzG1^mphp--ma7wyv^4gWBlotu5H()IdaZaOj_;~h`totCETYB7ja5`E%Ypt zXgReHTj?suVy>+pu#>;^Ȭ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 diff --git a/server/db/sql/scripts/Packrat.SCHEMA.sql b/server/db/sql/scripts/Packrat.SCHEMA.sql index 5d3586ff6..d76e5e8bf 100644 --- a/server/db/sql/scripts/Packrat.SCHEMA.sql +++ b/server/db/sql/scripts/Packrat.SCHEMA.sql @@ -259,6 +259,7 @@ CREATE TABLE IF NOT EXISTS `Metadata` ( CREATE TABLE IF NOT EXISTS `Model` ( `idModel` int(11) NOT NULL AUTO_INCREMENT, + `Name` varchar(255) NOT NULL, `DateCreated` datetime NOT NULL, `idVCreationMethod` int(11) NOT NULL, `Master` boolean NOT NULL, diff --git a/server/graphql/schema.graphql b/server/graphql/schema.graphql index 11044a6e1..4e95d5672 100644 --- a/server/graphql/schema.graphql +++ b/server/graphql/schema.graphql @@ -1,1644 +1,1646 @@ type Query { - areCameraSettingsUniform(input: AreCameraSettingsUniformInput!): AreCameraSettingsUniformResult! - getAccessPolicy(input: GetAccessPolicyInput!): GetAccessPolicyResult! - getAsset(input: GetAssetInput!): GetAssetResult! - getAssetDetailsForSystemObject(input: GetAssetDetailsForSystemObjectInput!): GetAssetDetailsForSystemObjectResult! - getAssetVersionsDetails(input: GetAssetVersionsDetailsInput!): GetAssetVersionsDetailsResult! - getCaptureData(input: GetCaptureDataInput!): GetCaptureDataResult! - getCaptureDataPhoto(input: GetCaptureDataPhotoInput!): GetCaptureDataPhotoResult! - getContentsForAssetVersions(input: GetContentsForAssetVersionsInput!): GetContentsForAssetVersionsResult! - getCurrentUser: GetCurrentUserResult! - getDetailsTabDataForObject(input: GetDetailsTabDataForObjectInput!): GetDetailsTabDataForObjectResult! - getFilterViewData: GetFilterViewDataResult! - getIngestionItemsForSubjects(input: GetIngestionItemsForSubjectsInput!): GetIngestionItemsForSubjectsResult! - getIngestionProjectsForSubjects(input: GetIngestionProjectsForSubjectsInput!): GetIngestionProjectsForSubjectsResult! - getIntermediaryFile(input: GetIntermediaryFileInput!): GetIntermediaryFileResult! - getItem(input: GetItemInput!): GetItemResult! - getItemsForSubject(input: GetItemsForSubjectInput!): GetItemsForSubjectResult! - getLicense(input: GetLicenseInput!): GetLicenseResult! - getModel(input: GetModelInput!): GetModelResult! - getObjectChildren(input: GetObjectChildrenInput!): GetObjectChildrenResult! - getObjectsForItem(input: GetObjectsForItemInput!): GetObjectsForItemResult! - getProject(input: GetProjectInput!): GetProjectResult! - getProjectDocumentation(input: GetProjectDocumentationInput!): GetProjectDocumentationResult! - getScene(input: GetSceneInput!): GetSceneResult! - getSourceObjectIdentifer(input: GetSourceObjectIdentiferInput!): GetSourceObjectIdentiferResult! - getSubject(input: GetSubjectInput!): GetSubjectResult! - getSubjectsForUnit(input: GetSubjectsForUnitInput!): GetSubjectsForUnitResult! - getSystemObjectDetails(input: GetSystemObjectDetailsInput!): GetSystemObjectDetailsResult! - getUnit(input: GetUnitInput!): GetUnitResult! - getUploadedAssetVersion: GetUploadedAssetVersionResult! - getUser(input: GetUserInput!): GetUserResult! - getVersionsForSystemObject(input: GetVersionsForSystemObjectInput!): GetVersionsForSystemObjectResult! - getVocabulary(input: GetVocabularyInput!): GetVocabularyResult! - getVocabularyEntries(input: GetVocabularyEntriesInput!): GetVocabularyEntriesResult! - getWorkflow(input: GetWorkflowInput!): GetWorkflowResult! - searchIngestionSubjects(input: SearchIngestionSubjectsInput!): SearchIngestionSubjectsResult! + areCameraSettingsUniform(input: AreCameraSettingsUniformInput!): AreCameraSettingsUniformResult! + getAccessPolicy(input: GetAccessPolicyInput!): GetAccessPolicyResult! + getAsset(input: GetAssetInput!): GetAssetResult! + getAssetDetailsForSystemObject(input: GetAssetDetailsForSystemObjectInput!): GetAssetDetailsForSystemObjectResult! + getAssetVersionsDetails(input: GetAssetVersionsDetailsInput!): GetAssetVersionsDetailsResult! + getCaptureData(input: GetCaptureDataInput!): GetCaptureDataResult! + getCaptureDataPhoto(input: GetCaptureDataPhotoInput!): GetCaptureDataPhotoResult! + getContentsForAssetVersions(input: GetContentsForAssetVersionsInput!): GetContentsForAssetVersionsResult! + getCurrentUser: GetCurrentUserResult! + getDetailsTabDataForObject(input: GetDetailsTabDataForObjectInput!): GetDetailsTabDataForObjectResult! + getFilterViewData: GetFilterViewDataResult! + getIngestionItemsForSubjects(input: GetIngestionItemsForSubjectsInput!): GetIngestionItemsForSubjectsResult! + getIngestionProjectsForSubjects(input: GetIngestionProjectsForSubjectsInput!): GetIngestionProjectsForSubjectsResult! + getIntermediaryFile(input: GetIntermediaryFileInput!): GetIntermediaryFileResult! + getItem(input: GetItemInput!): GetItemResult! + getItemsForSubject(input: GetItemsForSubjectInput!): GetItemsForSubjectResult! + getLicense(input: GetLicenseInput!): GetLicenseResult! + getModel(input: GetModelInput!): GetModelResult! + getObjectChildren(input: GetObjectChildrenInput!): GetObjectChildrenResult! + getObjectsForItem(input: GetObjectsForItemInput!): GetObjectsForItemResult! + getProject(input: GetProjectInput!): GetProjectResult! + getProjectDocumentation(input: GetProjectDocumentationInput!): GetProjectDocumentationResult! + getScene(input: GetSceneInput!): GetSceneResult! + getSourceObjectIdentifer(input: GetSourceObjectIdentiferInput!): GetSourceObjectIdentiferResult! + getSubject(input: GetSubjectInput!): GetSubjectResult! + getSubjectsForUnit(input: GetSubjectsForUnitInput!): GetSubjectsForUnitResult! + getSystemObjectDetails(input: GetSystemObjectDetailsInput!): GetSystemObjectDetailsResult! + getUnit(input: GetUnitInput!): GetUnitResult! + getUploadedAssetVersion: GetUploadedAssetVersionResult! + getUser(input: GetUserInput!): GetUserResult! + getVersionsForSystemObject(input: GetVersionsForSystemObjectInput!): GetVersionsForSystemObjectResult! + getVocabulary(input: GetVocabularyInput!): GetVocabularyResult! + getVocabularyEntries(input: GetVocabularyEntriesInput!): GetVocabularyEntriesResult! + getWorkflow(input: GetWorkflowInput!): GetWorkflowResult! + searchIngestionSubjects(input: SearchIngestionSubjectsInput!): SearchIngestionSubjectsResult! } input GetAccessPolicyInput { - idAccessPolicy: Int! + idAccessPolicy: Int! } type GetAccessPolicyResult { - AccessPolicy: AccessPolicy + AccessPolicy: AccessPolicy } scalar DateTime type AccessAction { - idAccessAction: Int! - Name: String! - SortOrder: Int! - AccessRole: [AccessRole] + idAccessAction: Int! + Name: String! + SortOrder: Int! + AccessRole: [AccessRole] } type AccessContext { - idAccessContext: Int! - Authoritative: Boolean! - CaptureData: Boolean! - Global: Boolean! - IntermediaryFile: Boolean! - Model: Boolean! - Scene: Boolean! - AccessContextObject: [AccessContextObject] - AccessPolicy: [AccessPolicy] + idAccessContext: Int! + Authoritative: Boolean! + CaptureData: Boolean! + Global: Boolean! + IntermediaryFile: Boolean! + Model: Boolean! + Scene: Boolean! + AccessContextObject: [AccessContextObject] + AccessPolicy: [AccessPolicy] } type AccessContextObject { - idAccessContextObject: Int! - idAccessContext: Int! - idSystemObject: Int! - AccessContext: AccessContext - SystemObject: SystemObject + idAccessContextObject: Int! + idAccessContext: Int! + idSystemObject: Int! + AccessContext: AccessContext + SystemObject: SystemObject } type AccessPolicy { - idAccessPolicy: Int! - idAccessContext: Int! - idAccessRole: Int! - idUser: Int! - AccessContext: AccessContext - AccessRole: AccessRole - User: User + idAccessPolicy: Int! + idAccessContext: Int! + idAccessRole: Int! + idUser: Int! + AccessContext: AccessContext + AccessRole: AccessRole + User: User } type AccessRole { - idAccessRole: Int! - Name: String! - AccessAction: [AccessAction] + idAccessRole: Int! + Name: String! + AccessAction: [AccessAction] } scalar Upload type Mutation { - createCaptureData(input: CreateCaptureDataInput!): CreateCaptureDataResult! - createCaptureDataPhoto(input: CreateCaptureDataPhotoInput!): CreateCaptureDataPhotoResult! - createItem(input: CreateItemInput!): CreateItemResult! - createModel(input: CreateModelInput!): CreateModelResult! - createProject(input: CreateProjectInput!): CreateProjectResult! - createScene(input: CreateSceneInput!): CreateSceneResult! - createSubject(input: CreateSubjectInput!): CreateSubjectResult! - createUnit(input: CreateUnitInput!): CreateUnitResult! - createUser(input: CreateUserInput!): CreateUserResult! - createVocabulary(input: CreateVocabularyInput!): CreateVocabularyResult! - createVocabularySet(input: CreateVocabularySetInput!): CreateVocabularySetResult! - discardUploadedAssetVersions(input: DiscardUploadedAssetVersionsInput!): DiscardUploadedAssetVersionsResult! - ingestData(input: IngestDataInput!): IngestDataResult! - updateObjectDetails(input: UpdateObjectDetailsInput!): UpdateObjectDetailsResult! - uploadAsset(file: Upload!, type: Int!): UploadAssetResult! + createCaptureData(input: CreateCaptureDataInput!): CreateCaptureDataResult! + createCaptureDataPhoto(input: CreateCaptureDataPhotoInput!): CreateCaptureDataPhotoResult! + createItem(input: CreateItemInput!): CreateItemResult! + createModel(input: CreateModelInput!): CreateModelResult! + createProject(input: CreateProjectInput!): CreateProjectResult! + createScene(input: CreateSceneInput!): CreateSceneResult! + createSubject(input: CreateSubjectInput!): CreateSubjectResult! + createUnit(input: CreateUnitInput!): CreateUnitResult! + createUser(input: CreateUserInput!): CreateUserResult! + createVocabulary(input: CreateVocabularyInput!): CreateVocabularyResult! + createVocabularySet(input: CreateVocabularySetInput!): CreateVocabularySetResult! + discardUploadedAssetVersions(input: DiscardUploadedAssetVersionsInput!): DiscardUploadedAssetVersionsResult! + ingestData(input: IngestDataInput!): IngestDataResult! + updateObjectDetails(input: UpdateObjectDetailsInput!): UpdateObjectDetailsResult! + uploadAsset(file: Upload!, type: Int!): UploadAssetResult! } type UploadAssetInput { - file: Upload! - type: Int! + file: Upload! + type: Int! } enum UploadStatus { - COMPLETE - FAILED + COMPLETE + FAILED } type UploadAssetResult { - status: UploadStatus! - idAssetVersions: [Int!] - error: String + status: UploadStatus! + idAssetVersions: [Int!] + error: String } input DiscardUploadedAssetVersionsInput { - idAssetVersions: [Int!]! + idAssetVersions: [Int!]! } type DiscardUploadedAssetVersionsResult { - success: Boolean! + success: Boolean! } input GetAssetVersionsDetailsInput { - idAssetVersions: [Int!]! + idAssetVersions: [Int!]! } type IngestIdentifier { - identifier: String! - identifierType: Int! + identifier: String! + identifierType: Int! } type IngestFolder { - name: String! - variantType: Int! + name: String! + variantType: Int! } type IngestPhotogrammetry { - idAssetVersion: Int! - dateCaptured: String! - datasetType: Int! - systemCreated: Boolean! - description: String! - cameraSettingUniform: Boolean! - datasetFieldId: Int - itemPositionType: Int - itemPositionFieldId: Int - itemArrangementFieldId: Int - focusType: Int - lightsourceType: Int - backgroundRemovalMethod: Int - clusterType: Int - clusterGeometryFieldId: Int - directory: String! - folders: [IngestFolder!]! - identifiers: [IngestIdentifier!]! + idAssetVersion: Int! + dateCaptured: String! + datasetType: Int! + systemCreated: Boolean! + description: String! + cameraSettingUniform: Boolean! + datasetFieldId: Int + itemPositionType: Int + itemPositionFieldId: Int + itemArrangementFieldId: Int + focusType: Int + lightsourceType: Int + backgroundRemovalMethod: Int + clusterType: Int + clusterGeometryFieldId: Int + directory: String! + folders: [IngestFolder!]! + identifiers: [IngestIdentifier!]! } type IngestUVMap { - name: String! - edgeLength: Int! - mapType: Int! + name: String! + edgeLength: Int! + mapType: Int! } enum RelatedObjectType { - Source - Derived + Source + Derived } type RelatedObject { - idSystemObject: Int! - name: String! - identifier: String - objectType: Int! + idSystemObject: Int! + name: String! + identifier: String + objectType: Int! } type IngestModel { - idAssetVersion: Int! - systemCreated: Boolean! - master: Boolean! - authoritative: Boolean! - creationMethod: Int! - modality: Int! - purpose: Int! - units: Int! - dateCaptured: String! - modelFileType: Int! - directory: String! - identifiers: [IngestIdentifier!]! - uvMaps: [IngestUVMap!]! - sourceObjects: [RelatedObject!]! - roughness: Int - metalness: Int - pointCount: Int - faceCount: Int - isWatertight: Boolean - hasNormals: Boolean - hasVertexColor: Boolean - hasUVSpace: Boolean - boundingBoxP1X: Float - boundingBoxP1Y: Float - boundingBoxP1Z: Float - boundingBoxP2X: Float - boundingBoxP2Y: Float - boundingBoxP2Z: Float + idAssetVersion: Int! + systemCreated: Boolean! + master: Boolean! + authoritative: Boolean! + creationMethod: Int! + modality: Int! + purpose: Int! + units: Int! + dateCaptured: String! + modelFileType: Int! + directory: String! + identifiers: [IngestIdentifier!]! + uvMaps: [IngestUVMap!]! + sourceObjects: [RelatedObject!]! + roughness: Int + metalness: Int + pointCount: Int + faceCount: Int + isWatertight: Boolean + hasNormals: Boolean + hasVertexColor: Boolean + hasUVSpace: Boolean + boundingBoxP1X: Float + boundingBoxP1Y: Float + boundingBoxP1Z: Float + boundingBoxP2X: Float + boundingBoxP2Y: Float + boundingBoxP2Z: Float } enum ReferenceModelAction { - Update - Ingest + Update + Ingest } type ReferenceModel { - idSystemObject: Int! - name: String! - fileSize: Int! - resolution: Int - boundingBoxP1X: Float - boundingBoxP1Y: Float - boundingBoxP1Z: Float - boundingBoxP2X: Float - boundingBoxP2Y: Float - boundingBoxP2Z: Float - action: ReferenceModelAction! + idSystemObject: Int! + name: String! + fileSize: Int! + resolution: Int + boundingBoxP1X: Float + boundingBoxP1Y: Float + boundingBoxP1Z: Float + boundingBoxP2X: Float + boundingBoxP2Y: Float + boundingBoxP2Z: Float + action: ReferenceModelAction! } type IngestScene { - idAssetVersion: Int! - systemCreated: Boolean! - identifiers: [IngestIdentifier!]! - referenceModels: [ReferenceModel!]! + idAssetVersion: Int! + systemCreated: Boolean! + identifiers: [IngestIdentifier!]! + referenceModels: [ReferenceModel!]! } type GetAssetVersionDetailResult { - idAssetVersion: Int! - SubjectUnitIdentifier: SubjectUnitIdentifier - Project: [Project!] - Item: Item - CaptureDataPhoto: IngestPhotogrammetry - Model: IngestModel - Scene: IngestScene + idAssetVersion: Int! + SubjectUnitIdentifier: SubjectUnitIdentifier + Project: [Project!] + Item: Item + CaptureDataPhoto: IngestPhotogrammetry + Model: IngestModel + Scene: IngestScene } type GetAssetVersionsDetailsResult { - valid: Boolean! - Details: [GetAssetVersionDetailResult!]! + valid: Boolean! + Details: [GetAssetVersionDetailResult!]! } input GetAssetInput { - idAsset: Int! + idAsset: Int! } type GetAssetResult { - Asset: Asset + Asset: Asset } type GetUploadedAssetVersionResult { - AssetVersion: [AssetVersion!]! + AssetVersion: [AssetVersion!]! } input GetContentsForAssetVersionsInput { - idAssetVersions: [Int!]! + idAssetVersions: [Int!]! } type AssetVersionContent { - idAssetVersion: Int! - folders: [String!]! - all: [String!]! + idAssetVersion: Int! + folders: [String!]! + all: [String!]! } type GetContentsForAssetVersionsResult { - AssetVersionContent: [AssetVersionContent!]! + AssetVersionContent: [AssetVersionContent!]! } type Asset { - idAsset: Int! - FileName: String! - FilePath: String! - idAssetGroup: Int - idVAssetType: Int - idSystemObject: Int - StorageKey: String - AssetGroup: AssetGroup - SystemObjectSource: SystemObject - AssetVersion: [AssetVersion] - VAssetType: Vocabulary - SystemObject: SystemObject + idAsset: Int! + FileName: String! + FilePath: String! + idAssetGroup: Int + idVAssetType: Int + idSystemObject: Int + StorageKey: String + AssetGroup: AssetGroup + SystemObjectSource: SystemObject + AssetVersion: [AssetVersion] + VAssetType: Vocabulary + SystemObject: SystemObject } type AssetVersion { - idAssetVersion: Int! - DateCreated: DateTime! - idAsset: Int! - idUserCreator: Int! - StorageHash: String! - StorageSize: Int! - StorageKeyStaging: String! - FileName: String! - Ingested: Boolean! - Version: Int! - Asset: Asset - User: User - SystemObject: SystemObject + idAssetVersion: Int! + DateCreated: DateTime! + idAsset: Int! + idUserCreator: Int! + StorageHash: String! + StorageSize: Int! + StorageKeyStaging: String! + FileName: String! + Ingested: Boolean! + Version: Int! + Asset: Asset + User: User + SystemObject: SystemObject } type AssetGroup { - idAssetGroup: Int! - Asset: [Asset] + idAssetGroup: Int! + Asset: [Asset] } input CreateCaptureDataInput { - Name: String! - idVCaptureMethod: Int! - DateCaptured: DateTime! - Description: String! - idAssetThumbnail: Int + Name: String! + idVCaptureMethod: Int! + DateCaptured: DateTime! + Description: String! + idAssetThumbnail: Int } type CreateCaptureDataResult { - CaptureData: CaptureData + CaptureData: CaptureData } input CreateCaptureDataPhotoInput { - idCaptureData: Int! - idVCaptureDatasetType: Int! - CaptureDatasetFieldID: Int! - ItemPositionFieldID: Int! - ItemArrangementFieldID: Int! - idVBackgroundRemovalMethod: Int! - ClusterGeometryFieldID: Int! - CameraSettingsUniform: Boolean! - idVItemPositionType: Int - idVFocusType: Int - idVLightSourceType: Int - idVClusterType: Int + idCaptureData: Int! + idVCaptureDatasetType: Int! + CaptureDatasetFieldID: Int! + ItemPositionFieldID: Int! + ItemArrangementFieldID: Int! + idVBackgroundRemovalMethod: Int! + ClusterGeometryFieldID: Int! + CameraSettingsUniform: Boolean! + idVItemPositionType: Int + idVFocusType: Int + idVLightSourceType: Int + idVClusterType: Int } type CreateCaptureDataPhotoResult { - CaptureDataPhoto: CaptureDataPhoto + CaptureDataPhoto: CaptureDataPhoto } input GetCaptureDataInput { - idCaptureData: Int! + idCaptureData: Int! } type GetCaptureDataResult { - CaptureData: CaptureData + CaptureData: CaptureData } input GetCaptureDataPhotoInput { - idCaptureDataPhoto: Int! + idCaptureDataPhoto: Int! } type GetCaptureDataPhotoResult { - CaptureDataPhoto: CaptureDataPhoto + CaptureDataPhoto: CaptureDataPhoto } type CaptureData { - idCaptureData: Int! - DateCaptured: DateTime! - Description: String! - idVCaptureMethod: Int! - idAssetThumbnail: Int - AssetThumbnail: Asset - VCaptureMethod: Vocabulary - CaptureDataFile: [CaptureDataFile] - CaptureDataGroup: [CaptureDataGroup] - CaptureDataPhoto: [CaptureDataPhoto] - SystemObject: SystemObject + idCaptureData: Int! + DateCaptured: DateTime! + Description: String! + idVCaptureMethod: Int! + idAssetThumbnail: Int + AssetThumbnail: Asset + VCaptureMethod: Vocabulary + CaptureDataFile: [CaptureDataFile] + CaptureDataGroup: [CaptureDataGroup] + CaptureDataPhoto: [CaptureDataPhoto] + SystemObject: SystemObject } type CaptureDataFile { - idCaptureDataFile: Int! - CompressedMultipleFiles: Boolean! - idAsset: Int! - idCaptureData: Int! - idVVariantType: Int! - Asset: Asset - CaptureData: CaptureData - VVariantType: Vocabulary + idCaptureDataFile: Int! + CompressedMultipleFiles: Boolean! + idAsset: Int! + idCaptureData: Int! + idVVariantType: Int! + Asset: Asset + CaptureData: CaptureData + VVariantType: Vocabulary } type CaptureDataGroup { - idCaptureDataGroup: Int! - CaptureData: [CaptureData] + idCaptureDataGroup: Int! + CaptureData: [CaptureData] } type CaptureDataPhoto { - idCaptureDataPhoto: Int! - idCaptureData: Int! - idVCaptureDatasetType: Int! - CameraSettingsUniform: Boolean - CaptureDatasetFieldID: Int - ClusterGeometryFieldID: Int - idVBackgroundRemovalMethod: Int - idVClusterType: Int - idVFocusType: Int - idVItemPositionType: Int - idVLightSourceType: Int - ItemArrangementFieldID: Int - ItemPositionFieldID: Int - CaptureData: CaptureData - VBackgroundRemovalMethod: Vocabulary - VCaptureDatasetType: Vocabulary - VClusterType: Vocabulary - VFocusType: Vocabulary - VItemPositionType: Vocabulary - VLightSourceType: Vocabulary + idCaptureDataPhoto: Int! + idCaptureData: Int! + idVCaptureDatasetType: Int! + CameraSettingsUniform: Boolean + CaptureDatasetFieldID: Int + ClusterGeometryFieldID: Int + idVBackgroundRemovalMethod: Int + idVClusterType: Int + idVFocusType: Int + idVItemPositionType: Int + idVLightSourceType: Int + ItemArrangementFieldID: Int + ItemPositionFieldID: Int + CaptureData: CaptureData + VBackgroundRemovalMethod: Vocabulary + VCaptureDatasetType: Vocabulary + VClusterType: Vocabulary + VFocusType: Vocabulary + VItemPositionType: Vocabulary + VLightSourceType: Vocabulary } input IngestSubjectInput { - id: Int - name: String! - arkId: String! - unit: String! + id: Int + name: String! + arkId: String! + unit: String! } input IngestProjectInput { - id: Int! - name: String! + id: Int! + name: String! } input IngestItemInput { - id: Int - name: String! - entireSubject: Boolean! + id: Int + name: String! + entireSubject: Boolean! } input IngestIdentifierInput { - identifier: String! - identifierType: Int! + identifier: String! + identifierType: Int! } input IngestFolderInput { - name: String! - variantType: Int! + name: String! + variantType: Int! } input IngestPhotogrammetryInput { - idAssetVersion: Int! - dateCaptured: String! - datasetType: Int! - systemCreated: Boolean! - description: String! - cameraSettingUniform: Boolean! - datasetFieldId: Int - itemPositionType: Int - itemPositionFieldId: Int - itemArrangementFieldId: Int - focusType: Int - lightsourceType: Int - backgroundRemovalMethod: Int - clusterType: Int - clusterGeometryFieldId: Int - directory: String! - folders: [IngestFolderInput!]! - identifiers: [IngestIdentifierInput!]! + idAssetVersion: Int! + dateCaptured: String! + datasetType: Int! + systemCreated: Boolean! + description: String! + cameraSettingUniform: Boolean! + datasetFieldId: Int + itemPositionType: Int + itemPositionFieldId: Int + itemArrangementFieldId: Int + focusType: Int + lightsourceType: Int + backgroundRemovalMethod: Int + clusterType: Int + clusterGeometryFieldId: Int + directory: String! + folders: [IngestFolderInput!]! + identifiers: [IngestIdentifierInput!]! } input IngestUVMapInput { - name: String! - edgeLength: Int! - mapType: Int! + name: String! + edgeLength: Int! + mapType: Int! } input RelatedObjectInput { - idSystemObject: Int! - name: String! - identifier: String - objectType: Int! + idSystemObject: Int! + name: String! + identifier: String + objectType: Int! } input IngestModelInput { - idAssetVersion: Int! - systemCreated: Boolean! - master: Boolean! - authoritative: Boolean! - creationMethod: Int! - modality: Int! - purpose: Int! - units: Int! - dateCaptured: String! - modelFileType: Int! - directory: String! - identifiers: [IngestIdentifierInput!]! - uvMaps: [IngestUVMapInput!]! - sourceObjects: [RelatedObjectInput!]! - roughness: Int - metalness: Int - pointCount: Int - faceCount: Int - isWatertight: Boolean - hasNormals: Boolean - hasVertexColor: Boolean - hasUVSpace: Boolean - boundingBoxP1X: Float - boundingBoxP1Y: Float - boundingBoxP1Z: Float - boundingBoxP2X: Float - boundingBoxP2Y: Float - boundingBoxP2Z: Float + idAssetVersion: Int! + systemCreated: Boolean! + master: Boolean! + authoritative: Boolean! + creationMethod: Int! + modality: Int! + purpose: Int! + units: Int! + dateCaptured: String! + modelFileType: Int! + directory: String! + identifiers: [IngestIdentifierInput!]! + uvMaps: [IngestUVMapInput!]! + sourceObjects: [RelatedObjectInput!]! + roughness: Int + metalness: Int + pointCount: Int + faceCount: Int + isWatertight: Boolean + hasNormals: Boolean + hasVertexColor: Boolean + hasUVSpace: Boolean + boundingBoxP1X: Float + boundingBoxP1Y: Float + boundingBoxP1Z: Float + boundingBoxP2X: Float + boundingBoxP2Y: Float + boundingBoxP2Z: Float } input ReferenceModelInput { - idSystemObject: Int! - name: String! - fileSize: Int! - resolution: Int - boundingBoxP1X: Float - boundingBoxP1Y: Float - boundingBoxP1Z: Float - boundingBoxP2X: Float - boundingBoxP2Y: Float - boundingBoxP2Z: Float - action: ReferenceModelAction! + idSystemObject: Int! + name: String! + fileSize: Int! + resolution: Int + boundingBoxP1X: Float + boundingBoxP1Y: Float + boundingBoxP1Z: Float + boundingBoxP2X: Float + boundingBoxP2Y: Float + boundingBoxP2Z: Float + action: ReferenceModelAction! } input IngestSceneInput { - idAssetVersion: Int! - systemCreated: Boolean! - identifiers: [IngestIdentifierInput!]! - referenceModels: [ReferenceModelInput!]! + idAssetVersion: Int! + systemCreated: Boolean! + identifiers: [IngestIdentifierInput!]! + referenceModels: [ReferenceModelInput!]! } input IngestOtherInput { - idAssetVersion: Int! - systemCreated: Boolean! - identifiers: [IngestIdentifierInput!]! + idAssetVersion: Int! + systemCreated: Boolean! + identifiers: [IngestIdentifierInput!]! } input IngestDataInput { - subjects: [IngestSubjectInput!]! - project: IngestProjectInput! - item: IngestItemInput! - photogrammetry: [IngestPhotogrammetryInput!]! - model: [IngestModelInput!]! - scene: [IngestSceneInput!]! - other: [IngestOtherInput!]! + subjects: [IngestSubjectInput!]! + project: IngestProjectInput! + item: IngestItemInput! + photogrammetry: [IngestPhotogrammetryInput!]! + model: [IngestModelInput!]! + scene: [IngestSceneInput!]! + other: [IngestOtherInput!]! } type IngestDataResult { - success: Boolean! + success: Boolean! } input AreCameraSettingsUniformInput { - idAssetVersion: Int! + idAssetVersion: Int! } type AreCameraSettingsUniformResult { - isUniform: Boolean! + isUniform: Boolean! } input GetLicenseInput { - idLicense: Int! + idLicense: Int! } type GetLicenseResult { - License: License + License: License } type License { - idLicense: Int! - Description: String! - Name: String! - LicenseAssignment: [LicenseAssignment] + idLicense: Int! + Description: String! + Name: String! + LicenseAssignment: [LicenseAssignment] } type LicenseAssignment { - idLicenseAssignment: Int! - idLicense: Int! - DateEnd: DateTime - DateStart: DateTime - idSystemObject: Int - idUserCreator: Int - License: License - SystemObject: SystemObject - UserCreator: User + idLicenseAssignment: Int! + idLicense: Int! + DateEnd: DateTime + DateStart: DateTime + idSystemObject: Int + idUserCreator: Int + License: License + SystemObject: SystemObject + UserCreator: User } input CreateModelInput { - Authoritative: Boolean! - idVCreationMethod: Int! - idVModality: Int! - idVPurpose: Int! - idVUnits: Int! - Master: Boolean! - idAssetThumbnail: Int + Name: String! + Authoritative: Boolean! + idVCreationMethod: Int! + idVModality: Int! + idVPurpose: Int! + idVUnits: Int! + Master: Boolean! + idAssetThumbnail: Int } type CreateModelResult { - Model: Model + Model: Model } input GetModelInput { - idModel: Int! + idModel: Int! } type GetModelResult { - Model: Model + Model: Model } type Model { - idModel: Int! - Authoritative: Boolean! - DateCreated: DateTime! - idAssetThumbnail: Int - idVCreationMethod: Int! - idVModality: Int! - idVPurpose: Int! - idVUnits: Int! - Master: Boolean! - AssetThumbnail: Asset - VCreationMethod: Vocabulary - VModality: Vocabulary - VPurpose: Vocabulary - VUnits: Vocabulary - ModelGeometryFile: [ModelGeometryFile] - ModelProcessingAction: [ModelProcessingAction] - ModelSceneXref: [ModelSceneXref] - SystemObject: SystemObject + idModel: Int! + Name: String! + Authoritative: Boolean! + DateCreated: DateTime! + idAssetThumbnail: Int + idVCreationMethod: Int! + idVModality: Int! + idVPurpose: Int! + idVUnits: Int! + Master: Boolean! + AssetThumbnail: Asset + VCreationMethod: Vocabulary + VModality: Vocabulary + VPurpose: Vocabulary + VUnits: Vocabulary + ModelGeometryFile: [ModelGeometryFile] + ModelProcessingAction: [ModelProcessingAction] + ModelSceneXref: [ModelSceneXref] + SystemObject: SystemObject } type ModelGeometryFile { - idModelGeometryFile: Int! - idAsset: Int! - idModel: Int! - idVModelFileType: Int! - BoundingBoxP1X: Float - BoundingBoxP1Y: Float - BoundingBoxP1Z: Float - BoundingBoxP2X: Float - BoundingBoxP2Y: Float - BoundingBoxP2Z: Float - FaceCount: Int - HasNormals: Boolean - HasUVSpace: Boolean - HasVertexColor: Boolean - IsWatertight: Boolean - Metalness: Float - PointCount: Int - Roughness: Float - Asset: Asset - Model: Model - VModelFileType: Vocabulary - ModelUVMapFile: [ModelUVMapFile] + idModelGeometryFile: Int! + idAsset: Int! + idModel: Int! + idVModelFileType: Int! + BoundingBoxP1X: Float + BoundingBoxP1Y: Float + BoundingBoxP1Z: Float + BoundingBoxP2X: Float + BoundingBoxP2Y: Float + BoundingBoxP2Z: Float + FaceCount: Int + HasNormals: Boolean + HasUVSpace: Boolean + HasVertexColor: Boolean + IsWatertight: Boolean + Metalness: Float + PointCount: Int + Roughness: Float + Asset: Asset + Model: Model + VModelFileType: Vocabulary + ModelUVMapFile: [ModelUVMapFile] } type ModelProcessingAction { - idModelProcessingAction: Int! - DateProcessed: DateTime! - Description: String! - idActor: Int! - idModel: Int! - ToolsUsed: String! - Actor: Actor - Model: Model - ModelProcessingActionStep: [ModelProcessingActionStep]! + idModelProcessingAction: Int! + DateProcessed: DateTime! + Description: String! + idActor: Int! + idModel: Int! + ToolsUsed: String! + Actor: Actor + Model: Model + ModelProcessingActionStep: [ModelProcessingActionStep]! } type ModelProcessingActionStep { - idModelProcessingActionStep: Int! - Description: String! - idModelProcessingAction: Int! - idVActionMethod: Int! - ModelProcessingAction: ModelProcessingAction - VActionMethod: Vocabulary + idModelProcessingActionStep: Int! + Description: String! + idModelProcessingAction: Int! + idVActionMethod: Int! + ModelProcessingAction: ModelProcessingAction + VActionMethod: Vocabulary } type ModelSceneXref { - idModelSceneXref: Int! - idModel: Int! - idScene: Int! - R0: Float - R1: Float - R2: Float - R3: Float - TS0: Float - TS1: Float - TS2: Float - Model: Model - Scene: Scene + idModelSceneXref: Int! + idModel: Int! + idScene: Int! + R0: Float + R1: Float + R2: Float + R3: Float + TS0: Float + TS1: Float + TS2: Float + Model: Model + Scene: Scene } type ModelUVMapChannel { - idModelUVMapChannel: Int! - ChannelPosition: Int! - ChannelWidth: Int! - idModelUVMapFile: Int! - idVUVMapType: Int! - ModelUVMapFile: ModelUVMapFile - VUVMapType: Vocabulary + idModelUVMapChannel: Int! + ChannelPosition: Int! + ChannelWidth: Int! + idModelUVMapFile: Int! + idVUVMapType: Int! + ModelUVMapFile: ModelUVMapFile + VUVMapType: Vocabulary } type ModelUVMapFile { - idModelUVMapFile: Int! - idAsset: Int! - idModelGeometryFile: Int! - UVMapEdgeLength: Int! - Asset: Asset - ModelGeometryFile: ModelGeometryFile - ModelUVMapChannel: [ModelUVMapChannel] + idModelUVMapFile: Int! + idAsset: Int! + idModelGeometryFile: Int! + UVMapEdgeLength: Int! + Asset: Asset + ModelGeometryFile: ModelGeometryFile + ModelUVMapChannel: [ModelUVMapChannel] } input PaginationInput { - first: Int - skip: Int - offset: Int - size: Int + first: Int + skip: Int + offset: Int + size: Int } input GetObjectChildrenInput { - idRoot: Int! - objectTypes: [Int!]! - objectsToDisplay: [Int!]! - metadataColumns: [Int!]! - search: String! - units: [Int!]! - projects: [Int!]! - has: [Int!]! - missing: [Int!]! - captureMethod: [Int!]! - variantType: [Int!]! - modelPurpose: [Int!]! - modelFileType: [Int!]! + idRoot: Int! + objectTypes: [Int!]! + objectsToDisplay: [Int!]! + metadataColumns: [Int!]! + search: String! + units: [Int!]! + projects: [Int!]! + has: [Int!]! + missing: [Int!]! + captureMethod: [Int!]! + variantType: [Int!]! + modelPurpose: [Int!]! + modelFileType: [Int!]! } type NavigationResultEntry { - idSystemObject: Int! - name: String! - objectType: Int! - idObject: Int! - metadata: [String!]! + idSystemObject: Int! + name: String! + objectType: Int! + idObject: Int! + metadata: [String!]! } type GetObjectChildrenResult { - success: Boolean! - error: String! - entries: [NavigationResultEntry!]! - metadataColumns: [Int!]! + success: Boolean! + error: String! + entries: [NavigationResultEntry!]! + metadataColumns: [Int!]! } type GetFilterViewDataResult { - units: [Unit!]! - projects: [Project!]! + units: [Unit!]! + projects: [Project!]! } input CreateSceneInput { - Name: String! - HasBeenQCd: Boolean! - IsOriented: Boolean! - idAssetThumbnail: Int + Name: String! + HasBeenQCd: Boolean! + IsOriented: Boolean! + idAssetThumbnail: Int } type CreateSceneResult { - Scene: Scene + Scene: Scene } input GetSceneInput { - idScene: Int! + idScene: Int! } type GetSceneResult { - Scene: Scene + Scene: Scene } input GetIntermediaryFileInput { - idIntermediaryFile: Int! + idIntermediaryFile: Int! } type GetIntermediaryFileResult { - IntermediaryFile: IntermediaryFile + IntermediaryFile: IntermediaryFile } type Scene { - idScene: Int! - HasBeenQCd: Boolean! - idAssetThumbnail: Int - IsOriented: Boolean! - Name: String! - AssetThumbnail: Asset - ModelSceneXref: [ModelSceneXref] - SystemObject: SystemObject + idScene: Int! + HasBeenQCd: Boolean! + idAssetThumbnail: Int + IsOriented: Boolean! + Name: String! + AssetThumbnail: Asset + ModelSceneXref: [ModelSceneXref] + SystemObject: SystemObject } type Actor { - idActor: Int! - idUnit: Int - IndividualName: String - OrganizationName: String - Unit: Unit - SystemObject: SystemObject + idActor: Int! + idUnit: Int + IndividualName: String + OrganizationName: String + Unit: Unit + SystemObject: SystemObject } type IntermediaryFile { - idIntermediaryFile: Int! - DateCreated: DateTime! - idAsset: Int! - Asset: Asset - SystemObject: SystemObject + idIntermediaryFile: Int! + DateCreated: DateTime! + idAsset: Int! + Asset: Asset + SystemObject: SystemObject } input UpdateObjectDetailsInput { - idSystemObject: Int! - idObject: Int! - objectType: Int! - data: UpdateObjectDetailsDataInput! + idSystemObject: Int! + idObject: Int! + objectType: Int! + data: UpdateObjectDetailsDataInput! } input UnitDetailFieldsInput { - Abbreviation: String - ARKPrefix: String + Abbreviation: String + ARKPrefix: String } input ProjectDetailFieldsInput { - Description: String + Description: String } input SubjectDetailFieldsInput { - Altitude: Float - Latitude: Float - Longitude: Float - R0: Float - R1: Float - R2: Float - R3: Float - TS0: Float - TS1: Float - TS2: Float + Altitude: Float + Latitude: Float + Longitude: Float + R0: Float + R1: Float + R2: Float + R3: Float + TS0: Float + TS1: Float + TS2: Float } input ItemDetailFieldsInput { - EntireSubject: Boolean - Altitude: Float - Latitude: Float - Longitude: Float - R0: Float - R1: Float - R2: Float - R3: Float - TS0: Float - TS1: Float - TS2: Float + EntireSubject: Boolean + Altitude: Float + Latitude: Float + Longitude: Float + R0: Float + R1: Float + R2: Float + R3: Float + TS0: Float + TS1: Float + TS2: Float } input CaptureDataDetailFieldsInput { - captureMethod: Int - dateCaptured: DateTime - datasetType: Int - systemCreated: Boolean - description: String - cameraSettingUniform: Boolean - datasetFieldId: Int - itemPositionType: Int - itemPositionFieldId: Int - itemArrangementFieldId: Int - focusType: Int - lightsourceType: Int - backgroundRemovalMethod: Int - clusterType: Int - clusterGeometryFieldId: Int - folders: [IngestFolderInput!]! + captureMethod: Int + dateCaptured: DateTime + datasetType: Int + systemCreated: Boolean + description: String + cameraSettingUniform: Boolean + datasetFieldId: Int + itemPositionType: Int + itemPositionFieldId: Int + itemArrangementFieldId: Int + focusType: Int + lightsourceType: Int + backgroundRemovalMethod: Int + clusterType: Int + clusterGeometryFieldId: Int + folders: [IngestFolderInput!]! } input ModelDetailFieldsInput { - size: Int - master: Boolean - authoritative: Boolean - creationMethod: Int - modality: Int - purpose: Int - units: Int - dateCaptured: DateTime - modelFileType: Int - uvMaps: [IngestUVMapInput!]! - roughness: Int - metalness: Int - pointCount: Int - faceCount: Int - isWatertight: Boolean - hasNormals: Boolean - hasVertexColor: Boolean - hasUVSpace: Boolean - boundingBoxP1X: Float - boundingBoxP1Y: Float - boundingBoxP1Z: Float - boundingBoxP2X: Float - boundingBoxP2Y: Float - boundingBoxP2Z: Float + size: Int + master: Boolean + authoritative: Boolean + creationMethod: Int + modality: Int + purpose: Int + units: Int + dateCaptured: DateTime + modelFileType: Int + uvMaps: [IngestUVMapInput!]! + roughness: Int + metalness: Int + pointCount: Int + faceCount: Int + isWatertight: Boolean + hasNormals: Boolean + hasVertexColor: Boolean + hasUVSpace: Boolean + boundingBoxP1X: Float + boundingBoxP1Y: Float + boundingBoxP1Z: Float + boundingBoxP2X: Float + boundingBoxP2Y: Float + boundingBoxP2Z: Float } input SceneDetailFieldsInput { - Links: [String!]! - AssetType: Int - Tours: Int - Annotation: Int - HasBeenQCd: Boolean - IsOriented: Boolean + Links: [String!]! + AssetType: Int + Tours: Int + Annotation: Int + HasBeenQCd: Boolean + IsOriented: Boolean } input ProjectDocumentationDetailFieldsInput { - Description: String + Description: String } input AssetDetailFieldsInput { - FilePath: String - AssetType: Int + FilePath: String + AssetType: Int } input AssetVersionDetailFieldsInput { - Creator: String - DateCreated: DateTime - Ingested: Boolean - Version: Int - StorageSize: Int + Creator: String + DateCreated: DateTime + Ingested: Boolean + Version: Int + StorageSize: Int } input ActorDetailFieldsInput { - OrganizationName: String + OrganizationName: String } input StakeholderDetailFieldsInput { - OrganizationName: String - MailingAddress: String - EmailAddress: String - PhoneNumberMobile: String - PhoneNumberOffice: String + OrganizationName: String + MailingAddress: String + EmailAddress: String + PhoneNumberMobile: String + PhoneNumberOffice: String } input UpdateObjectDetailsDataInput { - Name: String - Retired: Boolean - Unit: UnitDetailFieldsInput - Project: ProjectDetailFieldsInput - Subject: SubjectDetailFieldsInput - Item: ItemDetailFieldsInput - CaptureData: CaptureDataDetailFieldsInput - Model: ModelDetailFieldsInput - Scene: SceneDetailFieldsInput - ProjectDocumentation: ProjectDocumentationDetailFieldsInput - Asset: AssetDetailFieldsInput - AssetVersion: AssetVersionDetailFieldsInput - Actor: ActorDetailFieldsInput - Stakeholder: StakeholderDetailFieldsInput + Name: String + Retired: Boolean + Unit: UnitDetailFieldsInput + Project: ProjectDetailFieldsInput + Subject: SubjectDetailFieldsInput + Item: ItemDetailFieldsInput + CaptureData: CaptureDataDetailFieldsInput + Model: ModelDetailFieldsInput + Scene: SceneDetailFieldsInput + ProjectDocumentation: ProjectDocumentationDetailFieldsInput + Asset: AssetDetailFieldsInput + AssetVersion: AssetVersionDetailFieldsInput + Actor: ActorDetailFieldsInput + Stakeholder: StakeholderDetailFieldsInput } type UpdateObjectDetailsResult { - success: Boolean! + success: Boolean! } input GetDetailsTabDataForObjectInput { - idSystemObject: Int! - objectType: Int! + idSystemObject: Int! + objectType: Int! } type UnitDetailFields { - Abbreviation: String - ARKPrefix: String + Abbreviation: String + ARKPrefix: String } type ProjectDetailFields { - Description: String + Description: String } type SubjectDetailFields { - Altitude: Float - Latitude: Float - Longitude: Float - R0: Float - R1: Float - R2: Float - R3: Float - TS0: Float - TS1: Float - TS2: Float + Altitude: Float + Latitude: Float + Longitude: Float + R0: Float + R1: Float + R2: Float + R3: Float + TS0: Float + TS1: Float + TS2: Float } type ItemDetailFields { - EntireSubject: Boolean - Altitude: Float - Latitude: Float - Longitude: Float - R0: Float - R1: Float - R2: Float - R3: Float - TS0: Float - TS1: Float - TS2: Float + EntireSubject: Boolean + Altitude: Float + Latitude: Float + Longitude: Float + R0: Float + R1: Float + R2: Float + R3: Float + TS0: Float + TS1: Float + TS2: Float } type CaptureDataDetailFields { - captureMethod: Int - dateCaptured: String - datasetType: Int - systemCreated: Boolean - description: String - cameraSettingUniform: Boolean - datasetFieldId: Int - itemPositionType: Int - itemPositionFieldId: Int - itemArrangementFieldId: Int - focusType: Int - lightsourceType: Int - backgroundRemovalMethod: Int - clusterType: Int - clusterGeometryFieldId: Int - folders: [IngestFolder!]! + captureMethod: Int + dateCaptured: String + datasetType: Int + systemCreated: Boolean + description: String + cameraSettingUniform: Boolean + datasetFieldId: Int + itemPositionType: Int + itemPositionFieldId: Int + itemArrangementFieldId: Int + focusType: Int + lightsourceType: Int + backgroundRemovalMethod: Int + clusterType: Int + clusterGeometryFieldId: Int + folders: [IngestFolder!]! } type ModelDetailFields { - size: Int - master: Boolean - authoritative: Boolean - creationMethod: Int - modality: Int - purpose: Int - units: Int - dateCaptured: String - modelFileType: Int - uvMaps: [IngestUVMap!]! - roughness: Int - metalness: Int - pointCount: Int - faceCount: Int - isWatertight: Boolean - hasNormals: Boolean - hasVertexColor: Boolean - hasUVSpace: Boolean - boundingBoxP1X: Float - boundingBoxP1Y: Float - boundingBoxP1Z: Float - boundingBoxP2X: Float - boundingBoxP2Y: Float - boundingBoxP2Z: Float + size: Int + master: Boolean + authoritative: Boolean + creationMethod: Int + modality: Int + purpose: Int + units: Int + dateCaptured: String + modelFileType: Int + uvMaps: [IngestUVMap!]! + roughness: Int + metalness: Int + pointCount: Int + faceCount: Int + isWatertight: Boolean + hasNormals: Boolean + hasVertexColor: Boolean + hasUVSpace: Boolean + boundingBoxP1X: Float + boundingBoxP1Y: Float + boundingBoxP1Z: Float + boundingBoxP2X: Float + boundingBoxP2Y: Float + boundingBoxP2Z: Float } type SceneDetailFields { - Links: [String!]! - AssetType: Int - Tours: Int - Annotation: Int - HasBeenQCd: Boolean - IsOriented: Boolean + Links: [String!]! + AssetType: Int + Tours: Int + Annotation: Int + HasBeenQCd: Boolean + IsOriented: Boolean } type IntermediaryFileDetailFields { - idIntermediaryFile: Int! + idIntermediaryFile: Int! } type ProjectDocumentationDetailFields { - Description: String + Description: String } type AssetDetailFields { - FilePath: String - AssetType: Int + FilePath: String + AssetType: Int } type AssetVersionDetailFields { - Creator: String - DateCreated: DateTime - Ingested: Boolean - Version: Int - StorageSize: Int + Creator: String + DateCreated: DateTime + Ingested: Boolean + Version: Int + StorageSize: Int } type ActorDetailFields { - OrganizationName: String + OrganizationName: String } type StakeholderDetailFields { - OrganizationName: String - MailingAddress: String - EmailAddress: String - PhoneNumberMobile: String - PhoneNumberOffice: String + OrganizationName: String + MailingAddress: String + EmailAddress: String + PhoneNumberMobile: String + PhoneNumberOffice: String } type GetDetailsTabDataForObjectResult { - Unit: UnitDetailFields - Project: ProjectDetailFields - Subject: SubjectDetailFields - Item: ItemDetailFields - CaptureData: CaptureDataDetailFields - Model: ModelDetailFields - Scene: SceneDetailFields - IntermediaryFile: IntermediaryFileDetailFields - ProjectDocumentation: ProjectDocumentationDetailFields - Asset: AssetDetailFields - AssetVersion: AssetVersionDetailFields - Actor: ActorDetailFields - Stakeholder: StakeholderDetailFields + Unit: UnitDetailFields + Project: ProjectDetailFields + Subject: SubjectDetailFields + Item: ItemDetailFields + CaptureData: CaptureDataDetailFields + Model: ModelDetailFields + Scene: SceneDetailFields + IntermediaryFile: IntermediaryFileDetailFields + ProjectDocumentation: ProjectDocumentationDetailFields + Asset: AssetDetailFields + AssetVersion: AssetVersionDetailFields + Actor: ActorDetailFields + Stakeholder: StakeholderDetailFields } input GetSystemObjectDetailsInput { - idSystemObject: Int! + idSystemObject: Int! } type RepositoryPath { - idSystemObject: Int! - name: String! - objectType: Int! + idSystemObject: Int! + name: String! + objectType: Int! } type GetSystemObjectDetailsResult { - idObject: Int! - name: String! - retired: Boolean! - objectType: Int! - allowed: Boolean! - publishedState: String! - thumbnail: String - identifiers: [IngestIdentifier!]! - objectAncestors: [[RepositoryPath!]!]! - sourceObjects: [RelatedObject!]! - derivedObjects: [RelatedObject!]! - unit: RepositoryPath - project: RepositoryPath - subject: RepositoryPath - item: RepositoryPath + idObject: Int! + name: String! + retired: Boolean! + objectType: Int! + allowed: Boolean! + publishedState: String! + thumbnail: String + identifiers: [IngestIdentifier!]! + objectAncestors: [[RepositoryPath!]!]! + sourceObjects: [RelatedObject!]! + derivedObjects: [RelatedObject!]! + unit: RepositoryPath + project: RepositoryPath + subject: RepositoryPath + item: RepositoryPath } input GetSourceObjectIdentiferInput { - idSystemObjects: [Int!]! + idSystemObjects: [Int!]! } type SourceObjectIdentifier { - idSystemObject: Int! - identifier: String + idSystemObject: Int! + identifier: String } type GetSourceObjectIdentiferResult { - sourceObjectIdentifiers: [SourceObjectIdentifier!]! + sourceObjectIdentifiers: [SourceObjectIdentifier!]! } type AssetDetail { - idSystemObject: Int! - name: String! - path: String! - assetType: Int! - version: Int! - dateCreated: DateTime! - size: Int! + idSystemObject: Int! + name: String! + path: String! + assetType: Int! + version: Int! + dateCreated: DateTime! + size: Int! } input GetAssetDetailsForSystemObjectInput { - idSystemObject: Int! + idSystemObject: Int! } type GetAssetDetailsForSystemObjectResult { - assetDetails: [AssetDetail!]! + assetDetails: [AssetDetail!]! } type DetailVersion { - idSystemObject: Int! - version: Int! - name: String! - creator: String! - dateCreated: DateTime! - size: Int! + idSystemObject: Int! + version: Int! + name: String! + creator: String! + dateCreated: DateTime! + size: Int! } input GetVersionsForSystemObjectInput { - idSystemObject: Int! + idSystemObject: Int! } type GetVersionsForSystemObjectResult { - versions: [DetailVersion!]! + versions: [DetailVersion!]! } type SystemObject { - idSystemObject: Int! - Retired: Boolean! - idActor: Int - idAsset: Int - idAssetVersion: Int - idCaptureData: Int - idIntermediaryFile: Int - idItem: Int - idModel: Int - idProject: Int - idProjectDocumentation: Int - idScene: Int - idStakeholder: Int - idSubject: Int - idUnit: Int - idWorkflow: Int - idWorkflowStep: Int - Actor: Actor - Asset: Asset - AssetVersion: AssetVersion - CaptureData: CaptureData - IntermediaryFile: IntermediaryFile - Item: Item - Model: Model - Project: Project - ProjectDocumentation: ProjectDocumentation - Scene: Scene - Stakeholder: Stakeholder - Subject: Subject - Unit: Unit - Workflow: Workflow - WorkflowStep: WorkflowStep - AccessContextObject: [AccessContextObject] - Identifier: [Identifier] - LicenseAssignment: [LicenseAssignment] - Metadata: [Metadata] - SystemObjectVersion: [SystemObjectVersion] - SystemObjectDerived: [SystemObject] - SystemObjectMaster: [SystemObject] - UserPersonalizationSystemObject: [UserPersonalizationSystemObject] - WorkflowStepXref: [WorkflowStep] + idSystemObject: Int! + Retired: Boolean! + idActor: Int + idAsset: Int + idAssetVersion: Int + idCaptureData: Int + idIntermediaryFile: Int + idItem: Int + idModel: Int + idProject: Int + idProjectDocumentation: Int + idScene: Int + idStakeholder: Int + idSubject: Int + idUnit: Int + idWorkflow: Int + idWorkflowStep: Int + Actor: Actor + Asset: Asset + AssetVersion: AssetVersion + CaptureData: CaptureData + IntermediaryFile: IntermediaryFile + Item: Item + Model: Model + Project: Project + ProjectDocumentation: ProjectDocumentation + Scene: Scene + Stakeholder: Stakeholder + Subject: Subject + Unit: Unit + Workflow: Workflow + WorkflowStep: WorkflowStep + AccessContextObject: [AccessContextObject] + Identifier: [Identifier] + LicenseAssignment: [LicenseAssignment] + Metadata: [Metadata] + SystemObjectVersion: [SystemObjectVersion] + SystemObjectDerived: [SystemObject] + SystemObjectMaster: [SystemObject] + UserPersonalizationSystemObject: [UserPersonalizationSystemObject] + WorkflowStepXref: [WorkflowStep] } type SystemObjectVersion { - idSystemObjectVersion: Int! - idSystemObject: Int! - PublishedState: Int! - SystemObject: SystemObject + idSystemObjectVersion: Int! + idSystemObject: Int! + PublishedState: Int! + SystemObject: SystemObject } type Identifier { - idIdentifier: Int! - IdentifierValue: String! - idSystemObject: Int - idVIdentifierType: Int - SystemObject: SystemObject - VIdentifierType: Vocabulary + idIdentifier: Int! + IdentifierValue: String! + idSystemObject: Int + idVIdentifierType: Int + SystemObject: SystemObject + VIdentifierType: Vocabulary } type Metadata { - idMetadata: Int! - Name: String! - idAssetValue: Int - idSystemObject: Int - idUser: Int - idVMetadataSource: Int - ValueExtended: String - ValueShort: String - AssetValue: Asset - SystemObject: SystemObject - User: User - VMetadataSource: Vocabulary + idMetadata: Int! + Name: String! + idAssetValue: Int + idSystemObject: Int + idUser: Int + idVMetadataSource: Int + ValueExtended: String + ValueShort: String + AssetValue: Asset + SystemObject: SystemObject + User: User + VMetadataSource: Vocabulary } input CreateUnitInput { - Name: String! - Abbreviation: String! - ARKPrefix: String! + Name: String! + Abbreviation: String! + ARKPrefix: String! } type CreateUnitResult { - Unit: Unit + Unit: Unit } input CreateProjectInput { - Name: String! - Description: String! + Name: String! + Description: String! } type CreateProjectResult { - Project: Project + Project: Project } input CreateSubjectInput { - idUnit: Int! - Name: String! - idAssetThumbnail: Int - idGeoLocation: Int - idIdentifierPreferred: Int + idUnit: Int! + Name: String! + idAssetThumbnail: Int + idGeoLocation: Int + idIdentifierPreferred: Int } type CreateSubjectResult { - Subject: Subject + Subject: Subject } input CreateItemInput { - Name: String! - EntireSubject: Boolean! - idAssetThumbnail: Int - idGeoLocation: Int + Name: String! + EntireSubject: Boolean! + idAssetThumbnail: Int + idGeoLocation: Int } type CreateItemResult { - Item: Item + Item: Item } input GetSubjectsForUnitInput { - idUnit: Int! - pagination: PaginationInput = {} + idUnit: Int! + pagination: PaginationInput = {} } type GetSubjectsForUnitResult { - Subject: [Subject!]! + Subject: [Subject!]! } input GetItemsForSubjectInput { - idSubject: Int! - pagination: PaginationInput = {} + idSubject: Int! + pagination: PaginationInput = {} } type GetItemsForSubjectResult { - Item: [Item!]! + Item: [Item!]! } type SubjectUnitIdentifier { - idSubject: Int! - SubjectName: String! - UnitAbbreviation: String! - IdentifierPublic: String - IdentifierCollection: String + idSubject: Int! + SubjectName: String! + UnitAbbreviation: String! + IdentifierPublic: String + IdentifierCollection: String } input GetObjectsForItemInput { - idItem: Int! + idItem: Int! } type GetObjectsForItemResult { - CaptureData: [CaptureData!]! - Model: [Model!]! - Scene: [Scene!]! - IntermediaryFile: [IntermediaryFile!]! - ProjectDocumentation: [ProjectDocumentation!]! + CaptureData: [CaptureData!]! + Model: [Model!]! + Scene: [Scene!]! + IntermediaryFile: [IntermediaryFile!]! + ProjectDocumentation: [ProjectDocumentation!]! } input SearchIngestionSubjectsInput { - query: String! + query: String! } type SearchIngestionSubjectsResult { - SubjectUnitIdentifier: [SubjectUnitIdentifier!]! + SubjectUnitIdentifier: [SubjectUnitIdentifier!]! } input GetIngestionItemsForSubjectsInput { - idSubjects: [Int!]! + idSubjects: [Int!]! } type GetIngestionItemsForSubjectsResult { - Item: [Item!]! + Item: [Item!]! } input GetIngestionProjectsForSubjectsInput { - idSubjects: [Int!]! + idSubjects: [Int!]! } type GetIngestionProjectsForSubjectsResult { - Project: [Project!]! + Project: [Project!]! } input GetUnitInput { - idUnit: Int! + idUnit: Int! } type GetUnitResult { - Unit: Unit + Unit: Unit } input GetProjectInput { - idProject: Int! + idProject: Int! } type GetProjectResult { - Project: Project + Project: Project } input GetProjectDocumentationInput { - idProjectDocumentation: Int! + idProjectDocumentation: Int! } type GetProjectDocumentationResult { - ProjectDocumentation: ProjectDocumentation + ProjectDocumentation: ProjectDocumentation } input GetSubjectInput { - idSubject: Int! + idSubject: Int! } type GetSubjectResult { - Subject: Subject + Subject: Subject } input GetItemInput { - idItem: Int! + idItem: Int! } type GetItemResult { - Item: Item + Item: Item } type Unit { - idUnit: Int! - Abbreviation: String - ARKPrefix: String - Name: String! - Actor: [Actor] - Subject: [Subject] - SystemObject: SystemObject + idUnit: Int! + Abbreviation: String + ARKPrefix: String + Name: String! + Actor: [Actor] + Subject: [Subject] + SystemObject: SystemObject } type Project { - idProject: Int! - Name: String! - Description: String - ProjectDocumentation: [ProjectDocumentation] - SystemObject: SystemObject - Workflow: [Workflow] + idProject: Int! + Name: String! + Description: String + ProjectDocumentation: [ProjectDocumentation] + SystemObject: SystemObject + Workflow: [Workflow] } type ProjectDocumentation { - idProjectDocumentation: Int! - Description: String! - idProject: Int! - Name: String! - Project: Project - SystemObject: SystemObject + idProjectDocumentation: Int! + Description: String! + idProject: Int! + Name: String! + Project: Project + SystemObject: SystemObject } type Stakeholder { - idStakeholder: Int! - IndividualName: String! - OrganizationName: String! - MailingAddress: String - EmailAddress: String - PhoneNumberMobile: String - PhoneNumberOffice: String - SystemObject: SystemObject + idStakeholder: Int! + IndividualName: String! + OrganizationName: String! + MailingAddress: String + EmailAddress: String + PhoneNumberMobile: String + PhoneNumberOffice: String + SystemObject: SystemObject } type GeoLocation { - idGeoLocation: Int! - Altitude: Float - Latitude: Float - Longitude: Float - R0: Float - R1: Float - R2: Float - R3: Float - TS0: Float - TS1: Float - TS2: Float + idGeoLocation: Int! + Altitude: Float + Latitude: Float + Longitude: Float + R0: Float + R1: Float + R2: Float + R3: Float + TS0: Float + TS1: Float + TS2: Float } type Subject { - idSubject: Int! - idUnit: Int! - Name: String! - AssetThumbnail: Asset - idAssetThumbnail: Int - idGeoLocation: Int - idIdentifierPreferred: Int - GeoLocation: GeoLocation - Unit: Unit - IdentifierPreferred: Identifier - Item: [Item] - SystemObject: SystemObject + idSubject: Int! + idUnit: Int! + Name: String! + AssetThumbnail: Asset + idAssetThumbnail: Int + idGeoLocation: Int + idIdentifierPreferred: Int + GeoLocation: GeoLocation + Unit: Unit + IdentifierPreferred: Identifier + Item: [Item] + SystemObject: SystemObject } type Item { - idItem: Int! - EntireSubject: Boolean! - Name: String! - idAssetThumbnail: Int - idGeoLocation: Int - AssetThumbnail: Asset - GeoLocation: GeoLocation - Subject: Subject - SystemObject: SystemObject + idItem: Int! + EntireSubject: Boolean! + Name: String! + idAssetThumbnail: Int + idGeoLocation: Int + AssetThumbnail: Asset + GeoLocation: GeoLocation + Subject: Subject + SystemObject: SystemObject } input CreateUserInput { - Name: String! - EmailAddress: String! - SecurityID: String! + Name: String! + EmailAddress: String! + SecurityID: String! } type CreateUserResult { - User: User + User: User } type GetCurrentUserResult { - User: User + User: User } input GetUserInput { - idUser: Int! + idUser: Int! } type GetUserResult { - User: User + User: User } type User { - idUser: Int! - Active: Boolean! - DateActivated: DateTime! - EmailAddress: String! - Name: String! - SecurityID: String! - DateDisabled: DateTime - EmailSettings: Int - WorkflowNotificationTime: DateTime - AccessPolicy: [AccessPolicy] - AssetVersion: [AssetVersion] - LicenseAssignment: [LicenseAssignment] - Metadata: [Metadata] - UserPersonalizationSystemObject: [UserPersonalizationSystemObject] - UserPersonalizationUrl: [UserPersonalizationUrl] - Workflow: [Workflow] - WorkflowStep: [WorkflowStep] + idUser: Int! + Active: Boolean! + DateActivated: DateTime! + EmailAddress: String! + Name: String! + SecurityID: String! + DateDisabled: DateTime + EmailSettings: Int + WorkflowNotificationTime: DateTime + AccessPolicy: [AccessPolicy] + AssetVersion: [AssetVersion] + LicenseAssignment: [LicenseAssignment] + Metadata: [Metadata] + UserPersonalizationSystemObject: [UserPersonalizationSystemObject] + UserPersonalizationUrl: [UserPersonalizationUrl] + Workflow: [Workflow] + WorkflowStep: [WorkflowStep] } type UserPersonalizationSystemObject { - idUserPersonalizationSystemObject: Int! - idSystemObject: Int! - idUser: Int! - Personalization: String - SystemObject: SystemObject - User: User + idUserPersonalizationSystemObject: Int! + idSystemObject: Int! + idUser: Int! + Personalization: String + SystemObject: SystemObject + User: User } type UserPersonalizationUrl { - idUserPersonalizationUrl: Int! - idUser: Int! - Personalization: String! - URL: String! - User: User + idUserPersonalizationUrl: Int! + idUser: Int! + Personalization: String! + URL: String! + User: User } input CreateVocabularyInput { - idVocabularySet: Int! - SortOrder: Int! - Term: String! + idVocabularySet: Int! + SortOrder: Int! + Term: String! } type CreateVocabularyResult { - Vocabulary: Vocabulary + Vocabulary: Vocabulary } input CreateVocabularySetInput { - Name: String! - SystemMaintained: Boolean! + Name: String! + SystemMaintained: Boolean! } type CreateVocabularySetResult { - VocabularySet: VocabularySet + VocabularySet: VocabularySet } input GetVocabularyInput { - idVocabulary: Int! + idVocabulary: Int! } type GetVocabularyResult { - Vocabulary: Vocabulary + Vocabulary: Vocabulary } input GetVocabularyEntriesInput { - eVocabSetIDs: [Int!]! + eVocabSetIDs: [Int!]! } type VocabularyEntry { - eVocabSetID: Int! - Vocabulary: [Vocabulary!]! + eVocabSetID: Int! + Vocabulary: [Vocabulary!]! } type GetVocabularyEntriesResult { - VocabularyEntries: [VocabularyEntry!]! + VocabularyEntries: [VocabularyEntry!]! } type Vocabulary { - idVocabulary: Int! - idVocabularySet: Int! - SortOrder: Int! - Term: String! - VocabularySet: VocabularySet + idVocabulary: Int! + idVocabularySet: Int! + SortOrder: Int! + Term: String! + VocabularySet: VocabularySet } type VocabularySet { - idVocabularySet: Int! - Name: String! - SystemMaintained: Boolean! - Vocabulary: [Vocabulary] + idVocabularySet: Int! + Name: String! + SystemMaintained: Boolean! + Vocabulary: [Vocabulary] } input GetWorkflowInput { - idWorkflow: Int! + idWorkflow: Int! } type GetWorkflowResult { - Workflow: Workflow + Workflow: Workflow } type Workflow { - idWorkflow: Int! - DateInitiated: DateTime! - DateUpdated: DateTime! - idWorkflowTemplate: Int! - idProject: Int - idUserInitiator: Int - Project: Project - UserInitiator: User - WorkflowTemplate: WorkflowTemplate - WorkflowStep: [WorkflowStep] + idWorkflow: Int! + DateInitiated: DateTime! + DateUpdated: DateTime! + idWorkflowTemplate: Int! + idProject: Int + idUserInitiator: Int + Project: Project + UserInitiator: User + WorkflowTemplate: WorkflowTemplate + WorkflowStep: [WorkflowStep] } type WorkflowStep { - idWorkflowStep: Int! - DateCreated: DateTime! - idUserOwner: Int! - idVWorkflowStepType: Int! - idWorkflow: Int! - State: Int! - DateCompleted: DateTime - User: User - VWorkflowStepType: Vocabulary - Workflow: Workflow - WorkflowStepSystemObjectXref: [WorkflowStepSystemObjectXref] + idWorkflowStep: Int! + DateCreated: DateTime! + idUserOwner: Int! + idVWorkflowStepType: Int! + idWorkflow: Int! + State: Int! + DateCompleted: DateTime + User: User + VWorkflowStepType: Vocabulary + Workflow: Workflow + WorkflowStepSystemObjectXref: [WorkflowStepSystemObjectXref] } type WorkflowStepSystemObjectXref { - idWorkflowStepSystemObjectXref: Int! - idSystemObject: Int! - idWorkflowStep: Int! - Input: Boolean! - SystemObject: SystemObject - WorkflowStep: WorkflowStep + idWorkflowStepSystemObjectXref: Int! + idSystemObject: Int! + idWorkflowStep: Int! + Input: Boolean! + SystemObject: SystemObject + WorkflowStep: WorkflowStep } type WorkflowTemplate { - idWorkflowTemplate: Int! - Name: String! - Workflow: [Workflow] + idWorkflowTemplate: Int! + Name: String! + Workflow: [Workflow] } diff --git a/server/graphql/schema/model/mutations.graphql b/server/graphql/schema/model/mutations.graphql index 3348e4e66..66e53b1ff 100644 --- a/server/graphql/schema/model/mutations.graphql +++ b/server/graphql/schema/model/mutations.graphql @@ -5,6 +5,7 @@ type Mutation { } input CreateModelInput { + Name: String! Authoritative: Boolean! idVCreationMethod: Int! idVModality: Int! diff --git a/server/graphql/schema/model/resolvers/mutations/createModel.ts b/server/graphql/schema/model/resolvers/mutations/createModel.ts index 1bec325aa..a6dd7a8f2 100644 --- a/server/graphql/schema/model/resolvers/mutations/createModel.ts +++ b/server/graphql/schema/model/resolvers/mutations/createModel.ts @@ -4,10 +4,11 @@ import * as DBAPI from '../../../../../db'; export default async function createModel(_: Parent, args: MutationCreateModelArgs): Promise { const { input } = args; - const { Authoritative, idVCreationMethod, idVModality, idVPurpose, idVUnits, Master, idAssetThumbnail } = input; + const { Name, Authoritative, idVCreationMethod, idVModality, idVPurpose, idVUnits, Master, idAssetThumbnail } = input; const modelArgs = { idModel: 0, + Name, Authoritative, idVCreationMethod, idVModality, diff --git a/server/graphql/schema/model/types.graphql b/server/graphql/schema/model/types.graphql index c391d3322..668c93b5b 100644 --- a/server/graphql/schema/model/types.graphql +++ b/server/graphql/schema/model/types.graphql @@ -2,6 +2,7 @@ scalar DateTime type Model { idModel: Int! + Name: String! Authoritative: Boolean! DateCreated: DateTime! idAssetThumbnail: Int diff --git a/server/index.ts b/server/index.ts index 4f87115bc..f371fdaa8 100644 --- a/server/index.ts +++ b/server/index.ts @@ -49,7 +49,7 @@ app.get('/logtest', (_: Request, response: Response) => { app.get('/solrindex', async (_: Request, response: Response) => { const reindexer: ReindexSolr = new ReindexSolr(); - const success: boolean = await reindexer.FullIndex(); + const success: boolean = await reindexer.fullIndex(); response.send(`Solr Reindexing Completed: ${success ? 'Success' : 'Failure'}`); }); diff --git a/server/navigation/impl/NavigationSolr/ReindexSolr.ts b/server/navigation/impl/NavigationSolr/ReindexSolr.ts index 14611d50f..02a471f47 100644 --- a/server/navigation/impl/NavigationSolr/ReindexSolr.ts +++ b/server/navigation/impl/NavigationSolr/ReindexSolr.ts @@ -1,387 +1,500 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -import * as LOG from '../../../utils/logger'; -import * as CACHE from '../../../cache'; -import * as DBAPI from '../../../db'; -import { eSystemObjectType } from '../../../db'; -import { SolrClient } from './SolrClient'; - -type SubjectInfo = { - Unit: string | null; - UnitID: number | null; - Project: string[]; - ProjectID: number[]; -}; - -type ItemInfo = { - Unit: string[]; - UnitID: number[]; - Project: string[]; - ProjectID: number[]; - Subject: string[]; - SubjectID: number[]; -}; - -export class ReindexSolr { - private SubjectInfoMap: Map = new Map(); // map of Subject.idSubject -> Unit/Project info - private ItemInfoMap: Map = new Map(); // map of Item.idItem -> Unit/Project/Subject - - async FullIndex(): Promise { - const solrClient: SolrClient = new SolrClient(null, null, null); - solrClient._client.autoCommit = true; - - // await this.computeGraphDataFromUnits(); - - solrClient._client.add(await this.computeUnits(), function(err, obj) { if (err) LOG.logger.error('ReindexSolr.FullIndex -> computeUnits()', err); else obj; }); - solrClient._client.add(await this.computeProjects(), function(err, obj) { if (err) LOG.logger.error('ReindexSolr.FullIndex -> computeProjects()', err); else obj; }); - solrClient._client.add(await this.computeSubjects(), function(err, obj) { if (err) LOG.logger.error('ReindexSolr.FullIndex -> computeSubjects()', err); else obj; }); - solrClient._client.add(await this.computeItems(), function(err, obj) { if (err) LOG.logger.error('ReindexSolr.FullIndex -> computeItems()', err); else obj; }); - solrClient._client.add(await this.computeCaptureData(), function(err, obj) { if (err) LOG.logger.error('ReindexSolr.FullIndex -> computeCaptureData()', err); else obj; }); - solrClient._client.commit(function(err, obj) { if (err) LOG.logger.error('ReindexSolr.FullIndex -> commit()', err); else obj; }); - return true; - } - - /* #region Units */ - private async computeUnits(): Promise { - LOG.logger.info('ReindexSolr.computeUnits starting'); - const docs: any[] = []; - - const units: DBAPI.Unit[] | null = await DBAPI.Unit.fetchAll(); /* istanbul ignore if */ - if (!units) { - LOG.logger.error('ReindexSolr.computeUnits unable to retrieve units'); - return []; - } - - for (const unit of units) { - const oID: CACHE.ObjectIDAndType = { idObject: unit.idUnit, eObjectType: eSystemObjectType.eUnit }; - const sID: CACHE.SystemObjectInfo | undefined = await CACHE.SystemObjectCache.getSystemFromObjectID(oID); /* istanbul ignore if */ - if (!sID) { - LOG.logger.error(`ReindexSolr.computeUnits unable to compute idSystemObject for ${JSON.stringify(oID)}`); - continue; - } - - const doc: any = { - idSystemObject: sID.idSystemObject, - ObjectType: 'Unit', - idObject: unit.idUnit, - Retired: sID.Retired, - Name: unit.Name, - Abbreviation: unit.Abbreviation, - ARKPrefix: unit.ARKPrefix, - Unit: unit.Abbreviation, - UnitID: sID.idSystemObject, - ParentID: 0, - Identifier: this.computeIdentifiers(sID.idSystemObject) - }; - docs.push(doc); - } - LOG.logger.info(`ReindexSolr.computeUnits computed ${docs.length} documents`); - return docs; - } - /* #endregion */ - - /* #region Projects */ - private async computeProjects(): Promise { - LOG.logger.info('ReindexSolr.computeProjects starting'); - const docs: any[] = []; - - const projects: DBAPI.Project[] | null = await DBAPI.Project.fetchAll(); /* istanbul ignore if */ - if (!projects) { - LOG.logger.error('ReindexSolr.computeProjects unable to retrieve projects'); - return []; - } - - for (const project of projects) { - const oID: CACHE.ObjectIDAndType = { idObject: project.idProject, eObjectType: eSystemObjectType.eProject }; - const sID: CACHE.SystemObjectInfo | undefined = await CACHE.SystemObjectCache.getSystemFromObjectID(oID); /* istanbul ignore if */ - if (!sID) { - LOG.logger.error(`ReindexSolr.computeProjects unable to compute idSystemObject for ${JSON.stringify(oID)}`); - continue; - } - - const Unit: string[] = []; - const UnitID: number[] = []; - - const units: DBAPI.Unit[] | null = await DBAPI.Unit.fetchMasterFromProjects([project.idProject]); // TODO: consider placing this in a cache - if (units) { - for (const unit of units) { - Unit.push(unit.Abbreviation || ''); - const SO: DBAPI.SystemObject | null = await unit.fetchSystemObject(); - UnitID.push(SO ? SO.idSystemObject : 0); - } - } - - const doc: any = { - idSystemObject: sID.idSystemObject, - ObjectType: 'Project', - idObject: project.idProject, - Retired: sID.Retired, - Name: project.Name, - Description: project.Description, - Unit: Unit.length == 1 ? Unit[0] : Unit, - UnitID: UnitID.length == 1 ? UnitID[0] : UnitID, - Project: project.Name, - ProjectID: sID.idSystemObject, - ParentID: 0, - Identifier: this.computeIdentifiers(sID.idSystemObject) - }; - docs.push(doc); - } - LOG.logger.info(`ReindexSolr.computeProjects computed ${docs.length} documents`); - return docs; - } - /* #endregion */ - - /* #region Subjects */ - private async computeSubjects(): Promise { - LOG.logger.info('ReindexSolr.computeSubjects starting'); - const docs: any[] = []; - - const subjects: DBAPI.Subject[] | null = await DBAPI.Subject.fetchAll(); /* istanbul ignore if */ - if (!subjects) { - LOG.logger.error('ReindexSolr.computeSubjects unable to retrieve subjects'); - return []; - } - - for (const subject of subjects) { - const oID: CACHE.ObjectIDAndType = { idObject: subject.idSubject, eObjectType: eSystemObjectType.eSubject }; - const sID: CACHE.SystemObjectInfo | undefined = await CACHE.SystemObjectCache.getSystemFromObjectID(oID); /* istanbul ignore if */ - if (!sID) { - LOG.logger.error(`ReindexSolr.computeSubjects unable to compute idSystemObject for ${JSON.stringify(oID)}`); - continue; - } - - let Unit: string | null = null; - let UnitID: number | null = null; - const Project: string[] = []; - const ProjectID: number[] = []; - let IdentifierPreferred: string | null = null; - - const unit: DBAPI.Unit | null = (subject.idUnit != 0) ? await DBAPI.Unit.fetch(subject.idUnit) : null; - if (unit) { - Unit = unit.Abbreviation; - const SO: DBAPI.SystemObject | null = await unit.fetchSystemObject(); - UnitID = SO ? SO.idSystemObject : 0; - } - - const projects: DBAPI.Project[] | null = await DBAPI.Project.fetchMasterFromSubjects([subject.idSubject]); - if (projects) { - for (const project of projects) { - Project.push(project.Name); - const SO: DBAPI.SystemObject | null = await project.fetchSystemObject(); - ProjectID.push(SO ? SO.idSystemObject : 0); - } - } - - if (subject.idIdentifierPreferred) { - const ID: DBAPI.Identifier | null = await DBAPI.Identifier.fetch(subject.idIdentifierPreferred); - if (ID) - IdentifierPreferred = ID.IdentifierValue; - } - - const doc: any = { - idSystemObject: sID.idSystemObject, - ObjectType: 'Subject', - idObject: subject.idSubject, - Retired: sID.Retired, - Name: subject.Name, - IdentifierPreferred, - Unit, - UnitID, - Project: Project.length == 1 ? Project[0] : Project, - ProjectID: ProjectID.length == 1 ? ProjectID[0] : ProjectID, - Subject: subject.Name, - SubjectID: sID.idSystemObject, - ParentID: UnitID, - Identifier: this.computeIdentifiers(sID.idSystemObject) - }; - docs.push(doc); - this.SubjectInfoMap.set(subject.idSubject, { Unit, UnitID, Project, ProjectID }); - } - LOG.logger.info(`ReindexSolr.computeSubjects computed ${docs.length} documents`); - return docs; - } - /* #endregion */ - - /* #region Items */ - private async computeItems(): Promise { - LOG.logger.info('ReindexSolr.computeItems starting'); - const docs: any[] = []; - - const items: DBAPI.Item[] | null = await DBAPI.Item.fetchAll(); /* istanbul ignore if */ - if (!items) { - LOG.logger.error('ReindexSolr.computeItems unable to retrieve items'); - return []; - } - - for (const item of items) { - const oID: CACHE.ObjectIDAndType = { idObject: item.idItem, eObjectType: eSystemObjectType.eItem }; - const sID: CACHE.SystemObjectInfo | undefined = await CACHE.SystemObjectCache.getSystemFromObjectID(oID); /* istanbul ignore if */ - if (!sID) { - LOG.logger.error(`ReindexSolr.computeItems unable to compute idSystemObject for ${JSON.stringify(oID)}`); - continue; - } - - const Unit: string[] = []; - const UnitID: number[] = []; - let Project: string[] = []; - let ProjectID: number[] = []; - const Subject: string[] = []; - const SubjectID: number[] = []; - - const subjects: DBAPI.Subject[] | null = await DBAPI.Subject.fetchMasterFromItems([item.idItem]); - if (subjects) { - for (const subject of subjects) { - Subject.push(subject.Name); - const SO: DBAPI.SystemObject | null = await subject.fetchSystemObject(); - SubjectID.push(SO ? SO.idSystemObject : 0); - - const subjectInfo: SubjectInfo | undefined = this.SubjectInfoMap.get(subject.idSubject); - if (subjectInfo) { - if (subjectInfo.Unit) - Unit.push(subjectInfo.Unit); - if (subjectInfo.UnitID) - UnitID.push(subjectInfo.UnitID); - Project = Project.concat(subjectInfo.Project); - ProjectID = ProjectID.concat(subjectInfo.ProjectID); - } - - } - } - - const doc: any = { - idSystemObject: sID.idSystemObject, - ObjectType: 'Item', - idObject: item.idItem, - Retired: sID.Retired, - Name: item.Name, - EntireSubject: item.EntireSubject, - Unit: Unit.length == 1 ? Unit[0] : Unit, - UnitID: UnitID.length == 1 ? UnitID[0] : UnitID, - Project: Project.length == 1 ? Project[0] : Project, - ProjectID: ProjectID.length == 1 ? ProjectID[0] : ProjectID, - Subject: Subject.length == 1 ? Subject[0] : Subject, - SubjectID: SubjectID.length == 1 ? SubjectID[0] : SubjectID, - Item: item.Name, - ItemID: sID.idSystemObject, - ParentID: SubjectID.length == 1 ? SubjectID[0] : SubjectID, - Identifier: this.computeIdentifiers(sID.idSystemObject) - }; - docs.push(doc); - this.ItemInfoMap.set(item.idItem, { Unit, UnitID, Project, ProjectID, Subject, SubjectID }); - } - LOG.logger.info(`ReindexSolr.computeItems computed ${docs.length} documents`); - return docs; - } - /* #endregion */ - - /* #region CaptureData */ - private async computeCaptureData(): Promise { - LOG.logger.info('ReindexSolr.computeCaptureData starting'); - const docs: any[] = []; - - const captureDataPhotos: DBAPI.CaptureDataPhoto[] | null = await DBAPI.CaptureDataPhoto.fetchAll(); /* istanbul ignore if */ - if (!captureDataPhotos) { - LOG.logger.error('ReindexSolr.computeCaptureData unable to retrieve CaptureDataPhoto'); - return []; - } - - for (const captureDataPhoto of captureDataPhotos) { - const captureData: DBAPI.CaptureData | null = await DBAPI.CaptureData.fetchFromCaptureDataPhoto(captureDataPhoto.idCaptureDataPhoto); - if (!captureData) { - LOG.logger.error(`ReindexSolr.computeCaptureData unable to compute CaptureData from CaptureDataPhoto ${JSON.stringify(captureDataPhoto)}`); - continue; - } - - const oID: CACHE.ObjectIDAndType = { idObject: captureData.idCaptureData, eObjectType: eSystemObjectType.eCaptureData }; - const sID: CACHE.SystemObjectInfo | undefined = await CACHE.SystemObjectCache.getSystemFromObjectID(oID); /* istanbul ignore if */ - if (!sID) { - LOG.logger.error(`ReindexSolr.computeCaptureData unable to compute idSystemObject for ${JSON.stringify(oID)}`); - continue; - } - - let Unit: string[] = []; - let UnitID: number[] = []; - let Project: string[] = []; - let ProjectID: number[] = []; - let Subject: string[] = []; - let SubjectID: number[] = []; - const Item: string[] = []; - const ItemID: number[] = []; - - const items: DBAPI.Item[] | null = await DBAPI.Item.fetchMasterFromCaptureDatas([captureData.idCaptureData]); - if (items) { - for (const item of items) { - Item.push(item.Name); - const SO: DBAPI.SystemObject | null = await item.fetchSystemObject(); - ItemID.push(SO ? SO.idSystemObject : 0); - - const itemInfo: ItemInfo | undefined = this.ItemInfoMap.get(item.idItem); - if (itemInfo) { - Unit = Unit.concat(itemInfo.Unit); - UnitID = UnitID.concat(itemInfo.UnitID); - Project = Project.concat(itemInfo.Project); - ProjectID = ProjectID.concat(itemInfo.ProjectID); - Subject = Subject.concat(itemInfo.Subject); - SubjectID = SubjectID.concat(itemInfo.SubjectID); - } - } - } - - const vCaptureMethod: DBAPI.Vocabulary | undefined = await CACHE.VocabularyCache.vocabulary(captureData.idVCaptureMethod); - const vCaptureDatasetType: DBAPI.Vocabulary | undefined = await CACHE.VocabularyCache.vocabulary(captureDataPhoto.idVCaptureDatasetType); - const vItemPositionType: DBAPI.Vocabulary | undefined = captureDataPhoto.idVItemPositionType ? await CACHE.VocabularyCache.vocabulary(captureDataPhoto.idVItemPositionType) : undefined; - const vFocusType: DBAPI.Vocabulary | undefined = captureDataPhoto.idVFocusType ? await CACHE.VocabularyCache.vocabulary(captureDataPhoto.idVFocusType) : undefined; - const vLightSourceType: DBAPI.Vocabulary | undefined = captureDataPhoto.idVLightSourceType ? await CACHE.VocabularyCache.vocabulary(captureDataPhoto.idVLightSourceType) : undefined; - const vBackgroundRemovalMethod: DBAPI.Vocabulary | undefined = captureDataPhoto.idVBackgroundRemovalMethod ? await CACHE.VocabularyCache.vocabulary(captureDataPhoto.idVBackgroundRemovalMethod) : undefined; - const vClusterType: DBAPI.Vocabulary | undefined = captureDataPhoto.idVClusterType ? await CACHE.VocabularyCache.vocabulary(captureDataPhoto.idVClusterType) : undefined; - - const doc: any = { - idSystemObject: sID.idSystemObject, - ObjectType: 'CaptureData', - idObject: captureData.idCaptureData, - Retired: sID.Retired, - - Name: captureData.Name, - Description: captureData.Description, - DateCreated: captureData.DateCaptured, - CaptureMethod: vCaptureMethod ? vCaptureMethod.Term : '', - CaptureDatasetType: vCaptureDatasetType ? vCaptureDatasetType.Term : '', - CaptureDatasetFieldID: captureDataPhoto.CaptureDatasetFieldID, - ItemPositionType: vItemPositionType ? vItemPositionType.Term : '', - ItemPositionFieldID: captureDataPhoto.ItemPositionFieldID, - ItemArrangementFieldID: captureDataPhoto.ItemArrangementFieldID, - FocusType: vFocusType ? vFocusType.Term : '', - LightSourceType: vLightSourceType ? vLightSourceType.Term : '', - BackgroundRemovalMethod: vBackgroundRemovalMethod ? vBackgroundRemovalMethod.Term : '', - ClusterType: vClusterType ? vClusterType.Term : '', - ClusterGeometryFieldID: captureDataPhoto.ClusterGeometryFieldID, - CameraSettingsUniform: captureDataPhoto.CameraSettingsUniform, - - Unit: Unit.length == 1 ? Unit[0] : Unit, - UnitID: UnitID.length == 1 ? UnitID[0] : UnitID, - Project: Project.length == 1 ? Project[0] : Project, - ProjectID: ProjectID.length == 1 ? ProjectID[0] : ProjectID, - Subject: Subject.length == 1 ? Subject[0] : Subject, - SubjectID: SubjectID.length == 1 ? SubjectID[0] : SubjectID, - Item: Item.length == 1 ? Item[0] : Item, - ItemID: ItemID.length == 1 ? ItemID[0] : ItemID, - ParentID: ItemID.length == 1 ? ItemID[0] : ItemID, - Identifier: this.computeIdentifiers(sID.idSystemObject) - }; - docs.push(doc); - } - LOG.logger.info(`ReindexSolr.computeCaptureData computed ${docs.length} documents`); - return docs; - } - /* #endregion */ - - private async computeIdentifiers(idSystemObject: number): Promise { - const identifiersRet: string[] = []; - const identifiers: DBAPI.Identifier[] | null = await DBAPI.Identifier.fetchFromSystemObject(idSystemObject); - if (identifiers) { - for (const identifier of identifiers) - identifiersRet.push(identifier.IdentifierValue); - } - return identifiersRet; - } -} \ No newline at end of file +/* eslint-disable @typescript-eslint/no-explicit-any */ +import * as LOG from '../../../utils/logger'; +import * as CACHE from '../../../cache'; +import * as DBAPI from '../../../db'; +import { eSystemObjectType } from '../../../db'; +import { SolrClient } from './SolrClient'; + +export class ReindexSolr { + private objectGraphDatabase: DBAPI.ObjectGraphDatabase = new DBAPI.ObjectGraphDatabase(); + private hierarchyNameMap: Map = new Map(); // map of idSystemObject -> object name + + async fullIndex(): Promise { + const solrClient: SolrClient = new SolrClient(null, null, null); + solrClient._client.autoCommit = true; + + if (!(await this.objectGraphDatabase.fetch())) { + LOG.logger.error('ReindexSolr.fullIndex failed on ObjectGraphDatabase.fetch()'); + return false; + } + + let docs: any[] = []; + for (const [systemObjectIDType, objectGraphDataEntry] of this.objectGraphDatabase.objectMap) { + let doc: any = null; + + await this.extractCommonFields(doc, objectGraphDataEntry); + + switch (systemObjectIDType.eObjectType) { + case eSystemObjectType.eUnit: doc = await this.handleUnit(doc, objectGraphDataEntry); break; + case eSystemObjectType.eProject: doc = await this.handleProject(doc, objectGraphDataEntry); break; + case eSystemObjectType.eSubject: doc = await this.handleSubject(doc, objectGraphDataEntry); break; + case eSystemObjectType.eItem: doc = await this.handleItem(doc, objectGraphDataEntry); break; + case eSystemObjectType.eCaptureData: doc = await this.handleCaptureData(doc, objectGraphDataEntry); break; + case eSystemObjectType.eModel: doc = await this.handleModel(doc, objectGraphDataEntry); break; + case eSystemObjectType.eScene: doc = await this.handleScene(doc, objectGraphDataEntry); break; + case eSystemObjectType.eIntermediaryFile: doc = await this.handleIntermediaryFile(doc, objectGraphDataEntry); break; + case eSystemObjectType.eProjectDocumentation: doc = await this.handleProjectDocumentation(doc, objectGraphDataEntry); break; + case eSystemObjectType.eAsset: doc = await this.handleAsset(doc, objectGraphDataEntry); break; + case eSystemObjectType.eAssetVersion: doc = await this.handleAssetVersion(doc, objectGraphDataEntry); break; + case eSystemObjectType.eActor: doc = await this.handleActor(doc, objectGraphDataEntry); break; + case eSystemObjectType.eStakeholder: doc = await this.handleStakeholder(doc, objectGraphDataEntry); break; + + default: + case eSystemObjectType.eUnknown: doc = await this.handleUnknown(doc, objectGraphDataEntry); break; + } + + docs.push(doc); + if (docs.length >= 1000) { + solrClient._client.add(docs, function (err, obj) { if (err) LOG.logger.error('ReindexSolr.fullIndex adding cached records', err); else obj; }); + solrClient._client.commit(function (err, obj) { if (err) LOG.logger.error('ReindexSolr.fullIndex -> commit()', err); else obj; }); + docs = []; + } + } + + if (docs.length > 0) { + solrClient._client.add(docs, function (err, obj) { if (err) LOG.logger.error('ReindexSolr.fullIndex adding cached records', err); else obj; }); + solrClient._client.commit(function (err, obj) { if (err) LOG.logger.error('ReindexSolr.fullIndex -> commit()', err); else obj; }); + } + return true; + } + + private async extractCommonFields(doc: any, objectGraphDataEntry: DBAPI.ObjectGraphDataEntry): Promise { + const OGDEH: DBAPI.ObjectGraphDataEntryHierarchy = objectGraphDataEntry.extractHierarchy(); + + doc.idSystemObject = OGDEH.idSystemObject; + doc.Retired = OGDEH.retired; + doc.ObjectType = DBAPI.SystemObjectTypeToName(OGDEH.eObjectType); + doc.idObject = OGDEH.idObject; + + doc.ParentID = OGDEH.parents.length == 1 ? OGDEH.parents[0] : OGDEH.parents; + doc.ChildrenID = OGDEH.children.length == 1 ? OGDEH.children[0] : OGDEH.children; + doc.Identifier = this.computeIdentifiers(objectGraphDataEntry.systemObjectIDType.idSystemObject); + + let nameArray: string[] = []; + let idArray: number[] = []; + + for (const objInfo of OGDEH.units) { + let name: string | undefined = this.hierarchyNameMap.get(objInfo.idSystemObject); + if (!name) { + const unit: DBAPI.Unit | null = await DBAPI.Unit.fetch(objInfo.idObject); + if (unit) { + name = unit.Abbreviation || 'Unknown'; + this.hierarchyNameMap.set(objInfo.idSystemObject, name); + } else { + name = 'Unknown'; + LOG.logger.error(`Unable to compute Unit for ${JSON.stringify(objInfo)}`); + } + } + nameArray.push(name); + idArray.push(objInfo.idSystemObject); + } + if (nameArray.length > 0) { + doc.Unit = nameArray.length == 1 ? nameArray[0] : nameArray; + doc.UnitID = idArray.length == 1 ? idArray[0] : idArray; + nameArray = []; + idArray = []; + } + + for (const objInfo of OGDEH.projects) { + let name: string | undefined = this.hierarchyNameMap.get(objInfo.idSystemObject); + if (!name) { + const project: DBAPI.Project | null = await DBAPI.Project.fetch(objInfo.idObject); + if (project) { + name = project.Name; + this.hierarchyNameMap.set(objInfo.idSystemObject, name); + } else { + name = 'Unknown'; + LOG.logger.error(`Unable to compute Project for ${JSON.stringify(objInfo)}`); + } + } + nameArray.push(name); + idArray.push(objInfo.idSystemObject); + } + if (nameArray.length > 0) { + doc.Project = nameArray.length == 1 ? nameArray[0] : nameArray; + doc.ProjectID = idArray.length == 1 ? idArray[0] : idArray; + nameArray = []; + idArray = []; + } + + for (const objInfo of OGDEH.subjects) { + let name: string | undefined = this.hierarchyNameMap.get(objInfo.idSystemObject); + if (!name) { + const subject: DBAPI.Subject | null = await DBAPI.Subject.fetch(objInfo.idObject); + if (subject) { + name = subject.Name; + this.hierarchyNameMap.set(objInfo.idSystemObject, name); + } else { + name = 'Unknown'; + LOG.logger.error(`Unable to compute Subject for ${JSON.stringify(objInfo)}`); + } + } + nameArray.push(name); + idArray.push(objInfo.idSystemObject); + } + if (nameArray.length > 0) { + doc.Subject = nameArray.length == 1 ? nameArray[0] : nameArray; + doc.SubjectID = idArray.length == 1 ? idArray[0] : idArray; + nameArray = []; + idArray = []; + } + + for (const objInfo of OGDEH.items) { + let name: string | undefined = this.hierarchyNameMap.get(objInfo.idSystemObject); + if (!name) { + const item: DBAPI.Item | null = await DBAPI.Item.fetch(objInfo.idObject); + if (item) { + name = item.Name; + this.hierarchyNameMap.set(objInfo.idSystemObject, name); + } else { + name = 'Unknown'; + LOG.logger.error(`Unable to compute Item for ${JSON.stringify(objInfo)}`); + } + } + nameArray.push(name); + idArray.push(objInfo.idSystemObject); + } + if (nameArray.length > 0) { + doc.Item = nameArray.length == 1 ? nameArray[0] : nameArray; + doc.ItemID = idArray.length == 1 ? idArray[0] : idArray; + nameArray = []; + idArray = []; + } + + const ChildrenObjectTypes: string[] = []; + for (const childrenObjectType of OGDEH.childrenObjectTypes) ChildrenObjectTypes.push(DBAPI.SystemObjectTypeToName(childrenObjectType)); + doc.ChildrenObjectTypes = ChildrenObjectTypes.length == 1 ? ChildrenObjectTypes[0] : ChildrenObjectTypes; + + let VocabList: string[] = []; + VocabList = await this.computeVocabularyTerms(OGDEH.childrenCaptureMethods); + doc.ChildrenCaptureMethods = VocabList.length == 1 ? VocabList[0] : VocabList; + VocabList = []; + + VocabList = await this.computeVocabularyTerms(OGDEH.childrenVariantTypes); + doc.ChildrenVariantTypes = VocabList.length == 1 ? VocabList[0] : VocabList; + VocabList = []; + + VocabList = await this.computeVocabularyTerms(OGDEH.childrenModelPurposes); + doc.ChildrenModelPurposes = VocabList.length == 1 ? VocabList[0] : VocabList; + VocabList = []; + + VocabList = await this.computeVocabularyTerms(OGDEH.childrenModelFileTypes); + doc.ChildrenModelFileTypes = VocabList.length == 1 ? VocabList[0] : VocabList; + VocabList = []; + } + + private async computeVocabulary(idVocabulary: number): Promise { + const vocab: DBAPI.Vocabulary | undefined = await CACHE.VocabularyCache.vocabulary(idVocabulary); + return vocab ? vocab.Term : undefined; + } + + private async computeVocabularyTerms(IDs: number[]): Promise { + const retValue: string[] = []; + for (const ID of IDs) { + const vocab: string | undefined = await this.computeVocabulary(ID); + if (vocab) retValue.push(vocab); + } + return retValue; + } + + private async handleUnit(doc: any, objectGraphDataEntry: DBAPI.ObjectGraphDataEntry): Promise { + const unit: DBAPI.Unit | null = await DBAPI.Unit.fetch(objectGraphDataEntry.systemObjectIDType.idObject); + if (!unit) { + LOG.logger.error(`ReindexSolr.handleUnit failed to compute unit from ${JSON.stringify(objectGraphDataEntry.systemObjectIDType)}`); + return false; + } + doc.Name = unit.Name; + doc.Abbreviation = unit.Abbreviation; + doc.ARKPrefix = unit.ARKPrefix; + return true; + } + + private async handleProject(doc: any, objectGraphDataEntry: DBAPI.ObjectGraphDataEntry): Promise { + const project: DBAPI.Project | null = await DBAPI.Project.fetch(objectGraphDataEntry.systemObjectIDType.idObject); + if (!project) { + LOG.logger.error(`ReindexSolr.handleProject failed to compute project from ${JSON.stringify(objectGraphDataEntry.systemObjectIDType)}`); + return false; + } + doc.Name = project.Name; + doc.Description = project.Description; + return true; + } + + private async handleSubject(doc: any, objectGraphDataEntry: DBAPI.ObjectGraphDataEntry): Promise { + const subject: DBAPI.Subject | null = await DBAPI.Subject.fetch(objectGraphDataEntry.systemObjectIDType.idObject); + if (!subject) { + LOG.logger.error(`ReindexSolr.handleSubject failed to compute subject from ${JSON.stringify(objectGraphDataEntry.systemObjectIDType)}`); + return false; + } + + doc.Name = subject.Name; + if (subject.idIdentifierPreferred) { + const ID: DBAPI.Identifier | null = await DBAPI.Identifier.fetch(subject.idIdentifierPreferred); + if (ID) doc.IdentifierPreferred = ID.IdentifierValue; + } + return true; + } + + private async handleItem(doc: any, objectGraphDataEntry: DBAPI.ObjectGraphDataEntry): Promise { + const item: DBAPI.Item | null = await DBAPI.Item.fetch(objectGraphDataEntry.systemObjectIDType.idObject); + if (!item) { + LOG.logger.error(`ReindexSolr.handleItem failed to compute item from ${JSON.stringify(objectGraphDataEntry.systemObjectIDType)}`); + return false; + } + doc.Name = item.Name; + doc.EntireSubject = item.EntireSubject; + return true; + } + + private async handleCaptureData(doc: any, objectGraphDataEntry: DBAPI.ObjectGraphDataEntry): Promise { + const captureData: DBAPI.CaptureData | null = await DBAPI.CaptureData.fetch(objectGraphDataEntry.systemObjectIDType.idObject); + if (!captureData) { + LOG.logger.error(`ReindexSolr.handleCaptureData failed to compute capture data from ${JSON.stringify(objectGraphDataEntry.systemObjectIDType)}`); + return false; + } + const captureDataPhotos: DBAPI.CaptureDataPhoto[] | null = await DBAPI.CaptureDataPhoto.fetchFromCaptureData(captureData.idCaptureData); + if (!captureDataPhotos || captureDataPhotos.length != 1) { + LOG.logger.error(`ReindexSolr.handleCaptureData failed to find exactly 1 capture data photo for ${JSON.stringify(objectGraphDataEntry.systemObjectIDType)}`); + return false; + } + const captureDataPhoto: DBAPI.CaptureDataPhoto = captureDataPhotos[0]; + + doc.Name = captureData.Name; + doc.Description = captureData.Description; + doc.DateCreated = captureData.DateCaptured; + doc.CaptureMethod = await this.lookupVocabulary(captureData.idVCaptureMethod); + doc.CaptureDatasetType = await this.lookupVocabulary(captureDataPhoto.idVCaptureDatasetType); + doc.CaptureDatasetFieldID = captureDataPhoto.CaptureDatasetFieldID; + doc.ItemPositionType = await this.lookupVocabulary(captureDataPhoto.idVItemPositionType); + doc.ItemPositionFieldID = captureDataPhoto.ItemPositionFieldID; + doc.ItemArrangementFieldID = captureDataPhoto.ItemArrangementFieldID; + doc.FocusType = await this.lookupVocabulary(captureDataPhoto.idVFocusType); + doc.LightSourceType = await this.lookupVocabulary(captureDataPhoto.idVLightSourceType); + doc.BackgroundRemovalMethod = await this.lookupVocabulary(captureDataPhoto.idVBackgroundRemovalMethod); + doc.ClusterType = await this.lookupVocabulary(captureDataPhoto.idVClusterType); + doc.ClusterGeometryFieldID = captureDataPhoto.ClusterGeometryFieldID; + doc.CameraSettingsUniform = captureDataPhoto.CameraSettingsUniform; + return true; + } + + private async handleModel(doc: any, objectGraphDataEntry: DBAPI.ObjectGraphDataEntry): Promise { + const modelConstellation: DBAPI.ModelConstellation | null = await DBAPI.ModelConstellation.fetch(objectGraphDataEntry.systemObjectIDType.idObject); + if (!modelConstellation) { + LOG.logger.error(`ReindexSolr.handleModel failed to compute ModelConstellation from ${JSON.stringify(objectGraphDataEntry.systemObjectIDType)}`); + return false; + } + + doc.Name = modelConstellation.model.Name; + doc.DateCreated = modelConstellation.model.DateCreated; + + doc.CreationMethod = await this.computeVocabulary(modelConstellation.model.idVCreationMethod); + doc.Master = modelConstellation.model.Master; + doc.Authoritative = modelConstellation.model.Authoritative; + doc.Modality = await this.computeVocabulary(modelConstellation.model.idVModality); + doc.Units = await this.computeVocabulary(modelConstellation.model.idVUnits); + doc.Purpose = await this.computeVocabulary(modelConstellation.model.idVPurpose); + + const modelFileTypeMap: Map = new Map(); + const roughnessMap: Map = new Map(); + const metalnessMap: Map = new Map(); + const pointCountMap: Map = new Map(); + const faceCountMap: Map = new Map(); + const isWatertightMap: Map = new Map(); + const hasNormalsMap: Map = new Map(); + const hasVertexColorMap: Map = new Map(); + const hasUVSpaceMap: Map = new Map(); + const boundingBoxP1XMap: Map = new Map(); + const boundingBoxP1YMap: Map = new Map(); + const boundingBoxP1ZMap: Map = new Map(); + const boundingBoxP2XMap: Map = new Map(); + const boundingBoxP2YMap: Map = new Map(); + const boundingBoxP2ZMap: Map = new Map(); + const uvMapEdgeLengthMap: Map = new Map(); + const channelPositionMap: Map = new Map(); + const channelWidthMap: Map = new Map(); + const uvMapTypeMap: Map = new Map(); + + for (const modelGeometryFile of modelConstellation.modelGeometryFiles) { + const modelFileTypeWorker: string | undefined = await this.computeVocabulary(modelGeometryFile.idVModelFileType); + if (modelFileTypeWorker) modelFileTypeMap.set(modelFileTypeWorker, true); + if (modelGeometryFile.Roughness) roughnessMap.set(modelGeometryFile.Roughness, true); + if (modelGeometryFile.Metalness) metalnessMap.set(modelGeometryFile.Metalness, true); + if (modelGeometryFile.PointCount) pointCountMap.set(modelGeometryFile.PointCount, true); + if (modelGeometryFile.IsWatertight != null) isWatertightMap.set(modelGeometryFile.IsWatertight, true); + if (modelGeometryFile.HasNormals != null) hasNormalsMap.set(modelGeometryFile.HasNormals, true); + if (modelGeometryFile.HasVertexColor != null) hasVertexColorMap.set(modelGeometryFile.HasVertexColor, true); + if (modelGeometryFile.HasUVSpace != null) hasUVSpaceMap.set(modelGeometryFile.HasUVSpace, true); + if (modelGeometryFile.BoundingBoxP1X) boundingBoxP1XMap.set(modelGeometryFile.BoundingBoxP1X, true); + if (modelGeometryFile.BoundingBoxP1Y) boundingBoxP1YMap.set(modelGeometryFile.BoundingBoxP1Y, true); + if (modelGeometryFile.BoundingBoxP1Z) boundingBoxP1ZMap.set(modelGeometryFile.BoundingBoxP1Z, true); + if (modelGeometryFile.BoundingBoxP2X) boundingBoxP2XMap.set(modelGeometryFile.BoundingBoxP2X, true); + if (modelGeometryFile.BoundingBoxP2Y) boundingBoxP2YMap.set(modelGeometryFile.BoundingBoxP2Y, true); + if (modelGeometryFile.BoundingBoxP2Z) boundingBoxP2ZMap.set(modelGeometryFile.BoundingBoxP2Z, true); + } + + for (const modelUVMapFile of modelConstellation.modelUVMapFiles) { + uvMapEdgeLengthMap.set(modelUVMapFile.UVMapEdgeLength, true); + } + + for (const modelUVMapChannel of modelConstellation.modelUVMapChannels) { + channelPositionMap.set(modelUVMapChannel.ChannelPosition, true); + channelWidthMap.set(modelUVMapChannel.ChannelWidth, true); + const uvMapTypeWorker: string | undefined = await this.computeVocabulary(modelUVMapChannel.idVUVMapType); + if (uvMapTypeWorker) uvMapTypeMap.set(uvMapTypeWorker, true); + } + + const modelFileType: string[] = [...modelFileTypeMap.keys()]; + const roughness: number[] = [...roughnessMap.keys()]; + const metalness: number[] = [...metalnessMap.keys()]; + const pointCount: number[] = [...pointCountMap.keys()]; + const faceCount: number[] = [...faceCountMap.keys()]; + const isWatertight: boolean[] = [...isWatertightMap.keys()]; + const hasNormals: boolean[] = [...hasNormalsMap.keys()]; + const hasVertexColor: boolean[] = [...hasVertexColorMap.keys()]; + const hasUVSpace: boolean[] = [...hasUVSpaceMap.keys()]; + const boundingBoxP1X: number[] = [...boundingBoxP1XMap.keys()]; + const boundingBoxP1Y: number[] = [...boundingBoxP1YMap.keys()]; + const boundingBoxP1Z: number[] = [...boundingBoxP1ZMap.keys()]; + const boundingBoxP2X: number[] = [...boundingBoxP2XMap.keys()]; + const boundingBoxP2Y: number[] = [...boundingBoxP2YMap.keys()]; + const boundingBoxP2Z: number[] = [...boundingBoxP2ZMap.keys()]; + const uvMapEdgeLength: number[] = [...uvMapEdgeLengthMap.keys()]; + const channelPosition: number[] = [...channelPositionMap.keys()]; + const channelWidth: number[] = [...channelWidthMap.keys()]; + const uvMapType: string[] = [...uvMapTypeMap.keys()]; + + doc.ModelFileType = modelFileType.length == 1 ? modelFileType[0] : modelFileType; + doc.Roughness = roughness.length == 1 ? roughness[0] : roughness; + doc.Metalness = metalness.length == 1 ? metalness[0] : metalness; + doc.PointCount = pointCount.length == 1 ? pointCount[0] : pointCount; + doc.FaceCount = faceCount.length == 1 ? faceCount[0] : faceCount; + doc.IsWatertight = isWatertight.length == 1 ? isWatertight[0] : isWatertight; + doc.HasNormals = hasNormals.length == 1 ? hasNormals[0] : hasNormals; + doc.HasVertexColor = hasVertexColor.length == 1 ? hasVertexColor[0] : hasVertexColor; + doc.HasUVSpace = hasUVSpace.length == 1 ? hasUVSpace[0] : hasUVSpace; + doc.BoundingBoxP1X = boundingBoxP1X.length == 1 ? boundingBoxP1X[0] : boundingBoxP1X; + doc.BoundingBoxP1Y = boundingBoxP1Y.length == 1 ? boundingBoxP1Y[0] : boundingBoxP1Y; + doc.BoundingBoxP1Z = boundingBoxP1Z.length == 1 ? boundingBoxP1Z[0] : boundingBoxP1Z; + doc.BoundingBoxP2X = boundingBoxP2X.length == 1 ? boundingBoxP2X[0] : boundingBoxP2X; + doc.BoundingBoxP2Y = boundingBoxP2Y.length == 1 ? boundingBoxP2Y[0] : boundingBoxP2Y; + doc.BoundingBoxP2Z = boundingBoxP2Z.length == 1 ? boundingBoxP2Z[0] : boundingBoxP2Z; + doc.UVMapEdgeLength = uvMapEdgeLength.length == 1 ? uvMapEdgeLength[0] : uvMapEdgeLength; + doc.ChannelPosition = channelPosition.length == 1 ? channelPosition[0] : channelPosition; + doc.ChannelWidth = channelWidth.length == 1 ? channelWidth[0] : channelWidth; + doc.UVMapType = uvMapType.length == 1 ? uvMapType[0] : uvMapType; + return true; + } + + private async handleScene(doc: any, objectGraphDataEntry: DBAPI.ObjectGraphDataEntry): Promise { + const scene: DBAPI.Scene | null = await DBAPI.Scene.fetch(objectGraphDataEntry.systemObjectIDType.idObject); + if (!scene) { + LOG.logger.error(`ReindexSolr.handleScene failed to compute scene from ${JSON.stringify(objectGraphDataEntry.systemObjectIDType)}`); + return false; + } + doc.Name = scene.Name; + doc.IsOriented = scene.IsOriented; + doc.HasBeenQCd = scene.HasBeenQCd; + return true; + } + + private async handleIntermediaryFile(doc: any, objectGraphDataEntry: DBAPI.ObjectGraphDataEntry): Promise { + const intermediaryFile: DBAPI.IntermediaryFile | null = await DBAPI.IntermediaryFile.fetch(objectGraphDataEntry.systemObjectIDType.idObject); + if (!intermediaryFile) { + LOG.logger.error(`ReindexSolr.handleIntermediaryFile failed to compute intermediaryFile from ${JSON.stringify(objectGraphDataEntry.systemObjectIDType)}`); + return false; + } + doc.DateCreated = intermediaryFile.DateCreated; + return true; + } + + private async handleProjectDocumentation(doc: any, objectGraphDataEntry: DBAPI.ObjectGraphDataEntry): Promise { + const projectDocumentation: DBAPI.ProjectDocumentation | null = await DBAPI.ProjectDocumentation.fetch(objectGraphDataEntry.systemObjectIDType.idObject); + if (!projectDocumentation) { + LOG.logger.error(`ReindexSolr.handleProjectDocumentation failed to compute projectDocumentation from ${JSON.stringify(objectGraphDataEntry.systemObjectIDType)}`); + return false; + } + doc.Name = projectDocumentation.Name; + doc.Description = projectDocumentation.Description; + return true; + } + + private async handleAsset(doc: any, objectGraphDataEntry: DBAPI.ObjectGraphDataEntry): Promise { + const asset: DBAPI.Asset | null = await DBAPI.Asset.fetch(objectGraphDataEntry.systemObjectIDType.idObject); + if (!asset) { + LOG.logger.error(`ReindexSolr.handleAsset failed to compute asset from ${JSON.stringify(objectGraphDataEntry.systemObjectIDType)}`); + return false; + } + doc.FileName = asset.FileName; + doc.FilePath = asset.FilePath; + doc.AssetType = await this.lookupVocabulary(asset.idVAssetType); + return true; + } + + private async handleAssetVersion(doc: any, objectGraphDataEntry: DBAPI.ObjectGraphDataEntry): Promise { + const assetVersion: DBAPI.AssetVersion | null = await DBAPI.AssetVersion.fetch(objectGraphDataEntry.systemObjectIDType.idObject); + if (!assetVersion) { + LOG.logger.error(`ReindexSolr.handleAssetVersion failed to compute assetVersion from ${JSON.stringify(objectGraphDataEntry.systemObjectIDType)}`); + return false; + } + + const user: DBAPI.User | null = await DBAPI.User.fetch(assetVersion.idUserCreator); + if (!user) { + LOG.logger.error(`ReindexSolr.handleAssetVersion failed to compute idUserCreator from ${assetVersion.idUserCreator}`); + return false; + } + doc.UserCreator = user.Name; + doc.StorageHash = assetVersion.StorageHash; + doc.StorageSize = assetVersion.StorageSize; + doc.Ingested = assetVersion.Ingested; + doc.BulkIngest = assetVersion.BulkIngest; + return true; + } + + private async handleActor(doc: any, objectGraphDataEntry: DBAPI.ObjectGraphDataEntry): Promise { + const actor: DBAPI.Actor | null = await DBAPI.Actor.fetch(objectGraphDataEntry.systemObjectIDType.idObject); + if (!actor) { + LOG.logger.error(`ReindexSolr.handleActor failed to compute actor from ${JSON.stringify(objectGraphDataEntry.systemObjectIDType)}`); + return false; + } + doc.Name = actor.IndividualName; + doc.OrganizationName = actor.OrganizationName; + return true; + } + + private async handleStakeholder(doc: any, objectGraphDataEntry: DBAPI.ObjectGraphDataEntry): Promise { + const stakeholder: DBAPI.Stakeholder | null = await DBAPI.Stakeholder.fetch(objectGraphDataEntry.systemObjectIDType.idObject); + if (!stakeholder) { + LOG.logger.error(`ReindexSolr.handleStakeholder failed to compute stakeholder from ${JSON.stringify(objectGraphDataEntry.systemObjectIDType)}`); + return false; + } + + doc.Name = stakeholder.IndividualName; + doc.OrganizationName = stakeholder.OrganizationName; + doc.EmailAddress = stakeholder.EmailAddress; + doc.PhoneNumberMobile = stakeholder.PhoneNumberMobile; + doc.PhoneNumberOffice = stakeholder.PhoneNumberOffice; + doc.MailingAddress = stakeholder.MailingAddress; + return true; + } + + private async handleUnknown(doc: any, objectGraphDataEntry: DBAPI.ObjectGraphDataEntry): Promise { + LOG.logger.error(`ReindexSolr.fullIndex called with unknown object type from ${JSON.stringify(objectGraphDataEntry.systemObjectIDType)}`); + doc.Name = `Unknown ${JSON.stringify(objectGraphDataEntry.systemObjectIDType)}`; + return false; + } + + private async computeIdentifiers(idSystemObject: number): Promise { + const identifiersRet: string[] = []; + const identifiers: DBAPI.Identifier[] | null = await DBAPI.Identifier.fetchFromSystemObject(idSystemObject); + if (identifiers) { + for (const identifier of identifiers) identifiersRet.push(identifier.IdentifierValue); + } + return identifiersRet; + } + + private async lookupVocabulary(idVocabulary: number | null): Promise { + if (!idVocabulary) return ''; + const vocabulary: DBAPI.Vocabulary | undefined = await CACHE.VocabularyCache.vocabulary(idVocabulary); + return vocabulary ? vocabulary.Term : ''; + } +} diff --git a/server/navigation/interface/INavigation.ts b/server/navigation/interface/INavigation.ts index 74f2a0c72..b4fd110d1 100644 --- a/server/navigation/interface/INavigation.ts +++ b/server/navigation/interface/INavigation.ts @@ -7,34 +7,34 @@ export enum eMetadata { } export type NavigationFilter = { - idRoot: number, // idSystemObject of item for which we should get children; 0 means get everything - objectTypes: eSystemObjectType[], // empty array means give all appropriate children types - metadataColumns: eMetadata[], // empty array means give no metadata - search: string, // search string from the user - objectsToDisplay: eSystemObjectType[], // objects to display - units: number[], // idSystemObject[] for units filter - projects: number[], // idSystemObject[] for projects filter - has: eSystemObjectType[], // has system object filter - missing: eSystemObjectType[], // missing system object filter - captureMethod: number[], // idVocabulary[] for capture method filter - variantType: number[], // idVocabulary[] for variant type filter - modelPurpose: number[], // idVocabulary[] for model purpose filter - modelFileType: number[], // idVocabulary[] for model file type filter + idRoot: number; // idSystemObject of item for which we should get children; 0 means get everything + objectTypes: eSystemObjectType[]; // empty array means give all appropriate children types + metadataColumns: eMetadata[]; // empty array means give no metadata + search: string; // search string from the user + objectsToDisplay: eSystemObjectType[]; // objects to display + units: number[]; // idSystemObject[] for units filter + projects: number[]; // idSystemObject[] for projects filter + has: eSystemObjectType[]; // has system object filter + missing: eSystemObjectType[]; // missing system object filter + captureMethod: number[]; // idVocabulary[] for capture method filter + variantType: number[]; // idVocabulary[] for variant type filter + modelPurpose: number[]; // idVocabulary[] for model purpose filter + modelFileType: number[]; // idVocabulary[] for model file type filter }; export type NavigationResultEntry = { - idSystemObject: number, // idSystemObject of the entry - name: string, // Name of the object, for display purposes - objectType: eSystemObjectType, // system object type of the entry (eProject, eUnit, eSubject, eItem, eCaptureData, etc.) - idObject: number, // database ID of the object (e.g. Project.idProject, Unit.idUnit, Subject.idSubject, etc.) - metadata: string[] // array of metadata values, in the order of NavigationResult.metadataColumns, matching the order of NavigationFilter.metadataColumns + idSystemObject: number; // idSystemObject of the entry + name: string; // Name of the object, for display purposes + objectType: eSystemObjectType; // system object type of the entry (eProject, eUnit, eSubject, eItem, eCaptureData, etc.) + idObject: number; // database ID of the object (e.g. Project.idProject, Unit.idUnit, Subject.idSubject, etc.) + metadata: string[]; // array of metadata values, in the order of NavigationResult.metadataColumns, matching the order of NavigationFilter.metadataColumns }; export type NavigationResult = { - success: boolean, - error: string, - entries: NavigationResultEntry[], - metadataColumns: eMetadata[] + success: boolean; + error: string; + entries: NavigationResultEntry[]; + metadataColumns: eMetadata[]; }; export interface INavigation { diff --git a/server/tests/db/composite/ObjectGraph.setup.ts b/server/tests/db/composite/ObjectGraph.setup.ts index f40136d54..5a9712efc 100644 --- a/server/tests/db/composite/ObjectGraph.setup.ts +++ b/server/tests/db/composite/ObjectGraph.setup.ts @@ -119,7 +119,7 @@ export class ObjectGraphTestSetup { this.assetVersion3 = await UTIL.createAssetVersionTest({ idAsset: this.asset3.idAsset, idUserCreator: this.user1.idUser, DateCreated: UTIL.nowCleansed(), StorageHash: 'OA Test', StorageSize: 500, idAssetVersion: 0, Ingested: true, BulkIngest: false, FileName: '', StorageKeyStaging: '', Version: 0 }); this.asset4 = await UTIL.createAssetTest({ FileName: 'OA Test', FilePath: '/OA Test', idAssetGroup: null, idVAssetType: this.v1.idVocabulary, idSystemObject: null, StorageKey: UTIL.randomStorageKey('/'), idAsset: 0 }); this.assetVersion4 = await UTIL.createAssetVersionTest({ idAsset: this.asset4.idAsset, idUserCreator: this.user1.idUser, DateCreated: UTIL.nowCleansed(), StorageHash: 'OA Test', StorageSize: 500, idAssetVersion: 0, Ingested: true, BulkIngest: false, FileName: '', StorageKeyStaging: '', Version: 0 }); - this.model1 = await UTIL.createModelTest({ DateCreated: UTIL.nowCleansed(), idVCreationMethod: this.v1.idVocabulary, Master: true, Authoritative: true, idVModality: this.v1.idVocabulary, idVUnits: this.v1.idVocabulary, idVPurpose: this.v1.idVocabulary, idAssetThumbnail: this.assetT4.idAsset, idModel: 0 }); + this.model1 = await UTIL.createModelTest({ Name: 'OA Test', DateCreated: UTIL.nowCleansed(), idVCreationMethod: this.v1.idVocabulary, Master: true, Authoritative: true, idVModality: this.v1.idVocabulary, idVUnits: this.v1.idVocabulary, idVPurpose: this.v1.idVocabulary, idAssetThumbnail: this.assetT4.idAsset, idModel: 0 }); assigned = await this.asset2.assignOwner(this.model1); expect(assigned).toBeTruthy(); assigned = await this.asset3.assignOwner(this.model1); expect(assigned).toBeTruthy(); assigned = await this.asset4.assignOwner(this.model1); expect(assigned).toBeTruthy(); @@ -127,17 +127,17 @@ export class ObjectGraphTestSetup { this.asset5 = await UTIL.createAssetTest({ FileName: 'OA Test', FilePath: '/OA Test', idAssetGroup: null, idVAssetType: this.v1.idVocabulary, idSystemObject: null, StorageKey: UTIL.randomStorageKey('/'), idAsset: 0 }); this.assetVersion5 = await UTIL.createAssetVersionTest({ idAsset: this.asset5.idAsset, idUserCreator: this.user1.idUser, DateCreated: UTIL.nowCleansed(), StorageHash: 'OA Test', StorageSize: 500, idAssetVersion: 0, Ingested: true, BulkIngest: false, FileName: '', StorageKeyStaging: '', Version: 0 }); - this.model2 = await UTIL.createModelTest({ DateCreated: UTIL.nowCleansed(), idVCreationMethod: this.v1.idVocabulary, Master: true, Authoritative: true, idVModality: this.v1.idVocabulary, idVUnits: this.v1.idVocabulary, idVPurpose: this.v1.idVocabulary, idAssetThumbnail: null, idModel: 0 }); + this.model2 = await UTIL.createModelTest({ Name: 'OA Test', DateCreated: UTIL.nowCleansed(), idVCreationMethod: this.v1.idVocabulary, Master: true, Authoritative: true, idVModality: this.v1.idVocabulary, idVUnits: this.v1.idVocabulary, idVPurpose: this.v1.idVocabulary, idAssetThumbnail: null, idModel: 0 }); assigned = await this.asset5.assignOwner(this.model2); expect(assigned).toBeTruthy(); this.asset6 = await UTIL.createAssetTest({ FileName: 'OA Test', FilePath: '/OA Test', idAssetGroup: null, idVAssetType: this.v1.idVocabulary, idSystemObject: null, StorageKey: UTIL.randomStorageKey('/'), idAsset: 0 }); this.assetVersion6 = await UTIL.createAssetVersionTest({ idAsset: this.asset6.idAsset, idUserCreator: this.user1.idUser, DateCreated: UTIL.nowCleansed(), StorageHash: 'OA Test', StorageSize: 500, idAssetVersion: 0, Ingested: true, BulkIngest: false, FileName: '', StorageKeyStaging: '', Version: 0 }); - this.model3 = await UTIL.createModelTest({ DateCreated: UTIL.nowCleansed(), idVCreationMethod: this.v1.idVocabulary, Master: true, Authoritative: true, idVModality: this.v1.idVocabulary, idVUnits: this.v1.idVocabulary, idVPurpose: this.v1.idVocabulary, idAssetThumbnail: null, idModel: 0 }); + this.model3 = await UTIL.createModelTest({ Name: 'OA Test', DateCreated: UTIL.nowCleansed(), idVCreationMethod: this.v1.idVocabulary, Master: true, Authoritative: true, idVModality: this.v1.idVocabulary, idVUnits: this.v1.idVocabulary, idVPurpose: this.v1.idVocabulary, idAssetThumbnail: null, idModel: 0 }); assigned = await this.asset6.assignOwner(this.model3); expect(assigned).toBeTruthy(); this.asset7 = await UTIL.createAssetTest({ FileName: 'OA Test', FilePath: '/OA Test', idAssetGroup: null, idVAssetType: this.v1.idVocabulary, idSystemObject: null, StorageKey: UTIL.randomStorageKey('/'), idAsset: 0 }); this.assetVersion7 = await UTIL.createAssetVersionTest({ idAsset: this.asset7.idAsset, idUserCreator: this.user1.idUser, DateCreated: UTIL.nowCleansed(), StorageHash: 'OA Test', StorageSize: 500, idAssetVersion: 0, Ingested: true, BulkIngest: false, FileName: '', StorageKeyStaging: '', Version: 0 }); - this.model4 = await UTIL.createModelTest({ DateCreated: UTIL.nowCleansed(), idVCreationMethod: this.v1.idVocabulary, Master: true, Authoritative: true, idVModality: this.v1.idVocabulary, idVUnits: this.v1.idVocabulary, idVPurpose: this.v1.idVocabulary, idAssetThumbnail: null, idModel: 0 }); + this.model4 = await UTIL.createModelTest({ Name: 'OA Test', DateCreated: UTIL.nowCleansed(), idVCreationMethod: this.v1.idVocabulary, Master: true, Authoritative: true, idVModality: this.v1.idVocabulary, idVUnits: this.v1.idVocabulary, idVPurpose: this.v1.idVocabulary, idAssetThumbnail: null, idModel: 0 }); assigned = await this.asset7.assignOwner(this.model4); expect(assigned).toBeTruthy(); this.assetT5 = await UTIL.createAssetTest({ FileName: 'OA Test', FilePath: '/OA Test', idAssetGroup: null, idVAssetType: this.v1.idVocabulary, idSystemObject: null, StorageKey: UTIL.randomStorageKey('/'), idAsset: 0 }); diff --git a/server/tests/db/dbcreation.test.ts b/server/tests/db/dbcreation.test.ts index 92256759e..44bd1f845 100644 --- a/server/tests/db/dbcreation.test.ts +++ b/server/tests/db/dbcreation.test.ts @@ -831,6 +831,7 @@ describe('DB Creation Test Suite', () => { test('DB Creation: Model', async () => { if (vocabulary && assetThumbnail) model = await UTIL.createModelTest({ + Name: 'Test Model', DateCreated: UTIL.nowCleansed(), idVCreationMethod: vocabulary.idVocabulary, Master: true, @@ -847,6 +848,7 @@ describe('DB Creation Test Suite', () => { test('DB Creation: Model With Nulls', async () => { if (vocabulary) modelNulls = await UTIL.createModelTest({ + Name: 'Test Model with Nulls', DateCreated: UTIL.nowCleansed(), idVCreationMethod: vocabulary.idVocabulary, Master: true, diff --git a/server/tests/graphql/utils/index.ts b/server/tests/graphql/utils/index.ts index 66c6778fd..907925f55 100644 --- a/server/tests/graphql/utils/index.ts +++ b/server/tests/graphql/utils/index.ts @@ -106,6 +106,7 @@ class TestSuiteUtils { createModelInput = (idVocabulary: number): CreateModelInput => { return { + Name: 'Test Name', Authoritative: true, idVCreationMethod: idVocabulary, idVModality: idVocabulary, diff --git a/server/types/graphql.ts b/server/types/graphql.ts index e483ad478..252df3e02 100644 --- a/server/types/graphql.ts +++ b/server/types/graphql.ts @@ -879,6 +879,7 @@ export type LicenseAssignment = { }; export type CreateModelInput = { + Name: Scalars['String']; Authoritative: Scalars['Boolean']; idVCreationMethod: Scalars['Int']; idVModality: Scalars['Int']; @@ -905,6 +906,7 @@ export type GetModelResult = { export type Model = { __typename?: 'Model'; idModel: Scalars['Int']; + Name: Scalars['String']; Authoritative: Scalars['Boolean']; DateCreated: Scalars['DateTime']; idAssetThumbnail?: Maybe; diff --git a/server/utils/helpers.ts b/server/utils/helpers.ts index 6dfef8c97..16619c88a 100644 --- a/server/utils/helpers.ts +++ b/server/utils/helpers.ts @@ -12,21 +12,21 @@ import * as crypto from 'crypto'; import * as LOG from './logger'; export type IOResults = { - success: boolean, - error: string + success: boolean; + error: string; }; export type HashResults = { - hash: string, - dataLength: number, - success: boolean, - error: string + hash: string; + dataLength: number; + success: boolean; + error: string; }; export type StatResults = { - stat: Stats | null, - success: boolean, - error: string + stat: Stats | null; + success: boolean; + error: string; }; export class Helpers { From e5a27ee748988007be98cf495a8f538bb0b5da60 Mon Sep 17 00:00:00 2001 From: Jon Tyson <6943745+jahjedtieson@users.noreply.github.com> Date: Wed, 10 Feb 2021 01:33:03 -0800 Subject: [PATCH 216/239] Solr: * Added "/solrindexprofiled" server route for running the solr index generation under a profile ... which emits "profile.cpuprofile" in the server folder. Not sure yet if this is helpful for profiling, as viewing this file in Chrome Dev Tools is mostly unhelpful for determining why this operation is so slow. * Allow Models and CaptureData to not have related, derived objects * Short circuit object graph computations when we're running with an "ObjectGraphDatabase" -- in this case, we don't need to recurse / visit children if we've already processed the parent-child pair in question * Represent ParentID and ChildrenID as an array of SystemObject.idSystemObject values * Only allow a single caller to /solrindex and /solrindexprofile -- the rest exit early with a failure * Fixed solr document construction * Added testing of SystemObjectCache (to 100% coverage) * Added DBAPI testing (to 100% coverage) --- server/db/api/Model.ts | 28 +-- server/db/api/ModelUVMapChannel.ts | 2 + server/db/api/ModelUVMapFile.ts | 2 + server/db/api/composite/ObjectGraph.ts | 15 +- .../db/api/composite/ObjectGraphDataEntry.ts | 16 +- .../db/api/composite/ObjectGraphDatabase.ts | 84 ++++++--- server/index.ts | 6 + .../impl/NavigationSolr/ReindexSolr.ts | 168 ++++++++++++------ server/tests/cache/SystemObjectCache.test.ts | 90 ++++++++-- server/tests/db/dbcreation.test.ts | 69 +++++++ 10 files changed, 353 insertions(+), 127 deletions(-) diff --git a/server/db/api/Model.ts b/server/db/api/Model.ts index 4f31a6c12..82e44bb6c 100644 --- a/server/db/api/Model.ts +++ b/server/db/api/Model.ts @@ -6,11 +6,12 @@ import * as LOG from '../../utils/logger'; export class ModelConstellation { model: Model; - modelGeometryFiles: ModelGeometryFile[]; - modelUVMapFiles: ModelUVMapFile[]; - modelUVMapChannels: ModelUVMapChannel[]; + modelGeometryFiles: ModelGeometryFile[] | null; + modelUVMapFiles: ModelUVMapFile[] | null; + modelUVMapChannels: ModelUVMapChannel[] | null; - constructor(model: Model, modelGeometryFiles: ModelGeometryFile[], modelUVMapFiles: ModelUVMapFile[], modelUVMapChannels: ModelUVMapChannel[]) { + constructor(model: Model, modelGeometryFiles: ModelGeometryFile[] | null, + modelUVMapFiles: ModelUVMapFile[] | null, modelUVMapChannels: ModelUVMapChannel[] | null) { this.model = model; this.modelGeometryFiles = modelGeometryFiles; this.modelUVMapFiles = modelUVMapFiles; @@ -25,23 +26,8 @@ export class ModelConstellation { } const modelGeometryFiles: ModelGeometryFile[] | null = await ModelGeometryFile.fetchFromModel(idModel); - if (!modelGeometryFiles) { - LOG.logger.error(`ModelConstellation.fetch() unable to compute model geometry files from ${idModel}`); - return null; - } - - const modelUVMapFiles: ModelUVMapFile[] | null = await ModelUVMapFile.fetchFromModelGeometryFiles(modelGeometryFiles); - if (!modelUVMapFiles) { - LOG.logger.error(`ModelConstellation.fetch() unable to compute model uv map files from ${idModel}`); - return null; - } - - const modelUVMapChannels: ModelUVMapChannel[] | null = await ModelUVMapChannel.fetchFromModelUVMapFiles(modelUVMapFiles); - if (!modelUVMapChannels) { - LOG.logger.error(`ModelConstellation.fetch() unable to compute model uv map files from ${idModel}`); - return null; - } - + const modelUVMapFiles: ModelUVMapFile[] | null = modelGeometryFiles ? await ModelUVMapFile.fetchFromModelGeometryFiles(modelGeometryFiles) : null; + const modelUVMapChannels: ModelUVMapChannel[] | null = modelUVMapFiles ? await ModelUVMapChannel.fetchFromModelUVMapFiles(modelUVMapFiles) : null; return new ModelConstellation(model, modelGeometryFiles, modelUVMapFiles, modelUVMapChannels); } } diff --git a/server/db/api/ModelUVMapChannel.ts b/server/db/api/ModelUVMapChannel.ts index e0cb952d0..b6291ceb6 100644 --- a/server/db/api/ModelUVMapChannel.ts +++ b/server/db/api/ModelUVMapChannel.ts @@ -78,6 +78,8 @@ export class ModelUVMapChannel extends DBC.DBObject imple } static async fetchFromModelUVMapFiles(modelUVMapFiles: ModelUVMapFile[]): Promise { + if (modelUVMapFiles.length == 0) + return null; try { const idModelUVMapFiles: number[] = []; for (const modelUVMapFile of modelUVMapFiles) idModelUVMapFiles.push(modelUVMapFile.idModelUVMapFile); diff --git a/server/db/api/ModelUVMapFile.ts b/server/db/api/ModelUVMapFile.ts index 003646ced..994f224b7 100644 --- a/server/db/api/ModelUVMapFile.ts +++ b/server/db/api/ModelUVMapFile.ts @@ -77,6 +77,8 @@ export class ModelUVMapFile extends DBC.DBObject implements } static async fetchFromModelGeometryFiles(modelGeometryFiles: ModelGeometryFile[]): Promise { + if (modelGeometryFiles.length == 0) + return null; try { const idModelGeometryFiles: number[] = []; for (const modelGeometryFile of modelGeometryFiles) idModelGeometryFiles.push(modelGeometryFile.idModelGeometryFile); diff --git a/server/db/api/composite/ObjectGraph.ts b/server/db/api/composite/ObjectGraph.ts index 9b1239061..96e76df0d 100644 --- a/server/db/api/composite/ObjectGraph.ts +++ b/server/db/api/composite/ObjectGraph.ts @@ -163,10 +163,17 @@ export class ObjectGraph { // record relationship if (this.objectGraphDatabase) { if (relatedType) { - if (eMode == eObjectGraphMode.eAncestors) - this.objectGraphDatabase.recordRelationship(sourceType, relatedType); - else - this.objectGraphDatabase.recordRelationship(relatedType, sourceType); + if (eMode == eObjectGraphMode.eAncestors) { + if (!this.objectGraphDatabase.recordRelationship(sourceType, relatedType)) { + LOG.logger.info('ObjectGraph short circuited'); + return true; + } + } else { + if (!this.objectGraphDatabase.recordRelationship(relatedType, sourceType)) { + LOG.logger.info('ObjectGraph short circuited'); + return true; + } + } } } diff --git a/server/db/api/composite/ObjectGraphDataEntry.ts b/server/db/api/composite/ObjectGraphDataEntry.ts index 8e798bbd2..9091234cf 100644 --- a/server/db/api/composite/ObjectGraphDataEntry.ts +++ b/server/db/api/composite/ObjectGraphDataEntry.ts @@ -24,8 +24,8 @@ export class ObjectGraphDataEntryHierarchy { eObjectType: eSystemObjectType | null = null; idObject: number = 0; - parents: SystemObjectIDType[] = []; - children: SystemObjectIDType[] = []; + parents: number[] = []; // array of SystemObject.idSystemObject + children: number[] = []; // array of SystemObject.idSystemObject units: SystemObjectIDType[] = []; projects: SystemObjectIDType[] = []; @@ -45,8 +45,8 @@ export class ObjectGraphDataEntry { retired: boolean = false; // Derived data - childMap: Map = new Map(); // map of child objects - parentMap: Map = new Map(); // map of parent objects + childMap: Map = new Map(); // map of child objects -> child idSystemObject + parentMap: Map = new Map(); // map of parent objects -> parent idSystemObject ancestorObjectMap: Map = new Map(); // map of ancestor objects of significance (unit, project, subject, item) // Child data types @@ -62,11 +62,11 @@ export class ObjectGraphDataEntry { } recordChild(child: SystemObjectIDType): void { - this.childMap.set(child, true); + this.childMap.set(child, child.idSystemObject); } recordParent(parent: SystemObjectIDType): void { - this.parentMap.set(parent, true); + this.parentMap.set(parent, parent.idSystemObject); } // Returns true if applying objectGraphState updated the state of this ObjectGraphDataEntry @@ -135,8 +135,8 @@ export class ObjectGraphDataEntry { objectGraphDataEntryHierarchy.eObjectType = this.systemObjectIDType.eObjectType; objectGraphDataEntryHierarchy.idObject = this.systemObjectIDType.idObject; - objectGraphDataEntryHierarchy.parents = [...this.parentMap.keys()]; - objectGraphDataEntryHierarchy.children = [...this.childMap.keys()]; + objectGraphDataEntryHierarchy.parents = [...this.parentMap.values()]; + objectGraphDataEntryHierarchy.children = [...this.childMap.values()]; for (const systemObjectIDType of this.ancestorObjectMap.keys()) { switch (systemObjectIDType.eObjectType) { case eSystemObjectType.eUnit: objectGraphDataEntryHierarchy.units.push(systemObjectIDType); break; diff --git a/server/db/api/composite/ObjectGraphDatabase.ts b/server/db/api/composite/ObjectGraphDatabase.ts index 413d2fbf4..f6b3cf27d 100644 --- a/server/db/api/composite/ObjectGraphDatabase.ts +++ b/server/db/api/composite/ObjectGraphDatabase.ts @@ -9,9 +9,12 @@ export class ObjectGraphDatabase { objectMap: Map = new Map(); // map from object to graph entry details // used by ObjectGraph - async recordRelationship(parent: SystemObjectIDType, child: SystemObjectIDType): Promise { + // returns true if either parent or child is unknown to the database + // returns false if both parent and child are known to the database, in which case continued elaboration of this branch is not needed + async recordRelationship(parent: SystemObjectIDType, child: SystemObjectIDType): Promise { let parentData: ObjectGraphDataEntry | undefined = this.objectMap.get(parent); let childData: ObjectGraphDataEntry | undefined = this.objectMap.get(child); + const retValue: boolean = !(parentData && childData); if (!parentData) { const sID: CACHE.SystemObjectInfo | undefined = await CACHE.SystemObjectCache.getSystemFromObjectID(parent); /* istanbul ignore if */ @@ -32,9 +35,12 @@ export class ObjectGraphDatabase { parentData.recordChild(child); childData.recordParent(parent); + return retValue; } async fetch(): Promise { + LOG.logger.info('**********************************'); + LOG.logger.info('ObjectGraphDatabase.fetch starting'); if (!await this.computeGraphDataFromUnits()) return false; if (!await this.computeGraphDataFromProjects()) return false; if (!await this.computeGraphDataFromSubjects()) return false; @@ -50,6 +56,7 @@ export class ObjectGraphDatabase { if (!await this.computeGraphDataFromStakeholders()) return false; if (!(await this.applyGraphData())) return false; + LOG.logger.info('ObjectGraphDatabase.fetch completed successfully'); return true; } @@ -66,6 +73,7 @@ export class ObjectGraphDatabase { LOG.logger.error(`GraphDatabase.${functionName} unable to compute idSystemObject for ${JSON.stringify(oID)}`); return false; } + // LOG.logger.info(`ObjectGraphDatabase.computeGraphDataFromObject ${JSON.stringify(oID)} -> ${JSON.stringify(sID)}`); const OG: ObjectGraph = new ObjectGraph(sID.idSystemObject, eObjectGraphMode.eDescendents, 32, this); // this -> gather relationships for all objects! if (!await OG.fetch()) { @@ -74,14 +82,17 @@ export class ObjectGraphDatabase { } const objectIDAndType: SystemObjectIDType = { idSystemObject: sID.idSystemObject, idObject, eObjectType }; - if (!this.objectMap.has(objectIDAndType)) this.objectMap.set(objectIDAndType, new ObjectGraphDataEntry(objectIDAndType, sID.Retired)); + if (!this.objectMap.has(objectIDAndType)) + this.objectMap.set(objectIDAndType, new ObjectGraphDataEntry(objectIDAndType, sID.Retired)); return true; } private async computeGraphDataFromUnits(): Promise { + LOG.logger.info('ObjectGraphDatabase.computeGraphDataFromUnits'); // iterate across all Units; for each, compute ObjectGraph; extract ObjectGraph data into a "database" const units: Unit[] | null = await Unit.fetchAll(); /* istanbul ignore if */ - if (!units) return false; + if (!units) + return false; for (const unit of units) { if (!await this.computeGraphDataFromObject(unit.idUnit, eSystemObjectType.eUnit, 'computeGraphDataFromUnits')) continue; @@ -90,9 +101,11 @@ export class ObjectGraphDatabase { } private async computeGraphDataFromProjects(): Promise { + LOG.logger.info('ObjectGraphDatabase.computeGraphDataFromProjects'); // iterate across all Projects; for each, compute ObjectGraph; extract ObjectGraph data into a "database" const projects: Project[] | null = await Project.fetchAll(); /* istanbul ignore if */ - if (!projects) return false; + if (!projects) + return false; for (const project of projects) { if (!await this.computeGraphDataFromObject(project.idProject, eSystemObjectType.eProject, 'computeGraphDataFromProjects')) continue; @@ -101,9 +114,11 @@ export class ObjectGraphDatabase { } private async computeGraphDataFromSubjects(): Promise { + LOG.logger.info('ObjectGraphDatabase.computeGraphDataFromSubjects'); // iterate across all Subjects; for each, compute ObjectGraph; extract ObjectGraph data into a "database" const Subjects: Subject[] | null = await Subject.fetchAll(); /* istanbul ignore if */ - if (!Subjects) return false; + if (!Subjects) + return false; for (const Subject of Subjects) { if (!await this.computeGraphDataFromObject(Subject.idSubject, eSystemObjectType.eSubject, 'computeGraphDataFromSubjects')) continue; @@ -112,9 +127,11 @@ export class ObjectGraphDatabase { } private async computeGraphDataFromItems(): Promise { + LOG.logger.info('ObjectGraphDatabase.computeGraphDataFromItems'); // iterate across all Items; for each, compute ObjectGraph; extract ObjectGraph data into a "database" const Items: Item[] | null = await Item.fetchAll(); /* istanbul ignore if */ - if (!Items) return false; + if (!Items) + return false; for (const Item of Items) { if (!await this.computeGraphDataFromObject(Item.idItem, eSystemObjectType.eItem, 'computeGraphDataFromItems')) continue; @@ -123,9 +140,11 @@ export class ObjectGraphDatabase { } private async computeGraphDataFromCaptureDatas(): Promise { + LOG.logger.info('ObjectGraphDatabase.computeGraphDataFromCaptureDatas'); // iterate across all CaptureDatas; for each, compute ObjectGraph; extract ObjectGraph data into a "database" const CaptureDatas: CaptureData[] | null = await CaptureData.fetchAll(); /* istanbul ignore if */ - if (!CaptureDatas) return false; + if (!CaptureDatas) + return false; for (const CaptureData of CaptureDatas) { if (!await this.computeGraphDataFromObject(CaptureData.idCaptureData, eSystemObjectType.eCaptureData, 'computeGraphDataFromCaptureDatas')) continue; @@ -134,9 +153,11 @@ export class ObjectGraphDatabase { } private async computeGraphDataFromModels(): Promise { + LOG.logger.info('ObjectGraphDatabase.computeGraphDataFromModels'); // iterate across all Models; for each, compute ObjectGraph; extract ObjectGraph data into a "database" const Models: Model[] | null = await Model.fetchAll(); /* istanbul ignore if */ - if (!Models) return false; + if (!Models) + return false; for (const Model of Models) { if (!await this.computeGraphDataFromObject(Model.idModel, eSystemObjectType.eModel, 'computeGraphDataFromModels')) continue; @@ -145,9 +166,11 @@ export class ObjectGraphDatabase { } private async computeGraphDataFromScenes(): Promise { + LOG.logger.info('ObjectGraphDatabase.computeGraphDataFromScenes'); // iterate across all Scenes; for each, compute ObjectGraph; extract ObjectGraph data into a "database" const Scenes: Scene[] | null = await Scene.fetchAll(); /* istanbul ignore if */ - if (!Scenes) return false; + if (!Scenes) + return false; for (const Scene of Scenes) { if (!await this.computeGraphDataFromObject(Scene.idScene, eSystemObjectType.eScene, 'computeGraphDataFromScenes')) continue; @@ -156,9 +179,11 @@ export class ObjectGraphDatabase { } private async computeGraphDataFromIntermediaryFiles(): Promise { + LOG.logger.info('ObjectGraphDatabase.computeGraphDataFromIntermediaryFiles'); // iterate across all IntermediaryFiles; for each, compute ObjectGraph; extract ObjectGraph data into a "database" const IntermediaryFiles: IntermediaryFile[] | null = await IntermediaryFile.fetchAll(); /* istanbul ignore if */ - if (!IntermediaryFiles) return false; + if (!IntermediaryFiles) + return false; for (const IntermediaryFile of IntermediaryFiles) { if (!await this.computeGraphDataFromObject(IntermediaryFile.idIntermediaryFile, eSystemObjectType.eIntermediaryFile, 'computeGraphDataFromIntermediaryFiles')) continue; @@ -167,9 +192,11 @@ export class ObjectGraphDatabase { } private async computeGraphDataFromProjectDocumentations(): Promise { + LOG.logger.info('ObjectGraphDatabase.computeGraphDataFromProjectDocumentations'); // iterate across all ProjectDocumentations; for each, compute ObjectGraph; extract ObjectGraph data into a "database" const ProjectDocumentations: ProjectDocumentation[] | null = await ProjectDocumentation.fetchAll(); /* istanbul ignore if */ - if (!ProjectDocumentations) return false; + if (!ProjectDocumentations) + return false; for (const ProjectDocumentation of ProjectDocumentations) { if (!await this.computeGraphDataFromObject(ProjectDocumentation.idProjectDocumentation, eSystemObjectType.eProjectDocumentation, 'computeGraphDataFromProjectDocumentations')) @@ -179,9 +206,11 @@ export class ObjectGraphDatabase { } private async computeGraphDataFromAssets(): Promise { + LOG.logger.info('ObjectGraphDatabase.computeGraphDataFromAssets'); // iterate across all Assets; for each, compute ObjectGraph; extract ObjectGraph data into a "database" const Assets: Asset[] | null = await Asset.fetchAll(); /* istanbul ignore if */ - if (!Assets) return false; + if (!Assets) + return false; for (const Asset of Assets) { if (!await this.computeGraphDataFromObject(Asset.idAsset, eSystemObjectType.eAsset, 'computeGraphDataFromAssets')) continue; @@ -190,9 +219,11 @@ export class ObjectGraphDatabase { } private async computeGraphDataFromAssetVersions(): Promise { + LOG.logger.info('ObjectGraphDatabase.computeGraphDataFromAssetVersions'); // iterate across all AssetVersions; for each, compute ObjectGraph; extract ObjectGraph data into a "database" const AssetVersions: AssetVersion[] | null = await AssetVersion.fetchAll(); /* istanbul ignore if */ - if (!AssetVersions) return false; + if (!AssetVersions) + return false; for (const AssetVersion of AssetVersions) { if (!await this.computeGraphDataFromObject(AssetVersion.idAssetVersion, eSystemObjectType.eAssetVersion, 'computeGraphDataFromAssetVersions')) continue; @@ -201,9 +232,11 @@ export class ObjectGraphDatabase { } private async computeGraphDataFromActors(): Promise { + LOG.logger.info('ObjectGraphDatabase.computeGraphDataFromActors'); // iterate across all Actors; for each, compute ObjectGraph; extract ObjectGraph data into a "database" const Actors: Actor[] | null = await Actor.fetchAll(); /* istanbul ignore if */ - if (!Actors) return false; + if (!Actors) + return false; for (const Actor of Actors) { if (!await this.computeGraphDataFromObject(Actor.idActor, eSystemObjectType.eActor, 'computeGraphDataFromActors')) continue; @@ -212,9 +245,11 @@ export class ObjectGraphDatabase { } private async computeGraphDataFromStakeholders(): Promise { + LOG.logger.info('ObjectGraphDatabase.computeGraphDataFromStakeholders'); // iterate across all Stakeholders; for each, compute ObjectGraph; extract ObjectGraph data into a "database" const Stakeholders: Stakeholder[] | null = await Stakeholder.fetchAll(); /* istanbul ignore if */ - if (!Stakeholders) return false; + if (!Stakeholders) + return false; for (const Stakeholder of Stakeholders) { if (!await this.computeGraphDataFromObject(Stakeholder.idStakeholder, eSystemObjectType.eStakeholder, 'computeGraphDataFromStakeholders')) continue; @@ -225,6 +260,7 @@ export class ObjectGraphDatabase { /* #region Apply Graph Data */ private async applyGraphData(): Promise { + LOG.logger.info('ObjectGraphDatabase.applyGraphData'); // walk across all entries // for each entry, extract state: compute unit, project, subject, item, capture method, variant type, model purpose, and model file type // walk all children @@ -262,7 +298,7 @@ export class ObjectGraphDatabase { if (depth >= 32) return false; - const relationMap: Map | undefined = + const relationMap: Map | undefined = eDirection == eApplyGraphStateDirection.eChild ? objectGraphDataEntry.childMap : objectGraphDataEntry.parentMap; if (!relationMap) return true; @@ -297,9 +333,11 @@ export class ObjectGraphDatabase { const captureDataFiles: CaptureDataFile[] | null = await CaptureDataFile.fetchFromCaptureData(captureData.idCaptureData); if (captureDataFiles) { objectGraphState.variantTypes = new Map(); - for (const captureDataFile of captureDataFiles) objectGraphState.variantTypes.set(captureDataFile.idVVariantType, true); - } else LOG.logger.error(`ObjectGraphDatabase.applyGraphData() Unable to load CaptureDataFiles from ${systemObjectIDType}`); - } else LOG.logger.error(`ObjectGraphDatabase.applyGraphData() Unable to load CaptureData from ${systemObjectIDType}`); + for (const captureDataFile of captureDataFiles) + objectGraphState.variantTypes.set(captureDataFile.idVVariantType, true); + } + } else + LOG.logger.error(`ObjectGraphDatabase.applyGraphData() Unable to load CaptureData from ${systemObjectIDType}`); } break; case eSystemObjectType.eModel: { @@ -309,9 +347,11 @@ export class ObjectGraphDatabase { const modelGeometryFiles: ModelGeometryFile[] | null = await ModelGeometryFile.fetchFromModel(model.idModel); if (modelGeometryFiles && modelGeometryFiles.length > 0) { objectGraphState.modelFileTypes = new Map(); - for (const modelGeometryFile of modelGeometryFiles) objectGraphState.modelFileTypes.set(modelGeometryFile.idVModelFileType, true); - } else LOG.logger.error(`ObjectGraphDatabase.applyGraphData() Unable to load ModelGeometryFiles from ${systemObjectIDType}`); - } else LOG.logger.error(`ObjectGraphDatabase.applyGraphData() Unable to load Model from ${systemObjectIDType}`); + for (const modelGeometryFile of modelGeometryFiles) + objectGraphState.modelFileTypes.set(modelGeometryFile.idVModelFileType, true); + } + } else + LOG.logger.error(`ObjectGraphDatabase.applyGraphData() Unable to load Model from ${systemObjectIDType}`); } break; } diff --git a/server/index.ts b/server/index.ts index f371fdaa8..d573a4423 100644 --- a/server/index.ts +++ b/server/index.ts @@ -53,4 +53,10 @@ app.get('/solrindex', async (_: Request, response: Response) => { response.send(`Solr Reindexing Completed: ${success ? 'Success' : 'Failure'}`); }); +app.get('/solrindexprofiled', async (_: Request, response: Response) => { + const reindexer: ReindexSolr = new ReindexSolr(); + const success: boolean = await reindexer.fullIndexProfiled(); + response.send(`Solr Reindexing Completed: ${success ? 'Success' : 'Failure'}`); +}); + export { app }; diff --git a/server/navigation/impl/NavigationSolr/ReindexSolr.ts b/server/navigation/impl/NavigationSolr/ReindexSolr.ts index 02a471f47..0ea4f222e 100644 --- a/server/navigation/impl/NavigationSolr/ReindexSolr.ts +++ b/server/navigation/impl/NavigationSolr/ReindexSolr.ts @@ -8,8 +8,56 @@ import { SolrClient } from './SolrClient'; export class ReindexSolr { private objectGraphDatabase: DBAPI.ObjectGraphDatabase = new DBAPI.ObjectGraphDatabase(); private hierarchyNameMap: Map = new Map(); // map of idSystemObject -> object name + private static fullIndexUnderway: boolean = false; + + async fullIndexProfiled(): Promise { + LOG.logger.info('****************************************'); + LOG.logger.info('ReindexSolr.fullIndexProfiled() starting'); + return new Promise((resolve) => { + const inspector = require('inspector'); + const fs = require('fs'); + const session = new inspector.Session(); + session.connect(); + + session.post('Profiler.enable', async () => { + session.post('Profiler.start', async () => { + LOG.logger.info('ReindexSolr.fullIndexProfiled() fullIndex() starting'); + const retValue: boolean = await this.fullIndex(); + LOG.logger.info('ReindexSolr.fullIndexProfiled() fullIndex() complete'); + resolve(retValue); + + // some time later... + session.post('Profiler.stop', (err, { profile }) => { + // Write profile to disk, upload, etc. + if (!err) { + LOG.logger.info('ReindexSolr.fullIndexProfiled() writing profile'); + fs.writeFileSync('./profile.cpuprofile', JSON.stringify(profile)); + } + LOG.logger.info('ReindexSolr.fullIndexProfiled() writing profile ending'); + }); + }); + }); + }); + } async fullIndex(): Promise { + if (ReindexSolr.fullIndexUnderway) { + LOG.logger.error('ReindexSolr.fullIndex() already underway; exiting this additional request early'); + return false; + } + + let retValue: boolean = false; + try { + ReindexSolr.fullIndexUnderway = true; + retValue = await this.fullIndexWorker(); + } finally { + ReindexSolr.fullIndexUnderway = false; + } + return retValue; + } + + private async fullIndexWorker(): Promise { + const solrClient: SolrClient = new SolrClient(null, null, null); solrClient._client.autoCommit = true; @@ -20,27 +68,27 @@ export class ReindexSolr { let docs: any[] = []; for (const [systemObjectIDType, objectGraphDataEntry] of this.objectGraphDatabase.objectMap) { - let doc: any = null; + const doc: any = {}; await this.extractCommonFields(doc, objectGraphDataEntry); switch (systemObjectIDType.eObjectType) { - case eSystemObjectType.eUnit: doc = await this.handleUnit(doc, objectGraphDataEntry); break; - case eSystemObjectType.eProject: doc = await this.handleProject(doc, objectGraphDataEntry); break; - case eSystemObjectType.eSubject: doc = await this.handleSubject(doc, objectGraphDataEntry); break; - case eSystemObjectType.eItem: doc = await this.handleItem(doc, objectGraphDataEntry); break; - case eSystemObjectType.eCaptureData: doc = await this.handleCaptureData(doc, objectGraphDataEntry); break; - case eSystemObjectType.eModel: doc = await this.handleModel(doc, objectGraphDataEntry); break; - case eSystemObjectType.eScene: doc = await this.handleScene(doc, objectGraphDataEntry); break; - case eSystemObjectType.eIntermediaryFile: doc = await this.handleIntermediaryFile(doc, objectGraphDataEntry); break; - case eSystemObjectType.eProjectDocumentation: doc = await this.handleProjectDocumentation(doc, objectGraphDataEntry); break; - case eSystemObjectType.eAsset: doc = await this.handleAsset(doc, objectGraphDataEntry); break; - case eSystemObjectType.eAssetVersion: doc = await this.handleAssetVersion(doc, objectGraphDataEntry); break; - case eSystemObjectType.eActor: doc = await this.handleActor(doc, objectGraphDataEntry); break; - case eSystemObjectType.eStakeholder: doc = await this.handleStakeholder(doc, objectGraphDataEntry); break; + case eSystemObjectType.eUnit: await this.handleUnit(doc, objectGraphDataEntry); break; + case eSystemObjectType.eProject: await this.handleProject(doc, objectGraphDataEntry); break; + case eSystemObjectType.eSubject: await this.handleSubject(doc, objectGraphDataEntry); break; + case eSystemObjectType.eItem: await this.handleItem(doc, objectGraphDataEntry); break; + case eSystemObjectType.eCaptureData: await this.handleCaptureData(doc, objectGraphDataEntry); break; + case eSystemObjectType.eModel: await this.handleModel(doc, objectGraphDataEntry); break; + case eSystemObjectType.eScene: await this.handleScene(doc, objectGraphDataEntry); break; + case eSystemObjectType.eIntermediaryFile: await this.handleIntermediaryFile(doc, objectGraphDataEntry); break; + case eSystemObjectType.eProjectDocumentation: await this.handleProjectDocumentation(doc, objectGraphDataEntry); break; + case eSystemObjectType.eAsset: await this.handleAsset(doc, objectGraphDataEntry); break; + case eSystemObjectType.eAssetVersion: await this.handleAssetVersion(doc, objectGraphDataEntry); break; + case eSystemObjectType.eActor: await this.handleActor(doc, objectGraphDataEntry); break; + case eSystemObjectType.eStakeholder: await this.handleStakeholder(doc, objectGraphDataEntry); break; default: - case eSystemObjectType.eUnknown: doc = await this.handleUnknown(doc, objectGraphDataEntry); break; + case eSystemObjectType.eUnknown: await this.handleUnknown(doc, objectGraphDataEntry); break; } docs.push(doc); @@ -253,27 +301,25 @@ export class ReindexSolr { return false; } const captureDataPhotos: DBAPI.CaptureDataPhoto[] | null = await DBAPI.CaptureDataPhoto.fetchFromCaptureData(captureData.idCaptureData); - if (!captureDataPhotos || captureDataPhotos.length != 1) { - LOG.logger.error(`ReindexSolr.handleCaptureData failed to find exactly 1 capture data photo for ${JSON.stringify(objectGraphDataEntry.systemObjectIDType)}`); - return false; - } - const captureDataPhoto: DBAPI.CaptureDataPhoto = captureDataPhotos[0]; + const captureDataPhoto: DBAPI.CaptureDataPhoto | null = (captureDataPhotos && captureDataPhotos.length > 0) ? captureDataPhotos[0] : null; doc.Name = captureData.Name; doc.Description = captureData.Description; doc.DateCreated = captureData.DateCaptured; doc.CaptureMethod = await this.lookupVocabulary(captureData.idVCaptureMethod); - doc.CaptureDatasetType = await this.lookupVocabulary(captureDataPhoto.idVCaptureDatasetType); - doc.CaptureDatasetFieldID = captureDataPhoto.CaptureDatasetFieldID; - doc.ItemPositionType = await this.lookupVocabulary(captureDataPhoto.idVItemPositionType); - doc.ItemPositionFieldID = captureDataPhoto.ItemPositionFieldID; - doc.ItemArrangementFieldID = captureDataPhoto.ItemArrangementFieldID; - doc.FocusType = await this.lookupVocabulary(captureDataPhoto.idVFocusType); - doc.LightSourceType = await this.lookupVocabulary(captureDataPhoto.idVLightSourceType); - doc.BackgroundRemovalMethod = await this.lookupVocabulary(captureDataPhoto.idVBackgroundRemovalMethod); - doc.ClusterType = await this.lookupVocabulary(captureDataPhoto.idVClusterType); - doc.ClusterGeometryFieldID = captureDataPhoto.ClusterGeometryFieldID; - doc.CameraSettingsUniform = captureDataPhoto.CameraSettingsUniform; + if (captureDataPhoto) { + doc.CaptureDatasetType = await this.lookupVocabulary(captureDataPhoto.idVCaptureDatasetType); + doc.CaptureDatasetFieldID = captureDataPhoto.CaptureDatasetFieldID; + doc.ItemPositionType = await this.lookupVocabulary(captureDataPhoto.idVItemPositionType); + doc.ItemPositionFieldID = captureDataPhoto.ItemPositionFieldID; + doc.ItemArrangementFieldID = captureDataPhoto.ItemArrangementFieldID; + doc.FocusType = await this.lookupVocabulary(captureDataPhoto.idVFocusType); + doc.LightSourceType = await this.lookupVocabulary(captureDataPhoto.idVLightSourceType); + doc.BackgroundRemovalMethod = await this.lookupVocabulary(captureDataPhoto.idVBackgroundRemovalMethod); + doc.ClusterType = await this.lookupVocabulary(captureDataPhoto.idVClusterType); + doc.ClusterGeometryFieldID = captureDataPhoto.ClusterGeometryFieldID; + doc.CameraSettingsUniform = captureDataPhoto.CameraSettingsUniform; + } return true; } @@ -314,33 +360,39 @@ export class ReindexSolr { const channelWidthMap: Map = new Map(); const uvMapTypeMap: Map = new Map(); - for (const modelGeometryFile of modelConstellation.modelGeometryFiles) { - const modelFileTypeWorker: string | undefined = await this.computeVocabulary(modelGeometryFile.idVModelFileType); - if (modelFileTypeWorker) modelFileTypeMap.set(modelFileTypeWorker, true); - if (modelGeometryFile.Roughness) roughnessMap.set(modelGeometryFile.Roughness, true); - if (modelGeometryFile.Metalness) metalnessMap.set(modelGeometryFile.Metalness, true); - if (modelGeometryFile.PointCount) pointCountMap.set(modelGeometryFile.PointCount, true); - if (modelGeometryFile.IsWatertight != null) isWatertightMap.set(modelGeometryFile.IsWatertight, true); - if (modelGeometryFile.HasNormals != null) hasNormalsMap.set(modelGeometryFile.HasNormals, true); - if (modelGeometryFile.HasVertexColor != null) hasVertexColorMap.set(modelGeometryFile.HasVertexColor, true); - if (modelGeometryFile.HasUVSpace != null) hasUVSpaceMap.set(modelGeometryFile.HasUVSpace, true); - if (modelGeometryFile.BoundingBoxP1X) boundingBoxP1XMap.set(modelGeometryFile.BoundingBoxP1X, true); - if (modelGeometryFile.BoundingBoxP1Y) boundingBoxP1YMap.set(modelGeometryFile.BoundingBoxP1Y, true); - if (modelGeometryFile.BoundingBoxP1Z) boundingBoxP1ZMap.set(modelGeometryFile.BoundingBoxP1Z, true); - if (modelGeometryFile.BoundingBoxP2X) boundingBoxP2XMap.set(modelGeometryFile.BoundingBoxP2X, true); - if (modelGeometryFile.BoundingBoxP2Y) boundingBoxP2YMap.set(modelGeometryFile.BoundingBoxP2Y, true); - if (modelGeometryFile.BoundingBoxP2Z) boundingBoxP2ZMap.set(modelGeometryFile.BoundingBoxP2Z, true); - } - - for (const modelUVMapFile of modelConstellation.modelUVMapFiles) { - uvMapEdgeLengthMap.set(modelUVMapFile.UVMapEdgeLength, true); - } - - for (const modelUVMapChannel of modelConstellation.modelUVMapChannels) { - channelPositionMap.set(modelUVMapChannel.ChannelPosition, true); - channelWidthMap.set(modelUVMapChannel.ChannelWidth, true); - const uvMapTypeWorker: string | undefined = await this.computeVocabulary(modelUVMapChannel.idVUVMapType); - if (uvMapTypeWorker) uvMapTypeMap.set(uvMapTypeWorker, true); + if (modelConstellation.modelGeometryFiles) { + for (const modelGeometryFile of modelConstellation.modelGeometryFiles) { + const modelFileTypeWorker: string | undefined = await this.computeVocabulary(modelGeometryFile.idVModelFileType); + if (modelFileTypeWorker) modelFileTypeMap.set(modelFileTypeWorker, true); + if (modelGeometryFile.Roughness) roughnessMap.set(modelGeometryFile.Roughness, true); + if (modelGeometryFile.Metalness) metalnessMap.set(modelGeometryFile.Metalness, true); + if (modelGeometryFile.PointCount) pointCountMap.set(modelGeometryFile.PointCount, true); + if (modelGeometryFile.IsWatertight != null) isWatertightMap.set(modelGeometryFile.IsWatertight, true); + if (modelGeometryFile.HasNormals != null) hasNormalsMap.set(modelGeometryFile.HasNormals, true); + if (modelGeometryFile.HasVertexColor != null) hasVertexColorMap.set(modelGeometryFile.HasVertexColor, true); + if (modelGeometryFile.HasUVSpace != null) hasUVSpaceMap.set(modelGeometryFile.HasUVSpace, true); + if (modelGeometryFile.BoundingBoxP1X) boundingBoxP1XMap.set(modelGeometryFile.BoundingBoxP1X, true); + if (modelGeometryFile.BoundingBoxP1Y) boundingBoxP1YMap.set(modelGeometryFile.BoundingBoxP1Y, true); + if (modelGeometryFile.BoundingBoxP1Z) boundingBoxP1ZMap.set(modelGeometryFile.BoundingBoxP1Z, true); + if (modelGeometryFile.BoundingBoxP2X) boundingBoxP2XMap.set(modelGeometryFile.BoundingBoxP2X, true); + if (modelGeometryFile.BoundingBoxP2Y) boundingBoxP2YMap.set(modelGeometryFile.BoundingBoxP2Y, true); + if (modelGeometryFile.BoundingBoxP2Z) boundingBoxP2ZMap.set(modelGeometryFile.BoundingBoxP2Z, true); + } + } + + if (modelConstellation.modelUVMapFiles) { + for (const modelUVMapFile of modelConstellation.modelUVMapFiles) { + uvMapEdgeLengthMap.set(modelUVMapFile.UVMapEdgeLength, true); + } + } + + if (modelConstellation.modelUVMapChannels) { + for (const modelUVMapChannel of modelConstellation.modelUVMapChannels) { + channelPositionMap.set(modelUVMapChannel.ChannelPosition, true); + channelWidthMap.set(modelUVMapChannel.ChannelWidth, true); + const uvMapTypeWorker: string | undefined = await this.computeVocabulary(modelUVMapChannel.idVUVMapType); + if (uvMapTypeWorker) uvMapTypeMap.set(uvMapTypeWorker, true); + } } const modelFileType: string[] = [...modelFileTypeMap.keys()]; diff --git a/server/tests/cache/SystemObjectCache.test.ts b/server/tests/cache/SystemObjectCache.test.ts index 944cd6c15..0a622ef99 100644 --- a/server/tests/cache/SystemObjectCache.test.ts +++ b/server/tests/cache/SystemObjectCache.test.ts @@ -1,5 +1,6 @@ import * as DBAPI from '../../db'; -import { SystemObject, eSystemObjectType } from '../../db'; +import { Unit, Project, Subject, Item, CaptureData, Model, Scene, IntermediaryFile, ProjectDocumentation, + Asset, AssetVersion, Actor, Stakeholder, SystemObject, eSystemObjectType } from '../../db'; import { SystemObjectCache, ObjectIDAndType, SystemObjectInfo } from '../../cache'; import * as LOG from '../../utils/logger'; import * as H from '../../utils/helpers'; @@ -169,21 +170,74 @@ async function testSystemObject(SOExamine: DBAPI.SystemObject): Promise async function testObjectAndID(oID: ObjectIDAndType): Promise { // LOG.logger.info(`Testing ${JSON.stringify(oID)}`); let SO: SystemObject | null = null; + let SOI: SystemObjectInfo | undefined = undefined; const { idObject, eObjectType } = oID; switch (eObjectType) { - case eSystemObjectType.eUnit: SO = await SystemObject.fetchFromUnitID(idObject); break; - case eSystemObjectType.eProject: SO = await SystemObject.fetchFromProjectID(idObject); break; - case eSystemObjectType.eSubject: SO = await SystemObject.fetchFromSubjectID(idObject); break; - case eSystemObjectType.eItem: SO = await SystemObject.fetchFromItemID(idObject); break; - case eSystemObjectType.eCaptureData: SO = await SystemObject.fetchFromCaptureDataID(idObject); break; - case eSystemObjectType.eModel: SO = await SystemObject.fetchFromModelID(idObject); break; - case eSystemObjectType.eScene: SO = await SystemObject.fetchFromSceneID(idObject); break; - case eSystemObjectType.eIntermediaryFile: SO = await SystemObject.fetchFromIntermediaryFileID(idObject); break; - case eSystemObjectType.eProjectDocumentation: SO = await SystemObject.fetchFromProjectDocumentationID(idObject); break; - case eSystemObjectType.eAsset: SO = await SystemObject.fetchFromAssetID(idObject); break; - case eSystemObjectType.eAssetVersion: SO = await SystemObject.fetchFromAssetVersionID(idObject); break; - case eSystemObjectType.eActor: SO = await SystemObject.fetchFromActorID(idObject); break; - case eSystemObjectType.eStakeholder: SO = await SystemObject.fetchFromStakeholderID(idObject); break; + case eSystemObjectType.eUnit: { + SO = await SystemObject.fetchFromUnitID(idObject); + const unit: Unit | null = await Unit.fetch(oID.idObject); + SOI = (unit) ? await SystemObjectCache.getSystemFromUnit(unit) : undefined; + } break; + case eSystemObjectType.eProject: { + SO = await SystemObject.fetchFromProjectID(idObject); + const project: Project | null = await Project.fetch(oID.idObject); + SOI = (project) ? await SystemObjectCache.getSystemFromProject(project) : undefined; + } break; + case eSystemObjectType.eSubject: { + SO = await SystemObject.fetchFromSubjectID(idObject); + const subject: Subject | null = await Subject.fetch(oID.idObject); + SOI = (subject) ? await SystemObjectCache.getSystemFromSubject(subject) : undefined; + } break; + case eSystemObjectType.eItem: { + SO = await SystemObject.fetchFromItemID(idObject); + const item: Item | null = await Item.fetch(oID.idObject); + SOI = (item) ? await SystemObjectCache.getSystemFromItem(item) : undefined; + } break; + case eSystemObjectType.eCaptureData: { + SO = await SystemObject.fetchFromCaptureDataID(idObject); + const captureData: CaptureData | null = await CaptureData.fetch(oID.idObject); + SOI = (captureData) ? await SystemObjectCache.getSystemFromCaptureData(captureData) : undefined; + } break; + case eSystemObjectType.eModel: { + SO = await SystemObject.fetchFromModelID(idObject); + const model: Model | null = await Model.fetch(oID.idObject); + SOI = (model) ? await SystemObjectCache.getSystemFromModel(model) : undefined; + } break; + case eSystemObjectType.eScene: { + SO = await SystemObject.fetchFromSceneID(idObject); + const scene: Scene | null = await Scene.fetch(oID.idObject); + SOI = (scene) ? await SystemObjectCache.getSystemFromScene(scene) : undefined; + } break; + case eSystemObjectType.eIntermediaryFile: { + SO = await SystemObject.fetchFromIntermediaryFileID(idObject); + const intermediaryFile: IntermediaryFile | null = await IntermediaryFile.fetch(oID.idObject); + SOI = (intermediaryFile) ? await SystemObjectCache.getSystemFromIntermediaryFile(intermediaryFile) : undefined; + } break; + case eSystemObjectType.eProjectDocumentation: { + SO = await SystemObject.fetchFromProjectDocumentationID(idObject); + const projectDocumentation: ProjectDocumentation | null = await ProjectDocumentation.fetch(oID.idObject); + SOI = (projectDocumentation) ? await SystemObjectCache.getSystemFromProjectDocumentation(projectDocumentation) : undefined; + } break; + case eSystemObjectType.eAsset: { + SO = await SystemObject.fetchFromAssetID(idObject); + const asset: Asset | null = await Asset.fetch(oID.idObject); + SOI = (asset) ? await SystemObjectCache.getSystemFromAsset(asset) : undefined; + } break; + case eSystemObjectType.eAssetVersion: { + SO = await SystemObject.fetchFromAssetVersionID(idObject); + const assetVersion: AssetVersion | null = await AssetVersion.fetch(oID.idObject); + SOI = (assetVersion) ? await SystemObjectCache.getSystemFromAssetVersion(assetVersion) : undefined; + } break; + case eSystemObjectType.eActor: { + SO = await SystemObject.fetchFromActorID(idObject); + const actor: Actor | null = await Actor.fetch(oID.idObject); + SOI = (actor) ? await SystemObjectCache.getSystemFromActor(actor) : undefined; + } break; + case eSystemObjectType.eStakeholder: { + SO = await SystemObject.fetchFromStakeholderID(idObject); + const stakeholder: Stakeholder | null = await Stakeholder.fetch(oID.idObject); + SOI = (stakeholder) ? await SystemObjectCache.getSystemFromStakeholder(stakeholder) : undefined; + } break; case eSystemObjectType.eUnknown: LOG.logger.error('Invalid Test Case!'); expect(eObjectType).not.toEqual(eSystemObjectType.eUnknown); @@ -194,10 +248,18 @@ async function testObjectAndID(oID: ObjectIDAndType): Promise { if (!SO) return false; + expect(SOI).toBeTruthy(); + if (!SOI) + return false; + + expect(SOI.idSystemObject).toEqual(SO.idSystemObject); + expect(SOI.Retired).toEqual(SO.Retired); + const SOInfo: SystemObjectInfo | undefined = await SystemObjectCache.getSystemFromObjectID(oID); expect(SOInfo).toBeTruthy(); if (!SOInfo) return false; + expect(SOInfo).toEqual(SOI); expect(SOInfo.idSystemObject).toEqual(SO.idSystemObject); expect(SOInfo.Retired).toEqual(SO.Retired); diff --git a/server/tests/db/dbcreation.test.ts b/server/tests/db/dbcreation.test.ts index 44bd1f845..2506eccf1 100644 --- a/server/tests/db/dbcreation.test.ts +++ b/server/tests/db/dbcreation.test.ts @@ -2094,6 +2094,17 @@ describe('DB Fetch By ID Test Suite', () => { expect(modelUVMapChannelFetch).toBeTruthy(); }); + test('DB Fetch ModelUVMapChannel: ModelUVMapChannel.fetchFromModelUVMapFiles', async () => { + let modelUVMapChannelFetch: DBAPI.ModelUVMapChannel[] | null = null; + if (modelUVMapFile) { + modelUVMapChannelFetch = await DBAPI.ModelUVMapChannel.fetchFromModelUVMapFiles([modelUVMapFile]); + if (modelUVMapChannelFetch) { + expect(modelUVMapChannelFetch).toEqual(expect.arrayContaining([modelUVMapChannel])); + } + } + expect(modelUVMapChannelFetch).toBeTruthy(); + }); + test('DB Fetch By ID: ModelUVMapFile', async () => { let modelUVMapFileFetch: DBAPI.ModelUVMapFile | null = null; if (modelUVMapFile) { @@ -2117,6 +2128,17 @@ describe('DB Fetch By ID Test Suite', () => { expect(modelUVMapFileFetch).toBeTruthy(); }); + test('DB Fetch ModelUVMapFile: ModelUVMapFile.fetchFromModelGeometryFiles', async () => { + let modelUVMapFileFetch: DBAPI.ModelUVMapFile[] | null = null; + if (modelGeometryFile) { + modelUVMapFileFetch = await DBAPI.ModelUVMapFile.fetchFromModelGeometryFiles([modelGeometryFile]); + if (modelUVMapFileFetch) { + expect(modelUVMapFileFetch).toEqual(expect.arrayContaining([modelUVMapFile])); + } + } + expect(modelUVMapFileFetch).toBeTruthy(); + }); + test('DB Fetch By ID: Project', async () => { let projectFetch: DBAPI.Project | null = null; if (project) { @@ -3295,6 +3317,24 @@ describe('DB Fetch SystemObject Fetch Pair Test Suite', () => { } expect(SYOP).toBeTruthy(); }); + + test('DB Fetch SystemObject: SystemObjectTypeToName', async () => { + expect(DBAPI.SystemObjectTypeToName(DBAPI.eSystemObjectType.eUnit)).toEqual('Unit'); + expect(DBAPI.SystemObjectTypeToName(DBAPI.eSystemObjectType.eProject)).toEqual('Project'); + expect(DBAPI.SystemObjectTypeToName(DBAPI.eSystemObjectType.eSubject)).toEqual('Subject'); + expect(DBAPI.SystemObjectTypeToName(DBAPI.eSystemObjectType.eItem)).toEqual('Item'); + expect(DBAPI.SystemObjectTypeToName(DBAPI.eSystemObjectType.eCaptureData)).toEqual('Capture Data'); + expect(DBAPI.SystemObjectTypeToName(DBAPI.eSystemObjectType.eModel)).toEqual('Model'); + expect(DBAPI.SystemObjectTypeToName(DBAPI.eSystemObjectType.eScene)).toEqual('Scene'); + expect(DBAPI.SystemObjectTypeToName(DBAPI.eSystemObjectType.eIntermediaryFile)).toEqual('Intermediary File'); + expect(DBAPI.SystemObjectTypeToName(DBAPI.eSystemObjectType.eProjectDocumentation)).toEqual('Project Documentation'); + expect(DBAPI.SystemObjectTypeToName(DBAPI.eSystemObjectType.eAsset)).toEqual('Asset'); + expect(DBAPI.SystemObjectTypeToName(DBAPI.eSystemObjectType.eAssetVersion)).toEqual('Asset Version'); + expect(DBAPI.SystemObjectTypeToName(DBAPI.eSystemObjectType.eActor)).toEqual('Actor'); + expect(DBAPI.SystemObjectTypeToName(DBAPI.eSystemObjectType.eStakeholder)).toEqual('Stakeholder'); + expect(DBAPI.SystemObjectTypeToName(DBAPI.eSystemObjectType.eUnknown)).toEqual('Unknown'); + expect(DBAPI.SystemObjectTypeToName(null)).toEqual('Unknown'); + }); }); describe('DB Fetch Xref Test Suite', () => { @@ -3604,6 +3644,16 @@ describe('DB Fetch Special Test Suite', () => { expect(captureDataPhotoFetch).toBeTruthy(); }); + test('DB Fetch Special: CaptureDataPhoto.fetchFromCaptureData', async () => { + let captureDataPhotoFetch: DBAPI.CaptureDataPhoto[] | null = null; + if (captureData && captureDataPhoto && captureDataPhotoNulls) { + captureDataPhotoFetch = await DBAPI.CaptureDataPhoto.fetchFromCaptureData(captureData.idCaptureData); + if (captureDataPhotoFetch) + expect(captureDataPhotoFetch).toEqual(expect.arrayContaining([captureDataPhoto, captureDataPhotoNulls])); + } + expect(captureDataPhotoFetch).toBeTruthy(); + }); + test('DB Fetch Special: Identifier.fetchFromIdentifierValue', async () => { const identifierFetch: DBAPI.Identifier[] | null = await DBAPI.Identifier.fetchFromIdentifierValue(identifierValue); expect(identifierFetch).toBeTruthy(); @@ -3748,6 +3798,21 @@ describe('DB Fetch Special Test Suite', () => { expect(modelFetch).toBeTruthy(); }); + test('DB Fetch Special: ModelConstellation', async () => { + let modelConstellation: DBAPI.ModelConstellation | null = null; + + if (model && modelNulls) { + modelConstellation = await DBAPI.ModelConstellation.fetch(model.idModel); + if (modelConstellation) { + expect(modelConstellation.model).toEqual(model); + expect(modelConstellation.modelGeometryFiles).toEqual(expect.arrayContaining([modelGeometryFile, modelGeometryFileNulls])); + expect(modelConstellation.modelUVMapFiles).toEqual(expect.arrayContaining([modelUVMapFile])); + expect(modelConstellation.modelUVMapChannels).toEqual(expect.arrayContaining([modelUVMapChannel])); + } + } + expect(modelConstellation).toBeTruthy(); + }); + test('DB Fetch Special: Project.fetchMasterFromSubjects', async () => { let projectFetch: DBAPI.Project[] | null = null; if (subject && subjectNulls) { @@ -5467,6 +5532,7 @@ describe('DB Null/Zero ID Test', () => { expect(await DBAPI.CaptureDataGroup.fetchFromXref(0)).toBeNull(); expect(await DBAPI.CaptureDataGroupCaptureDataXref.fetch(0)).toBeNull(); expect(await DBAPI.CaptureDataPhoto.fetch(0)).toBeNull(); + expect(await DBAPI.CaptureDataPhoto.fetchFromCaptureData(0)).toBeNull(); expect(await DBC.CopyArray(null, DBAPI.SystemObject)).toBeNull(); expect(await DBC.CopyObject(null, DBAPI.SystemObject)).toBeNull(); expect(await DBAPI.GeoLocation.fetch(0)).toBeNull(); @@ -5498,6 +5564,7 @@ describe('DB Null/Zero ID Test', () => { expect(await DBAPI.Model.fetch(0)).toBeNull(); expect(await DBAPI.Model.fetchFromXref(0)).toBeNull(); expect(await DBAPI.Model.fetchDerivedFromItems([])).toBeNull(); + expect(await DBAPI.ModelConstellation.fetch(0)).toBeNull(); expect(await DBAPI.ModelGeometryFile.fetch(0)).toBeNull(); expect(await DBAPI.ModelGeometryFile.fetchFromModel(0)).toBeNull(); expect(await DBAPI.ModelProcessingAction.fetch(0)).toBeNull(); @@ -5509,8 +5576,10 @@ describe('DB Null/Zero ID Test', () => { expect(await DBAPI.ModelSceneXref.fetchFromModel(0)).toBeNull(); expect(await DBAPI.ModelUVMapChannel.fetch(0)).toBeNull(); expect(await DBAPI.ModelUVMapChannel.fetchFromModelUVMapFile(0)).toBeNull(); + expect(await DBAPI.ModelUVMapChannel.fetchFromModelUVMapFiles([])).toBeNull(); expect(await DBAPI.ModelUVMapFile.fetch(0)).toBeNull(); expect(await DBAPI.ModelUVMapFile.fetchFromModelGeometryFile(0)).toBeNull(); + expect(await DBAPI.ModelUVMapFile.fetchFromModelGeometryFiles([])).toBeNull(); expect(await DBAPI.Project.fetch(0)).toBeNull(); expect(await DBAPI.Project.fetchDerivedFromUnits([])).toBeNull(); expect(await DBAPI.Project.fetchMasterFromSubjects([])).toBeNull(); From f5e4a6e45bc8b2325b16347e49430d8841e150a8 Mon Sep 17 00:00:00 2001 From: Jon Tyson <6943745+jahjedtieson@users.noreply.github.com> Date: Fri, 12 Feb 2021 02:24:29 -0800 Subject: [PATCH 217/239] Solr: * Fixed broken usage of Maps with objects as keys. Typescript maps use the "Same Value Equal" comparison (similar to operator ===), which compares the references of objects and not the object values. It's fine to use value types as keys (numbers, strings, enums), but not objects * Fixed application of child attributes to parent nodes, by making sure that we start applying if the current node has values for object type, capture method, variant type, model purpose, and/or model file type. * Always use array values for Solr schema document fields that are multivalued, even if the current document has just a single value * Represent parent-less and child-less nodes with the special array value of [0] in the ParentID and ChildrenID document fields --- .../db/api/composite/ObjectGraphDataEntry.ts | 98 ++++++++++--------- .../db/api/composite/ObjectGraphDatabase.ts | 37 ++++--- .../impl/NavigationSolr/ReindexSolr.ts | 40 ++++---- .../storage/interface/AssetStorageAdapter.ts | 4 +- 4 files changed, 92 insertions(+), 87 deletions(-) diff --git a/server/db/api/composite/ObjectGraphDataEntry.ts b/server/db/api/composite/ObjectGraphDataEntry.ts index 9091234cf..02b76736d 100644 --- a/server/db/api/composite/ObjectGraphDataEntry.ts +++ b/server/db/api/composite/ObjectGraphDataEntry.ts @@ -45,9 +45,9 @@ export class ObjectGraphDataEntry { retired: boolean = false; // Derived data - childMap: Map = new Map(); // map of child objects -> child idSystemObject - parentMap: Map = new Map(); // map of parent objects -> parent idSystemObject - ancestorObjectMap: Map = new Map(); // map of ancestor objects of significance (unit, project, subject, item) + childMap: Map = new Map(); // map of child idSystemObject -> child objects + parentMap: Map = new Map(); // map of parent idSystemObject -> parent objects + ancestorObjectMap: Map = new Map(); // map of ancestor objects of significance (unit, project, subject, item), idSystemObject -> object info // Child data types childrenObjectTypes: Map = new Map(); @@ -62,67 +62,69 @@ export class ObjectGraphDataEntry { } recordChild(child: SystemObjectIDType): void { - this.childMap.set(child, child.idSystemObject); + this.childMap.set(child.idSystemObject, child); + // LOG.logger.info(`${JSON.stringify(this.systemObjectIDType)} children: ${this.childMap.size}`); } recordParent(parent: SystemObjectIDType): void { - this.parentMap.set(parent, parent.idSystemObject); + this.parentMap.set(parent.idSystemObject, parent); + // LOG.logger.info(`${JSON.stringify(this.systemObjectIDType)} parents: ${this.parentMap.size}`); } // Returns true if applying objectGraphState updated the state of this ObjectGraphDataEntry applyGraphState(objectGraphState: ObjectGraphState, eDirection: eApplyGraphStateDirection): boolean { let retValue: boolean = false; - switch (eDirection) { - case eApplyGraphStateDirection.eSelf: - case eApplyGraphStateDirection.eChild: - if (objectGraphState.ancestorObject) { - if (!this.ancestorObjectMap.has(objectGraphState.ancestorObject)) { - this.ancestorObjectMap.set(objectGraphState.ancestorObject, true); - retValue = true; - } - } - break; - case eApplyGraphStateDirection.eParent: - if (objectGraphState.eType) { - if (!this.childrenObjectTypes.has(objectGraphState.eType)) { - this.childrenObjectTypes.set(objectGraphState.eType, true); - retValue = true; - } + if (eDirection == eApplyGraphStateDirection.eSelf || + eDirection == eApplyGraphStateDirection.eChild) { + if (objectGraphState.ancestorObject) { + if (!this.ancestorObjectMap.has(objectGraphState.ancestorObject.idSystemObject)) { + this.ancestorObjectMap.set(objectGraphState.ancestorObject.idSystemObject, objectGraphState.ancestorObject); + retValue = true; } + } + } - if (objectGraphState.captureMethod) { - if (!this.childrenCaptureMethods.has(objectGraphState.captureMethod)) { - this.childrenCaptureMethods.set(objectGraphState.captureMethod, true); - retValue = true; - } + if (eDirection == eApplyGraphStateDirection.eSelf || + eDirection == eApplyGraphStateDirection.eParent) { + if (objectGraphState.eType) { + if (!this.childrenObjectTypes.has(objectGraphState.eType)) { + this.childrenObjectTypes.set(objectGraphState.eType, true); + retValue = true; } + } - if (objectGraphState.variantTypes) { - for (const variantType of objectGraphState.variantTypes.keys()) { - if (!this.childrenVariantTypes.has(variantType)) { - this.childrenVariantTypes.set(variantType, true); - retValue = true; - } - } + if (objectGraphState.captureMethod) { + if (!this.childrenCaptureMethods.has(objectGraphState.captureMethod)) { + this.childrenCaptureMethods.set(objectGraphState.captureMethod, true); + retValue = true; } + } - if (objectGraphState.modelPurpose) { - if (!this.childrenModelPurposes.has(objectGraphState.modelPurpose)) { - this.childrenModelPurposes.set(objectGraphState.modelPurpose, true); + if (objectGraphState.variantTypes) { + for (const variantType of objectGraphState.variantTypes.keys()) { + if (!this.childrenVariantTypes.has(variantType)) { + this.childrenVariantTypes.set(variantType, true); retValue = true; } } + } + + if (objectGraphState.modelPurpose) { + if (!this.childrenModelPurposes.has(objectGraphState.modelPurpose)) { + this.childrenModelPurposes.set(objectGraphState.modelPurpose, true); + retValue = true; + } + } - if (objectGraphState.modelFileTypes) { - for (const modelFileType of objectGraphState.modelFileTypes.keys()) { - if (!this.childrenModelFileTypes.has(modelFileType)) { - this.childrenModelFileTypes.set(modelFileType, true); - retValue = true; - } + if (objectGraphState.modelFileTypes) { + for (const modelFileType of objectGraphState.modelFileTypes.keys()) { + if (!this.childrenModelFileTypes.has(modelFileType)) { + this.childrenModelFileTypes.set(modelFileType, true); + retValue = true; } } - break; + } } return retValue; } @@ -135,9 +137,13 @@ export class ObjectGraphDataEntry { objectGraphDataEntryHierarchy.eObjectType = this.systemObjectIDType.eObjectType; objectGraphDataEntryHierarchy.idObject = this.systemObjectIDType.idObject; - objectGraphDataEntryHierarchy.parents = [...this.parentMap.values()]; - objectGraphDataEntryHierarchy.children = [...this.childMap.values()]; - for (const systemObjectIDType of this.ancestorObjectMap.keys()) { + objectGraphDataEntryHierarchy.parents = [...this.parentMap.keys()]; + objectGraphDataEntryHierarchy.children = [...this.childMap.keys()]; + + // LOG.logger.info(`${JSON.stringify(this.systemObjectIDType)} -Parents-> ${JSON.stringify(this.parentMap.keys())} (${JSON.stringify(this.parentMap.size)})`); + // LOG.logger.info(`${JSON.stringify(this.systemObjectIDType)} -Parents-> ${JSON.stringify(objectGraphDataEntryHierarchy.parents)} -Children-> ${JSON.stringify(objectGraphDataEntryHierarchy.children)}`); + + for (const systemObjectIDType of this.ancestorObjectMap.values()) { switch (systemObjectIDType.eObjectType) { case eSystemObjectType.eUnit: objectGraphDataEntryHierarchy.units.push(systemObjectIDType); break; case eSystemObjectType.eProject: objectGraphDataEntryHierarchy.projects.push(systemObjectIDType); break; diff --git a/server/db/api/composite/ObjectGraphDatabase.ts b/server/db/api/composite/ObjectGraphDatabase.ts index f6b3cf27d..fe3a61c60 100644 --- a/server/db/api/composite/ObjectGraphDatabase.ts +++ b/server/db/api/composite/ObjectGraphDatabase.ts @@ -6,14 +6,15 @@ import * as CACHE from '../../../cache'; import * as LOG from '../../../utils/logger'; export class ObjectGraphDatabase { - objectMap: Map = new Map(); // map from object to graph entry details + objectMap: Map = new Map(); // map from SystemObject.idSystemObject to graph entry details // used by ObjectGraph // returns true if either parent or child is unknown to the database // returns false if both parent and child are known to the database, in which case continued elaboration of this branch is not needed async recordRelationship(parent: SystemObjectIDType, child: SystemObjectIDType): Promise { - let parentData: ObjectGraphDataEntry | undefined = this.objectMap.get(parent); - let childData: ObjectGraphDataEntry | undefined = this.objectMap.get(child); + // LOG.logger.info(`${JSON.stringify(parent)} -> ${JSON.stringify(child)}`); + let parentData: ObjectGraphDataEntry | undefined = this.objectMap.get(parent.idSystemObject); + let childData: ObjectGraphDataEntry | undefined = this.objectMap.get(child.idSystemObject); const retValue: boolean = !(parentData && childData); if (!parentData) { @@ -22,7 +23,7 @@ export class ObjectGraphDatabase { LOG.logger.error(`ObjectGraphDatabase.recordRelationship unable to compute idSystemObject for ${JSON.stringify(parent)}`); parentData = new ObjectGraphDataEntry(parent, sID ? sID.Retired : false); - this.objectMap.set(parent, parentData); + this.objectMap.set(parent.idSystemObject, parentData); } if (!childData) { const sID: CACHE.SystemObjectInfo | undefined = await CACHE.SystemObjectCache.getSystemFromObjectID(child); /* istanbul ignore if */ @@ -30,7 +31,7 @@ export class ObjectGraphDatabase { LOG.logger.error(`ObjectGraphDatabase.recordRelationship unable to compute idSystemObject for ${JSON.stringify(child)}`); childData = new ObjectGraphDataEntry(child, sID ? sID.Retired : false); - this.objectMap.set(child, childData); + this.objectMap.set(child.idSystemObject, childData); } parentData.recordChild(child); @@ -60,11 +61,6 @@ export class ObjectGraphDatabase { return true; } - getObjectGraphDataEntry(oID: CACHE.ObjectIDAndType, sID: CACHE.SystemObjectInfo): ObjectGraphDataEntry | undefined { - const systemObjectIDType: SystemObjectIDType = { idSystemObject: sID.idSystemObject, idObject: oID.idObject, eObjectType: oID.eObjectType }; - return this.objectMap.get(systemObjectIDType); - } - /* #region Compute Graph Data */ private async computeGraphDataFromObject(idObject: number, eObjectType: eSystemObjectType, functionName: string): Promise { const oID: CACHE.ObjectIDAndType = { idObject, eObjectType }; @@ -81,9 +77,10 @@ export class ObjectGraphDatabase { return false; } - const objectIDAndType: SystemObjectIDType = { idSystemObject: sID.idSystemObject, idObject, eObjectType }; - if (!this.objectMap.has(objectIDAndType)) - this.objectMap.set(objectIDAndType, new ObjectGraphDataEntry(objectIDAndType, sID.Retired)); + if (!this.objectMap.has(sID.idSystemObject)) { + const objectIDAndType: SystemObjectIDType = { idSystemObject: sID.idSystemObject, idObject, eObjectType }; + this.objectMap.set(sID.idSystemObject, new ObjectGraphDataEntry(objectIDAndType, sID.Retired)); + } return true; } @@ -266,15 +263,15 @@ export class ObjectGraphDatabase { // walk all children // determine if applicable computed values (unit, project, subject, item) have been applied to child; if not: // apply computed value - // descend into children (extract state, apply, descend) + // descend into children (recurse: extract state, apply, descend) // walk all parents // determine if applicable computed values (capture method, variant type, model purpose, and model file type) have been applied to parent; if not: // apply computed value - // ascend into parents (extract state, apply, ascend) + // ascend into parents (recurse: extract state, apply, ascend) let retValue: boolean = true; - for (const [systemObjectIDType, objectGraphDataEntry] of this.objectMap) { - const objectGraphState = await this.extractState(systemObjectIDType); + for (const objectGraphDataEntry of this.objectMap.values()) { + const objectGraphState = await this.extractState(objectGraphDataEntry.systemObjectIDType); retValue = this.applyGraphState(objectGraphDataEntry, objectGraphState) && retValue; } return retValue; @@ -298,13 +295,13 @@ export class ObjectGraphDatabase { if (depth >= 32) return false; - const relationMap: Map | undefined = + const relationMap: Map | undefined = eDirection == eApplyGraphStateDirection.eChild ? objectGraphDataEntry.childMap : objectGraphDataEntry.parentMap; if (!relationMap) return true; - for (const systemObjectIDType of relationMap.keys()) { - const relationEntry: ObjectGraphDataEntry | undefined = this.objectMap.get(systemObjectIDType); + for (const systemObjectIDType of relationMap.values()) { + const relationEntry: ObjectGraphDataEntry | undefined = this.objectMap.get(systemObjectIDType.idSystemObject); if (relationEntry) { if (relationEntry.applyGraphState(objectGraphState, eDirection)) // if applying this state changes things, then recurse: diff --git a/server/navigation/impl/NavigationSolr/ReindexSolr.ts b/server/navigation/impl/NavigationSolr/ReindexSolr.ts index 0ea4f222e..db17a5549 100644 --- a/server/navigation/impl/NavigationSolr/ReindexSolr.ts +++ b/server/navigation/impl/NavigationSolr/ReindexSolr.ts @@ -67,12 +67,12 @@ export class ReindexSolr { } let docs: any[] = []; - for (const [systemObjectIDType, objectGraphDataEntry] of this.objectGraphDatabase.objectMap) { + for (const objectGraphDataEntry of this.objectGraphDatabase.objectMap.values()) { const doc: any = {}; await this.extractCommonFields(doc, objectGraphDataEntry); - switch (systemObjectIDType.eObjectType) { + switch (objectGraphDataEntry.systemObjectIDType.eObjectType) { case eSystemObjectType.eUnit: await this.handleUnit(doc, objectGraphDataEntry); break; case eSystemObjectType.eProject: await this.handleProject(doc, objectGraphDataEntry); break; case eSystemObjectType.eSubject: await this.handleSubject(doc, objectGraphDataEntry); break; @@ -114,8 +114,8 @@ export class ReindexSolr { doc.ObjectType = DBAPI.SystemObjectTypeToName(OGDEH.eObjectType); doc.idObject = OGDEH.idObject; - doc.ParentID = OGDEH.parents.length == 1 ? OGDEH.parents[0] : OGDEH.parents; - doc.ChildrenID = OGDEH.children.length == 1 ? OGDEH.children[0] : OGDEH.children; + doc.ParentID = OGDEH.parents.length == 0 ? [0] : OGDEH.parents; + doc.ChildrenID = OGDEH.children.length == 0 ? [0] : OGDEH.children; doc.Identifier = this.computeIdentifiers(objectGraphDataEntry.systemObjectIDType.idSystemObject); let nameArray: string[] = []; @@ -137,8 +137,8 @@ export class ReindexSolr { idArray.push(objInfo.idSystemObject); } if (nameArray.length > 0) { - doc.Unit = nameArray.length == 1 ? nameArray[0] : nameArray; - doc.UnitID = idArray.length == 1 ? idArray[0] : idArray; + doc.Unit = nameArray; + doc.UnitID = idArray; nameArray = []; idArray = []; } @@ -159,8 +159,8 @@ export class ReindexSolr { idArray.push(objInfo.idSystemObject); } if (nameArray.length > 0) { - doc.Project = nameArray.length == 1 ? nameArray[0] : nameArray; - doc.ProjectID = idArray.length == 1 ? idArray[0] : idArray; + doc.Project = nameArray; + doc.ProjectID = idArray; nameArray = []; idArray = []; } @@ -181,8 +181,8 @@ export class ReindexSolr { idArray.push(objInfo.idSystemObject); } if (nameArray.length > 0) { - doc.Subject = nameArray.length == 1 ? nameArray[0] : nameArray; - doc.SubjectID = idArray.length == 1 ? idArray[0] : idArray; + doc.Subject = nameArray; + doc.SubjectID = idArray; nameArray = []; idArray = []; } @@ -203,31 +203,32 @@ export class ReindexSolr { idArray.push(objInfo.idSystemObject); } if (nameArray.length > 0) { - doc.Item = nameArray.length == 1 ? nameArray[0] : nameArray; - doc.ItemID = idArray.length == 1 ? idArray[0] : idArray; + doc.Item = nameArray; + doc.ItemID = idArray; nameArray = []; idArray = []; } const ChildrenObjectTypes: string[] = []; - for (const childrenObjectType of OGDEH.childrenObjectTypes) ChildrenObjectTypes.push(DBAPI.SystemObjectTypeToName(childrenObjectType)); - doc.ChildrenObjectTypes = ChildrenObjectTypes.length == 1 ? ChildrenObjectTypes[0] : ChildrenObjectTypes; + for (const childrenObjectType of OGDEH.childrenObjectTypes) + ChildrenObjectTypes.push(DBAPI.SystemObjectTypeToName(childrenObjectType)); + doc.ChildrenObjectTypes = ChildrenObjectTypes; let VocabList: string[] = []; VocabList = await this.computeVocabularyTerms(OGDEH.childrenCaptureMethods); - doc.ChildrenCaptureMethods = VocabList.length == 1 ? VocabList[0] : VocabList; + doc.ChildrenCaptureMethods = VocabList; VocabList = []; VocabList = await this.computeVocabularyTerms(OGDEH.childrenVariantTypes); - doc.ChildrenVariantTypes = VocabList.length == 1 ? VocabList[0] : VocabList; + doc.ChildrenVariantTypes = VocabList; VocabList = []; VocabList = await this.computeVocabularyTerms(OGDEH.childrenModelPurposes); - doc.ChildrenModelPurposes = VocabList.length == 1 ? VocabList[0] : VocabList; + doc.ChildrenModelPurposes = VocabList; VocabList = []; VocabList = await this.computeVocabularyTerms(OGDEH.childrenModelFileTypes); - doc.ChildrenModelFileTypes = VocabList.length == 1 ? VocabList[0] : VocabList; + doc.ChildrenModelFileTypes = VocabList; VocabList = []; } @@ -539,7 +540,8 @@ export class ReindexSolr { const identifiersRet: string[] = []; const identifiers: DBAPI.Identifier[] | null = await DBAPI.Identifier.fetchFromSystemObject(idSystemObject); if (identifiers) { - for (const identifier of identifiers) identifiersRet.push(identifier.IdentifierValue); + for (const identifier of identifiers) + identifiersRet.push(identifier.IdentifierValue); } return identifiersRet; } diff --git a/server/storage/interface/AssetStorageAdapter.ts b/server/storage/interface/AssetStorageAdapter.ts index 7a74cd617..63279b58b 100644 --- a/server/storage/interface/AssetStorageAdapter.ts +++ b/server/storage/interface/AssetStorageAdapter.ts @@ -435,9 +435,9 @@ export class AssetStorageAdapter { } // clear StorageKeyStaging and updated Ingested flag from this retired asset version - assetVersion.StorageKeyStaging = ''; /* istanbul ignore next */ + assetVersion.StorageKeyStaging = ''; assetVersion.Ingested = true; - if (!await assetVersion.update()) { + if (!await assetVersion.update()) /* istanbul ignore next */ { const error: string = `Unable to clear staging storage key from AssetVersion ${JSON.stringify(assetVersion)}`; LOG.logger.error(error); return { assets, assetVersions, success: false, error }; From aa8651337f1f2f3025ca21e33675c0aca4f2784a Mon Sep 17 00:00:00 2001 From: Jon Tyson <6943745+jahjedtieson@users.noreply.github.com> Date: Sat, 13 Feb 2021 10:54:03 -0800 Subject: [PATCH 218/239] Solr: * Added package for solr-client types * Fixed issue in Solr indexing in which all objects were incorrectly deemed to have children of their own type * Solr queries starting point ... we're constructing the query now, executing it, and logging the results so I can prep for parsing the output and translating into the form needed by the navigation interface * Use solr-client types --- .../db/api/composite/ObjectGraphDataEntry.ts | 6 +- .../impl/NavigationSolr/NavigationSolr.ts | 463 +++++------------- .../impl/NavigationSolr/ReindexSolr.ts | 11 +- .../impl/NavigationSolr/SolrClient.ts | 6 +- server/package.json | 1 + yarn.lock | 24 +- 6 files changed, 162 insertions(+), 349 deletions(-) diff --git a/server/db/api/composite/ObjectGraphDataEntry.ts b/server/db/api/composite/ObjectGraphDataEntry.ts index 02b76736d..7d97e6c5e 100644 --- a/server/db/api/composite/ObjectGraphDataEntry.ts +++ b/server/db/api/composite/ObjectGraphDataEntry.ts @@ -85,15 +85,17 @@ export class ObjectGraphDataEntry { } } - if (eDirection == eApplyGraphStateDirection.eSelf || - eDirection == eApplyGraphStateDirection.eParent) { + if (eDirection == eApplyGraphStateDirection.eParent) { if (objectGraphState.eType) { if (!this.childrenObjectTypes.has(objectGraphState.eType)) { this.childrenObjectTypes.set(objectGraphState.eType, true); retValue = true; } } + } + if (eDirection == eApplyGraphStateDirection.eSelf || + eDirection == eApplyGraphStateDirection.eParent) { if (objectGraphState.captureMethod) { if (!this.childrenCaptureMethods.has(objectGraphState.captureMethod)) { this.childrenCaptureMethods.set(objectGraphState.captureMethod, true); diff --git a/server/navigation/impl/NavigationSolr/NavigationSolr.ts b/server/navigation/impl/NavigationSolr/NavigationSolr.ts index f56765f69..f3b4b36b7 100644 --- a/server/navigation/impl/NavigationSolr/NavigationSolr.ts +++ b/server/navigation/impl/NavigationSolr/NavigationSolr.ts @@ -1,384 +1,175 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ + +import solr from 'solr-client'; +import { ClientRequest } from 'http'; + import * as NAV from '../../interface'; import * as LOG from '../../../utils/logger'; import * as CACHE from '../../../cache'; import * as DBAPI from '../../../db'; // import * as H from '../../../utils/helpers'; import { eSystemObjectType } from '../../../db'; -import { ObjectIDAndType } from '../../../cache'; +import { SolrClient } from './SolrClient'; +import { Vocabulary } from '../../../types/graphql'; +import { eMetadata } from '../../interface'; -export class NavigationSolr implements NAV.INavigation { - async getObjectChildren(filter: NAV.NavigationFilter): Promise { - return (filter.idRoot == 0) - ? await NavigationSolr.getRoot(filter) - : await NavigationSolr.getChildren(filter); - } - - private static async getRoot(filter: NAV.NavigationFilter): Promise { - let entries: NAV.NavigationResultEntry[] = []; +interface SolrQueryResult { + result: any; + error: any; +} - for (const eObjectType of filter.objectTypes) { - switch (eObjectType) { - case eSystemObjectType.eUnit: entries = entries.concat(await NavigationSolr.computeRootUnits(filter)); break; - case eSystemObjectType.eProject: entries = entries.concat(await NavigationSolr.computeRootProjects(filter)); break; +export class NavigationSolr implements NAV.INavigation { + private _solrClient: SolrClient; - case eSystemObjectType.eSubject: - case eSystemObjectType.eItem: - case eSystemObjectType.eCaptureData: - case eSystemObjectType.eModel: - case eSystemObjectType.eScene: - case eSystemObjectType.eIntermediaryFile: - case eSystemObjectType.eAsset: - case eSystemObjectType.eAssetVersion: - case eSystemObjectType.eProjectDocumentation: - case eSystemObjectType.eActor: - case eSystemObjectType.eStakeholder: - case eSystemObjectType.eUnknown: - default: - return { success: false, error: 'Not implemented', entries, metadataColumns: filter.metadataColumns }; - } + constructor() { + this._solrClient = new SolrClient(null, null, null); + } - } - return { success: true, error: '', entries, metadataColumns: filter.metadataColumns }; + // #region INavigation interface + async getObjectChildren(filter: NAV.NavigationFilter): Promise { + const SQ: solr.Query = await this.computeSolrQuery(filter); + return await this.executeSolrQuery(filter, SQ); } + // #endregion - private static async getChildren(filter: NAV.NavigationFilter): Promise { - // LOG.logger.info(`NavigationSolr.getChildren(${JSON.stringify(filter)})`); - const oID: CACHE.ObjectIDAndType | undefined = await CACHE.SystemObjectCache.getObjectFromSystem(filter.idRoot); - if (!oID) - return { success: false, error: `NavigationSolr.getChildren unable to fetch information for filter ${JSON.stringify(filter)}`, entries: [], metadataColumns: filter.metadataColumns }; + // #region Compute Query + private async computeSolrQuery(filter: NAV.NavigationFilter): Promise { + LOG.logger.info(`NavigationSolr.computeSolrQuery ${JSON.stringify(filter)}`); + let SQ: solr.Query = this._solrClient._client.query(); - const OG: DBAPI.ObjectGraph = new DBAPI.ObjectGraph(filter.idRoot, DBAPI.eObjectGraphMode.eDescendents, 1); /* istanbul ignore if */ - if (!await OG.fetch()) - return { success: false, error: `NavigationSolr.getChildren unable to fetch descendents for filter ${JSON.stringify(filter)}`, entries: [], metadataColumns: filter.metadataColumns }; + // search: string; // search string from the user + if (filter.search) + SQ = SQ.q(`_text_:*${filter.search}*`); + else + SQ = SQ.q('*:*'); - const entries: NAV.NavigationResultEntry[] = []; + // idRoot: number; // idSystemObject of item for which we should get children; 0 means get everything + if (filter.idRoot) + SQ = SQ.matchFilter('idSystemObject', filter.idRoot); - switch (oID.eObjectType) { - case eSystemObjectType.eUnit: await NavigationSolr.computeChildrenForUnitOrProject(filter, oID, OG, entries); break; - case eSystemObjectType.eProject: await NavigationSolr.computeChildrenForUnitOrProject(filter, oID, OG, entries); break; - case eSystemObjectType.eSubject: await NavigationSolr.computeChildrenForSubject(filter, oID, OG, entries); break; - case eSystemObjectType.eItem: await NavigationSolr.computeChildrenForItem(filter, oID, OG, entries); break; + // objectTypes: eSystemObjectType[]; // empty array means give all appropriate children types - case eSystemObjectType.eCaptureData: - case eSystemObjectType.eModel: - case eSystemObjectType.eScene: - case eSystemObjectType.eIntermediaryFile: - case eSystemObjectType.eAsset: - case eSystemObjectType.eAssetVersion: - case eSystemObjectType.eProjectDocumentation: - case eSystemObjectType.eActor: - case eSystemObjectType.eStakeholder: /* istanbul ignore next */ - case eSystemObjectType.eUnknown: /* istanbul ignore next */ - default: - return { success: false, error: 'Not implemented', entries, metadataColumns: filter.metadataColumns }; - } - return { success: true, error: '', entries, metadataColumns: filter.metadataColumns }; - } + // objectsToDisplay: eSystemObjectType[]; // objects to display + SQ = await this.computeFilterParamFromSystemObjectType(SQ, filter.objectsToDisplay, 'ObjectType', '||'); - /* #region Units */ - private static async computeRootUnits(filter: NAV.NavigationFilter): Promise { - const entries: NAV.NavigationResultEntry[] = []; + // units: number[]; // idSystemObject[] for units filter + // projects: number[]; // idSystemObject[] for projects filter - const units: DBAPI.Unit[] | null = await DBAPI.Unit.fetchAll(); /* istanbul ignore if */ - if (!units) { - LOG.logger.error('NavigationSolr.getRoot unable to retrieve units'); - return []; - } + // has: eSystemObjectType[]; // has system object filter + SQ = await this.computeFilterParamFromSystemObjectType(SQ, filter.has, 'ChildrenObjectTypes', '&&'); - for (const unit of units) { - const oID: CACHE.ObjectIDAndType = { idObject: unit.idUnit, eObjectType: eSystemObjectType.eUnit }; - const sID: CACHE.SystemObjectInfo | undefined = await CACHE.SystemObjectCache.getSystemFromObjectID(oID); /* istanbul ignore if */ - if (!sID) { - LOG.logger.error(`NavigationSolr.getRoot unable to compute idSystemObject for ${JSON.stringify(oID)}`); - continue; - } + // missing: eSystemObjectType[]; // missing system object filter + SQ = await this.computeFilterParamFromSystemObjectType(SQ, filter.missing, '!ChildrenObjectTypes', '&&'); // TODO: does ! work here? - const entry: NAV.NavigationResultEntry = { - idSystemObject: sID.idSystemObject, - name: unit.Abbreviation || '', - objectType: eSystemObjectType.eUnit, - idObject: unit.idUnit, - metadata: NavigationSolr.computeMetadataForUnit(unit, filter.metadataColumns) - }; - entries.push(entry); - } - return entries; - } - - private static async computeChildrenForUnitOrProject(filter: NAV.NavigationFilter, oID: CACHE.ObjectIDAndType, OG: DBAPI.ObjectGraph, entries: NAV.NavigationResultEntry[]): Promise { - filter; oID; /* istanbul ignore else */ - if (OG.subject) { - for (const subject of OG.subject) { - const oIDSubject: ObjectIDAndType = { idObject: subject.idSubject, eObjectType: eSystemObjectType.eSubject }; - const sID: CACHE.SystemObjectInfo | undefined = await CACHE.SystemObjectCache.getSystemFromObjectID(oIDSubject); /* istanbul ignore else */ - if (sID) - entries.push({ - idSystemObject: sID.idSystemObject, - name: subject.Name, - objectType: eSystemObjectType.eSubject, - idObject: subject.idSubject, - metadata: await NavigationSolr.computeMetadataForSubject(subject, filter.metadataColumns) - }); - else - LOG.logger.error(`NavigateDB.computeChildrenForUnit unable to fetch information for ${JSON.stringify(oIDSubject)}`); - } - } - } + // captureMethod: number[]; // idVocabulary[] for capture method filter + // variantType: number[]; // idVocabulary[] for variant type filter + // modelPurpose: number[]; // idVocabulary[] for model purpose filter + // modelFileType: number[]; // idVocabulary[] for model file type filter + SQ = await this.computeFilterParamFromVocabIDArray(SQ, filter.captureMethod, 'ChildrenCaptureMethods'); + SQ = await this.computeFilterParamFromVocabIDArray(SQ, filter.variantType, 'ChildrenVariantTypes'); + SQ = await this.computeFilterParamFromVocabIDArray(SQ, filter.modelPurpose, 'ChildrenModelPurposes'); + SQ = await this.computeFilterParamFromVocabIDArray(SQ, filter.modelFileType, 'ChildrenModelFileTypes'); - private static computeMetadataForUnit(unit: DBAPI.Unit, metadataColumns: NAV.eMetadata[]): string[] { - const metadata: string[] = []; - for (const metadataColumn of metadataColumns) { + // metadataColumns: eMetadata[]; // empty array means give no metadata + const filterColumns: string[] = []; + for (const metadataColumn of filter.metadataColumns) { switch (metadataColumn) { - case NAV.eMetadata.eUnitAbbreviation: metadata.push(unit.Abbreviation || ''); break; /* istanbul ignore next */ - - default: - case NAV.eMetadata.eItemName: - case NAV.eMetadata.eSubjectIdentifier: - metadata.push(''); - break; + case eMetadata.eUnitAbbreviation: filterColumns.push('Unit'); break; + case eMetadata.eSubjectIdentifier: filterColumns.push('Subject'); break; + case eMetadata.eItemName: filterColumns.push('Item'); break; } } - return metadata; - } - /* #endregion */ - - /* #region Projects */ - private static async computeRootProjects(filter: NAV.NavigationFilter): Promise { - const entries: NAV.NavigationResultEntry[] = []; - - const projects: DBAPI.Project[] | null = await DBAPI.Project.fetchAll(); /* istanbul ignore if */ - if (!projects) { - LOG.logger.error('NavigationSolr.getRoot unable to retrieve projects'); - return []; - } - for (const project of projects) { - const oID: CACHE.ObjectIDAndType = { idObject: project.idProject, eObjectType: eSystemObjectType.eProject }; - const sID: CACHE.SystemObjectInfo | undefined = await CACHE.SystemObjectCache.getSystemFromObjectID(oID); /* istanbul ignore if */ - if (!sID) { - LOG.logger.error(`NavigationSolr.getRoot unable to compute idSystemObject for ${JSON.stringify(oID)}`); - continue; - } + if (filterColumns.length > 0) + SQ = SQ.fl(filterColumns); - const entry: NAV.NavigationResultEntry = { - idSystemObject: sID.idSystemObject, - name: project.Name, - objectType: eSystemObjectType.eProject, - idObject: project.idProject, - metadata: await NavigationSolr.computeMetadataForProject(project, filter.metadataColumns) - }; - entries.push(entry); - } - return entries; + return SQ; } - // computeChildrenForUnitOrProject handles project's subject children, for the time being - - private static async computeMetadataForProject(project: DBAPI.Project, metadataColumns: NAV.eMetadata[]): Promise { - const metadata: string[] = []; - for (const metadataColumn of metadataColumns) { - switch (metadataColumn) { - case NAV.eMetadata.eUnitAbbreviation: { - const units: DBAPI.Unit[] | null = await DBAPI.Unit.fetchMasterFromProjects([project.idProject]); // TODO: consider placing this in a cache - let unitAbbreviation: string = ''; /* istanbul ignore else */ - if (units) { - for (let index = 0; index < units.length; index++) - unitAbbreviation += ((index > 0) ? ', ' : '') + units[index].Abbreviation; - } - metadata.push(unitAbbreviation); - } break; /* istanbul ignore next */ - - default: - case NAV.eMetadata.eItemName: - case NAV.eMetadata.eSubjectIdentifier: - metadata.push(''); - break; - } - } - return metadata; + private async computeFilterParamFromSystemObjectType(SQ: solr.Query, systemObjectTypes: eSystemObjectType[], filterSchema: string, operator: string): Promise { + const filterValueList: string[] | null = await this.transformSystemObjecTypeArrayToStrings(systemObjectTypes); + return this.computeFilterParam(SQ, filterValueList, filterSchema, operator); } - /* #endregion */ - - /* #region Subjects */ - private static async computeChildrenForSubject(filter: NAV.NavigationFilter, oID: CACHE.ObjectIDAndType, OG: DBAPI.ObjectGraph, entries: NAV.NavigationResultEntry[]): Promise { - filter; oID; - const subject: DBAPI.Subject | null = (OG.subject) ? OG.subject[0] : /* istanbul ignore next */ await DBAPI.Subject.fetch(oID.idObject); /* istanbul ignore if */ - if (!subject) { - LOG.logger.error(`NavigateDB.computeChildrenForSubject unable to fetch subject information for ${JSON.stringify(oID)}`); - return; - } - /* istanbul ignore else */ - if (OG.item) { - for (const item of OG.item) { - const oIDITem: ObjectIDAndType = { idObject: item.idItem, eObjectType: eSystemObjectType.eItem }; - const sID: CACHE.SystemObjectInfo | undefined = await CACHE.SystemObjectCache.getSystemFromObjectID(oIDITem); /* istanbul ignore else */ - if (sID) - entries.push({ - idSystemObject: sID.idSystemObject, - name: item.Name, - objectType: eSystemObjectType.eItem, - idObject: item.idItem, - metadata: await NavigationSolr.computeMetadataForItem(item, subject, filter.metadataColumns) - }); - else - LOG.logger.error(`NavigateDB.computeChildrenForSubject unable to fetch information for ${JSON.stringify(oIDITem)}`); - } - } + private async computeFilterParamFromVocabIDArray(SQ: solr.Query, vocabFilterIDs: number[], filterSchema: string): Promise { + const filterValueList: string[] | null = await this.transformVocabIDArrayToStrings(vocabFilterIDs); + return this.computeFilterParam(SQ, filterValueList, filterSchema, '&&'); } - private static async computeMetadataForSubject(subject: DBAPI.Subject, metadataColumns: NAV.eMetadata[]): Promise { - const metadata: string[] = []; - for (const metadataColumn of metadataColumns) { - switch (metadataColumn) { - case NAV.eMetadata.eUnitAbbreviation: { - const unit: DBAPI.Unit | null = await DBAPI.Unit.fetch(subject.idUnit); - metadata.push(unit ? (unit.Abbreviation || /* istanbul ignore next */ '') : /* istanbul ignore next */ ''); - } break; - - case NAV.eMetadata.eSubjectIdentifier: { - const identifier: DBAPI.Identifier | null = (subject.idIdentifierPreferred) - ? await DBAPI.Identifier.fetch(subject.idIdentifierPreferred) - : null; - metadata.push(identifier ? identifier.IdentifierValue : ''); - } break; /* istanbul ignore next */ + private computeFilterParam(SQ: solr.Query, filterValueList: string[] | null, filterSchema: string, operator: string): solr.Query { + if (!filterValueList) + return SQ; - default: - case NAV.eMetadata.eItemName: - metadata.push(''); - break; - } + let filterParam: string = ''; + for (const filterValue of filterValueList) { + if (filterParam) + filterParam += ` ${operator} ${filterSchema}:`; // TODO: does this work for && and ||? + filterParam += `${filterValue}`; } - return metadata; + return SQ.matchFilter(filterSchema, filterParam); } - /* #endregion */ - - /* #region Items */ - private static async computeChildrenForItem(filter: NAV.NavigationFilter, oID: CACHE.ObjectIDAndType, OG: DBAPI.ObjectGraph, entries: NAV.NavigationResultEntry[]): Promise { - filter; oID; - const item: DBAPI.Item | null = (OG.item) ? OG.item[0] : /* istanbul ignore next */ await DBAPI.Item.fetch(oID.idObject); /* istanbul ignore if */ - if (!item) { - LOG.logger.error(`NavigateDB.computeChildrenForItem unable to fetch item information for ${JSON.stringify(oID)}`); - return; - } /* istanbul ignore else */ - - if (OG.captureData) { - for (const captureData of OG.captureData) { - const vCaptureMethod: DBAPI.Vocabulary | undefined = await CACHE.VocabularyCache.vocabulary(captureData.idVCaptureMethod); - const oIDCD: ObjectIDAndType = { idObject: captureData.idCaptureData, eObjectType: eSystemObjectType.eCaptureData }; - const sID: CACHE.SystemObjectInfo | undefined = await CACHE.SystemObjectCache.getSystemFromObjectID(oIDCD); /* istanbul ignore else */ - if (sID) - entries.push({ - idSystemObject: sID.idSystemObject, - name: `Capture Set${vCaptureMethod ? ' ' + vCaptureMethod.Term : /* istanbul ignore next */ ''}`, /* : ${H.Helpers.convertDateToYYYYMMDD(captureData.DateCaptured)} */ - objectType: eSystemObjectType.eCaptureData, - idObject: captureData.idCaptureData, - metadata: await NavigationSolr.computeMetadataForItemChildren(/* captureData, */ item, filter.metadataColumns) - }); - else - LOG.logger.error(`NavigateDB.computeChildrenForItem unable to fetch information for ${JSON.stringify(oIDCD)}`); - } - } /* istanbul ignore else */ - - if (OG.model) { - for (const model of OG.model) { - const vPurpose: DBAPI.Vocabulary | undefined = await CACHE.VocabularyCache.vocabulary(model.idVPurpose); - const oIDModel: ObjectIDAndType = { idObject: model.idModel, eObjectType: eSystemObjectType.eModel }; - const sID: CACHE.SystemObjectInfo | undefined = await CACHE.SystemObjectCache.getSystemFromObjectID(oIDModel); /* istanbul ignore else */ - if (sID) - entries.push({ - idSystemObject: sID.idSystemObject, - name: `Model${vPurpose ? ' ' + vPurpose.Term : /* istanbul ignore next */ ''}`, /* : ${H.Helpers.convertDateToYYYYMMDD(model.DateCreated)} */ - objectType: eSystemObjectType.eModel, - idObject: model.idModel, - metadata: await NavigationSolr.computeMetadataForItemChildren(/* model, */ item, filter.metadataColumns) - }); - else - LOG.logger.error(`NavigateDB.computeChildrenForItem unable to fetch information for ${JSON.stringify(oIDModel)}`); - } - } /* istanbul ignore else */ - if (OG.scene) { - for (const scene of OG.scene) { - const oIDScene: ObjectIDAndType = { idObject: scene.idScene, eObjectType: eSystemObjectType.eScene }; - const sID: CACHE.SystemObjectInfo | undefined = await CACHE.SystemObjectCache.getSystemFromObjectID(oIDScene); /* istanbul ignore else */ - if (sID) - entries.push({ - idSystemObject: sID.idSystemObject, - name: `Scene ${scene.Name}`, - objectType: eSystemObjectType.eScene, - idObject: scene.idScene, - metadata: await NavigationSolr.computeMetadataForItemChildren(/* scene, */ item, filter.metadataColumns) - }); - else - LOG.logger.error(`NavigateDB.computeChildrenForItem unable to fetch information for ${JSON.stringify(oIDScene)}`); + private async transformSystemObjecTypeArrayToStrings(systemObjectTypes: eSystemObjectType[]): Promise { + const termList: string[] = []; + for (const systemObjectType of systemObjectTypes) { + const filterValue = DBAPI.SystemObjectTypeToName(systemObjectType); + if (!filterValue) { + LOG.logger.error(`NavigationSolr.computeSolrQuery handling invalid system object type ${systemObjectType}`); + continue; } + termList.push(filterValue); } - } - - private static async computeMetadataForItem(item: DBAPI.Item, subject: DBAPI.Subject, metadataColumns: NAV.eMetadata[]): Promise { - const metadata: string[] = []; - for (const metadataColumn of metadataColumns) { - switch (metadataColumn) { - case NAV.eMetadata.eUnitAbbreviation: { - const unit: DBAPI.Unit | null = await DBAPI.Unit.fetch(subject.idUnit); - metadata.push(unit ? (unit.Abbreviation || /* istanbul ignore next */ '') : /* istanbul ignore next */ ''); - } break; - - case NAV.eMetadata.eSubjectIdentifier: { - const identifier: DBAPI.Identifier | null = (subject.idIdentifierPreferred) - ? await DBAPI.Identifier.fetch(subject.idIdentifierPreferred) - : /* istanbul ignore next */ null; - metadata.push(identifier ? identifier.IdentifierValue : /* istanbul ignore next */ ''); - } break; - case NAV.eMetadata.eItemName: - metadata.push(`Item ${item.Name}`); - break; + return (termList.length > 0) ? termList : null; + } - /* istanbul ignore next */ - default: - metadata.push(''); - break; + private async transformVocabIDArrayToStrings(vocabFilterIDs: number[]): Promise { + const termList: string[] = []; + for (const idVocabFilter of vocabFilterIDs) { + const vocabFilter: Vocabulary | undefined = await CACHE.VocabularyCache.vocabulary(idVocabFilter); + if (!vocabFilter) { + LOG.logger.error(`NavigationSolr.computeSolrQuery handling invalid vocabulary value ${idVocabFilter}`); + continue; } + termList.push(vocabFilter.Term); } - return metadata; + + return (termList.length > 0) ? termList : null; } - /* #endregion */ + // #endregion - private static async computeMetadataForItemChildren(item: DBAPI.Item, metadataColumns: NAV.eMetadata[]): Promise { - const subjects: DBAPI.Subject[] | null = await DBAPI.Subject.fetchMasterFromItems([item.idItem]); - const metadata: string[] = []; - for (const metadataColumn of metadataColumns) { - switch (metadataColumn) { - case NAV.eMetadata.eUnitAbbreviation: { /* istanbul ignore else */ - if (subjects && subjects.length > 0) { // TODO: deal with multiple subjects - const unit: DBAPI.Unit | null = await DBAPI.Unit.fetch(subjects[0].idUnit); - metadata.push(unit ? (unit.Abbreviation || /* istanbul ignore next */ '') : /* istanbul ignore next */ ''); - } else - metadata.push(''); - } break; + // #region Execute Query + private async executeSolrQuery(filter: NAV.NavigationFilter, SQ: solr.Query): Promise { + LOG.logger.info('NavigationSolr.executeSolrQuery'); + const entries: NAV.NavigationResultEntry[] = []; + const queryResult: SolrQueryResult = await this.executeSolrQueryWorker(SQ); + if (queryResult.error) + return { success: false, error: `Solr Query Failure: ${JSON.stringify(queryResult.error)}`, entries, metadataColumns: filter.metadataColumns }; + /* + const entry: NAV.NavigationResultEntry = { + idSystemObject: sID.idSystemObject, + name: unit.Abbreviation || '', + objectType: eSystemObjectType.eUnit, + idObject: unit.idUnit, + metadata: NavigationSolr.computeMetadataForUnit(unit, filter.metadataColumns) + }; + */ + LOG.logger.info(JSON.stringify(queryResult.result)); + return { success: true, error: '', entries, metadataColumns: filter.metadataColumns }; + } - case NAV.eMetadata.eSubjectIdentifier: { /* istanbul ignore else */ - if (subjects && subjects.length > 0) { // TODO: deal with multiple subjects - const identifier: DBAPI.Identifier | null = (subjects[0].idIdentifierPreferred) - ? await DBAPI.Identifier.fetch(subjects[0].idIdentifierPreferred) - : /* istanbul ignore next */ null; - metadata.push(identifier ? identifier.IdentifierValue : /* istanbul ignore next */ ''); + private executeSolrQueryWorker(SQ: solr.Query): Promise { + return new Promise((resolve) => { + const request: ClientRequest = this._solrClient._client.search(SQ, + function (err, obj) { + if (err) { + LOG.logger.error('NavigationSolr.executeSolrQueryWorker', err); + resolve({ result: null, error: err }); } else - metadata.push(''); - } break; - - case NAV.eMetadata.eItemName: - metadata.push(`Item ${item.Name}`); - break; - - /* istanbul ignore next */ - default: - metadata.push(''); - break; - } - } - return metadata; + resolve({ result: obj, error: null }); + }); + request; + }); } + // #endregion } diff --git a/server/navigation/impl/NavigationSolr/ReindexSolr.ts b/server/navigation/impl/NavigationSolr/ReindexSolr.ts index db17a5549..22d6e7c3f 100644 --- a/server/navigation/impl/NavigationSolr/ReindexSolr.ts +++ b/server/navigation/impl/NavigationSolr/ReindexSolr.ts @@ -57,10 +57,7 @@ export class ReindexSolr { } private async fullIndexWorker(): Promise { - const solrClient: SolrClient = new SolrClient(null, null, null); - solrClient._client.autoCommit = true; - if (!(await this.objectGraphDatabase.fetch())) { LOG.logger.error('ReindexSolr.fullIndex failed on ObjectGraphDatabase.fetch()'); return false; @@ -93,15 +90,15 @@ export class ReindexSolr { docs.push(doc); if (docs.length >= 1000) { - solrClient._client.add(docs, function (err, obj) { if (err) LOG.logger.error('ReindexSolr.fullIndex adding cached records', err); else obj; }); - solrClient._client.commit(function (err, obj) { if (err) LOG.logger.error('ReindexSolr.fullIndex -> commit()', err); else obj; }); + solrClient._client.add(docs, undefined, function (err, obj) { if (err) LOG.logger.error('ReindexSolr.fullIndex adding cached records', err); else obj; }); + solrClient._client.commit(undefined, function (err, obj) { if (err) LOG.logger.error('ReindexSolr.fullIndex -> commit()', err); else obj; }); docs = []; } } if (docs.length > 0) { - solrClient._client.add(docs, function (err, obj) { if (err) LOG.logger.error('ReindexSolr.fullIndex adding cached records', err); else obj; }); - solrClient._client.commit(function (err, obj) { if (err) LOG.logger.error('ReindexSolr.fullIndex -> commit()', err); else obj; }); + solrClient._client.add(docs, undefined, function (err, obj) { if (err) LOG.logger.error('ReindexSolr.fullIndex adding cached records', err); else obj; }); + solrClient._client.commit(undefined, function (err, obj) { if (err) LOG.logger.error('ReindexSolr.fullIndex -> commit()', err); else obj; }); } return true; } diff --git a/server/navigation/impl/NavigationSolr/SolrClient.ts b/server/navigation/impl/NavigationSolr/SolrClient.ts index b58b864f5..4631cb109 100644 --- a/server/navigation/impl/NavigationSolr/SolrClient.ts +++ b/server/navigation/impl/NavigationSolr/SolrClient.ts @@ -2,13 +2,13 @@ import solr from 'solr-client'; export class SolrClient { - _client: any; + _client: solr.Client; - constructor(host: string | null, port: string | null, core: string | null) { + constructor(host: string | null, port: number | null, core: string | null) { if (!host) host = 'packrat-solr'; if (!port) - port = '8983'; + port = 8983; if (!core) core = 'packrat'; this._client = solr.createClient({ host, port, core }); diff --git a/server/package.json b/server/package.json index ad0bd4e48..35b8e0a94 100644 --- a/server/package.json +++ b/server/package.json @@ -77,6 +77,7 @@ "@prisma/cli": "2.4.1", "@types/jest": "26.0.13", "@types/node-fetch": "2.5.7", + "@types/solr-client": "0.7.4", "jest": "24.9.0", "ts-jest": "24.3.0" }, diff --git a/yarn.lock b/yarn.lock index 622f1e2db..95e1390e7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3552,6 +3552,13 @@ "@types/express-serve-static-core" "*" "@types/mime" "*" +"@types/solr-client@0.7.4": + version "0.7.4" + resolved "https://registry.yarnpkg.com/@types/solr-client/-/solr-client-0.7.4.tgz#d5c2a671491bc3e22967da28f29c025a095cb086" + integrity sha512-F8nrlGxR/wPIzWlNTSS5oi50qDTBbb8cpf5y3IS6t10s6M65CxN9pC6ekoantYlJNtvpEUIe+UV00DbZGm+QqQ== + dependencies: + "@types/node" "*" + "@types/stack-utils@^1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e" @@ -9559,6 +9566,13 @@ is-color-stop@^1.0.0: rgb-regex "^1.0.1" rgba-regex "^1.0.0" +is-core-module@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.2.0.tgz#97037ef3d52224d85163f5597b2b63d9afed981a" + integrity sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ== + dependencies: + has "^1.0.3" + is-data-descriptor@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" @@ -14692,7 +14706,15 @@ resolve@1.15.0: dependencies: path-parse "^1.0.6" -resolve@1.x, resolve@^1.10.0, resolve@^1.12.0, resolve@^1.13.1, resolve@^1.14.2, resolve@^1.17.0, resolve@^1.3.2, resolve@^1.8.1: +resolve@1.x: + version "1.20.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975" + integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A== + dependencies: + is-core-module "^2.2.0" + path-parse "^1.0.6" + +resolve@^1.10.0, resolve@^1.12.0, resolve@^1.13.1, resolve@^1.14.2, resolve@^1.17.0, resolve@^1.3.2, resolve@^1.8.1: version "1.17.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444" integrity sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w== From 0918a9cfc8cd6eebaba05b1f953495401f5cf156 Mon Sep 17 00:00:00 2001 From: Jon Tyson <6943745+jahjedtieson@users.noreply.github.com> Date: Mon, 15 Feb 2021 19:58:51 -0800 Subject: [PATCH 219/239] Solr: Query integration progress * Translate object type enum to string for Solr queries * Added debug query logging * Implemented parent->child navigation via Solr queries * Attempt to handle "objectsToDisplay" (used for root queries) and "objectTypes" (used for non-root queries) * Handle filtering by unit and project * Retrieve needed data from Solr for population of query results (idSystemObject, ObjectType, idObject, Name, and ChildrenID) * For now, retrieve 1000 rows from Solr ... to be replaced with paging * Extract initial metadata from Solr --- server/db/api/SystemObjectPairs.ts | 21 ++++ .../impl/NavigationSolr/NavigationSolr.ts | 114 ++++++++++++++---- server/tests/db/dbcreation.test.ts | 18 +++ 3 files changed, 127 insertions(+), 26 deletions(-) diff --git a/server/db/api/SystemObjectPairs.ts b/server/db/api/SystemObjectPairs.ts index 562b456a2..79ff25336 100644 --- a/server/db/api/SystemObjectPairs.ts +++ b/server/db/api/SystemObjectPairs.ts @@ -364,6 +364,27 @@ export function SystemObjectTypeToName(eObjectType: eSystemObjectType | null): s } } +export function SystemObjectNameToType(objectTypeName: string | null): eSystemObjectType { + switch (objectTypeName) { + case 'Unit': return eSystemObjectType.eUnit; + case 'Project': return eSystemObjectType.eProject; + case 'Subject': return eSystemObjectType.eSubject; + case 'Item': return eSystemObjectType.eItem; + case 'Capture Data': return eSystemObjectType.eCaptureData; + case 'Model': return eSystemObjectType.eModel; + case 'Scene': return eSystemObjectType.eScene; + case 'Intermediary File': return eSystemObjectType.eIntermediaryFile; + case 'Project Documentation': return eSystemObjectType.eProjectDocumentation; + case 'Asset': return eSystemObjectType.eAsset; + case 'Asset Version': return eSystemObjectType.eAssetVersion; + case 'Actor': return eSystemObjectType.eActor; + case 'Stakeholder': return eSystemObjectType.eStakeholder; + + default: + case 'Unknown': return eSystemObjectType.eUnknown; + } +} + export class SystemObjectPairs extends SystemObject implements SystemObjectPairsBase { Actor: Actor | null = null; Asset_AssetToSystemObject_idAsset: Asset | null = null; diff --git a/server/navigation/impl/NavigationSolr/NavigationSolr.ts b/server/navigation/impl/NavigationSolr/NavigationSolr.ts index f3b4b36b7..b81626623 100644 --- a/server/navigation/impl/NavigationSolr/NavigationSolr.ts +++ b/server/navigation/impl/NavigationSolr/NavigationSolr.ts @@ -34,7 +34,6 @@ export class NavigationSolr implements NAV.INavigation { // #region Compute Query private async computeSolrQuery(filter: NAV.NavigationFilter): Promise { - LOG.logger.info(`NavigationSolr.computeSolrQuery ${JSON.stringify(filter)}`); let SQ: solr.Query = this._solrClient._client.query(); // search: string; // search string from the user @@ -44,16 +43,19 @@ export class NavigationSolr implements NAV.INavigation { SQ = SQ.q('*:*'); // idRoot: number; // idSystemObject of item for which we should get children; 0 means get everything - if (filter.idRoot) - SQ = SQ.matchFilter('idSystemObject', filter.idRoot); - - // objectTypes: eSystemObjectType[]; // empty array means give all appropriate children types - - // objectsToDisplay: eSystemObjectType[]; // objects to display - SQ = await this.computeFilterParamFromSystemObjectType(SQ, filter.objectsToDisplay, 'ObjectType', '||'); + if (filter.idRoot) { + SQ = SQ.matchFilter('ParentID', filter.idRoot); + // objectsToDisplay: eSystemObjectType[]; // objects to display + SQ = await this.computeFilterParamFromSystemObjectType(SQ, filter.objectsToDisplay, 'ObjectType', '||'); + } else + // objectTypes: eSystemObjectType[]; // empty array means give all appropriate children types + SQ = await this.computeFilterParamFromSystemObjectType(SQ, filter.objectTypes, 'ObjectType', '||'); // units: number[]; // idSystemObject[] for units filter + SQ = await this.computeFilterParamFromNumbers(SQ, filter.units, 'UnitID', '||'); + // projects: number[]; // idSystemObject[] for projects filter + SQ = await this.computeFilterParamFromNumbers(SQ, filter.projects, 'ProjectID', '||'); // has: eSystemObjectType[]; // has system object filter SQ = await this.computeFilterParamFromSystemObjectType(SQ, filter.has, 'ChildrenObjectTypes', '&&'); @@ -71,7 +73,7 @@ export class NavigationSolr implements NAV.INavigation { SQ = await this.computeFilterParamFromVocabIDArray(SQ, filter.modelFileType, 'ChildrenModelFileTypes'); // metadataColumns: eMetadata[]; // empty array means give no metadata - const filterColumns: string[] = []; + const filterColumns: string[] = ['idSystemObject', 'ObjectType', 'idObject', 'Name', 'ChildrenID']; // fetch standard fields for (const metadataColumn of filter.metadataColumns) { switch (metadataColumn) { case eMetadata.eUnitAbbreviation: filterColumns.push('Unit'); break; @@ -83,21 +85,36 @@ export class NavigationSolr implements NAV.INavigation { if (filterColumns.length > 0) SQ = SQ.fl(filterColumns); + SQ = SQ.rows(1000); + LOG.logger.info(`NavigationSolr.computeSolrQuery ${JSON.stringify(filter)}: ${SQ.build()}`); return SQ; } private async computeFilterParamFromSystemObjectType(SQ: solr.Query, systemObjectTypes: eSystemObjectType[], filterSchema: string, operator: string): Promise { - const filterValueList: string[] | null = await this.transformSystemObjecTypeArrayToStrings(systemObjectTypes); - return this.computeFilterParam(SQ, filterValueList, filterSchema, operator); + const filterValueList: string[] | null = await this.transformSystemObjectTypeArrayToStrings(systemObjectTypes); + return this.computeFilterParamFromStrings(SQ, filterValueList, filterSchema, operator); } private async computeFilterParamFromVocabIDArray(SQ: solr.Query, vocabFilterIDs: number[], filterSchema: string): Promise { const filterValueList: string[] | null = await this.transformVocabIDArrayToStrings(vocabFilterIDs); - return this.computeFilterParam(SQ, filterValueList, filterSchema, '&&'); + return this.computeFilterParamFromStrings(SQ, filterValueList, filterSchema, '&&'); + } + + private computeFilterParamFromStrings(SQ: solr.Query, filterValueList: string[] | null, filterSchema: string, operator: string): solr.Query { + if (!filterValueList || filterValueList.length == 0) + return SQ; + + let filterParam: string = ''; + for (const filterValue of filterValueList) { + if (filterParam) + filterParam += ` ${operator} ${filterSchema}:`; // TODO: does this work for && and ||? + filterParam += `${filterValue}`; + } + return SQ.matchFilter(filterSchema, filterParam); } - private computeFilterParam(SQ: solr.Query, filterValueList: string[] | null, filterSchema: string, operator: string): solr.Query { - if (!filterValueList) + private computeFilterParamFromNumbers(SQ: solr.Query, filterValueList: number[] | null, filterSchema: string, operator: string): solr.Query { + if (!filterValueList || filterValueList.length == 0) return SQ; let filterParam: string = ''; @@ -109,7 +126,7 @@ export class NavigationSolr implements NAV.INavigation { return SQ.matchFilter(filterSchema, filterParam); } - private async transformSystemObjecTypeArrayToStrings(systemObjectTypes: eSystemObjectType[]): Promise { + private async transformSystemObjectTypeArrayToStrings(systemObjectTypes: eSystemObjectType[]): Promise { const termList: string[] = []; for (const systemObjectType of systemObjectTypes) { const filterValue = DBAPI.SystemObjectTypeToName(systemObjectType); @@ -141,19 +158,44 @@ export class NavigationSolr implements NAV.INavigation { // #region Execute Query private async executeSolrQuery(filter: NAV.NavigationFilter, SQ: solr.Query): Promise { LOG.logger.info('NavigationSolr.executeSolrQuery'); + let error: string = ''; const entries: NAV.NavigationResultEntry[] = []; const queryResult: SolrQueryResult = await this.executeSolrQueryWorker(SQ); - if (queryResult.error) - return { success: false, error: `Solr Query Failure: ${JSON.stringify(queryResult.error)}`, entries, metadataColumns: filter.metadataColumns }; - /* - const entry: NAV.NavigationResultEntry = { - idSystemObject: sID.idSystemObject, - name: unit.Abbreviation || '', - objectType: eSystemObjectType.eUnit, - idObject: unit.idUnit, - metadata: NavigationSolr.computeMetadataForUnit(unit, filter.metadataColumns) - }; - */ + if (queryResult.error) { + error = `Solr Query Failure: ${JSON.stringify(queryResult.error)}`; + LOG.logger.error(`NavigationSolr.executeSolrQuery: ${error}`); + return { success: false, error, entries, metadataColumns: filter.metadataColumns }; + } + if (!queryResult.result || !queryResult.result.response || queryResult.result.response.numFound === undefined || + (queryResult.result.response.numFound > 0 && !queryResult.result.response.docs)) { + error = `Solr Query Response malformed: ${JSON.stringify(queryResult.result)}`; + LOG.logger.error(`NavigationSolr.executeSolrQuery: ${error}`); + return { success: false, error, entries, metadataColumns: filter.metadataColumns }; + } + + if (queryResult.result.response.numFound > 0 && !queryResult.result.response.docs) { + error = `Solr Query Response malformed: ${JSON.stringify(queryResult.result)}`; + LOG.logger.error(`NavigationSolr.executeSolrQuery: ${error}`); + return { success: false, error, entries, metadataColumns: filter.metadataColumns }; + } + + for (const doc of queryResult.result.response.docs) { + if (!doc.idSystemObject || !doc.ObjectType || !doc.idObject || !doc.Name || !doc.ChildrenID) { + LOG.logger.error(`NavigationSolr.executeSolrQuery: malformed query response document ${doc}`); + continue; + } + + const entry: NAV.NavigationResultEntry = { + idSystemObject: parseInt(doc.idSystemObject), + name: doc.Name || '', + objectType: DBAPI.SystemObjectNameToType(doc.ObjectType), + idObject: doc.idObject, + metadata: this.computeMetadata(doc, filter.metadataColumns) + }; + + entries.push(entry); + } + LOG.logger.info(JSON.stringify(queryResult.result)); return { success: true, error: '', entries, metadataColumns: filter.metadataColumns }; } @@ -171,5 +213,25 @@ export class NavigationSolr implements NAV.INavigation { request; }); } + + private computeMetadata(doc: any, metadataColumns: NAV.eMetadata[]): string[] { + const metadata: string[] = []; + for (const metadataColumn of metadataColumns) { + switch (metadataColumn) { + case NAV.eMetadata.eUnitAbbreviation: + metadata.push((doc.Unit) ? doc.Unit.join(', ') : ''); + break; + + case NAV.eMetadata.eItemName: + metadata.push((doc.Item) ? doc.Item.join(', ') : ''); + break; + + case NAV.eMetadata.eSubjectIdentifier: + metadata.push((doc.Subject) ? doc.Subject.join(', ') : ''); + break; + } + } + return metadata; + } // #endregion } diff --git a/server/tests/db/dbcreation.test.ts b/server/tests/db/dbcreation.test.ts index 2506eccf1..ae25f6ee4 100644 --- a/server/tests/db/dbcreation.test.ts +++ b/server/tests/db/dbcreation.test.ts @@ -3335,6 +3335,24 @@ describe('DB Fetch SystemObject Fetch Pair Test Suite', () => { expect(DBAPI.SystemObjectTypeToName(DBAPI.eSystemObjectType.eUnknown)).toEqual('Unknown'); expect(DBAPI.SystemObjectTypeToName(null)).toEqual('Unknown'); }); + + test('DB Fetch SystemObject: SystemObjectNameToType', async () => { + expect(DBAPI.SystemObjectNameToType('Unit')).toEqual(DBAPI.eSystemObjectType.eUnit); + expect(DBAPI.SystemObjectNameToType('Project')).toEqual(DBAPI.eSystemObjectType.eProject); + expect(DBAPI.SystemObjectNameToType('Subject')).toEqual(DBAPI.eSystemObjectType.eSubject); + expect(DBAPI.SystemObjectNameToType('Item')).toEqual(DBAPI.eSystemObjectType.eItem); + expect(DBAPI.SystemObjectNameToType('Capture Data')).toEqual(DBAPI.eSystemObjectType.eCaptureData); + expect(DBAPI.SystemObjectNameToType('Model')).toEqual(DBAPI.eSystemObjectType.eModel); + expect(DBAPI.SystemObjectNameToType('Scene')).toEqual(DBAPI.eSystemObjectType.eScene); + expect(DBAPI.SystemObjectNameToType('Intermediary File')).toEqual(DBAPI.eSystemObjectType.eIntermediaryFile); + expect(DBAPI.SystemObjectNameToType('Project Documentation')).toEqual(DBAPI.eSystemObjectType.eProjectDocumentation); + expect(DBAPI.SystemObjectNameToType('Asset')).toEqual(DBAPI.eSystemObjectType.eAsset); + expect(DBAPI.SystemObjectNameToType('Asset Version')).toEqual(DBAPI.eSystemObjectType.eAssetVersion); + expect(DBAPI.SystemObjectNameToType('Actor')).toEqual(DBAPI.eSystemObjectType.eActor); + expect(DBAPI.SystemObjectNameToType('Stakeholder')).toEqual(DBAPI.eSystemObjectType.eStakeholder); + expect(DBAPI.SystemObjectNameToType('Unknown')).toEqual(DBAPI.eSystemObjectType.eUnknown); + expect(DBAPI.SystemObjectNameToType(null)).toEqual(DBAPI.eSystemObjectType.eUnknown); + }); }); describe('DB Fetch Xref Test Suite', () => { From 8f69246e930aaeae91f6d0c04621dd43b263f3d6 Mon Sep 17 00:00:00 2001 From: Jon Tyson <6943745+jahjedtieson@users.noreply.github.com> Date: Tue, 16 Feb 2021 16:08:17 -0800 Subject: [PATCH 220/239] Solr: * Enable explicit testing of NavigationDB by enabling specific creation of a navigation implementation instead of using the configuration's default ... and allow for the configuration's default to be known and used by the system. * Rename Solr schema fields to represent sourcing and usage ("Common", "Hierarchy", "Unit", "Subject", "Item", etc.) * Implemented extraction of metadata from Solr response and translation into metadata array required by navigation interface --- server/config/index.ts | 1 + .../config/solr/data/packrat/conf/schema.xml | 158 +++++++------- server/navigation/impl/NavigationDB.ts | 30 +-- .../impl/NavigationSolr/NavigationSolr.ts | 160 +++++++++++--- .../impl/NavigationSolr/ReindexSolr.ts | 200 ++++++++---------- server/navigation/interface/INavigation.ts | 67 +++++- .../navigation/interface/NavigationFactory.ts | 7 +- .../navigation/impl/NavigationDB.test.ts | 6 +- 8 files changed, 387 insertions(+), 242 deletions(-) diff --git a/server/config/index.ts b/server/config/index.ts index a17a8ed50..4103e2133 100644 --- a/server/config/index.ts +++ b/server/config/index.ts @@ -17,6 +17,7 @@ enum COLLECTION_TYPE { } enum NAVIGATION_TYPE { + DEFAULT, DB = 'db', SOLR = 'solr' } diff --git a/server/config/solr/data/packrat/conf/schema.xml b/server/config/solr/data/packrat/conf/schema.xml index 5a1d3ebcc..0d6574c7e 100644 --- a/server/config/solr/data/packrat/conf/schema.xml +++ b/server/config/solr/data/packrat/conf/schema.xml @@ -7,96 +7,96 @@ - + - - + + - - - - - - + + + + + - - + + - + - + - + - - - - - - - - - - - + + + + + + + + + + + - + - - - - - - + + + + + + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + - - + + - + + - - - - - + + + + + - - - - + + + + @@ -105,17 +105,17 @@ - - - - - - - - - - - + + + + + + + + + + + @@ -130,11 +130,11 @@ - --> - --> - --> - --> - --> + --> + --> + --> + --> + --> RkMp^@#)<3bc|u)! zLy;qX6=|1V?@~{nM66(AckiZu4xjUAJ$lEC#oF^@j}KNdXk`&YL$w|A?Qt#g9$5e6 z))d)gKRoVR?PS+8$@V7()rLZr}8vvi<+qi-r7_0h@8RUC7u}`ycBI0WOndBSrVm z6i;s_CP-uZgR+pZUJt)ae9naUw{_k!66ro?3@tqJi=~j>v%&O;sD6{d$89xZteTe) zv`zUF#1MDq1YgT;(KFD^S+>nwi%SxpvF|DKU0m0|VU^(A%hdO}S+LLg5t6f~wCEA^ zCocIJUSpe;Xim+eZiKRV{C1-#<-1m=S!(rGGZ(4PYp>uF(eqhzPCxxqT5LL0AZN)X z@blaS5JKKGUU@QBzpK^K_qF+*Xl^C6X+SR_%wiC|ee33ijE_Ioi-zGIwS~P~41e5) zXrD#nWGvdu^EDfEH-~_8!nftIWZ-oc5c`;RR-;pIX=D5Q`UhO3vUo+jvR?>vfItZq zJDp^FBQdl)L8jf>*nSi2Aa{-bmm2(Q*^Ku5PciA5r4imx**J9|J*!RXRE=;YoqOXc z1?(09j=Zx7{F48=)%*uLBK-EB+{p=g{s@Bq0WL!(2%XaMyhRpgRo%!aonZ4X)%`%w zhDbUFgPuyCz=VQLT1B@t&J?uD|HrYi!1%McD8G!$ zJ&y}hsJ!N*Ds6xicfy<_)igYmRnpUOn=DAJqoa`@0}Dgu5Hj~}`Y<)Z#b#7Gyp!=X z*M$yC1G*C(FeBe;L~2&cgxh}}(zScPy8^gE$`UtJ-GIMER|+3;os-&BztbQ{zTLRZ z#9R1y^4<&+*c>-Ok_n4J&>h~0ew`_Sm~H7j>m}E2b)_p3b;sgSR4$X*9#kzsjDhj- z39%RxFIFhBVbkg9OS0f@&%{W>pM*{s;e4tWJfJXf;7vfABx935%8Rbxcb3 zoO}|=SSuY?EBR2!M4VzcKz#Qk;7(87lP>-r-&838+b3`YC+9VM-%@^gC|zl9)n+!I zf$7_Mj=83oa!=`UU@5QmSF!MUu(Pl z201iL+qSqN+L)cV%X>31ah(*5H}}GP(@ZN)xKi^lU%q}}Ei=D~;-#)_;%c^YD=`-p zeKj~B@qVc4e4O%ZYN#7)98m!a>m+fj)%I|l@YXWgiN~9`1PPI)JqIy2N@0l$;1f2P1yL zeYi&P*Xj)OI&a=vAh+w1vN}+C=ZGLy=qP%m7%C{16B`vx%@+$373Rs)*0BZ$6vTd2 zDEU{|ZI#ML^faU(zr6EUT=R1AymWgMB)fu+ZJk)P63R$1(b=i4sg&eSmu(0B#rWLF zk48lbw>fSmL-F778=pIc#)t@~D7#;DjeJ&L`Pa$#j`X!DQKHXH%fdx9CZ-|B$QVxi zPXOx0&?x$;Fadiw_zy=r3jXul!z|&sj72V6fqxlf<2>7(9gu49wk*3_@v)1SG*Z*g zE!jdYnT&APEa3_UhHTY2oA7?%Lm(X6<|jz0>ztQ@kvPdbPEW@_W_X`r`wb6EKE4dD zqh{o5T={6vhcK}sCLOhOqBPAqm2*h?oO#>jiq=+}b}nL8np=vsRh{}R;4P5PWk0Dg zjpkB5nEj2sYBwK>4y56*RyoM@Jl_2p-g$e5#b|yi!h5XfZYoxbzQc;vGPf^Wp%@3F zJwsoE7Xz>VL#3i=y&<>rkt}6^EA8HD&1JevZgtf#BpP5H&2}x{$*-!h!(m9CuiK|B zZk4D|rqsxJ%**suUP%O#LMo7(iFKkwftwImQb(OSHJ6p-B}UCyPmnaUP-mSz)VID8 zn?U}54e&wr7p-FQLbPFwAp9S23Wfe$DcqUA`}++vMj zrugdCt}l7n9U(GWF*#-Ys|=?#4kA?Gr+2ap3Ow^qH}q6F(>s;lP~JG2N2tr{MhmZs zF~}<}(|R?zoo{Gj*=Cpo?kP5YQkT?JiN^<+*_f1jgvR+n35oe?Jkn+~Xb@NKM=|1F z)&)X!O4TwfI=HPI^v;n_K0i-wq z9e*kV;2fHI%+9Rk$sskMRK z(sQ56KKj-V?B~`Fry4}wQI~^~QFh&Wo{1RV~)dN!?!zYVFy4px;+5ek>5WlXr7L9_&i;k@>{J8JlMh1 z7mqtn{$})$PvA9bm6iKts^cdJw$6`D5t~WyLYz5gIL*A9_n{cE!s0XHaXP zEoI?jCLornisR%UuG!}y(Jc+5Wzb#vmg)~?yw-hb9rg@YPY7P;ciVtMU?@8FWM)&p z1m4<&4LhzQ7A#Bi;1F}JUgvqDbw>NqDM{VstWU14lne1hrlo}cW8~l>ljm{v_}!3h zhG73yN{;MRMl(Jh{*Nx>1&N`dv@PQpSk$AaYwNG$oJ*W`iLLQ=`@fQ6Ij`+O?0EF2|PtCa_l)EHm+Vh3?TNZoE1Aqdf{bmL`f}KFGCcKS!)GU8WF5M74uW=72~nf zC>?*U$^I2N4!gsS*hB-F47EqhHftc|Ke^9;QKgUIUxWPN?BqeX+_>xM`2Rv+gPI0gC4bU*UOYZ3gV;hBwt>AtI6q2M`_ z$Js>DdS|^COT|mv!zQz3pKTv>^PFi0!8Qc%F))swh=_>7Nlter+<$ccc+v!JVA_3E=yk2 zZwK7AeJn{`b8_l6jy)2D5QX$cA94vCB z@k{z7)~`W%QlZmItRX95rnB@AdbahR%f2IVD9?j7+~cgeHqm4mUOC zzk$pLH#$AregC|6oNt_UwQpo+x29)ff19-D94*$Eq0Rf0lu~wn%`r{;8G=#@+ixs@ z2Ne&En3%ci3~kTI-tb#ewNlSmUmyJI7oS^dv9yE>xup^W)PmO=H%&2%++}VjnZA}QXJ+q3PgN zrp13BwJC|@-gm+^HgizV@U`n%a+5zXUdCqpRKJS2>Fm{vgQ9Djij`$fib3&BCBHbG z6EB|IBd{P=+&TBXYSr%)2&7>)g~D};zc(sk*jBN2cl@NdmpDSy5_*moU*#BJcJAx7 z=ySakYCltCw@68#dMTy+a|`W^2Y6LVB*6a}&)V-;ysI_t%jDGR9>X{H#}6Q*2~$jv z9@%+5Fq~q0dk>1Tobh?QUcNM7IhK0b&+jp4U3Oqv(e-`!6v$%34s))ckYc^|uoBl9 zVeyQU<2w|S&2JP);XXsIc+VU%VYEL~q2KA8efnYWTmRWl)L&o%`163r=W^8H+YMI* z?(riM^uaXFd0j%f~NX?Dc}nmkt^pHf`&|!a3brPZ&mH z%i-f4NNZqV|MGaJ<^5cnzUf~_vwA}FFLeA>1^5oh-=cO&`xDbaV%mUv(#d+BT*FK7 zWjEoqx%72s?F zj^Aifs6j^r2MXfT7&{(NeAQOj?}b~{|L-<^0;Ul|v_j#9I59AtBIIk(FOx8-<b95Z)hBpEcP}KbYa%e zBEUqI00vu=yf$f2Nwj8>S;y?lK8dfx{7&HA#04B9Vdm?#?6y)!63|9c*y*jlUlQUm-H$SDJ`&mXp->v3iO6-ehQ}8+sG7d?roZ`s zd7HHO*=Jgzg#SdzlJ&>tiJ#D$w*KXFD-pnNwM2HZZ8^eq1wLJuuXBj@|x( zc>AL~_`Eu1a)KFL?fVS#k#h#e=Xw&RzFSVvE8Ue;zi5VdMJm_=2gev%;s|`20d$GUUK!ULva3%pnOBy8FPjwjU1NuT`%{n%#{SbUTZwh7ARh z!yA4vErG)^F)N0z$A&A@OM0gMsnx}u_ivJSg75q17uZ{$_@Y&G&9WaXm6{>5Uqy54Fzv46!^S0uq%uRy=kb$1kSl5yw;)-e(v7>sOFY; zr!V04VPNr}PuwB;lSiIWVJ31BAQ|#c)`P2jZzYJt71VBJRcf~C{}3BWbn@Di+$D`O zg6nF)Rx+nqg9>13Md&Nekihst_O;m4o27y2UUAasAB~DxVZ(sSt}M-n2-)`lPa@^Z*)cq5uLv4mIdormTJ8YgZJzNRET&~T;x!@{t7Dpk(; z5e+6UVQz(+&-xuw+k<;kFA<%0U{}w(b?FK;%}0VCs4-Jui(Fsp`ep|ejqy>a4uQRu zuhFBn=M>7fSLhzD3%KgMo82)vs>L(&h#Ikbt`hWj>dyFnPt8{6a`+oak(9vj(NO`0wWTU@?c8eBMW4ix*cM-lpp@a{DKSZ;v3r(cd(0 zr^}4Vt=33p%4SXV=X z4r9Uh!(t@0+8ab<63;RjqLtu)GiUWPluvlHvu05LD>l4ykDw!h^;#=~7Pg*46%JQ&G! zGPX((2ZiB(ytGl}4ZIE6=qb>Wgx88Gy23dtL#k6lx)D&=t(L*HF=R$s?Q@2#TQhqe z_VpU}xlCNPG@Q!e<1=3Ly;tS)HAd|FO*B`bRj^p&)S)9kyL@!*Vxg$psa{JMc+QlR zlv2yBAAs7_)n?@OH*)-rF*8^19?q#B6X6Xkf$8Dhn%l*9ge-ctMq!6%@YP+Ogn zXn_ulJeSyyf0I9cPSSEw8jbg624^NxD~f$5b=zH4oQcRQcJLhK&|xwq_c|%6Zm7V@ zf{WUSUmYwluim#nr3^6qjI!?f{4y7%4r7g*RLZ@oSci@y@+rM5(~MNgIrFv=Nmom& zAyU&}b&#Q%)U?ut=-2lxHT$suhdT|I4TWrO8=R@^I zmb?M2L-wmeWZw4Jz8b%NPtjleeLC-^wiL7i^G_%L|A=rOr*`3uCWMbe_79TiFZA1q zl+ZE2JJFb-2P_s(cqHDohm^=d7}Fqyyq7hVo)L#|wKQPU_v@^T{huTnKIKIQe%5h5|kBS~UR zb3GHp4LrcksoWguS1rn_=h}21EWWg*{xcOnigU6$|8Y6arj3I!35hzni*$1F%c(<3 z{bb5`*<50b_BE~2&p&Bh4VTJ2Q@pOl;}kk)LcbuB zmKb65X^nEP2&7p(KV?NgT;2+SGOA3O5y}l8y!&7d&PP{@lfIE47XHIHr&c}Yiz!Qzy zgDDc>+QSmS2|Kq_XOazA3W>!fRPvNttf80Q#Gl@}0@h)@oRyl)adsbBqv;?O|E_n~ zaD`-x$5_z)5jf%%YU})|Z!&ToaP|C`rj+G5yU9`Uwsbh3{DjI`L@%(G}4XrDu( zJ84OahTUu+{>jXun~BNg=K*};>ySKJpm_T2 zRBnUz?^iB|6N*waxp^suIsu+M;H%U6_iM_G7UU(>7kHe;Y16T`lwAm)-fB9)s=95|1Puk6QZWnW?_^H| z%Y7_a9m_{8*#7SHT3q71cnDz(*p(TK!=Nj-QZ-Tcgrg9}naZgN=rS6S?Z0Ba`V}ki z5&Rj6Wcn_CpU-qm|EM?4oW)}$=&_^i`MS3gg8OMlaJ`=^=_pP#=2xX0iDC?CU>~Vm z=OubhHz#QvlQP?9?f9YFt0P{65tdbiX#tp7>x{FzF*4B+?ZDfRlpsF+j^MJc4f&37 zw~Hwbo7+aZ`d(=URl{fs8i(H9Bc)JIS6ViEiHkT6XZZAyzvIjde>pmSN9k6o)Lb7Fmk~tpwi)h~Gud7v5Iw()6WqN$=dMD_NV4};yS4ru z;0e=KxM*!R|5h)H>N8}Eimnp)lYt=a^o%jTG$@t!O`!|`l)}<0T%JHtU zN>tU`A&)7!2x&5P+gGAiIRM%TEyTKJ+}fVYVCR-YQI`s$Eu(G2&rgoA3@?dnOYK(P zRG|UK7yU}*!s}8_kD}WFo<1Apamma^NY5X-qe4W=o030&z1QM$rchpG6@O`d>R|1N zSzkq5HTUCjHcGdcvd#S43l5U!V-XIXjc8Q;XM)^5(R9Dx2Dh2~rHy}|kGV8YVoCIB z$5FSJNUNnYWs!S0rEk8tKIQtp!w`JPK{S^Vnt=nifvLqOKu$u?c4H*75h3GTN;*NR za##5p0*js_xvnmxKAy6+Q2|dM^_UvhU(oy0*869(ijGc$QZUq*o=OWoE`5!Bo*3u$ zs~{hr1ButgFYn7eoI8@Y9bZSA%`4I0*ecv$D+;bVyaMWE+cPX5$#EAsdxEEj zwu4Nn%o1MU;3~t@elp`H75J?luy5w$MGQH~_%BmBX2oCZXff<2A-3G7KA-3<&Al1(XtZGMkQvO6^ zl`GH&K@xfdwtZKMYqz_&eddwDz*9C*;o*W+ z4N<0o1nTI-lY-{yR?#Jl-Y!B>F2Xn_K5AJ)Btzm%fL2Lz1#}Z|I)pUNro<4dsC$m` zLryCy(DMS1U}8fNH%c)pHK|K|Ct;Ws_MB9J^r5zoF5;`G`l-0w4B$Jn(4=*GnEZ+5_^fM zwfM`3k0Ds~6@hVp*GV?=^a@?{Zw)Q@N0BBxWKkXF^LJh!2!HQjKkUR?TTZ1G1H)rw zg_|fD*??tQF1~!$ExT}y6UH#3iFZW%}w?p!}Y2ASa5|hjZ>5Xo0H?5w3 z#x8y49~`MDI-hVA749y{0(oI|5J#^ZgYSJdne{vE&O7eYD;*jtm5B3_5}2whE@CP! zxPJX`K^VUqJxH}RsW-EUCf`iA*U7Fu%t0hWFRZ04!mOYz_+1y4S{y6TE~bxL9knC- zP4+BUMiii{Tw*#IkxlZP@d?*q{!x3oQR(v-{2EHRbiLviaoC z9)mkCuQi4$$*N`zHo^y3Il% zAaE&Vn0_(4GCKJn+I&i&n?1G!)LoAv@X}}Camm96e8J7>4YB*-2>U`t*XxzQRXI3_ zrFrtWdkq5YEb#|XHWCd|yzRGKA^N(kvwa);X5$#+bGzpDw?sTME2|+I&|9^h|zJ9lApbM@kKmt-*5+ zKjNQGQL0zu!|cv|E~p~Zr(0>Nl*3dOb>!*gX}?!~eOo%&pVWT7sgJxH#o@9w`ohLJ z&_THCqFr$&lB8qY(e-{Pulv^hmiT&ci*uT8!6Nj=hnQY&?7Ct)9*NpqwN>N$G`#gm zWUbSul0#|f7qg*Wmz|qE%Z$a9Ux;za5Vd#9DjoPh^T6)3`;F+d+qk;(Dqmhs;iK;v zRwiU36Bp9W{5&t|HpctzF?F+8_1tSaXI1Q}jkEpGJ)sQdHsVRBGfsq8_F&cNJmFrX zjY5C)NyB3GqoZQwLqk+iEb5Z!9M&o_EP5%ct#q3OKZWS#Yl0W1*StXubeu$G(fFAH z`>u2Co4?cTBK^uNQaZf7;-WWUzEm{0wG`KaXSR@%@Rr?|?M0N5`znw2rkSUmXsdWi zFERd&lPC~R0tipPumVU9M<9!p1en z+^Jr1kbGcBn%}&W$)(dX-<5fxw;#!En-Xxqh&`_cX_;>enqC< zncZ9Gq-t{MACnjKnJj-hP+0j^m>Ysi1)-LDPP_-YqRh?HaW9^8i2$A`Z zDDMgPII!l>-S4Iw`UKMdqT7o)A#eH#Au^KF%NV~kPP_3ViDp)rfQYF6c9P>Nz$*l; zNTJz2*T-o-W&a)gc5(N7ck<0*V`X9hig;)^l=a8AsFR2$liK3I&z+pNmW!9pQ`|1q zZu~er_qZ&;NZC}cT+t><9%3d}U1%vHq%J55~l;U&L%a-FbeJ^`#gh8C;(y1v_ody)Rg2JD$ltRs2@B zb^^}B@qPQSoNc{qR9_yc1m=AKTDMAT6Lg5yD?YOCn~rPgm8GxpJhvOZV!$()8S^%E z#5eOWqD68an`q?ZDLJwRGKNG}5j1OD`rPJt-yGs_@^ab+_H$smTs0y0TKf15+JB^i zhI%zL0f#l-&byo7?W&qdkt+cgGr^4xY{^&~)YB8Una!tyuLtkQa8{^wYO5*BeC|2% z;umU?)qa>dgb0IhsdbH}ta#(wOf?UG6ORFq=c)z$>X^WC)D{V)iqT6(&fZpGA3J`> zN`GM@h6&)CLBtK?9f4>~>!#2BlIPgOQFh&i_IjS}%8!Nm$t~+eK2H?d$+Nhld~W^Aq?^M( zS8biIQ=x#v$Y~|VeC8h=ZL0v|3|LD#Z=G_A-_CACXU^gA3d{x*U4YX^YKKS*J^ z;!oaw-iz~0BENMg5*Im!@WxcGWR?a@Qgny0_3{|VwP6NpT%p$o8Qf|UKE77rmwW*@ z&1}0Tu4jfuFI9{VE9);EZWAZ#yaRnUZ|)Tz;Q>Z?d)W}!<#0!zZ_^nyBI$uP_Whij zj}y#>Iko=Vp9%1Z>mVWokfhwM-EeAqI$?Wj6nt?`-ycc3huSz@XohZeFxW`N@P6go zMqVvx!N|U^%ed~Gcm+hw`-QmB7J^w3rWEL@UEWlgr7sFA76C=wH z6ABJKr<1tOXXFqrO0PZQBE7Yh24GZwMV0Wv*bZHeFC?UUrJ%=3^uWaCgzlu+zy-92dCoDUHY0RJaY*=+kHgo#@71RiujM7l|7 zSJ!}r96yIJ08cvdt@3eJ5l% zdS&tkQXXCx2~RV`hp2vEAdcRH9{c{S9t6NZ7~2~o0;!F^HJ7bg?i|V%lEJDZHNpzp z*&{1-d zL2K)O2;td?D)?~9Fb2pom>DLJ-T*Ci@%&tujQb;e1myU%n{3mODWV}5+ExI2taJ6z zXL-qKhj`wyYHmPh5fn`GajZRBzRsFP)z%k*r09{_f;vl>=vzRW3W{daa3Ow?TwPpD zJsCqXC0aD+?yQ6BsQrX(LPi(4;GXuU{?qnbd*vs#+n$}3MxHQf1hvS^Pdu_8Y>U$h z_7G1yAik-krq4T1(#P7wzcz6$vLpFyhJ|yvbL*(#^sX;ta#O!p{|3HvuaBxzuFQ7>IQnJxcA}EubYt(1Rn_+ z2bf8iGQT}vvkAT3RO%G6&h#6alzmP=zZ1!Rs`OLz_qw$;QqHVxLl&0ZI|wHE=yJ0P zv4FfPWG{w?)F_Q|mwfno!3h$2-#RX?u;AA|4~@cnnY+6^k@9Y7kNtj2{8G9xM$KS_ zQs)(*UCQ)aLe-g3b9`GRsGAw3NfM`BxRadCpLf^E|DrsnwNZOZ%;LPAzP%b}P*4#m zOQyVMS;ESss7F{h*O3O3bgT8O=sMDb7^wES^D`U6TWq6@vev(V&rfl7cTdM4Y>mA4 zudbZYauQRfqIF;Eb%L*d0{5s|TIbaURX@$huSd>ABb@lwI~Q6JN_xz><_gnYBJa}j zvCdJqeOG$Et!X=8lI!(3OP3%ehP2j&dK6)iUVgW>8!Fn=aENavt#X3d(|u*vF`kn6 zJs<=~vMXd_A_{nzY0>BQoy+mvkJ9IMS@~_>O4GcXY~Wl=Xi%*?g2YK0!GWg=>2*AE z{k3aXmM2Wb#NJDajU5ix`No7k+{@#&!~uzX-f^iJivy(I>gJrp%A&3CgsuGj7?((= zH?nRQ6aK1F@6Rf?v*pUB*Qt1BD{CnKF>+1|KC^CIZF&elxh!X6$0z9PxH)(YP_nIK zp7D+eg?p-?aY+nG?K}-~;xyD|$Ay#v{^B&!ekq}P#2mV{!3GQ=U&bMRc(TE%tXuJQ zQlbxvvDB$zH8srSGly%V1n$-_u7yXm;@bSQY)(2?+hxCHLEx z|1UGlaIlaL6#KoxqA~T7NEu%xVm{*JAM?cEnTVvub?`^?9rY6U2G?2_(}w zrQ>zS0XkLvHV6oAUy@LrtHgxzVLt|F29|s4tTx_PYky9HE$>BA^``p@I~HEqZJo8L z>7v4u-{JKk)ThsHR?Mdpm~eMCMTuA84N48)C!Wb}!38$4sH)#h<5TM{ypmYPUE(mY z8l0j%B9b5t2LqdbaY@`Pse;)@>e`AsG2f8t6sn*Oiz(q{#z@PAO8L$71Dliiv4BJ# zMNb^lT*-6+ewg*}amE8YGG^8+&RA{V-jJ7o_>c>ffq4JM>6AM z<%%k8N{j!CueXefYg^WagOd>4-Q7L7dvJGm8iKn8clXc`+}+)s;O_1kEa=<&eBU`c z+4r71#`@8#dyHOl%~@5ms%F*mgu8(`=3`Kzyiaf%&+?uFwj@JChpKt?e6}a7+(*ThZ>`w+2&^vDvBL_2I&l9~|B(aniO_f9{IU#C1Gbknn$H(O?nsHJhi3I<(Q z(H2u@mC@ar!t@hx=9g*TwC#MCIe?-jj#Cmk5|bOR> zin#ywP{LDt!|QYYv-7x!U5XsRextg8i>4f+<3ew~ltzi|k^p1>TY=X}IKNI@46$lD zp~>yUUCj4QT``lz0-u#*9ztUsnK#$9tL%hRy*-f8xfSV5&A-HAphY+4BLCqMnT7WK z>L^B$@mIpiEsa~&hf}MmfP}S!AEAFr^&%~#XbnyBi0v1#1R2@Un#$}mmK(8t^w$$s znx~F1|a2CrzRmHlh1=W;--!Y%5w z*gnJ1W+Y;!rYD|MxW#0Oo$;MR`n@7U35TA;Ch<1(A($RXA^uqNNQrVN!L~f+JEZPU zTPNmBX0?``FE2bUSI6)xWcVF@P58!=SYq+;@T$h>IZM|@Gc7+)=Gz;r_TJxu>N-Au;!e5}gEr6GC3zcR zpmLcpOg3H<&?t3f#kmvnivC_1c$4^krA4PuvRLk+hHCnHV~ILKG7ptY8oswAyIVE;*nz z0oKHI6B#QD-nX(z>KhhY>9P3SY2;$~*&8-UW=|o2f4{ia-s@k3dRyl zzvAV-Gum5=XT+n5c+Esge7wN5(nH z`&*_TC8=i@Y^aKTzA5i`%}^xfApF1&4*C>8k| zW=%F~T6{4--U$U1H6&J>9O+)z1N7lFH&*Yr)f0RVQayi8{Fvb{?RWj*b231;Q4Whx zbKq*ObMfFae35V}C1$!oLZVXOvwDE;qa-NS{Ck}5MF?3leAuo`zs+-rN5+V58TJ-< z6P3{(YOK4Za#A2H?_ z<7n<8eDzS$P$?b1D)GjKGW+BeY>4#Zv{1mO-{)bVl!HkFQlPU=)lOcDyPMKZ7h;ar zg#L0r0cTLxHgvpYQ#YhZtb{jUp!4J%UjkU`{l&U_#_O;Mqzu94jN1$!4H!^u*;-=A zDqWhgCxkj4iUwj~ti7T*X0DUJCG7suLuoFRXN@T8IJt34>&NSOr&qTekl*UU=ON7d za`=D)l>TJDELjiqnjZ*gHHqj~3OaaaEIJb6{C>{Yu-USdcHwL^BIR_upY|>ytHV;@ z=?d3iySS~)(^tu}!K!lALt8qn1020Evq1eMk&T>(sk6A9O+pqLFG?))P+jl3?zK0% zWAdqNPj7=3{Ub(6E#Dr8909Vx?{od9((nesLi(RjpWawF{JkynxB6C%Aq z1h3gF71PvjTUfk>vQ4(CojZNCl%93UB2nmKsY9cuTX4FN)q0lSvB^?%6HtwiV4=RW zQ9oY)c}5$vc_@|Qn?+$7F3!Wqhm_#0@s@cTy%X^D9fYjEdWPo@k?Lu5lqZ+zl=K9& z`n=sjcVUd0_l7PN?aQ64<3FtJpAV4_qdK3^^7O8Jb}wwNb6vAUk^iLSx?DqAiT3ssISKDD8`dts!m5U#fz?uu$F-amo((&?=IcKzsaN)v|B8b1S%e(_3#ymSk1P0EkQ zVEX&Vr9%XFmV_yGc=(Oy99TKa&t~ovns}IE(}p@pmvnk`woeOcaxH~RHjB&4CH1R3 zm}z&?Q*pvMcWxZiUsK7{F)WddU2ro_XGZ){2PMgs>B$G|!fuhnzU{%&Rl)>AcsnLr z?#N6;E^Qrgo+hRISgg0Mbul*kVY>lr49XYN&gaj_a4zM<*^MIRYV$r>G6>vSE44lu zZ}Wawa^@6e;Z>+t_q{|(HE^p)IG!p!wah||ivmn8_sh4iNp4bnS0fSh^(r_KK zMPqYYKSH$vBVsxqA>vdzkG-mn;V#hXaV=u5g+}M2_0O5Dyxcis49$8_)~&`J!{z5M zX)VOp^Zvff7&x#2L!5A}%rihDy>7e>^Q*lo`3P4NY@)O&@BNW-eH!#aH^8cn4ir5D zg&}WTReU*bQ)>N+9g)|5Qjy)A=rEoL+o1?pWhAsHYRsT;HhVI7j zljH_GjXEl;se?SxNaYQ~uw*i?T@umfL=!3_o&ZnV)vQ9O-Az+T;cGmKZ3$gSq#@2Z zB?fHh1Wbyo+{cMmn?B=`Bv&q`Pbi!5jnF3_aox7H8b8#ys9}2SM4&x#1Ux5)a`3&c zXsfrJSMFH`4<&Ri5+KD21d z5K3>1F77y$D_$&<+#kEg7HMt}b0J7a25IFRp_>G@2*~5^D)Yj0OYgMI46i&Wtc!+95tp!T}Fu5#gGmy}3v5S!4?mA@}Nd--9#L44xvDKjspRYGwralyD;scir2*llv= zQC>0c^T2rWstA62&`bxwK;^I1`?HYaKX(#;(Tx&Nx5c^tZNVVH5yb_?VMh19)$`B^ zkG7OxeD@B6b3l!_lP%jw`KeKVy2DPds{6w+oUU%iW|Ys?(p~<;d%%30=0I-^4I~4$ z!Rd|*e^UzLB)T(RhIXr}c{NkS@_R+CL-~jXOLqZ$!t7+iwgQXqZ3|iL6)Ttkm82V* zBGpl~;zVry6Dk`?dot?4(+KJFdub4d%&3&2#Kh?itbEJ!Q=NSyXLcfyGRvd%HORPC z&N7oc;`yJxjJCyu#SWEtYz$Ak-Zj?g<1UU1O>+X2E>Xck@yEy4`7Jjt%JXEs3G$Fm zKe+IN#pVO{K*&p$1(1k!7r#U7S5pVsfKmYL0ej@3e@_wIdbQ)LkH>rn`t7_1%ju2OHU z0vM`F3X?C#@_yOkdv7Ffn>}sq&$N{x9Omjj0|UQ0L-REtVJUUOTCcL1cVJUzk-^^+ zhRFFGLTVxDvw#i5glr)WD=;AbkX6n-|E)uwvp_$YL1T~!7q3jST&+Gz949N4+&^!o z8WiGMXJp95D#5th?{8h|t<&B6Q00%9(` zaI|JxPl@3~c);pWIRLXf2cKYKX^MJ!-)i0hzZ*_{0Rtn0!}Fj*x0BzP(AT4%LBCn- znMOYSGM6n>N5(qi?#euc$aj3}*IBG8ZBCfI;`hdt9?}RI!1}9}nssKy8o-GoxrZYi()TSHAG-7d1p(Q0%olM=tCH+$<)mKt;F=pgy6F<` zXP}L^v%x!q+dHG~o|bkS+}}=^NZJ0xMSJWvokIb)$LJvzRsHF4qG;a>s;mQ3anFir zAz?au-{akOhK~pQO|Km|O@)9GWXmxeHC_oT(Q5NG<^COyg{hU60+kgI)1*~m`Tg%& zdm{iomhZ(aCgbJNuXnI4>JnF2&as=zF_tJt>mKc(q$W?Xe~gqKQ)rRjr%tma)f9@f~>?ry5==g8&T3^;)nVV9kDT0DCJtk9n4t2XYVaxQjCn2xc-WTn?{x!RO1^>+{4$UFX|v$X$l-BdK${ z1=OO2`^VxsqHOv~PJr261wI#>Z2$!MJzh{LX}Bh>-iWFVq)FJ>WQ;YLy|1));T8uOt~d+zT4%KL(zu4c#MyGw z8RSrBVyp3DJ1=>nu~wEG{2Vs9xuk6{`E}i`hNb)M(0%_OSgenD7kX3PgNsf$^f$f?^;`2JUfnbIYE>(8J%QNkI84+@opzOTzTQt zTL%(#f@{@cr8jGv`7hs46~!E__RT_-me^`3DEF7$qa(&vMy^lc+)EcjcFRBN z$nsTRK>3?>K`-JMG%|q0ei}(t@sWYWPEfh^U1qtN))OHG^hnm_K0WW?b`AS2-w%90 zzr*wP%5SUDPr)Tw#O(e+Db2yfZWe+lPb#nS;hIr!WLW+5{Fz0sg1?b=p`x|Wa_^v7 zx`L7g6PPNhU}I0$qmCTl!fDH}RG6h3*vCF`pW8Ndh2L{88Fw~xlZgKv58HjDEo$#! z(PVaIz^vEI_Q&HQ6(*coAFfkHy>3%lGh6KC_WEcX05uO_dL+| z3VcFs7(U(}^LBX2tk$~JZQH5^w~DPp>17TQsQcEiRzP9c5NFIAga=1u+BITMZDqFj>JIC)u1DE^&~HA1T*FI%Qk*G zk=(BmjYt<(#u}2g(IQ)Oj3w=D7xFu&W+sRbLz8rHLo}RXCw*u6GPhuGUywJnr%05I zAbcctj0^Qxr9}NyjRyZb?Aa5O6`r#GHroek@`!5PR#bYG50{Mrj~`Mq-%Q!Y%kN4V1KRj4$t8*Fh^_0Ek(7uEED%mZD_Zzk@$2f zRwC|j<9>$olU`_Z?Hg=U>={y{lM|Zh(dUH!Olk@#yV@j2Y;4E=mJ)T7%KKqe*$9CF zf=?`z?uCC`4b`eD@LN~VXD@&;K8bo}!&e-Em4Yz5{=$brFk~t(Jpkbb&$}D3g6!&e ze9yjU!Z`3o5j)kdD-F5bx|ET7EDld1s3Lc#LA7_o<_q7lcU+k?I6(*9TbK&~C$H7u zK?;C}(W3OwW4j5mr;74LFNw)u+gfV*%F2be6`{{_5J1p_ZfFQi&)@Om1IvIA(EY9s zzlRiKD)^_>$sw12c2ir6WlH606(av@-;ZznmIo8Sy09z5H-y{$yTEUHBohT+c|EUE z7jN9c;N6WY0wp+sd_xHgRI2oixTp6vZ+URhWFcJh_70hC?97sl&_~v&ESe0QiOU6d z@2$hp;~3g0oem&aHy8>wq632hrmag6<7a)?pu3|wf_12S7Y`S)naixyI&KEB4J)I3N@4ZnVo5`Mv@bx+$rNKAk2 z7zCWWCXG2BNB*W%aSH4=0VoUY)+3ZwjjPRE)5l3$my z(d2Lv8H2eHx@4+LjVvGS5j$g2DjTIWD;1mQHZsxxr|JqQB~BlvM9zb(O`ZAX);7YP za?%yU{x!x981S};{PBLgO11LED^iInI)3_eg%gJSbMn~+NZ!4c>oh6bl02><(eDIX?bD~oV-#7Lzl~&*Uv%MQk zQR>eOs~nrit}&RG%1b^J;#980wUFkorhS%5`pAmUgb~5Pr3Ru@cc1dPzXSD;4n)pn zz!NqG8O<@Qr3Mt;^4Dy)z3ff+TA=2Vs+sAWw2Hi`m_%aGjtMtv^5exb6^xc=-iQA4kizgR$k&1+L&`UZURa z9-dwAFnGq$EVgMSbg3QErP`=P9%J4vP4eYPty3T7ADiCqQtRe{9NS>}kK-E3NI@{v z+PEch-j_FvISfXtA3HBvDiE7Nh5*?N3F|pP?4i0=PGIn20I+xt_wF6 z51U0H-C*)lQ|8OEfWEd}dC9q+X8C+cgQnfm!2XAbonl8eUu>Cu7=q{+W&EmYD?ntaBQ5!B>i)jxBi*rp&hnf#)ml zGL2CY{pJ)!96Q7;>%5PDg^~tuM1GGLjNFO+3er5@+ePg+&rqP^fy>w8&HJ*j(C#Gh zkY7Ty)M+ffvD4|aj%NX#tQ)oNFz0tRKG}xFzl8EhF23Cu<{vxWK`d-qDEw}JH=z92 zb&B6Zh!Qj|U)PZLB!6WUBVrcRLc$hzkQ*<*{#?hh9t?|ze=ZY9i_P0*O@EE$o;!7x zHU!WPuSE|UlO&y<8rUV~i6dm$4PbGfp%ejQl&N2SN7l@+f$p3V zL5YGGh}TUXiD>nZEQ_ia2@B8F;8dFsjG{zP+4~JTS6@v!sFIdyFMayfr`?Yut46m} z*n!)=%uK-RC%U$_e*Foj-Icd?0p0FPq9ed;>UHDoini6`Hog63)zRgVjG#t*5UbH$ zTT8FuFM3;;ES>s${+3A2a_9;82?%WhJEx3W)eLSn8v(BuQy$mzK=@f{uR=kCz;0DP zNGI27TS6a?rFD-3W9_xE6;Qudbq43fa_fb+31i1)8=vD=8$K5=AV7qD9@DK0i6z`1 zZ}9eq*UI|i3(sAlKs#a@Tmb1WlnINBtK?puSwDO(haoEncDpaa)LSJ7P6kZF3VU;< zBD#Tkm^+yII&D^VF$*@ZT(q{$HfHUB3!OF#Elqsug|qG!T;gs3K9{63VgBN|8A*IVIVcSP~wVpXj;MZKQY^O zD?~igsYn;IOq6tB9Cf-xISbU8n&Z1LjNPhT$H#w_)YkGNucqbP)%<*>l{@966h_N* zIz}0tMFf7_N1q-O(9k^jjXhC$?E1>$ zpbL;b$sFpKm(TAt^w%E3hyWKFN(oi33g4C*4fz`P@U`2sXH*@EK%rdZ3Zw5U=;R+* zf6|yt)R^<(>sRHJ`(U)Jylj#^M0dK`g4mQKb-ThWpWX=(%I9FOj+4Di#=(npiB(Kc z#^nhH3}`t%g_hcC>bs%S7;Dj9d$R&qa38F<1fKqFkzB z1@t2}Tnv91f_f*!5N)Bx#}{qpBf(!(nUNP3z%_?yEHc<@boWv+;lLs7CD$Ro`};MJ zJNOya&Pb$}REH3 zULyGd+3|7q44l%XQpRtMS#J;UxI(tn+nShZS+Mb!`KQx!D|h~q+O?%B1!nY;%8e;4 z4cAAY&hK_h--pX^Sp#${6Q?2ZU>J9`aNbsYZT+=6ZATfGU(02n@{iBKKbsn7$`Wk5 zfWGJ7WaZ_XDBCdfx;{j9yJVagC{gX%Sk#TUD)nDm_ZgbdfIx1W!`C!58hsyk4%|pG zb~MkHw|np)Yu<+@^@6Yw_N=*vRpv07V2-;64RIJeUVc3(?T8c1U>qHYw;u&|UTqN% zX}1T$eDJsU{JVMlU41Elh%c3=;0kmR`WJHfJjHmw@EW<(YmUynmy^}@(Yc28J<-%9ma7< zBJ;(XO$-F!?u?~IqF%d9CpK{pS=<^n{ecPjlJme-c z{auHM)Qn*!>!^$jIpA77%on7Rx3A7rX#uEiTOND)GaBdKGd@ot@~4kcJdvzzH_XXO zICiG;_hh*8Ey;VA&dwC=@qELZsy@?6Zt?F#m;AfBg#^Q;U+=E&D@vKBJ{ft$G_7|k zU8C8z2P!SZ6*GJOOoF@1<+i(r zOPC)88{*Yc`L7~LZCi_Q%riT!Q=H4oucCgZVNQ!Wk{3J=_jCx{}PEuvHGSK(%?zf|R4*52@JE&^K z@GFmIq{H})yMr&&;TL)nYrP~=%?;3U7e+YNj~yf?kaY9tPMRA9)a?TnIQ5`ZF^^-+ zsO>~NuSMkrS6usUy|gu^x!msQpun>~J5$rmJfT2C{T{kx%p*cFCqpflq)tSs^|#8W zK{2zy;r7M290fX0*?LHl7-wU*agVcnxVr>SUxSHnTqBLFn9n)T9$?_)Y5OqcI+=C; zTfGe}(T1OLR+U$@R`wUvPg(jZq<>LtaO{yi(G3M)0#`YX-JZ0}y@ME))RUscka#%R=kxMWVSr#h9}8p*^FVi=gjHl`78w!hG=FP0`M->sEn5digQh$El zazBXzHQoEZsdrY*3=V$uWeCNw$se`af8QR{#?z8CKBS%nSDuDt$b^} zm}u00#*?MN z@nF+jlIA33O%E9)MLG^MCr?Y(9Q~x>)a#PM;LSSSef8{&kx3WBq^=zo!X`;+OQ`+% zYwhLYbYiirT@^AqAt3>a2uHdPH&m$H47Uf75GQ@5#?sbPK}39M*=pm*fEBD2!Nfg9 z{B8NwpvB!oX4L~AvNh9Q+nxV%^F*1J(_6&p@>@Rh!$<_uS&4u{+D_}m^RQa-{5OSs z^FpE%eU+Y0iJL&ABS=x$Fp&NqM1LAG@RC6^wmIIzlw0Y1jv!o-naBzb>?aRRvOcBhN3*%X zL#*%y-+A2%U%(CNb6x8XjxI)Ja25}8n?}Y`%$@kh5*EQ(K2JJ>h$}R9ESo6nkBX%R zBJ{MZY@{8t)A6JJ53S)L@>`_-6aK2pM@Z0G6TO<2h^~$Z(9H7gL`&-07=NAQ1I(Vo68+FLFNX9?jx8>HI0sxlRLry7(x7Y%1lWLS7?d-0?gCaV zZD}c8Y-P<|MRaZecJF#2T_kND)XzG}lVb?FuGT5pP8@*^LNG(xJz_M7n;yY5p(pSW z5p-a!R=H9A_i$RFJiSZ3<#roj&BLqeFCdo<^(V!w+}NhqmrkDb7ZuA^2YWP-;r9*`lwicr zgk9L+T^CV$o%W&_+~@<>YlMalcX_;d{}hqcZYbb!tI6YSGn5M5EUcx^@qkN1|I$3Q z6`v#%(9mGk5WRP6R*S&FrllR?+6aEMDgst$X&alf<7$U=`Rkpdl_kOPMfUy%$>>$q zPmaEFlJIJDT!MjkZ?W*<)TSG?1N#)8xf7(Dd85Sp*C4TKzPZlR+Gc(95)(;iI=W9Ngk8lPIL=(hh%Un z0F<;X57eHfKh5%X1eiAs45XJcGO{3f>vUI|@cq0mbp@vo63jsWB$-lrjzCg5_61R5ueCfC3@j0zLZN{uvdLr~P z(bs#PHZwN|K!A=6RIYExk1T$`_bP@H7#Ic!p+{wIZKY>&yMLUXue}o);dQlImX4j) zIx|s_b5I?|fGcS=Sv~s(Z`gNg{&pJD5U5@)e!|!4?l?b+LM>&&rbo6p@|351uzquH zL6UOob))pDN(j-U;l18tvi*VuC~d%Jeu8v_5EZ$I2FXC#O`w67WB8}9_WPZe=U?zX zXUq{if9yqUAl|hIA|vID{%(Zz56g7#Mn+5sP7?7k;4{knjyyIhU#8T`HiRz$ zmn%n2_c(=clxCLs*L;#E(JVeQ+9-OpF5phaF*q~9A@-=9_ zy8X#(j<2>&`r9LK&pOGwf$L56-7T8z@!b9^hvq=`RhICjBBJas*|v)t-TKfKUvgqp zZ-h=SMSu7_EK~FF@)>6Nx{p_Eq}H#v9@rRK_H$gGPj|K07GM#J(GfcF;2e$M@Sv-$ zf?p*-kp#a*BZT(~W=|kbvh^3zq!KOgG?B%KQ`sXdKm)woW(z39&km=UJ@?ZRT<-ev z@CgDmp3o$B*kW*tSOkU^%7SZ5bM;)sD$Ga+d5Nh9xVtUP-v&qRDRDO5G~A|FS`I2b0Aap6eQogGrFZmhbw{)~q3l>G7W7SAYWyU!?8wyx9u!X&+2xcM<- zPMgM`e>(Rax?DdXKaJ|Yj_qSG=kyyOQCxSiYm}@MqYip^Y{W4mS6E3}jXGSPb{xc9 zk87i*F$Nal1?PM)?7{T9QESiD5kY0_CUhEV6iC_VXuxgF3(o3qxpJB&3KWQ zxxbFfI#E5oxYi7%t*19AGEZU3L1eLAAC*!1u}COV3x3`Ts}id!H-z#m?WY8>f7DW+ zWRjgK($_ZCJl=K~FAK|1_a&04_r@PT%kG_}bUwt%;&Yp1`CR9ZPgE}MKjJiUm`acC zRiGw_xV)YkdfxWWa0F^w&FOn5JRW+2cUIcTlYSo1oZU6cwT_4weO<@*3+3Ws-oNQ)4;>_bvcZ^2>MeO;q7ae64|+4?-^Fcb>uTyouqetQ$9@guy_zd}s2NXrrfNSi;fM<>2Ia(Jj80B(XmG zu`sxLr*IRol7A}q)b!)L_{R(40DT&#*6%Xvw9xL=+6zIp_y<7W!SXhUC_+#BYSKEg< zFmP~LF|ncP=3OXlU_)#<*)xGZHG3x<@dxNOG0STy6VHX`DSo%1G*dd}EW{hK` zjT!n*7nUa{xbqfuMP`FEdn5$&C33bnA+w~$lslwiAHSy6AHQCDD6d5%6G+=-m&mw# z)eb$S@{^O_tGJ`)qVsq^m(erg*NY7dp{#sx=!uHreh`G-0a~GhFBn9zgmeKWDTrVQ z(g#AJG~)3YLxTz2UQco*&vFzt!XQ(GqY+g&20Y z2bc>ACC(P(-t%}^^o_We9~}a6WA_q&JiaxCBqV$kJAz_w z-Z?~%V}FsN|n?&HW1)o-@WpHJA2or&HGUE$*-xJ6+C268r2$xR*`xf;g5%>+x> z+5S!AMk1vij*m2cMK(%gzsE0ZPn=1(&^!j?#_jXDtXvFn=4tu0HE%76HnQ^O){KCA z=QZ@BX5|kzy_1|d5=31noON5lg1okBuo1ipt#gy4nu5lWpAPK2R52wK;K&CI0%+Z} zbC;{EJN7KX6syk|gY>Fe2YM?QdJP}MOyaqC!Z0i3?kClr5`#_gI^6k~^_rPfVC>oA z1e6huM!AtC*ZuLyqa=#mfR zennM0B`W@n?4c#+jjVTWj(^uWaV#(RnXZpKHznUVPuAh7?5=0$ki6W8KDS~S+kB16 zH*ru2BpEZxpKs6s5B|AQn@Lo3(W*W^LOrgDOc?!5#L@0;ke4+kzl@A!^J^*vB=)#N zOx5Y$RG|`$c3g8y!e=5QNsf~lm$e*U6Qmipkn^dRN!MfixV>mRV8aJUBzKh-c27!V z$EJPn0wABl2BPI>3IpX{6xnf@tVYe9niYVcPEYSQlY`hO-l%P^nMzoZ$MyN@fwwzg zFCW|p=U$nS3SwMk!>f>!Rqat0tn0N6C8C-hy@sk>Vh?St$x934AVeweN0e07RuBAA zYt2c;wYYR-qIzpmXWk3eygru^t{d>Rz!Y;Ms+V-?I{L8b2^LR*HUWQNJua zp;5T@(X1C#*SgoNJ0PPqPC+q1>|hBvAgbeA{@slHvD^N&HXzw(?`3iLy=B8d9(gF2 zPJsba)4-->Z@5ly{F<`Vr`&7?9X>qGa*&Vy7qi*C_D@=)rG{~7rwnv;{Y_ItmAAYv zQ=-VZWGLzlOe?Ji%=0YakK&MZe!!r^?2loK0%YJDjzfnoz5<5^2MW(E+fq+eQk@JL z7Y_97oG(5pPdh-+*cSC+4=rC?C~=$_^*&9%icWnVkzMjK^JOQs`V*)C3HDSQQq)hf zkIrk?X?aLmyCcQCJQ@~9XW~9mh`c6ne`96i5bZAs z+e-29?F6W6|AbNeT9In99)CfE(3mSxP!8>HeRU4VX!oX!yZLgpD~f-S_iB6MZCzQN zLbk(D8*if%puOWD6IxF`cCC11yfO0&OBEUzc79p)L>KWw;Het6@{%^q45Nv?v>a67 zk=aN?AFn2zbO4>k85f1xUnG!3hfKydK#JDRW0HSk|Bt>^rLJ0b8qqM4NQ3mhrq4iIQIlr2-=C*E z281Np_3bEdGv#VvD9SUKFLg)-lU9xN8$HWpv<7lIl2b3uu;Llvb+t3pHPzq3`=#lZ z3xd>nqr=L?d*Q?|r0UCX;Jh{ME)u*On##TBxt7gW_>0X{NRft+4&i5P>ddC9|AlO| zsYn#NPfz8v#}?B}dHOu#K9T>mwfE}HZqNmqfRi_x&~iL zHP2_k)zeO=`W9K%uc(ISS?FpOf#PwWhl856yqcTuE^wHc;2@1{vh-!fA7Q(+yb{!U zzp6W5@fWbaK`yDtx*7b|#|{X1VYdH+8$3jmi1!O0nL^wj1;m3K^7gO8_qTHteYpApF~fv+vhel62c}UAmDeL2kiiAoAjs+;uPhfnKfW zMAjGQ+=o96li+4GNtbIo{~5ZM)hdr&Z(sZs4RV7PmWP#LpEM+cyo4|^nCQr)?|n3B zyn5kYGuZ{V4n5lCGf4obleCE(T)m@xWJ^5O&5 zi+V$IzH^4YPBk+u@to3ndL)dyB2N)9se!2{rtdGeT?tQucLMD&?|hSMV2(Se&zrY+ zfF4V&aHN#T1y1D1g-Y{%H#8*&Y|M13FLd!LRuNeT%?WJnGN2~DB%>mPdK~ci7CTdr(2G z^#ya(A55?hudpViT4jOg)v($I3Ba#qKf?RS@`Kb$jiby)t*FRObylBWg9Os>>OSdL zB^?4qlfESBd1upbwEgbT>_GaHVvYtC2@f3! zkFzrAyMJQ=&}$W|e66bQ9aS4bB2X^$2{EeaSUQ%ejr;yhR#r}{m<3d7zhHBB{uN(< z#kSsRu1u_xux?XUI*=0IY?t1S;^Bs&P%T3ACU7|m*bR{SQes~6`FP-_wbbXETiK#J zfrsU{n^|kzt5)r#{55w8?)ggnv$ZV$I62qkRoW29)@eJ_8QQ5Oofi4xX+B_Iu4FG9 zO>Q8yk9h{)+Ppl?%CHqMIxBN-NqS)eCR%BNPZK5}poe1Oadx>ys7|M$6_ifN)&h~$ zO5x(-%Ep>q-nq`?-u>+w|8sK&1#3c^%dAwSe*k-fN}|d_hvC$VRYcRi18zOwOrb~Y z$)V;!*yeLXjnCX3A6w_K2!#LyfaE%5lX~fVBlyo84OcA5IEk(`S_}(LCnf#yI5>Zr z`2QJ#Ji`0SO_ZvslfCQn1q5cs+aI?N=yzj3^@)x{<3j7`f zIwJyd7UZPK` zsoP4aD?O?VdM2E<-LHE|t+YNHOwYIf;)6Q4iHeoJ*cbhZ>TV0jwRD90tNFq}M1&Nn ziNYrDz@OGp;|?twRrX)CE&ua{K`!U!cO=l^kw`aM*zf+Q8#wHPv<8hju#j6L8t}b) zQf!!bMi42jbV{<1LNn>?KD)oep&8b_bO+dIQmusH!gJjdG+0eBf~VAnR7{`9^w{2* z`}*IP84?@@AIzSJZ0BTixPY_k2%2n%mQzZM3YFBGczA`AqBV4NtR7_hPNS_Q$J{l)CIczoRA zTsVM*`>U^XX}Ddgalj7r_Pkb*9v5czr_-cEV3q4Zd+;FWxUl+?VElBP#^EbPN+bT` zW6}R()HkFc;XEGHi{Sp-^mNSxy?caH(xDRGEcw#bJoTj?s-K!IgI-;O3T>2%?uMDI zUCg!qyGCH&T+cSc2(DJ*0VY&T1+AOpu*e4-q3z?Hj_8uV|L!0A0BF#7I*_qHhcl8n zUaaf2hnR4O?zHCP{Am_m2bD2^4JcPMn$`~Pg`z-R{iqdCU@J-GTqqPlj(h+uVMFfXX+f7fce z5Wo2@4~Cl5i}1UVG;g*~YOGz_wQH z(r%UUoK?Rxc|i@&<JC$EudhA^M?V50^z0>Faj;Q)C z?RG1JB`P@~%0j!BPJfP9Z&;bE4~<=|fp;I3Qj0&bk7i@cbUfB;bRty2TZ{bB>A50Z zBO9tc^z5)4`#wB-&r`{4TV(C13``(2D!ryZ8u&C{etS}^Y!`v^ZR!{RKwPa2} zw%SgK(yw>Rd{iEaFz;h5WQeMKk%kDE+|_4kH7;)2=o!-2{M+g~It)RXJ~?1&L)`b{ z^ZVOZOVL>}(CohY-mTv>N70R5x4m1%=joP36SGPY4u|arYVWCK>aHozDX#(eI6%F{ z<0;i?4d+s&CBU$TBVazA-H{`z=?<4E%Xw%4Aeyu3Y?n?J8{tY?&H_?hFs1v?jKw zP}Q+MF-3LgpWlEqrD_||htXv*z2ZbLsP_+xMY4o^RvQef4M9Pwl-ub*f)mTe$ISRO zWKBN=+;{a7kO(ATyff6xdx0Sw=dFqKbO0kQdreGv{EVtx2^zH_p`=n0a1x^b?W&># z>!VXf217>r_l1Q6{^hd)Muk&wG}1Q|N|^c10N6%FEbhXv@0Jtys%XB&(}ukP3K3_g zQDZ#j(Q=r8{7|@P5}N=0|Nr&SC(-PKGQuEy>v|Duj+J?vx^hK3;++qL6v_2EU#L^i zM~f>B#&XZ%Rgd2{J(AEf?r?ePBRwRK?$Kq*7uYZ`P54S*&GWOXhVXrYTyzze_y9t} zvxj|bak+T?I?l+@$}liyI~#LT7@%xDCklzc1s3Y8E40ZMh|;H#xb)l()6mvrf>l$K zKpCx_ujADH3xVrByZrx-~WHLaf9=J!~P_^W-Eb3)%y{8 zumy`Au9u;p0jl^N#7cX2ox1w?0ly6a-0;5c*6NWbDz zZvD#!+MCtoB|$e-m+P0?T3GQ5NmUkPJd6vDF-s=eg;1>;DtJZnliJ#Hozi!MLU~}7 z7emAo^YE-Q)g&!m5fJ_vMIfs~6koFq3F;9+&_lv9(g3SMCIBsGUpI*H_dYl3m`rN~!Alw(#0zeN)JuzfL`)0z-Dg0Y@`4JXY|6x% zUO}?t_WYWx&2d&frQS7Q5($RK<@m+a;5_?9cfVo}?V6cFe zgNMh|5 z0O{@$X^;{@ngNECPKQQdm>~vGKw4T6c<21y^*NsNUB~|(W@fEtvG>03`?{_>cJlqp zfxuw}yS4afy_?THs8mw}UH$(ybtP08plF{@xLl&cQVL|;u?(?mv~5-y&)t9DTmo$d z(DCfBUeWA>2mpg|^vw>w8zo&ol6p%6R~z|W0`om3p8KyJnzi>)|A1tApJa?<~c;12VuxjHxR&5;vyF?w`{`-W9FX6li z+jTcLZ`SzVzluT|BC8=@$oN+j>$Cz5k6r1`0OOiQ2SFQ!k_oetW@6wKeESUGSuD<< zHJSHFN26)*rHMClT&X~bG*q~?P(-#$)-C2}rt;ID{JQKLjPbCS^*wr zQfFbVuxs|FwL>yOclMVMU>kjt!d$QOqPa|K^(u2M(%C=c+H7?x7?u4ir_YV$`=^Nl zY3moW!)Sqv0ept=K{qi1a@tf(WxZoOug)}nR&Om=2^c-VVn3q8>|}cFi+dp4rLQ*+ z+|T1(ST-2si!i;GfnqCNh+=zUM*oEz_84Q-OAM6bltd;=3M>HQQzZW!DRke5A5(0I0C!B{2{x zkmPzf-mg8PY;1yuSu%5(6_&~y;h;n1vF|Y8rMei4nSzYl7|st(xTuZ)ye3m{{4WSf_ghkb-Dgn$EhsGk=TkKK`jdKXsb5S zaw<#nscNwfS%~RgPCNCeAh@?|?VDcDrxH>rmOBrE*36N)cvVGL8$60_b=u}&imf^Z z+~`h)IFttuiCD~k<+JulO3GuB_2{cLX>)QWUf4&R83O}A2$fIwIVEjER=#CT`boM7 zpbzOO4i|h3)n#V93hB6Dh97^5X3EK~f5B<@UAp}W=q>oBTowwY`cE$(wh$GHV^Rmp z(3AQ*u#i2%B#ZV1BtJ~~??zh|=GWLrFobSKUW4)LG)dAMrqOnwLRlrUcOXV#mi z2m`oob~b54lY^5AOQ~O#j?Ux1Xzg7Thr#u6D?ZbTa&S2}9nxuoiZN>k7Et*=Kvto3Z;m79+k?@B*@G1cB z-M!^nx2h-=XmsucX39hP>}tw;V2cy2ESuabo`%qR&FCB`fG!?0O=QHOaK|40>TUpH z7SyPE*1V0PC~~h#kWo&Wv#6UQh(JGJZ zXRbYWU93!GwcGNDp1zn1mSZDP)SlO)`8k|r+T}%!u|!19i}Jk$&1YaY%l?CjI(XcM zC{T|qUtL^+;x6RHYw+H$&xvTm_o8smIRHGWTM^1rL?eMg{LIPsd%xOV03~(QKEG|> zf1A(%{!akoCyieAvsWhe%{u>EAJik(o(3QCHS%wM)4eE3V(S#oObWkmi4;OX)G#6P zcf|*cO_P3c5rj;-Cg~`P9(g56QYCl3C)fm0E`O#bLT_+b&58{YGG&NBRvm?8}l@Ic6~!Z6_^%#_y57btmmY$W}eWYhik^rs(McsE~~ ziDK>HQ=Jt*GyI(F(xKNlzLTl@-Bb};Gd}))Znxo4Zo{Jn85^L0ceX1Ly)WypT@h1# z{OdT!cK6hzaeffeGbnvH_^rZ;!2MWj4sIKiq}}YWor~*D*K+$+k3hbxrRtC!+k*rX zb1>FJP+tD)pj!#eo5$1(UF<@qI7 z^b>3w_jMgtH6noWRd!v44H}H{?^HhAC1TXvZoQ+N=A}kBGorSsw`Z*GZSjW)i#AIj z*6P-lMmqESi+9QmdL&Y`%iG-Wk|e?CC!57uxY2b?NLt^L&$#rht>QoGsc?WKI|GR( z^q~A9PjVQT`X3vHqSpS?~Bgd>n!27Nexiu^Qq-IVbVRb4g$mShM2mcbFtFBNMTib~^XKWY#IkrIB z7vVZCU61s-NW6t*gnZCspEe80z8!r*6tjXS-7 z-^?({dNowH%Ex950c}3x7t`*XOKu=dcPzHns?K=2mH#AxDG6wBE^~hWZF0_Xd{lmh z8ZHC(SC{J7Ejix^d1+7JzDEPHfy@tFPu#0HD3t@Q_Qm!$1nUqHgSH*PyU?K18wNcd zn%7sOx<{72!BoTKbz$dJVGF4kt`;kO2Qqy@DGM|r$3FYJZm#f|Pti#V75?AGPxR=y z>CSThX#JY1a_`@iZ@jDd$6kmNSSXDv&V10|+$i}D&KG^6;}hSG*oQLoPOt`17ldn{0tOO*4)p4e2o{?NJiNbs>7S^HpGk~d6;`>GdSC7LVYK!+1GJKb1 z+{T7q(Y40)@oU66BwXo$6jgtjMiC21LjxC7ehN3?Ft^q3xmkX0R{7|d@C*iR=yEik z%bjx~t#TDh-A@AR;=rujJm`^A-Eb)fXXW`8aWYHuef9tboG>kCDk5A4rYiWCFXAGu z)HNmochirw__j$;KhpM+zfxX^;T#SO*qX@$|GKmXZ*7|IpBDsxcMqgHPUn?l#{dH) zXD?HNDF%EJ7m`B*%uw8Eos}jh9vvr18!Oz*D}v2>w0)!M9l&wekU_$AIO?31@yLq(FD$*q>_22wZ59yT9RCl~oR>wk6{t_SA?liyy^a}o!w@uzhn6S@r3oOIQj0xoW+WP_~(*D(APi6 za7-v7jWu$=j`@*1|6JxhwAlLUT*`HisNfrS>i1WR&2;a$bGQ&{Yz!Zfq{yi|51b9i zk(Spxz6m!`2O*^&9}p#mupck%EduV z`6}~;3D9E{aNr*A`S@Jk`Opus)ZzpM(Jv&Ws5G%K>skEMzN!-J_?}`mjHFGCHNHZe zQIW0$F1`bW>>TJ>jmy>UR~Y!Vw=+pU9(=G@J)Y5T z48Pq;&k==1r0Hr*XjM8`JAj@kBf>>MK`>u|%kPsnWqIn-9!8=E6J4bVke}1}sU`Dy6+sDq;H*Jb^FTMWY*UrS*uZIi8)V6Li5Yo~kqbLg4`#Fv09w7oLza^;5th^lNYc1e zJ{@YI!l8GB*+~;)O+vDHIQ(rEKP^5XTrCUN{d9i;f04!feOtyc1PgNAiH*51=hoFE z)l!G}>fExw#k&g(u=$L&gx?gRs22T!9Ix^JaVS;RsH>r6$zR*cihj&ZLIaTcCc9OW zE+TD*@s*&H@B(RRVoS~gH3Cl3#jEpu`U~$z3wr;3WQ880WA@$LXJzs6f3uz*Q9uu> zEoQ2mZW5fPYHjo0%93hDKxKpes~EODxge_k;wbAO-j?*qc+83XmwW*HLF_y$il`sp zN)X!>e}AzjL_B!}_m94k{Ya7U^cRxlZoYfbib#A@pHQ>~Z=Vxg37sFoVsdN#V)D#b zX4JT2-G&BLzn0wWt)2J-W}YNmnQ&TRR!SEOlS!2NFED;j)+ge7^$U-J5d9~)^`|M* zg@?BXb-oPJU-2)#21g9{JPy(ER^C@4(YC0Xi`J^L%rn;Q=x%jD0^D>Z=61Hd_QYhA z^kuM2ge&xQD9+_3q6!Fh;ggSHiuJm3;F|9Z+G0sB)${~x`Lh&z>ffqZmrGXT|Ma=_ z6aU;rdk?#jc#8+TdCHyF9kSrMWL94V@JOOwZTe-fh(M)|9x*G1(cO1k>(YP52?$e^ z!|;W6cwF{&LI4dntl*|MRG!48{`p=Ra-bpU-};(7K}%Pf9U1pH$M0l*F77SBsN6&+QTf;Y7t4RvNU!aV-DaWVY{yi6SgaTuJz?5hr-$m z9D}-BYr`DQ7l)kciyPF9ViSeU&MmG;^R6~fVaSi9f`S4_+_VVdnc-dHh=4WTA8VO^ zQ6`iJ*3%h&v_ilz^f9a6@R)ZWr{8xoYwuG_3&=WXZsM)93$0*2G{<^l^tvoy&!fWV z59yCpMLDe6!?04_-|L7*)IxrnSD1_{03Rw2*U=(|=yjpl;;9czhMAVn=(l|hh#8_3 zifQuFNU^ef68B3hV5u3rqmt3*@7Jh^(o&x_;=+~~ikuGYElb4C@{hJ9g3+i7zGL@32!(vknR~;Udsee{v zfl;4~RO;5J=}fB)hU*TgWx}oiUTjx1@2j)ITb+HC(*xZf^XQ*m9dR)1uY8TVU0?K* z(2|z&J%3}`dYNhZ(SH>UorhhQ+MS2r520yDuk@y?3}~@#i?}YHj0-C!Ue1&AB>m#K z`KJsB*^2p86k?9ScYkwVf;g4*c$lTrwsDqHW68gQwLY_;7k!UxD6k-VT6Au(dDsapy!rX8k9M{zCTbqt~YFpaC+$R{5jfp8k?`y z-{nDqnpvrcYe)m^dG<>+wHa0XDN{(&Sh@nA@7Yr&Q)|Uo3CD1|4F-~(aT&K6G(Fxt z!37O>Egy;4BTy4{*O6ba0bv+8sO$1VZ=Vr*DFeCY19S6`>vF61C7j zVva;KPfB5%#`*{RQ^HxQ$TI}EW5_yd!^1W(tZw_bBQ@^3kQ{pn$rh;M7$nblxs$<` zLw^C1d#2k$$46MJ3@Fp|7?ObbRIkAeE=)~vhQs+@&&_+4RY1D{C~dReAKy=b!`fGT z6aSu_X1mg)*df6%gKH@sz{s7bFlLPj-Sfn~2n|aD0uPYNP!6J|*Bf$5SonU35{FVx z%1!QyVs~C_2BwPuCnObA_niL-5d-O3k)tX1?+7|fepLGYqsRF@5gprt(5eLIZkX98 z=V;+;T|KAqDJJulh|;4R-2V?CAG z_Tn{J`mcfZ{Z}xWEU1tR9PzVVclVJE!pS(2(sNb`W4lz)cA6VdUA2%lRcca(b;?gP z%r(!G42D2^8{pVGxQJTN7On-Cmf5k9`@oXO7m{6AZbYcU^9a z@hx$4>-L|XD0thPR?-0@vv7WS|Myn@GfJXS{XLZA<-(Ikzg5n#EzZz1U&;8(t`t5K zcSiRJk5O?jl``GsMSexX#xHLn`tvZC_*N{N#hrmJ%IZNtjRYrgQ->hR%Eri!(<}VZ z7V79%whzvKzY!3|b`K+uV&C%e@WHl^h2l;t=)9@GP9a;cf2 z&r$VN*xI!FS_dp3jV82QjWJE4Z0sICJODCIp`H)3P0<}C3Ob<0EGf4rCS|ayp*Nyk zs3gMFrOs(eUW9LyNbHgvzoT#%ro%x8dPVVK^h$9}ke6{7p^19B8%J|N3H>TKm4o!G5eK8B2T-JD*arB#rA z?J|^n#k9$5I7QNo%II|hntt_A613uv_1h~FP1evrFk}ptAc9@L5I|OTY24l~sw`dj z+71?<1KRQuTl8;>&mvzq{GIIm8+`u%4{{1}SORDNSad&i0wG8xi`-NsW-}Q_vsIB) z!6#NLcaoSlQ=}^|P|fAoy#SF@d1~0V{FQsnO_&IL21;HPbYr7n`Nm0*VU& zsHX0JiiGGxQo^4nh2`<6HQ-8o&$QtaY;noJ!AtnSZIYq zb=arW1|CwmoHe?7_`9_%pNla^*k)5be40c)XJ1QaP@FF&xXZv716_7l9GVA;STvtip$^ytaxZj|PDh#0WbsWuvB5s=6x`=ue zBlq;FiG?I@-#r@_NE`pkmo=#vs&vKwiVmz$7wOLX&5c3BRQ${~M~#ag$X3FeAdgdD|>SP#CLFen+xcN!#HAl#NIzUyUyQ z3_t~4hO-A(%9#3$QoSU+J^2Uq*=-W#s6NK5seys@$b0qj)6jwEvSalbv8@2{ zk%&+njU!Dy&4y9Rlw#7i2^fO*&{T-keYZ9%%=Ma)yH8{ro7!F5_(Y~3-lb_a8gbGM ztKWHuL-O`ft{tpOH?O1HZrsl1K0!G;chIykoFexmYJ-)MX1qGKz!sTj3+F^?I1CE( zo&;^#9@cPW#shWe<>hLtn=L!O$}rD3_-loJ^#tUtU2+U|!XUb#ttC32jOkynP2R-M z?3NxVQE?r&M!;If0a*_buW?s7GTvNo_mhKMUB?er+HB1Ouk{1A3ewS?uY3b85=*ns zv)-c->d`%{H_igmW>-GyIY;+YH~kbtl$Q0)rbNmzIBML*IEZ{^@A4l zezN`Etlt<2jgh7}STVEO!+-05odP&QsnL-yHaU)+BKKa5!XM>7LnjkxYU}s&pL;q+ z3JjtyEV&4j63&0p#JCR;a*)VAD0$SnCD)`foG*abGaQ16haoK;8WI0QVE=rs#}DiB z!(A0tG8&s=_Ua2vM9I$EM)Tbr+)g5UN;56~W_@NBkF+edu4j4hDx7)zead@T@Od8|_|bk{tW$EvHf>No(5&9^5g z_+?g4z^*1W>iUj+#pBgRtO47MX^OmDNvr!~SqT!ct*^Ge6}U~N9~A`!xZTRYo7YmW z?BPZ)Ti$P zs`LhrY#zQerGQ7s)hS;5{9(owG$4#l`x)9v-yh}o9u|O5m!=|(~4iB+2rqF;VMNIO07?zX%-V#_#@0aL`L!}A(?{i80V*{Q}U)^F@EBLCs%zYOzli#hTnM3Wa!|6S1U?~ir}EiCv6mX4rN070KMlb-KR zl6SmK9|**@qA)`Z5?n3gi=W3d7pXG_UIaD`CiV4CV@*&qX6&7Za16Na4GQQ$h#`-~ z2_&lb^*;VLaX|@D!28WMl!QSWrSEw`$T-XI@o7m+y$M`!i_99b2NR8_%7#r8KCDCQ zY_A`=HXE;!zxpE5Vx2$&vYB$1`2Kvd`t-6befjJMgpkCnQ}qga=Z<_VwX(~Y55@Lq z|D$&UJZXl5+ZJ$kfShXiC*4H}$@rPTD7iG0Z% zuCn%IkHMIs3cRIy+W`rPD*CCZwEBB*fkp3jPoEa;n}v}y^LC;%k2y^V|bz( zxAWHIm__aGIjQ@Esa5rjjNnzC&r-w*tQN0Ij#rF{d7>~6@*}Ec|7Q{}_+lkK=o&p` zO;W^?c5}i?>)s)a zmW>jDS;798*(hGcrImM>KYzpIe^v#ADXOr37uCG0Y=83BKgN!62Vw;e$hW|IyG`X9 z^`z!=)bj8supQtgVV#=3B_2k$ply=g2OS%GtKdX|=;K-{2_422SE%G5UuQ%l-9xBN zv5aUg5HimvzVEiHvOZIRIh{p6%2hv&fp!f2=xIsw?EUmrL`k~~X7$yd-Zw}(wj&%g zfv6$C>H#d;DZuc<3UUh|qaLv>p7aDgcc!)2SPa@;y7yJm8Me*0Aobi_;Oj=qObLt2 zaN9+(MV7@nPjTNelcv%a>Up`0zE>}(E=>m`tV$SJKYo_y#UBHI+IgN_zBhhSzCUA} zwRaJ62^TQ$vTRi4%s(3Ny?6**AKa$iWLclJD1U6y-1tP4UFb6oSVRp%jU7~=`QVrL zq0$(*jp-_cGs-@Dk=ER_#d*P%?ZkrkV%9O>aQ_4dlzd%8_xL$PsqnW&{%x-R?_u%@ z+WXq$ydVE9R=X`Maf;>Zo5`DxY|muTFqQs-sG%XQ0zH=whJiL|b? zT+AS~i8pF$S$*tguQ6ftA-l|9L_(+wObvX$P-_Z1v!=x~^1^nPlo553y|XHwv z2^Z|A<9-@H=EMDVd;8vBuW6bivECMNrX(Tjf{&A#3Yi-jnP2=-&K8VD%L{_hrOE$@ zef_E!*5E_|hMtq6IR&b9ar&WBO5y@Dy0UByUdPzZ=yvyeuW|R)m7M97Z0zoBD!m(W zXwARuh;ry`3tW-dzbEzW#UGLy=tA_OL;uyGh(j>1cdx$*4Zr zdViJV!Q8&mcDW0(&tvOQ(C9;IM5yzo<*G*V%~5(s)#Ee_;puSc=zH>6=$lJef{%)W zevWn{lF(|NdB|;?x~95fWER0!6T64tyZSh-nhL8iNj?2tA7_^Xg5DmguiytB9W}>t zmjCd>uNBbU-_Vq4*%fm&6fSJ^6*R3cyuM_TA`!kJL(b_2{EIsLPgjl3LvLk&(DloJ z-)!Gc0E_WGF&6m*wxhEzhxkLKiQXt|Uja{#4$`d*5WB-G@y!d9t-udbUU;WYQFr^HsziOz0i`tK^#{{zR^<))t{dVkNCd|q`RGE&Z3 zzZPgUw?;L4|{BfsPp8&b`<+7rtmd~&}7g~|mkTlMtWJS5n0dPOt8Rgkb zP1_xDKtNVVAhPNP#L&-lL_!OzU__5hcEkQF%3+hnvMAix*2h%~tbnTde07K*&yr)e z-PeZF{y_3I{qOf8%R?LMd6CT}{zs<}{C~PH^fr;p`EU9WXNj@z* z$;uhtsZcmA8@o#flToNpf@5?B)A5Wi>ic8KGgfv%ktF5pe2x?b=(`GDB|DMH@RzA1 zYGfgeF~2JRfYoB&%B@N;u8x&ZbG(qmrD<9gDy49F`4o@eM(0qW3^Pq5B&!QEri72i zj^-)H>>bW5TO4Qs$(|9(PI_@DtREZ{pm}>Wh0T17cWl*oT1y)OO!aa0@PXAX=eKNv z=Z1KwZ5k^Tdotvst7_)g^Y7AMIeu$)g+3W ze?TN~{w`jzKBAI=K`$n@dU?lyIOhx9OEoM3{M|Mg9rS<%eVP?=H?s=(__HjKzyJ8b zglxTR&~Zjml3k7a4NhcUCwu1<-$%EWy*14iH5PXFCj{clUfp%2>Ye&`FgZe4dD*&L zmx3(JZ@yNJk54Y9aafl!*L`43(boZAQiZfd|I>_>P`ZCL6X?z?wEd2+ zs8B!=cEXlDtC<3hk@%qdW3mGwx)D*OWKvULC2Nu_6aH|EgzWREv#*g9PcIKGj)ZL+ z@TZOPNetS78xbyK6mc2*n(QLv#)>iDN-ApqGqTq`!h~CH>%Wa{{7M%6Fj3;&1g~I*l72* z07D!ynMgy5^@bQdgV@UJB5Q9}unU^b`2b`UIQBl}xe;e19r=9-V^5~!qjlO zJ&0PsnZ0I%A!9}Z>o{UU$M4b%6X;t+0Bb@py}RspYrV>Dm#;q5VXGAR22~V?>XM9rPS|Gv^AOj$u7lB&yL7w$56Sw4PNRuFlrP zk>2B{(o$!lZx|ov0Pmg$(T-FmU!!9*P*iF+Qub+`f(rkhn8@oE1-4xgH|^HuC_WS9 z*#nWFHMtb3_-KTwt1OiRaU*t=V^ zl&mVxo4&lhT@d&)t{R%l!pcy3S7+SQ;QYdQH=#`5;Nm@IlO+k@BkbS!3{{XhI^ zS38BUj#YvzxqMiE`)WD`EOOQFCif7a`4~Um@P0)R5!K-=Dn=GA2Pex^!Hj1Ts@Y-R zyVjf@B9SLW?j?_YP1rSNI{B_OO-E-}?dCtSirn9~I(nV=U_zFzjM3teNs^w+Z)fno zfd7~KK;6GnBwuD9ve;0L5>wV^dS|dsrIL1aV6{TX-MUhS(<=rNnpkE;dgT}&?1^}b zYet$fQyDqZ`Eo6xYu#ck9CnKxV1zAiZHCcx$yu?xJ~F!SWVS>E^lOHXv2h&&C}P@d zS!!K$pQx4SM>XvIBMAxD4a!ngjU9UW*1S_Q z9|H~`n@Qf3^A%GjzK%ZyPQ|0%7emMLh>5oaknZd8?#A(Ed~k^6a@XZCWL$%#xkVt~ z9cW>9`Ru3a?O3PPbsujX+R)Lh&cKd`X7zg)j*k!T`W-BHUG>~tmTP_|F^fn&YrWod zlZA+KzCZdkAeE^u3u?i1^Jw}I5d#zr*6I0%(+A>x<`#6U_y^ilFZ)+{DNC(nJx;!sXntS8f{Spu6zeXc^LJYND%0S4`bNXdZa0x1C?0h7~(2rP@;Ya{+KT z+4;rC6{rdaL;#^Ray5bjbZu-@WyRw?(c%$|mGdm~Fg#kav}*kD9j!ipd;#>`cpY@U zxCQ<(l&>Cm90eNyMhB-3oxGc+OkR(FFiC+JHLMQ_0UD+TwtMY0Te)+LC-h*l_V{l1 z&m2zqZ7CGrm4EBw_Q9$9b}!zv?G@=FM#6coarwSI%SY!7tI;^~<*vP>$BDa_*sHEz zQ-erQ4)UGzAa)zJgOGUFpvSj4z3G!Tru$5g2bJyr+OGc*?S6SQ#a?UGs4V+^TJ~7N z!oql?bx*O$O!|xQ$S4zsw(+XBL6nN~bIJLX3U?XwWkQNS zXa!F+v2@c7Z>DfNB<| zU$;80sK6<)v=lJoXm7T{8I$#+{VHr|CsHxmNM4D!QNoC3kDnvRvifR*NvRY1;cr zxqUGp{ff7vVRx87KgP}V8&u(Tf>jEIP=Vcex=h^sAUAK@3qLe%w~+O}C6#*g0{T$w z3wrbt%x$sjEyd)9U1?Hc9P+>|U7KfhyCM6;AXR{rik+Jaf z$b!TqLYvcvE5=w2u^I4W-_Be_w{Dv^%qXrXoor{-?|{%+sWHsw^Oo2=e+@0 ztkow+)#0R!w(#+#T8f{3q=ZMKL*?`R>%nbXEHX#v{H+PM;vG5f)25)CIk9uMkY7}j zs7F#>>#f&2TP%wKSrZ)rc1JQ#l;+4&q)xpsW?hE?C@AZS6>B%iKpQ`wb506hf&_sl zG2qVD(UQh=dOYhW#nMvsTcqMD?q`hONak#0E$O@RZMvi$6fL;PX+bQcN4UD<&p`mjEgzbuxQ zWZFucVvcn_qyCnhw1A#>6S$rUYCb4Onba^~)qP9z<#Op)bm_=v$l=9;lf+;Y~T6>Ho z0siF!MpVvOd>A9te!u-V=$jw3HzT!NUYI2aTkRGw@lBd0%j*bIFV z{gAZBr92xBIi%twyf~u`>izdWpU{OE_Le(St?Q$(9WRX55@9zP4cOVjL6u zDQ1L6vyAGKg>SKh7pTeM;=|tJr=FXfFuE!y_1G@=hb_{w?tZ)qMURAKoK8-3FAwcO zRTRcoE0sSgY)ZA2gBoeR5P^5b45b3BJ|$};%XQA-q(i0b@vif&20*FJkEtxDA}oV_ zgTNXUIkak_3rjxt$EbRKm0k!KFc~gD^;qD|#;|iYmJregl@*y+x9S9~t&a*dF2Us7 zV{20W-X5miMGF`67g^iJIAjko7tC|M-Vp_KgWKPt69r6Hm$P-Cg9E0#fyN`*UsvXc zJhyzPPVt2qv%{3-?%^-WGPAl4dv997ds)+zn(_CH9YB^(PP?4h9g9m5r&U51YPr^WH%DQs}7g|3gcg~uCEtm zc)j;HAc(;r$`{!_l@HS^f3{VsA;L9$ZuRA@#uM)+qdoHd%CYmI%WSRNPX9>!{ytsk zZthV3NHP=?{25y8Z;QlqTf)hU^CH^<2AyH=DNlIMocL)}^9pgwzsY$2gqY{9t9+RL z-4o`|B1=?N^`uVsI$$qMGqV~g1VM-4M+%-aFPOqVoQc?<@~yg%*kNi3$Eibx#T5*Wx^2``}(O5?d(?*9r zb~v>R7sh+JsxNqo6wF4xy zsTI_Hbv=B3_7#clTdTKzL7hj(;5B*w!h)eft#Z}hH}iXJXDSc7&^??f+aD8~0a=UW zw0bT3z9BymwPxKLA~?a9D|zSWx!BICDKD3TMDEqRs&zU(q&-HhGXFwG8Mg8bt%?~; zaQa62K1s9)rhrJQ_{G?=;hm?(r5!R<|Di-JZ0muXXvDwM2YU}fdwC?s#f2&H&W-rR z*L&nSxw*x*Y1#TbA4F4PePkws^-$;D!EqFKeK)P5k`%3ocXuD}yO0Hoav~3-VJN25 zrNhW#$pXjeKgvLU$2W97`1BFcWpaT9ajy$tiZh-I5gwahB>-1XRG`k{CibMxjkSo<_TX~{fR<@zHnbEVD{P^2>j`BIHd=+(>vbeWnG(Ds z0!jsa{uIR+X(SHZPuYIL)>SkI!JC+kG!@yj)h9a^!JtL%$duYs)NJ)s=O>s+`{fzMo z9G?;>e$p8_7Ek3`_9AdIhVEj0Rn#I9lZ9Ncnqd7Dup#GB5RQWWP$xHJCeDl!U%a4f zkYHJRl}PR)rZxB<{^>u*=8&<5g-w^U9-*?W-SbfxV3AmLd}7I6_fTHaat0N389oj@ z$6ry;RY}PX#PX=zJ+#R7`x)_1a!Dn#-?5&Cmuj!}I)pA@cIH;T{s%bwhn{{9hc)pV zvAP4S_&VZ#^f-s2E%D8ZtPGn)1w5iyD8k(WOb>mnUXUCXyzC9$^u>?n*dihQN)%;e zvMu7W=6e^I;Vp?R6@M6x@tx}Nu*SE^i*8>%2Rn0uqj2d@?cIs1dyFP2yKuDlLX>GG z4%s@9fZ1W2JcGlE5`LZ!l`OaV4D0DF@L2&XAL8nvTNz|#U64x=w!ClHD#QD)z9`{d z6UKb$arfh5s_#k(tK+R8rAI# zY1(uBoq%q_`nrZU!~l;1=H`>M10zn~(*UgLBfW{q8`kjA@T6fK_IL+BAadn{6OS zotyT`#rzGTL(H$RwIl(-S$v571mpS*heY6ed$hQGKXE^yu68lxmgUw82HQRFEju}{ zDh#$^>GbK74*9;lY3=6ge|mAy+<*^Q(xT(#GTK|QFKWV$C$Bq z0A7%Q^Bf;Q!?|7}7V~ef`FD&&PkCrstnV3+=T+^V($1mJ$o@g_loQp-$Z;j!_?^f8a1T zuabJl3X!lSBcSN3=Sqt{gvqH;Fv1(P9;>UX8?}kJo(Wlt9XzfdzY7W<)Xpo?;vF%h zEct*3X>T&twQ)&s@%O>*tAwa0>Vs>85`&d3vng@bJ8Se{da3HpE`!xj(`I#o3gE5_ z(n)DPnH4WWQJRA*Lyco3^=0Hn;Dk#2TXh9pT_rqzy#kLimDISZ*faUmW&*GjQ7g1% z7Z4n!_lXnOCF5|QSbk;I75c#ew#;vTiwwO?aFG#_SXuqd#6s8tx>pEUO4CkoUlG{( z642!@lYb#R^LB|i&d}Rlb)1n4t8>$A(%n=+ulZs{+JzS;+v`KVfBsS8YG3x6+3I8M zUj^T%4Uhn42<8D|J$S8!vj3F!m|2G+3|k`<%ZbOHfj`1;*Z8VM_Ii=|u|dOz=M#O^#>rPXE$x~%b+AY)FS(X0-Up(SNWBXS$Mu>m3>YBa4Vt!05$z-#xFrY#k zmQ%XI#n}4Z8o$6R{B!8A0_2sSEE>=w@}Jojyii$h{hZJqA10+zXjhD_)1%cX&D@W) zm^1mEp8B2XKxt7H?PHmZJg*=4AUio`JO)D777s~qDUK+2;D^k9M-sL;T)L}8e_>sc z-VgTjc-{7xlzg+xA^}`NqQfKXmGr6izMO|yqO*F#+mt<{aXnhY_fEiDi)k9R%F6y- zS*6UXcf8&%jTFccf_$WuBXY$^6t`WNYok>Nr-p+`J!xHh9DLRByM;7>k8 zj_Dg@UZ5UNhQ5eFB$54(Tx}--+#E#r*PFsrw8aB(1aRmYZdR zf7+wFvodv6Am_V<<`9;kqiCiAl~=uHw+A0*@X0;4w|gkq<@Xxz`4Fc)2v;G~xP^9avmn6TOqG=* zHb=)1N33sJk^uFvyVI7q3OPZR8Dq7x^o?H1-wF|r8~gv!^_D?#Zo$?t?lObBCP;91 zw-6*ig1ZwKU~qREJOl~B9fAdS_u%gC!96(NoO-`|PHt81zg_&*$78^crF&ERPBKuVYFI6?TwVNf!o|6qwovckV1R12 zhrnDh_NxgaJS+)kL}!O)0KbiOsS$L;&CgH}H}>e{OCRVZtsDn_XvOuO{|sTWp`49yJ7T}YX2&2en|Lz&M_uQ^1(J2g3_YT=s$sC!g)qfgwE{01E#NCXFm2Ubn+ z@^hGa?zi`|H1zesl(<#9*}`6{@_URGtNr$iLPRa+_n)Pn(fbz49fve-Gn^6E@b9s zU6%XbBAPws=1P)qB6dZxvw$pjXsf-eTBCrwgvU;Zy-EJnRj z!keZvI#8)WjMIRCfWzfGoNqeYHXNg;eiB_WGMHMOX0?WCf3dpEat}C2<_^|du>*Y~ zlK0Ll9#~H$ZkPnvP1ps_gm+(**wv4|j5|DfY1MScth9I_f2$&}b*ernKVRT$s%xk+ zMn`8F2*lT|ONaX|T!E z_=A)qo~arxK^h6~Pu?~usCGhm9J(M$q@I{@H!hpW5*3&Y)R}FreWkoL_O#*bskikK<0RVyIBy8o zsvIUdAkemCs#w4G^~mq}=J(C7JwMsP-^bAL3;UPj_Gjc&0i8z+1H8OHYHlT!DG{zpby%a!^$) z#j^>N2v109N?B)g7>1khO&xU*_$h?WlXTvdY9w`Ww)*-80kx`YfN-P_KW$C4R^IdExMu<4Wo(UvE`vwgk1=!+t^1Y&XK#5JONjShr2-y<~^_ z74^wG>*IU9ABac23MB(Dbrq0d<5bs!X$K)r4lK*NEP8SA-2}bCK}0ig2|x4t_jKHz zv6J*t(%OnbITUuIjEyWe^VRkH@`vqS@5?;Yp|%bdNATv!Uhl{9jwqkMu8Bh?YMC@y zmkKkVdc$G;t|K!SzsGX{iZ3!E#$TUJOBZfS$GcCee(AfaA^VB#O&16qAX$Z$rN976 z#jLe&$oE8ouP=LDrriBEwY{#F9&R4Qe9bex?=S-=LvHxQeEOeP*hKxcT$jV4Z}4s; zW!SrWo0nv<2DR?SErXFh^Z>s-v%BBQ1y;VJZj$A)CBNVvT{@!uq*QxCSeA3Cw%4?d zlWIxhcsO>>SqF{9uul~@U8le}YGwaDkbg9DZvvo6{sWA9*nh&-04cdFF#<>@-K}#n zGOT9(O6NLHDB>@r0LgwTi`<<0&{#iKg&6~QBPoPC7@=>jI1tkNE3P(1ICOo zICGOHW|~@*ozubC^#5r4xT_*qeO(yp3)~qd8a3j2l-p3oc);-McV>6huKBEu-uz*B zG6Wo^me{k=;MS2r1*_Z9Clx?I{K&{ zkE>%ICmXy_{;u~z`Ne@wRL_%5C<24UW>%K+TrGg*$1Jnv!2EQagV)v(DkOJsS88}h za<&AnDjZ~MCw^8N{u~0M%vhYOvoD8N%O6BMVzMt5%6!d8Du+}KVvPjpW7>E^LsoB`tGwU{QYro+`0)9J{hCse<2Zxmr1Y6YB zt;q0xyL-ScRb6si!n;7ECo+A%i{R&xNFB<$fE1WWg9j4FUz9N-3ggWLNy8K*b zHEIDao46K;%$_c4iAk?(qaIyjv%da$AJt!5B0c#&Vp08mYuKyi4v1o zkD*FI_we-FCJ`hHd?+W01bS8dY*MF971%X;Nu`+oxFwJRv}=LhMd-*+Y8>|c1%;o> z2CHeLwS3kb=&XpzNh`fMEK_F$S$XeKq(5%u=DIrG6pbiqx+(?;fP@5MO10xnHEg)c z7A=sz%xlc?f-si$szhKlV^s*?$|fdoIAhJqw8t8T*=l8%4z3IElcRga)|bE;v>I!n z{-x?X7Oagfma9!*Z|v@JB^oKs$#pyg0~TG~r5Z160$UFs^#1P>;7}^H5kZi%u^-p( zkzz$k#$084st&c$!CvX<>vwD!%D(HUXKh3hhIhU1#~Pv>%Z~Azy&giJE>A@r0_Pp? z=6Kau8!Txut1IrLN^g^r5_Ra- zw7jRSwU<6o@84-kQ5)0?r)l03r&ns%v!_i2ScGTWP5|%@nno0wJa4(@kdAx3j63QU z8&g)#I`9Q7Y0TSxtkZo^7K5lXc5z3x8ZIy-NKN$eX3Km%&SS@u&}{B{UG?LC_1+)& zhQ$S&yz;*%@1O?JNPTDg3O+c(p6u`&IPt2DopezjI$;?={X`|33A~w06<2-PUnsku zh&!C}(ff&zin*n=oZ+FCe&(+Z0WoyH6hXddz#wF4dm+>8?iJ^mzxvm$o8$N&2Z*)~ z7k73AuLA@3*!{60hZ<&vnr7;yRH_nh0aIYuNK6a@XSxETA@hT-O>W*B;qCWwXX#P> zrGQ%zhS{9Ppyo^6ogG}N2VaM|_80iISiP@h)!Vz}&I;%A5mSy!O&44fPX30u&muNHHl#Gk!uMoV;Ormd znXC&pvkA3b1wsUvpXzs$Y>hpp!p$A;Iu;fV0N@E=8>}kHVf+}PE6A~H)%TLQ%68+Z z_3~mlEhJMo@ZCz)LL}QavICvEV>M~OUvR(k z6G{c;@>-3bZ06Ikm`}vsVL_|MbIsG1-@VYo`2!)SGyx^-%Skcv)|5e@DC6X=p$Y7& zy(mKZYcqckuVl*_)pTHTq>1NXWwLq6kk|mbD1wJb)qRNj81>Za)iHsVAjX1Gsr6xF zu1EDyAF3j^#El67KK@4hzx$Bz2P53`-b&4L^T_{EMH;oEj5E?6cTzKi48#CfIEw9Y zyc6IR2YL`K<4}SYh~3}iagljDM#i5PZqJ?hRDEK|=qFwiC=9mU-VC%vGtp^AndP8i z?|>h{`cnv5vCi;>95z$%2B7D7$m!S+ti zs!$^7jC=>CSGUGer6>`C#jt)6hZ@+$Qtl*o@Xg*`F;VlazriMaJ}m6?{ch-xv-LdD=E|grq)^(~=x1k;dOd6U4+)?_C2!D1+c{dX;J#JPhQKs|ywU|io z5|hYs0+^B~n14))8 zJM$#W)v>vg0;N+k-^7Ngq{yw?N>q2PNYFtm`?HJ5H=L^FOi_>F$%@RC6y1t47f9>+ zK80PFhP|C2kf=Jc*J04)%0(wFx1q2q%*Vhk5$u zE&I@Rf_WCRR!e-PW1|HoT3ZSr?z#zrhP**Qy1`(8kZoh$l~24Xkg{rR_c zyFr&O_*LxQ_LwC8@NYI`Y z;i(nN{18Khy=v()hO}PPVB+Qj@sW~5je15=(aVI_cZ~nS^p9|MWe6xExh8K4T4ed7 zYz`n-gsFQ~hli4hX8#(heb~jea5fSFk>rA3kJGnyc9MI)!fv<8=2F5L!S1J0wy9kg zhaVD7&)nCNaT3e@(#xul}f#Iz<2oPSkk2mZY*$gKn{gAaW z_RfDVMs;@nQo3)GQTjvp((+`AeuMYS?g_It=IClv%kGc^1s8frT?1_82XYg7=`j*w z;!4~xGMR)j`RS?Gfasy4Z4$Vcyks1sGF`Yqe>eL_< z^gy=lk(z7SZrb=)-&xHBueN#{qVeda^+cRA?VEkx%RK5z zWCF;xp#x1;o-Yj_`})EzIywfN7-X5_$xu({JD=S&i8&xIW4hwUNI zij;@`dPPjZu(DC98pkTfyX+w zMkg1zGiE-alpnH|+Ho7vc2^G@w!52dq>{+od-n$cnl!#^ljQ8vWRx|M1^*<9ol2I* zXUH`4HALmR@ENOS4@dfs>HC9XZVrPv?3?*tgGe2@K>rO0Z$5|sjzuenWW>TqFQ=1a zXg_!|I9^^5vyi$5ML;D6DC|n6CYb12_@?foVi?2Xrj@igLX`!sG7ksIs=uaeog9;? zj=~?A!T*FGQb0$YCoHeG)C9pHF2m}ldg{plQ~UJ_g--l8L^ON)8dcKx=z3u+zp~u2 z353HK7fpN+XhZ3JNUk}r@GYP;*T`d_%>9!GD%Ai{$;HU2EucTRz+}k*PM7%cw#M z*5HA(G_}UCND--*aKI59RF89IJOX-DX}i0NmfmSM6Nd5UqZVEY9{l!%M1%xRX}a?3 zydh{c)Hk>hY{!JO4gz$QVnX0pli05Cv@!9ALlqE*%sK1YRIiJSqAFxMHBsYQgxOK~ z0y6pzXe`Vv0oovI6s)u4SZ}RR?LvYuIO>?-W&3uz1}rLM&FMcZ7I~Xm)U)=5XgF%p2=b zvY0ssNrjSF-+DWU^+UvPaUfWH_I|gqZpX2H{H7&`wnC2kj1Q^p_xVrv(S#sp{?im& zKOZBcgA4}P49W=cE%z*Z>V#3r&olm3je+~$puVcV6Y4U%5!U96J|1kW#GZOb3x+(H z=Y5x=O0Sj7-j`$*vmc1P;n^+wcT>RseDQyf&!_@g8uMuFeUPL5uQnD#hmB~MJdur) zWSU@c6VG9ZyXkc;!Q4_GpLBv#Em@uj^b(*-D*Py@Vp+Qs0FNJSEyAzrvK}&J-Pwr{ zmKO2aIGOY_{ImAEWZu86d}vOE;pS$YJ;>Sgk7*v!z?G7nh&aGqPr;1UcYC(nfx~CL zS~vGpHVL2m(ILejAnUk!hEre^88>E1AxHyKcJ%uLcyc!ee-+jWIc>KsW z2bHtUTr{TP&!s@p>P$*(NC9Hzww~5Rqw7+v4B7_TQm;xd2_I@nZJBn|cm+6v8JKe4 zlA;v4TNAU`xHDKta{>td-j95^;x70OZJvm(Kf9S0-hq z{+;^GTh7zo*lTwKJr#op$h`U+Je89#vz^ZQ5yBhE&*@l9SynV%nnZk0l*wnMN$S7+L1X@R~#r4MXFacgOu&8T|G~SGKXS=<6M3T__d=QxZ(@zEQ#uE zQ>GkVJJTN^$(NZ&u`lN_D-FKAs{#**Cynx6PqkBk;o%%(F6nPlPkh@%zOi(a%ViEL z6&0f8a6)I(F8;r10R+@Z^s*~Ck`TCVwlV!`w=rLnQ@`6r+jY^$E+_h)BiZaoo-oc%P<=Vx-=QoTrr2)!e?wU;mG~i^=dE2wHUz0B#>FVu zL=Ks@4s9h6D0Fs@LgUi=36V0GRoG67sTa)Cd0sU$Q-}S_YDPA4;G6)vHEcGRannT5 z<*(Pl%V(`=X1dU1i$`A5Moo_h;-b~fJ1vqwcDwNAvLHCEoMv7w8>Ye$M7>&X7-fQkb!g(zieH~ULl>+h zQbLQiBx^cC-c;QfI5K)9g_k8C>p3H-zBICg?s_th?5i+@10_GwT3C z{UG(MI2b_az+s|E?qYN;m>WrT>WXXvYeqovLvBp+9_NCHl2qcVtPUTYkqIwM#Ugvu z#0{JGsn)_*L^|q0d)2gc#0hE))j4~`1SbZ8Ic<*W2{Ezx9-iF#JVaRfiP-BUi0kH< zFLQ}<^$sv+hYNHysKFf*Hvi__7s-8kjAws-Z|1OFyH7ZQz1V~kDJ}-<_W|rc#f<5$ z;e6bcuY1$)CR!KUcH5UUyXbmxi~4Z~&8eP+eegJWn`$ZExE>q+&KfX(AMpkXdI z?2<@=Gi1;Cjkoc|D{BtXr53v>BR{WfCC@(g}n#f59W+> zd10q5JTL0I*1Bu=7|F(e#T$Pf9uuIQ{d>kwHoE_X1p5Gjk%-Bwax*kpYyM2(v>D*Y3PA zDJ52qwiU@wwO@=0xpifw%*Lbv)oxT6+pd8=o4%EARnye{BbdI19H8xdM}CbOqJ2oJ zo=Sv`ptqb!mNqCC8U`{pUQPdb2sx?^eXGv}IjH^idXmHKl}tiKjZlu}p)vPK&4dpOkx(-sQLLX#WKc2PUp-h?M6>7>1}%zCjFAEdb{qytY}*UewKN#>Gx z-m)53Cqg@4F6&2ndYc61G?(gx2jttEzla+Opvbj5J=aqbD*JtQc0$ZNsoda4X&9P` z@$qt5eQx)=GlX=wQ0ykvk&AiGKX2Ik*|W8Rr|IP2sW&vT7u#T!#2DdP9P15RC$zWA zs6*cR2tX=H-07Nci6>Y|U0P^e;f>qnjC!TMMN_+`kQ0KTE_>3q3c3^%Y+T=>tX z7aE9Ow)Syu&E13Lxl{Jhxv#ON@Em$^7;RjDA*CTfdzIZ11?cBGk)iyG)yFg3QDtkD zyT^hsi^hMu8uq2lvK6N7ZowZtm%c)R6s>l#h$#DW7*M;#R3DC{@f^ueOKRN(eZsLc z$T=<1xl?12++~X>Hdrs!;}+uU%L^wgzt1?i-nhMBOLc$0oUWc~s1ufz8(1U^sR*#r z_%w~5i1vw;l!fNapeSh|E_8bYt82h?P+(DMbabpFckMx71-AxC$oQ}QY+~t7DA1__ z-w+JT#%X4M4k9W+I{&!B`vxC_6ar4XhAvKy2&R{Ul&ZhMXW_-}B*Zf^M=2||a)&%9 z#t}9v3dTH<2COrMqe$8EC-DI{YTvgH+rrZxeBEgH6!1cZM*A(#}h541kQ&exU-xaj2xO*ncejLq6Mfw zuiY5FEVVDp?TP;LJQ_zbG2IwG-~2er-=jJsc|-GsqMRcF{3J@bJoin`nf$>A{!X)s}`hX6H+R&=vE?vZLvj`I(@GU;oi1?y~Kl^0wRmar$+BuMTjPBg- zb1*v-`neM>kn@mr|H4fs&5zf6pB})MPO!?P`&?@8c~7=Vc)@_K-KGZ`Yw?pJfo?zf zx9#xnVgCG|alG@ww?cn7)HFk2qE+0xhb!a_k}-6_+xx1G+XVR;^}H)q+$*=qX0OTz za?J?IH{D!o$Fz`^TJoulY&P7RS@?}R1w#AoD5C6bQ5sDklCu3J2q}gZI)8tNg&;8G znG4rGhsazf&*tjLw3H!~PprG2vN;F^^{WeM?S2d?-kwvM9cVW>*?ksCuhN$LtVjnm zM^`u>3pHX`G?`{0w8NBa{kgL5`a2=BXFWkJSoJ$E=#$6DE@2YLx6$MfQESxM0;6|Kma2W5X-?bq}3exsLCb- z)NEaqc?H;xa*hIMFmF?%N#U_io7YkaFEeO@$uMIdHv(Z!-~}zxM_vI`ZRRy*Nt5KU zs8a|L&0nwCzX#yCy&vOE_kQttCq`2tgsM^yt^>-wH7T`neVh+|>K1#<#gw|ey|c7D ze#|F@Ru=c|Alr?Ybl-DKNU7}f-%TSr7ROs_b|!<<~OAw>2#?F29XusG&90_)3wFzC^+M=O8sB*~tTLs$W#v)I*3+Y%7LRF~WXs^QHCF}? zUHC1fs6v{u7Z!XdB!U8X=aD7{RA9S`q9O~4ey-5g8xy&kBBN-9NW;ogxO|ez$Ejv5(<4=c(@1(dC;RXq~BJU{U@cN0H&?#i8p zIzWhLV-4{j#?nH1t}iaN75e=?p?F(TrU{ttu`O3vl-b(sBt7BGEe;POl-ueFX^rJF znGDbeyOlDpdW%X^bz@4$lVBmh`q7`!X7g-6G_8wt4!tFmv}+-CJ`-LL_h@qP`Z?-* z)tG*F=G5$D2?VZ3+$`F#E$aSAuiKIXE^vfx;t`acE(InXlff~gt9~3bdlL%LYsk#Q z)v$k68jg~ogwn_WhplMM-w1Vk$XNnu7tYiWdPzM=dkr{M?O@$TCG*G)*J@ zi{9pEdiJ)?Uua6NE@Np}i|UNP=vZaQa+w6V;Kq6ao%y~_Y-zkIeZO62nE&#@e>of& zxW@|1R-}iD!yJm-AP5R5AwSaI8?BzmXXuc>K{7c5j%5sH?J=&T&T`M=dVF1hYX0I) zfIIfY&<1!2L6y>0r8_edX6##L3z+Cc)xDUyi&J_IltV=1SK3`wDBV12B!S&GGzZDn;9I+eMbCx9MXkQTc@_!OydZbcuaz6w zIWtTpvyxXaXcy+eWvUPuqVGXnOec1gD8SiCL4*jNyu-zVLBUCkW|WYez@m?PoE$ypFxyH0R&G?2zcW4nR#5M{uQsT_w=k(eH4182s))N_=OtJQQ{$w)&i#-p*-{ z&UBh!(^pRbomtm;XI~8n_bR zl*1${s4u_#o}p`Sqi+&qQL6S`@~wVQGI}6Oi zgbx17S^N6J6-{MzgU}^O(dqOpPj50Ucw07+?gCo zQC}p!+W`=x3&mbV&33|l4f^o=b{{gb))t-~0sVFMYs}3;$->}#{WE;B(*ZE-r}){T zUJga-*Vv*AD{=0T?EGKJiEUkh;-e58Y4plcWMCc}jY^E&d))jP6$DpUSOOn|Smr@w zOPm|O$CV!-Psicsv;O+y$==JGLbuVyuF0II-qY2ZugW5pQ@BzP(44rnC?QR_IaTC# zZ`P18VcM14ybK%dhLl*7pZyq=A2eY_hn3`{!v(m~Hf;>*46S;c;yzq9Xa3`((p8;n zQ)uYj>3ca_aVV;vT-l} zFgya$6R870dTRK3H|5*E)(CMxdTc0oxMIQnA>ONq+fhfwe`ob9p--%hWhUiu`hUYW zN1||Xh!I{`x~fQ{l&uEHSTssD5-s>zDJB$&#S8ll-%^z6D^#(Rk&*DsUjxI3O!$2d zn=T2lKVN=ww#}K@Vom$R32u?c`Dj+zT_pIIToVw`4^^8f5NJDnX?^IKWFw&Yhk7|f zlhL7YK))t-uEy5D?92JoT_rv*X}Orpr-R!s=SLRTfV2OrXpDv3T9PyT9NFiTtFeh* z+Gyz~cD)+(V%uCK%R5d44RsqDIE~l|Ru#EK+29%u>F*?@jFNhZa;^BvRL?e0rIWT+ zzZ}uBUr9OPXu%STN+j0^LA9)*m%F7h{wJeCA+GC2DtcSAPOI$@#Ez%E!f0x0 zDoM7LxVRTYv^2pWK&4pRTwb4!X#tSG>mT##rVR+WC$9vBFwOyzs9dg~>y1ZE^%v?Iwtp^eP zzScApG5^u#IH*h}1{y~pHDsJ$c~3;Z{PfK?7aoH`Mr=y?=yBu%4@cGgjZc^WmQT`{ zf;drz{3{V0`do(@8-N=vjb^h_2cuSM^5R^FmkstQmaXs{^=>(jajb$aZ+zzi90~Tc z2e|J3FE4aJx-3*ppYAGKBlaJjvcE;J0(X&GzKO5EQdG08xmfC@)ca~w6s3)fRA_;b z!bOpptH|ECT}7rYbW9e9#(OY;|3qMuc(TP5tS&xX%9ZuguLE z5o++-A_mfQ-}|tM8mi?@XYA1*cqdm>>youH>w3|5rJWT-yIZNP=$!<6D$Bj^-EQO~ zYS+mXItluTJ<_)DY-Qj`#Nd4PghDg-=mmB8QORV&KG;Hs^<6E^yB|UlBh=@ZWZ#%H(e)7G7J^lsW$w|^Hf)V3IzS(?(FnSYbkPlwM4BiKwOupo>{XlKZAgc1 zzv~NCQO4P-lbrbIgbL&t)F`W`g43;-1Z-A4=F;Org z(?Vi|D5Vc2#Q8`=)@^Kkejd!nlH@p*mf6oETnx~967aw?`{@M*oQ6dvXEN#J%2ZPC z^~c#$DVxQVej#~d!JJ9lUuOb=3EZb>Q?mQb;r!+$7B;8q_8h@orXp65VE6&g`Bh{v zp6bD|lmTCZx;XczIzP0DlZ*aR?kE)R5Qm5LZYS4Y_qNi@LXmHMoHTr|Uwhq;!|K&H ztj5bP2Q^T(+An+Pp1~{m&i8ZexD@$n0-5;(V-a|(ks&p zzN<@r+K%vfyyGd+oCSG;CY8nfhF3i=388!!cW_(7-EX(HfM;SM5E=5|o_5z-xO;_= zPne&MZ(e2kTW+N|3R2|Zyh&E1vGu2_B#?I5XeiDCzlln@R?O|P-G)dV;;c92TRu`~ zX(P!!!i=!Z*b=nfig#D3`PXQv;DRb&yiTaaL(SY{{Xgf*5bPXU}8H z;OnRka*kteauH}9R{HJQm|)KvjEGHxE3ud>k6lUoNyw-$YtIrp&$-@cW5h^U106Io zOpj~+gX#aD*X$^#T3_3^C9vOkXbCyJq-*P6(WgknKT(V>l8gF0!k{C;g6Mm$zt-)H z@s3S)!a`2&fx+=zN=z+EI39mL%?j1br`rjvE^M2$d1P{Bt{ioqqr*tPquEEAJ{yQT zBC?hHWHpc?LpS0fB{8&?6ragr)Ae>;l#PiQHp;&;z)BO87uHp5LT&ls!xn`M)1s_O zMc_reG|8(_vW->(>1`8nSdS-Dx)W^m(kX+n~uPQi<)CzYQZ9ZeIV)nY?DEq0tr zBCD@H7>loccT(_ygFk{+PjmX*sJ=qRi3cx0U(v`bAZAP3Xx3`|+qm_*&M1uuCh0~O zHO;S^>EFU&I>}h3X+Y1Yfu}>4g{7ZOzRARBhR0aBVYAiC!;N;ckJEzh%OT&jTFNxzD-1Qp2NZvn-%`9Ns;{_~8PAKc$bB#mzY*04dwHL?VpYB{g6fI4-6W%-5G1 z*f+4Zd>#uqE%xqTh?D!Cj`L?e`Tx!VB63jN6mc7L>G%&f&$s~o#t@_?GdA-|SEh=o z&f9GZMgDiHJu*Q&OP>`h7bcHaSoxS=8yn@Gx5FcolT5V4sS83j_?K*n41gibasKdF zI#T}!Ub>^WBYr{oCRb`i?bea#=FQ+=O|9pC{_=H{3;Hlx zD0Xb|`l?Zq1Sr@H2-Yk!DDlV*V76%q8OQ2T`r^2$NkL{Tm(keuZ$IL$zRSfs)c4;@{yb2` zz@(}CsyVtD9_aPiNKA#I zfn1z_H#4~jF?sl*NJ3_7+cD)y+7sS)$-V8wT0sO-x1+u?#FBcd9)&#Mw_>vg%L0(M z^K|ZqjVn**vf5f7aNhg*`L{8>y+c@IUNoH$8~I_=0tp`|<~1;2H5)b>sb)bU;KW_W z(P-evq(h3(O|J0G_JH2dty=hK9!x*jbkXB92JulE*V9Am^M1-RB$BX6qn-k>BC1gv{ zY8~^Rr{}`Sgc)LOE{ZL?^R~^bT?!mp1kV>u7ZhTj^s3l0(n5@O#FpBg=#PfIUW_lH z0>fc?!=>em7X2n)grUr8+6U}Ka2o)H9Xw)hu$?L(CsZK-PrKw=;1}0NNgHr{blTf0 zHd42fhJhZ@?j}yHtP3O4zN53kn%N%8>AoQPvYDgb=1SUCUPCT+7x;8>h3a)Z%>``? z1xMCz8n6cb#-~`WD--V9^6}Qb1_vYpzgJHW#9_|-q0?tl=FY? z58Iw_icsbcCEF1}vYv1e;ubZnGVMOafByBgvUVM%cy$^qU(wkYx^U8}r8jWS)- zIF`+&TpgoE%|ogUjk`_+sa6h7A+C=1(;dJ|28j-yShabXcG$4rnEV~TL1d^C9u@)c zZWO>8IytA=;>7iYmB0b?Ljr2vrA3tr z+AqKRT>YA(2pIxgpFG8~>2Q9i%8o-_YPX+8Z1#;?-AierX&NC5cTimN2IrVcS8#(bRoazz?H^o ztK3{E*NbfqA)f;Qq3bS()NjN0)nPe~rxkV;OcPkPcIJE0Mwz-06YVk{D(1jsl(7%8 zLbg1rbwU6liE$Yv9XfapZpw-lqvl?_`>EF?lX(+-%;?mFw1b`>wbR2Wov{+M{!1hO#WY{$e*%m9udK zE97@wOvPaqa9#lB>~!Pd0N@m+ymRS+cFD|5QsCC_3r~-ImgDi@ipqj1cf_!G?BPWA zv`!xkWf=XMi1`p?SY7#;UY-3ZFqYJ&zES*2c3$lto$~+rg*1o5w5n2GVUh`OUN*2R z)BnaI4Gb`f>|%*9-sfHpu!@9ZM*G1mQD})~+W@DzUC+xGOi}I4h^VvAwf~{0`mJRC zmi+ecJENe94d-0Q{DIEGEMGBePekt`uMm$(7`8G|2m!O$YKY7$VuVt(U6~m2UgO*b)9(5^t;dXxx0R?tKI!XrQO4mUdzNc$MW=3CN(|1_WQXeGi-qT zxQymXxAEp>&>YEwa20<8n+4Z{^88VN&&??E!%2VhNj= zrRn{6+R|^%CgY z^brEj7z_)xNRjuP@Wl9Iy`PHsbX>1zb9`Te2vw^cMQ)icl9E(~8qwVn?V9=X}K zg5Fbp>vMuFF$y8_^b@X7=QqQZZ^&Ue-lksrX|?yuqua&XKrPK`R(AWRMm$_jL1HMt zp(;6~>{SRP$=6@#?z8J~Tjfz_M6e-_`Fk(iT!$T&B`g|84S;P4ny~;WhFK$O@CrXA zNE*P)fYxX|gEreOgiHgon{5QOs_*ow-fOt-K1S4CNGas_3-$p5DBnY~0m?BIf&V4c z5Qn2MeuJ57i9D(SHZYcxBF9X%b+2*$Vs%Y2%3kxntBVgz9&HUWs9M6*k(RC=VISDM zZSKG4v?k`B)Q|GLZuTgBd zAEB!|Wtg0CT;+`2g_?ujh?Wvl|Evz&s*&66`p9l(5!H8urR>+`1~e-yo38R?e5Oa4d_-7qIl?>gACxDO2=bgI_hxGJF_6jWeuRw=Fof3~N5mf%Gy=rY~s(^0p65nL9 zUjU45sN3ugdsUNx)YX@QnF{5gI8I0HIXDF3haZ@URgk8{Yg@JTd??9hL<+l4M&!bT z6bs3(ZA!n}S9mD&^OzN!=)7)W*+W*o zh`7q~Zh$?zbAJ3~oSOVt)_T?z2^B1QjqXDn0Q+0lYW-)xcy9$6M>niA-%Z`{rA*`WtUQt4(19O!71HpvU;|gs;`I z@R%5NgBk$HxmLOKfcblehw`*K`HJ9_q{m z37^IGNE6|^;Y1^jy1Wfyifx?74CSn!*_4wVPIpzS{e%7bA3zpM@%Ou;`Fhse*gt@o zoOfx3JUZ+g>Ix*V05^3H5Ekj4ai0v5K89In#1dt&*)im6@y=v>JzDDK8DMlT9!dn1t?IE1}3~*tj0|*-B3rySHNHR>#I- z>#`!BYc;6SS?W)msZhkZ_c3SwSORlI4~XP4)Pp_y9p97e4xvU6ovwbeQ-yNU9dxjI z^xkC4EtSUh?Z4xE&Nc{L?*_?H1RRdn?|x^JoszEb zqYKm(>C81n$t&53P|}(Gt>VgR_}u~rDcjSks94LG=)hC$lVDq6hQDI*`C{a;vw#o2?Q`Tj^YfTsL za5LFVv%H3Ipe>|Px|rOo2%}>)6J_Gie`qXpagC! z;!5Vc36|8o$t1!{bdRrwil4HhIWyt`Dg+ls@DrplbEHj6A^lGu&R!?2N3XBynleVy z11sZAm|H%MkA5uKcq%jq17oR*H#2-+A9EJLvp}HdSvDy}`6@xz zw_SoH9ZOv0Z_HL~98|3o3oNMmlVfmqhqD3$`xHq8aRH!88C8;uN>L0s+nOv}fWrhz zdcp5O7#)bwSS>e3l|E$N>aA(yM!<#$n8HXQ0pZKgt`Wl_{5aqJS8c_eNC!7auo?n9y5NtYTb$e2w^FtrOM|NX5%;y~9_MC!iLN*ILZ$ z?e&8ohsh!?;MqReP(T^Bs>AJ>q=CEOX4^!k6<93tx{kuS>9z2pK+}uPlXMVgr@*UG z#yLBOl(>AbXj8H(qbBvDe)Z5CA=PlRonczDiBtVoKr4qm*F$EVW#+faDK?6}vkIkX zbCLT(O9By$d%o}%Hyb$zZbb24X0u^4@5sA;W>Gg2VbFRn*HLGIB)C5o+Wvo(y>(C= z+txlD2<{Nv9YXNH;67Lg5Zv9}-F<=vCup!B!DVoFx8UyX?%$ky?mg%H-h2P~s=BD% z)m7be&)RE0>)C5P3+B}OUK?#}F2O`mnq{@=dM@kA*G~`gTni72TG3&MoWUHPpTGf- zF-TcV2i(OTsa0@o84oGPsmB8^q|A3TwDVZ5d2bQKeu#Hi)W*uWI$hj}7u4PUCl3CP zR}A{2Et3fUFJ9WeamO4D%-j!ST&Ay8M!%WDQ<}HGxZ75mC>&0=^z=|iy1uob**M>2 z_cfQrx8TcYS0s9i4+g9h)>WYN0^H$g^w0h$#ji+JEY4)>!1Tqe%NuKlYsis-n{ z&6Bjm#)6%h5Wm%Kn_w7%0oU=tblT0f+U!x!uKdEj(%52lfoZAo=M9h0<*wF`Z`(N6 z1$QRuzv-oyh8RF)#jGo$JWBdRt<7~#kav(iU&l|3(QVn!K7lmqQJN7mBb&UrI*!Va z4<|$4#DKxNk~KXZk7t|m$G^&1=0+CX_y>P?-%*clJ9u4hD`D7?R93DF5968L3quy~Rxi6XZF>*oK;H0! zi+SPTT~zWX<-Y9We`4JK5cvLo?7_CnSD?xN9qGC9L*d6Mf~v&3crt4CYQ&-5*~pR% z|KcJ{3W546(JI2ZkUx$^8P$61>U4RxEQR>iGt6wmv{Z}_BUR=)Q5mZSOoAl z!M&~#rx0_Y9Ey*pWVJegV#&+Bs7pRq=}vf)B{8H1LuT6WysVTl@pTd=UNq7Oc%my4?K0c=H)}w* z&CD=%B=Vm5IYO-SmY{Tj8M;hF2N2Yvx{+_Tsh+nBj%YLT&Gmf*sJ_N zl(G;n1EO236*~7wNWu*a`%tgOlQGscI*Z;GcYZvK0Oi1)GC7bC)y6uAOR~lmey<+3 zI-#8?WRR4u%(VvNUiLVs`8weUI8;+VR;H)yL~Gt1ng*++*=VWj)=tFc;1u-oqfUit zin0i%>n_tT9yct~dxh3?0XR4!o7p8WXNR#VZD^v^Y11HyeM9|qwxwvf{SIzlY}b#n zM)IudmHnw+F@o{L0BovN*1TAD9YL#Z-@J(X;acnF*lm9M*mcs!hEi6*BLrhc-rboS zd7HFYl6l{KZFzj*;kz9nSu7OumhO4S2LiGeY683EG(ks3Wh`Ao!5#3xjT)JLgn1F(IO1*bv znmmpn+JF2u@und_&o@%p0u9u@EP0q)mK29DJ%-R}N_k2Vc@UAwF zHz_1S0IM;wtjc$NH}glWY(xxsNW-O#hN1~=|#k-0*8E87^Seaw;I~07~Cl{Vsm*-3EYG> zi9m?+L&-Umr`Uf#KgP!(1FO4+SLZ>XE3p74VynEaOo*RI;pMcw7N z4+q6u@j}2e8v-4z&UnZV-J}#z!n!g*e9Vz$3&U<|ht9IR9fX>-qW58kl ztveKgoJ-|_V0E)-x9T}B*Ji(TBP+ZVV)ZM`D%S6|5u0q|;AvD)z#YyO;4{l{OA)j? z{~O^%Nlar=$frG;lzu)WV$*fKlcve{I!-4;t0oQk*UiW@ET8vX5{WplAPC+JQmwZX z9>U)(g2AC=fke(6xq^*Y8O^izq@ME?{&ed^P6XRWvZ840Fl~3}QQp3vnI6Gg$Y2Zy z3nSx}+}UEYVZ5S&Psa7Sm#OLQnDa`_#=wH)_Bl8zGIC*}0FAtHk&W-1V{|M4k%;U( zZd+7fmBKc`Lqec>^#e(`55v1^!)AR%pyMZUB+~}X5GjOMbtr0HoDtKh5N(^`fe-TeMxeAU7gFf4@tv8y&UE{|dt>&%VJJzMHDn<+}d1pZNLTUeXmdAfzF!aIGK& z31?E*tNDYg7Twip&9?Fp)AVS@B#PA`2&9E#13Oiu%lzPxjqogWulo+GK)tVrmz5dKaLrroMU~+c7(EDFxo;z|3!RED_lW*6`H9H}rhzqS{8eOXRV%(G(z`wA~M+2&El9KK6{(KmjFPs4(rK3)R z;fEo&Gkj~2Z<-Ylz_W;qkt5)Ag#pxir<#K?omC*xYfd=gwDmlh_3I_FMK9m6LaqXS zf;%kcdGGO~pW$ZO!<;>`FZSrNpXjr-y|u_kOKwNTi;F-b(~RcTwAj4G=*5G74H^^5+gx(7@8Nbsp{xEM`?}YBq zcS2BNa~FYRVBa&9u3OGkiX@`*7P=H1u}A+4nb2s>C*=LT?-(;IpAM;OqbSraO`5~G zIR?or-t9K&=R>u>SA1Xk0AIhh0iQ>|y@OBxiS76@Ap`{93$_LPpS#MWA%74OqOyme zb~FlMBTUUltDO1Y04B)_=Se|eJQhjAB&6tKT-$;Fp4{jXjVF_=k*GpkmyzAF%uH4OkKsouCJFuC(m&CUFDk4U==ha#I8 zE2F|le>pt?3_`qZR%#Z-sk3yadMR8VhF07D$eFGY za57$U9AJilHwd{D0F&D8kjiAPmAq{BaKmc*u6A+<3eNKfYp?oxEw;WqAU|GQ%G(&M z15SKYe#g^c5ZEt76Z7i#MH7RKBxB~*3(z#t1{KWpo_H1tX9WV$4rktj<{M=~au?Ae znmSit_0{|AG}0?wxHf@v_krd)c6Ua0&bLEc3*7>cxgA7%1?o0QFAe*=x>-Vz zDm?g8eh#YGaL?4wlLZPm5xyxneHPKVI%Uyb5-qA-7z*MFN%P~BtBXzM5TTs$ z95_6ziU~+BSx^H;Eb=ZWwmHS=x6p&GrR;y4Z9t10gurOgg?4`W17Ne>K`OKepPWjl z{*Xfq5Risk6!2JZ84nWm4<(=D(lXz#hcb$-!#L;*g=NE^H2|7eMObg!dHos;3iMwQ zB| zzJKVA3gtr&x!E&7JWIIbmk=xKbGXtat{6V#dKh!5k2EZ1$QAvq2~k=QuR#M9Gl1q4 z%6_aAhW6T7s@Iu~2|S_@@1f6)e+g*tE7;umVolK>h+$Y{xT@U@hdCF=X{7=zG2lQ1 zA-uO1*0o8P2LQiDB6O0BnK-%Hucnaj0pXu9e-8wAqMsPm&}>!mkdAJyUy&&$D2qWp zp(J@b@^Cqipvf70K;MPt?5JL|t)Kg&&*R0CiI)13|CjfO0U~I#EZws6Wye`0wX3fW zE?(PLPOj#!_>{-6V<{QFHlbmMIbZl)QKz>su)mj9qCFPSNK$H^>+NJ^6UaT^Q*Pi>#wpZ&rEzXf+TVOc&3xUJ2-uL{e9x*d$P=+cZ7)N(}NW2YnhLT zrBLf0MOQGs*)#8yLNL~q_r8=Say5a7gG;VKgbb1E^G>r)n*&iS{_fG}Qo73d@cuP# z(vaCvdxr+!%kY%@4l&%R55^n3l3+=B+K`fOe2@B@6E6;-jgOc=W=f%mq;SSI*XCD* z*?>I2v5WRwiKKzgye8t>?KD82Ph9|P|5m-S)Yn27cR^m#bq)+im27ewz~YN;w_^*A z#n2d94hh`pml_pFirJzLZ6Y|YZBF1o*IeNLx3vS`fXR4+IEn1xg1&uVW$02SIc!&- zt)GyYrGO9u zY`dOw0iw-sqxDbrJwjR+x2g}{;VuOZW`0O4th}c?-(8CFnw}4KAxmr8COs`dKT!WQ zU!_@V%8A0os=ZvO-Y^Nic6_%{3P)Kt8(S7}5$cJBfKb%L>utZF%k44zldkVXOQ=&; z5h37>uIsaFm#yesMNLhhO$Sdy5B**wb(r^g@-Q_MyxkLJ`gNb=C)d078rf)3nAnc! z8#4LVyPXbmNcG%WALd#}4>QKjS|z4-GjZth(uBdGuC%OF)Lfz0;l|pO-46a|#9r7@(Q`F`2VhDiM)LvtvOY#(7`B5W0nZz5^ZaDY zPOyuBYbucqMGfa#fWM{d<%Edw7PR00ZBv^jkmzf%_sv_soZ3KbeyS5}iMhwcpk2)F z>&P2utNU=R&N6d9cWRW<9!70qv|9NebOvuNY)V3J@oRqcN{Jo1mz9(x+So#MmTFn) zHyCi)vCMBc&RZ6yv1}=>aTz|E^^!*tRa42!3OCo11+HYvPN>$rcD&q`9qhtmnP;;# zA>{~`Zhl_{QkS8PWxeg0BA|;aMX4ybjhDq z3P5xyf$|`@m4StN)4P?^TR7^gTh`XyE>k?ot_bWdvucfIp=@~*#tbP1vZ8-fV)(ND zk;lQxUiv3zT`}}W?(mRYitC?PXl)&Ger=zg@CnA118OdSe#4TZiItxUOAVf)NFMY@yhuV_+y-3iAY=f%%xJK0;6m zQ}0gB|Em}Dr>uDUCM1veX8=511}*+qP-m$t(FPOBev@>$#m~JCs#NH*{-(?maKKw9 zl-Q^>dzo|GrTlvm`3c;%&3#rm$+55lZ#{7SZ@M!8IiGjjHVyBxt~u+n8```-YB(-s zV~2D-oY}*56+|p)e&Hdsv?Df+vdooenXTSI zWx>wm1&ISs_(8Z+P+`DDQUnR;c>*`G$b+@|;Ke5(AN%T`DR9 z51n4Z*W+c2;&Itd7rgpBQgubWMz;x?LVVct=T93~&%sPy_cuA<0&7##g`ce#F-%Y z!C$OtUfU71xhSk#p5F3^?&~@*+f<+0<5bGU5Kz`<78V+pGt=sZR(0!s&Dt$QEk?M) zD=Zb8D>FxhPJQYU0|;k(D-Ry4Qj$Dvm+zj@lV{zgmHiQ zFio5JY=RJW;Le3GNIk}KOyOPdIs2)43HJgP0Soy>j=-o3>cE#cpfrM|pAI-^1-HXv zXI>FVZ_(__OMk252fukeu+_YC7Uh50yI{#$Fey$b_U z)N4ThKI&hob?vsaklyXKf?f-5|5^wF9{=d|-D+L~ss4ix=vNP>aM6ks6{jQRlKlvc zgIZUi{P6+1N5;CyYf6gC9L&qQmOV>kZ)mAfiVUR&t;$9SGBOWghYI5&WYp<)NoMnM zOSe4!N6*mzaPDa2?WguP#O+YkQ8DKL;)eaFrGTnX=rYn=yY@6b`;nbKuJl5-cB~kU zZj_|~!pjt#wrM5nD6(iU;;{6WX7O^Tr(cUkFniBL*x#Gbh;63TaiZIa?0@TOYt+I5 zyEIn|DG9ycMVUE_gXrz(Rp`}9Zlyal@J*sakONCeHJkUHHPvUtgD!MhOp(r_a`HC1e6peVbp|{rprlv=&W@#eO ze|%Sd;($FQR69bS_-4~&Rj(VC*G(I*$7S^6!6*-4U@$#*?3GnA=KaNf3M3&&Z%*%Z zTkGbj`mq$@e5UIlgv|EUw^y0lclL_`p1T$ep)z{Y8}L`&E2Ll>nO~?)SI10E?)z~% zEv`7wDnLSQ_4exKuORXFM1%|rXZDyAd9=smFy-**N7di=rHMM4DS!ZmttwJryri^9 zC~^~j9L{Oe94&ZPv|)try;4!R7d{33a{t*32Rg`;G4A^M;%Fy zGSu|sC`W?R9|;VB?~%xI8x^~I0#ig`$|zw(JrEiuBYS(0TTc1;&7Cw~>|xY1J(e#O zw*=}}tSKEWNKP#{=Z?=P`=U?H1hIfK+a90Ro*AQJ{I!|GKp;>GoQRs}glk6J--v?r zGX$!`e^f;J)5Mo$A*aheFEzJ+^5(Y`kSXBPZVpB`!iBE6sVL|`?J|8UfFgD*_b!x@ zcNZp!n=oYRI|qu$dpDQ}6mZivE19K`7%oTWtfos%@q)7+7oPouV<V`m8Dt-`EUGHI@b znn+giy61|C`ctx!+{^iD`bH>0aM&Lg1Nv8RA~}FQhXuz80MM@3|7rkC3`l;ybzU!n zxu?uX)skO0jLJhuGq|ep_XLq?? zLp$83p&cU(n%MNYn==xrgoMyq?{oD|=PxWFA`xoG5RwSPaTy%KcL{E8ep9J9&5lvH zQ|*8NR)nNUZN5_eVAX&r!wn zc6rgbaU8Mw&N_3v8idV7&$>8yt#j9nnZ2#U;>TmfwJiv`O)fU#b*K9(cL0<|c*(%ai><7kK#J8(1q^89FRX}Q!Z5w`~ssbEj& z5QB=z_z{6Nwhq7OdLDkdsfxwC!Yj1iNVt?G+@JW?@`@fir@j#`){TI&Od|K>v_GI%%CEfcNG1!*O%o=&NKy4_3X9g7|I z@;kwDh5kHEW`SP7EBn8Tcg_P+}seb%7kW