diff --git a/.env.template b/.env.template index 3a2097331..4014a50c4 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= @@ -12,3 +19,12 @@ EDAN_SERVER= EDAN_APPID= OCFL_STORAGE_ROOT= OCFL_STAGING_ROOT= + +# SOLR +PACKRAT_SOLR_PORT= +PACKRAT_SOLR_HOST= + + + + + diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fc96adce7..e74055b23 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 @@ -61,12 +64,16 @@ 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 + # Install E2E dependencies in CI mode + - name: Install E2E dependencies + run: cd e2e && yarn install --frozen-lockfile && cd .. + # Run tests using test DB - name: Run Tests run: yarn test @@ -85,6 +92,8 @@ jobs: PACKRAT_DB_PORT: 3306 MYSQL_ROOT_PASSWORD: packrat REACT_APP_SERVER_ENDPOINT: http://packrat-server + PACKRAT_SOLR_PORT: 8983 + PACKRAT_SOLR_HOST: packrat-solr steps: - name: Checkout code @@ -97,14 +106,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 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/README.md b/README.md index 4cede32aa..a820f9c06 100644 --- a/README.md +++ b/README.md @@ -1,37 +1,41 @@ # 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: -1. Install the dependencies: +#### 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. -``` -yarn ``` +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. + -2. Build existing packages: +1. Install the dependencies: ``` -yarn build +yarn ``` -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` - -5. If you want to follow debug logs for `client` or `server` container then just run `yarn log:client` or `yarn log:server` +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` -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. +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 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:** @@ -45,8 +49,74 @@ yarn start:client yarn start:server ``` -**For common:** +# Alternative docker workflow: ``` -yarn start:common -``` +# 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* + +## 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 +``` diff --git a/client/config-overrides.js b/client/config-overrides.js index 62661fadc..579fa29c6 100644 --- a/client/config-overrides.js +++ b/client/config-overrides.js @@ -1,3 +1,10 @@ +/** + * 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'); -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..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", @@ -41,7 +40,10 @@ "@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", + "env-cmd": "10.1.0", "formik": "2.1.5", "formik-material-ui": "3.0.0", "framer-motion": "2.6.13", @@ -56,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", @@ -69,7 +72,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/", @@ -86,5 +90,9 @@ "last 1 firefox version", "last 1 safari version" ] + }, + "volta": { + "node": "12.18.4", + "yarn": "1.22.4" } } diff --git a/client/public/favicon.ico b/client/public/favicon.ico index bcd5dfd67..3b69d8ecf 100644 Binary files a/client/public/favicon.ico and b/client/public/favicon.ico differ diff --git a/client/public/logo192.png b/client/public/logo192.png index fc44b0a37..f4dd28018 100644 Binary files a/client/public/logo192.png and b/client/public/logo192.png differ diff --git a/client/public/logo512.png b/client/public/logo512.png index a4e47a654..8c63e52f7 100644 Binary files a/client/public/logo512.png and b/client/public/logo512.png differ 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/assets/images/default-thumbnail.png b/client/src/assets/images/default-thumbnail.png new file mode 100644 index 000000000..8b1064ac2 Binary files /dev/null and b/client/src/assets/images/default-thumbnail.png differ diff --git a/client/src/components/controls/CheckboxField.tsx b/client/src/components/controls/CheckboxField.tsx new file mode 100644 index 000000000..63b29be98 --- /dev/null +++ b/client/src/components/controls/CheckboxField.tsx @@ -0,0 +1,44 @@ +/** + * CheckboxField + * + * This component renders checkbox field used in ingestion and repository UI. + */ +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'; + +interface CheckboxFieldProps extends ViewableProps { + label: string; + name: string; + value: boolean | null; + onChange: ((event: React.ChangeEvent, checked: boolean) => void) | undefined; + required?: boolean; +} + +function CheckboxField(props: CheckboxFieldProps): React.ReactElement { + 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 ( + + + + ); +} + +export default CheckboxField; \ No newline at end of file diff --git a/client/src/components/controls/DateInputField.tsx b/client/src/components/controls/DateInputField.tsx new file mode 100644 index 000000000..92119bd7d --- /dev/null +++ b/client/src/components/controls/DateInputField.tsx @@ -0,0 +1,61 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/** + * DateInputField + * + * This component renders id date input fields used in metadata components. + */ +import DateFnsUtils from '@date-io/date-fns'; +import { fade, makeStyles } from '@material-ui/core/styles'; +import { KeyboardDatePicker, MuiPickersUtilsProvider } from '@material-ui/pickers'; +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: { + width: '50%', + background: palette.background.paper, + 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, + fontFamily: typography.fontFamily, + [breakpoints.down('lg')]: { + minWidth: 160, + maxWidth: 160, + '& > div > input': { + fontSize: '0.8em', + } + } + } +})); + +interface DateInputFieldProps extends ViewableProps { + value: Date; + onChange: (date: MaterialUiPickersDate, value?: string | null | undefined) => void; +} + +function DateInputField(props: DateInputFieldProps): React.ReactElement { + const { value, onChange, disabled = false, updated = false } = props; + const classes = useStyles(updated); + + return ( + + + + ); +} + +export default DateInputField; \ No newline at end of file diff --git a/client/src/components/controls/DebounceNumberInput.tsx b/client/src/components/controls/DebounceNumberInput.tsx new file mode 100644 index 000000000..f750b85eb --- /dev/null +++ b/client/src/components/controls/DebounceNumberInput.tsx @@ -0,0 +1,54 @@ +/** + * 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'; +import { ViewableProps } from '../../types/repository'; + +const useStyles = makeStyles(({ palette, typography, breakpoints }) => ({ + input: { + width: '16%', + outline: 'none', + 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, + fontWeight: typography.fontWeightRegular, + fontFamily: typography.fontFamily, + [breakpoints.down('lg')]: { + fontSize: '0.8em', + minWidth: 50, + maxWidth: 50, + } + } +})); + +interface DebounceNumberInputProps extends ViewableProps { + value?: number | null; + name: string; + onChange: (event: React.ChangeEvent) => void; +} + +function DebounceNumberInput(props: DebounceNumberInputProps): React.ReactElement { + const { value, name, onChange, disabled = false, updated = false } = props; + const classes = useStyles(updated); + + return ( + + ); +} + +export default DebounceNumberInput; \ No newline at end of file diff --git a/client/src/components/controls/InputField.tsx b/client/src/components/controls/InputField.tsx new file mode 100644 index 000000000..86739abe5 --- /dev/null +++ b/client/src/components/controls/InputField.tsx @@ -0,0 +1,67 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ +/** + * IdInputField + * + * This component renders id input fields used in metadata components. + */ +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 }) => ({ + input: { + width: '50%', + outline: 'none', + 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, + fontFamily: typography.fontFamily, + [breakpoints.down('lg')]: { + fontSize: '0.8em', + minWidth: 160, + maxWidth: 160, + } + } +})); + +interface InputFieldProps extends ViewableProps { + label: string; + value?: number | string | null; + name: string; + onChange: (event: React.ChangeEvent) => void; + type?: string; +} + +function InputField(props: InputFieldProps): React.ReactElement { + 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 } }; + + return ( + + + + ); +} + +export default InputField; \ No newline at end of file diff --git a/client/src/components/controls/LoadingButton.tsx b/client/src/components/controls/LoadingButton.tsx index 38fa5ec61..5c1fa9823 100644 --- a/client/src/components/controls/LoadingButton.tsx +++ b/client/src/components/controls/LoadingButton.tsx @@ -1,5 +1,12 @@ -import React from 'react'; +/** + * 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'; +import React from 'react'; import Progress from '../shared/Progress'; type LoadingButtonProps = ButtonProps & { @@ -7,11 +14,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/controls/RepositoryIcon.tsx b/client/src/components/controls/RepositoryIcon.tsx index b9ebfabbb..5cc918c95 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'; @@ -9,8 +14,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,21 +25,22 @@ const useStyles = makeStyles(({ typography, breakpoints }) => ({ }, initial: { fontSize: 10, - fontWeight: typography.fontWeightBold, + fontWeight: typography.fontWeightMedium, color: ({ textColor }: RepositoryIconProps) => textColor, } })); -interface RepositoryIconProps { +export interface RepositoryIconProps { objectType: eSystemObjectType; backgroundColor: string; textColor: string; + overrideText?: string | undefined; } -function RepositoryIcon(props: RepositoryIconProps): React.ReactElement { - const { objectType } = props; +export function RepositoryIcon(props: RepositoryIconProps): React.ReactElement { + const { objectType, overrideText } = props; const classes = useStyles(props); - const initial = getTermForSystemObjectType(objectType).toUpperCase().slice(0, 1); + const initial = !overrideText ? getTermForSystemObjectType(objectType).toUpperCase().slice(0, 1) : overrideText; return ( @@ -42,5 +48,3 @@ function RepositoryIcon(props: RepositoryIconProps): React.ReactElement { ); } - -export default RepositoryIcon; \ No newline at end of file diff --git a/client/src/pages/Ingestion/components/Metadata/Photogrammetry/SelectField.tsx b/client/src/components/controls/SelectField.tsx similarity index 53% rename from client/src/pages/Ingestion/components/Metadata/Photogrammetry/SelectField.tsx rename to client/src/components/controls/SelectField.tsx index 9327cef48..55ea039f4 100644 --- a/client/src/pages/Ingestion/components/Metadata/Photogrammetry/SelectField.tsx +++ b/client/src/components/controls/SelectField.tsx @@ -1,37 +1,48 @@ +/** + * SelectField + * + * This component renders select input fields used in metadata components. + */ 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'; +import { VocabularyOption } from '../../store'; +import { ViewableProps } from '../../types/repository'; +import FieldType from '../shared/FieldType'; -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: (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 + fontFamily: typography.fontFamily, + [breakpoints.down('lg')]: { + fontSize: '0.8em', + minWidth: 180, + maxWidth: 180, + } }, })); -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; } function SelectField(props: SelectFieldProps): React.ReactElement { - const { label, value, name, width, required, error, options, onChange } = 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' }; + const rowFieldProps = { alignItems: 'center', justifyContent: 'space-between', style: { borderRadius: 0 } }; return ( + {identifierTypes.map(({ idVocabulary, Term }, index) => {Term})} + + + + ); + })} + + + + ); +} + +function Header(): React.ReactElement { + const classes = useStyles(); + + return ( + + + + Identifer + + + Identifer Type + + + ); +} + +export default IdentifierList; \ No newline at end of file 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/NewTabLink.tsx b/client/src/components/shared/NewTabLink.tsx new file mode 100644 index 000000000..666a5072b --- /dev/null +++ b/client/src/components/shared/NewTabLink.tsx @@ -0,0 +1,39 @@ +/** + * 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); + + const customProps = { + onClick: event => event.stopPropagation() + }; + + if (props.children) { + return ( + + {props.children} + + ); + } + + return ; +} + +export default NewTabLink; \ No newline at end of file diff --git a/client/src/components/shared/PrivateRoute.tsx b/client/src/components/shared/PrivateRoute.tsx index 31d898922..64157a7ae 100644 --- a/client/src/components/shared/PrivateRoute.tsx +++ b/client/src/components/shared/PrivateRoute.tsx @@ -1,22 +1,22 @@ /** * 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 { 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 { +interface PrivateRouteProps extends RouteProps { component?: React.ComponentType; - children?: unknown; + children?: React.ReactNode; } -function PrivateRoute({ component: Component, children, ...rest }: PrivateRouteProps & RouteProps): React.ReactElement { - const user = useUser(state => state.user); +function PrivateRoute({ component: Component, children, ...rest }: PrivateRouteProps): React.ReactElement { + 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 e3d47449a..86efddf89 100644 --- a/client/src/components/shared/Progress.tsx +++ b/client/src/components/shared/Progress.tsx @@ -1,10 +1,16 @@ -import React from 'react'; +/** + * Progress + * + * Simple circular progress component. + */ 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: { - animationDuration: '750ms' + animationDuration: '650ms' } })); @@ -12,7 +18,7 @@ function Progress({ className, ...props }: CircularProgressProps): React.ReactEl const classes = useStyles(); return ( - + ); } diff --git a/client/src/components/shared/PublicRoute.tsx b/client/src/components/shared/PublicRoute.tsx index 8e207ca54..4e4ca73a8 100644 --- a/client/src/components/shared/PublicRoute.tsx +++ b/client/src/components/shared/PublicRoute.tsx @@ -1,19 +1,20 @@ /** * PublicRoute - * Renders a route based on authentication and restriction specified + * + * 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 { +interface PublicRouteProps extends RouteProps { restricted?: boolean; component: React.ComponentType; } -function PublicRoute({ component: Component, restricted = false, ...rest }: PublicRouteProps & RouteProps): React.ReactElement { - const user = useUser(state => state.user); +function PublicRoute({ component: Component, restricted = false, ...rest }: PublicRouteProps): React.ReactElement { + const user = useUserStore(state => state.user); const render = props => ( !!user && restricted ? : 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 80% rename from client/src/components/shared/Sidebar/SidebarBottomNavigator.tsx rename to client/src/components/shared/SidebarBottomNavigator.tsx index 7fa27347f..6ea3972cb 100644 --- a/client/src/components/shared/Sidebar/SidebarBottomNavigator.tsx +++ b/client/src/components/shared/SidebarBottomNavigator.tsx @@ -1,25 +1,34 @@ +/** + * 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'; 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, alignItems: 'center', justifyContent: 'space-between', - width: '51vw', + width: '53vw', padding: '20px 0px', - marginLeft: 40, - background: palette.background.paper + marginLeft: 20, + background: palette.background.paper, }, 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 +53,6 @@ function SidebarBottomNavigator(props: SidebarBottomNavigatorProps): React.React let leftButton = ( { 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 48aa9c825..2b082895a 100644 --- a/client/src/index.tsx +++ b/client/src/index.tsx @@ -1,48 +1,61 @@ +/** + * 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'; +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, ToastContainer } from 'react-toastify'; +import { Slide, toast, ToastContainer } from 'react-toastify'; import 'react-toastify/dist/ReactToastify.css'; -import { 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'; -import { About, Home, Login } from './pages'; -import theme from './theme'; -import { AliveScope } from 'react-activation'; +import { Home, Login } from './pages'; import * as serviceWorker from './serviceWorker'; -import { useUser } from './store'; +import { useUserStore, useVocabularyStore } from './store'; +import theme from './theme'; +import { getHeaderTitle } from './utils/shared'; function AppRouter(): React.ReactElement { const [loading, setLoading] = useState(true); - const initialize = useUser(state => state.initialize); + const initialize = useUserStore(state => state.initialize); + const updateVocabularyEntries = useVocabularyStore(state => state.updateVocabularyEntries); const initializeUser = useCallback(async () => { - await initialize(); - setLoading(false); - }, [initialize]); + try { + await initialize(); + await updateVocabularyEntries(); + setLoading(false); + } catch { + toast.error('Cannot connect to the server, please try again later'); + } + }, [initialize, updateVocabularyEntries]); useEffect(() => { initializeUser(); }, [initializeUser]); - let content: React.ReactNode = ; + let content: React.ReactNode = ; if (!loading) { content = ( - + - - + ); } @@ -53,6 +66,9 @@ function App(): React.ReactElement { return ( + + {getHeaderTitle()} + + ); 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/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 3d070de56..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'; @@ -7,7 +12,7 @@ import { HOME_ROUTES, resolveRoute } from '../../../constants'; import { Colors } from '../../../theme'; import { Link } from 'react-router-dom'; -const useStyles = makeStyles(({ palette, spacing, typography }) => ({ +const useStyles = makeStyles(({ palette, spacing, typography, breakpoints }) => ({ container: { display: 'flex', alignItems: 'center', @@ -22,16 +27,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/Home/index.tsx b/client/src/pages/Home/index.tsx index c7f1c1ce8..9bcc446de 100644 --- a/client/src/pages/Home/index.tsx +++ b/client/src/pages/Home/index.tsx @@ -1,12 +1,19 @@ -import React, { useState } from 'react'; -import { Box, } from '@material-ui/core'; +/** + * 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 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 +29,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 +42,7 @@ function Home(): React.ReactElement {
- + 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 100d4c827..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'; @@ -11,12 +16,12 @@ const useStyles = makeStyles(({ palette }) => ({ alignItems: 'flex-start', justifyContent: 'center', flexDirection: 'column', - padding: '0.8rem 1.25rem', + padding: '0.8rem', 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/Model/BoundingBoxInput.tsx b/client/src/pages/Ingestion/components/Metadata/Model/BoundingBoxInput.tsx new file mode 100644 index 000000000..4a4758aa0 --- /dev/null +++ b/client/src/pages/Ingestion/components/Metadata/Model/BoundingBoxInput.tsx @@ -0,0 +1,99 @@ +/** + * BoundingBoxInput + * + * This is the component used in Model metadata component for + * bounding box input. + */ +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 extends ViewableProps { + modelFields?: ModelDetailFields | null; + boundingBoxP1X?: number | null; + boundingBoxP1Y?: number | null; + boundingBoxP1Z?: number | null; + boundingBoxP2X?: number | null; + boundingBoxP2Y?: number | null; + boundingBoxP2Z?: number | null; + onChange: (event: React.ChangeEvent) => void; +} + +function BoundingBoxInput(props: BoundingBoxInputProps): React.ReactElement { + 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 ( + + + + + + + + + + + + + + + ); +} + +export default BoundingBoxInput; \ No newline at end of file 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..8f13dd557 --- /dev/null +++ b/client/src/pages/Ingestion/components/Metadata/Model/ObjectSelectModal.tsx @@ -0,0 +1,123 @@ +/** + * ObjectSelectModal + * + * 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, Toolbar, Typography } from '@material-ui/core'; +import { makeStyles } from '@material-ui/core/styles'; +import React, { useEffect, useState } from 'react'; +import { toast } from 'react-toastify'; +import { apolloClient } from '../../../../../graphql'; +import { StateRelatedObject } from '../../../../../store'; +import { GetSourceObjectIdentiferDocument, GetSourceObjectIdentiferInput, GetSourceObjectIdentiferQuery } from '../../../../../types/graphql'; +import RepositoryFilterView from '../../../../Repository/components/RepositoryFilterView'; +import RepositoryTreeView from '../../../../Repository/components/RepositoryTreeView'; + +const useStyles = makeStyles(({ palette, spacing, breakpoints }) => ({ + title: { + marginLeft: spacing(2), + textAlign: 'center', + flex: 1, + }, + appBar: { + position: 'relative', + color: palette.background.paper + }, + repositoryContainer: { + display: 'flex', + flexDirection: 'column', + padding: 20, + paddingBottom: 0, + [breakpoints.down('lg')]: { + padding: 10 + } + }, + loader: { + color: palette.background.paper + } +})); + +interface ObjectSelectModalProps { + open: boolean; + 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 [isSaving, setIsSaving] = useState(false); + + useEffect(() => { + setSelected(selectedObjects); + }, [selectedObjects]); + + const onSave = async (): Promise => { + try { + if (isSaving) return; + setIsSaving(true); + const idSystemObjects: number[] = selected.map(({ idSystemObject }) => idSystemObject); + const input: GetSourceObjectIdentiferInput = { + idSystemObjects + }; + + const { data }: ApolloQueryResult = await apolloClient.query({ + query: GetSourceObjectIdentiferDocument, + variables: { + input + } + }); + + if (data) { + const { getSourceObjectIdentifer } = data; + const { sourceObjectIdentifiers } = getSourceObjectIdentifer; + + const selectedSourceObjects: StateRelatedObject[] = selected.map((selected: StateRelatedObject, index: number) => ({ + ...selected, + identifier: sourceObjectIdentifiers[index]?.identifier + })); + onSelectedObjects(selectedSourceObjects); + } + } catch (error) { + toast.error('Error occurred while fetching identifiers'); + } + setIsSaving(false); + }; + + const onSelect = (sourceObject: StateRelatedObject): void => { + setSelected([...selected, sourceObject]); + }; + + const onUnSelect = (idSystemObject: number): void => { + const updatedSelected = selected.filter(sourceObject => sourceObject.idSystemObject !== idSystemObject); + setSelected(updatedSelected); + }; + + return ( + + + + + + Select Source Objects + + + + + + + + + + ); +} + +export default ObjectSelectModal; diff --git a/client/src/pages/Ingestion/components/Metadata/Model/RelatedObjectsList.tsx b/client/src/pages/Ingestion/components/Metadata/Model/RelatedObjectsList.tsx new file mode 100644 index 000000000..844d0b515 --- /dev/null +++ b/client/src/pages/Ingestion/components/Metadata/Model/RelatedObjectsList.tsx @@ -0,0 +1,166 @@ +/** + * 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 { 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'; + +const useStyles = makeStyles(({ palette }) => ({ + container: { + display: 'flex', + width: (viewMode: boolean) => viewMode ? undefined : '52vw', + flexDirection: 'column', + borderRadius: 5, + padding: 10, + marginTop: (viewMode: boolean) => viewMode ? 10 : 0, + backgroundColor: (viewMode: boolean) => viewMode ? palette.secondary.light : palette.primary.light + }, + list: { + paddingTop: 10, + paddingLeft: (viewMode: boolean) => viewMode ? 0: 10, + borderRadius: 5, + backgroundColor: palette.secondary.light, + }, + header: { + ...sharedLabelProps + }, + label: sharedLabelProps, + labelUnderline: { + textDecoration: 'underline', + cursor: 'pointer' + }, + removeIcon: { + marginLeft: 20, + cursor: 'pointer' + }, + addButton: { + ...sharedButtonProps, + marginTop: 5 + } +})); + +interface RelatedObjectsListProps extends ViewableProps { + relatedObjects: StateRelatedObject[]; + type: RelatedObjectType; + onAdd: () => void; + onRemove?: (id: number) => void; +} + +function RelatedObjectsList(props: RelatedObjectsListProps): React.ReactElement { + const { relatedObjects, type, onAdd, onRemove, viewMode = false, disabled = false } = props; + const classes = useStyles(viewMode); + + const titles = [`${type.toString()} Object(s)`, 'Identifier', 'Object Type']; + const hasRelatedObjects = !!relatedObjects.length; + + const buttonLabel: string = viewMode ? 'Connect' : 'Add'; + + return ( + +
+ {hasRelatedObjects && ( + + {relatedObjects.map((sourceObject: StateRelatedObject, index: number) => ( + + ))} + + )} + + + ); +} + +interface ObjectHeader { + titles: string[]; +} + +export function Header(props: ObjectHeader): React.ReactElement { + const { titles } = props; + const classes = useStyles(false); + const [title1, title2, title3] = titles; + + return ( + + + {title1} + + + {title2} + + + {title3} + + + ); +} + +interface ItemProps { + sourceObject: StateRelatedObject; + onRemove?: (id: number) => void; + viewMode?: boolean; +} + +function Item(props: ItemProps): React.ReactElement { + const { sourceObject, onRemove, viewMode = false } = props; + const { idSystemObject, name, identifier, objectType } = sourceObject; + const classes = useStyles(viewMode); + + const remove = () => onRemove?.(idSystemObject); + + return ( + + + + {name} + + + + {identifier} + + + {getTermForSystemObjectType(objectType)} + + + {!viewMode && } + + + ); +} + +export default RelatedObjectsList; diff --git a/client/src/pages/Ingestion/components/Metadata/Model/UVContents.tsx b/client/src/pages/Ingestion/components/Metadata/Model/UVContents.tsx new file mode 100644 index 000000000..a8c1ee945 --- /dev/null +++ b/client/src/pages/Ingestion/components/Metadata/Model/UVContents.tsx @@ -0,0 +1,98 @@ +/** + * UVContents + * + * This component renders the uv map type selector for contents present in + * the uploaded assets + */ +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 { ViewableProps } from '../../../../../types/repository'; +import { ContentHeader, EmptyContent, useStyles } from '../Photogrammetry/AssetContents'; + +interface UVContentsProps extends ViewableProps { + initialEntry: number | null; + uvMaps: StateUVMap[]; + options: VocabularyOption[]; + onUpdate: (id: number, mapType: number) => void; +} + +function UVContents(props: UVContentsProps): React.ReactElement { + const { uvMaps, options, initialEntry, onUpdate, viewMode = false, disabled = false } = props; + + return ( + + + + + {uvMaps.map(({ id, name, edgeLength, mapType }: StateUVMap, index: number) => { + const update = ({ target }) => onUpdate(id, target.value); + + return ( + + ); + })} + + + ); +} + +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; + disabled: boolean; +} + +export function Content(props: ContentProps): React.ReactElement { + const { fieldName, value, name, edgeLength, initialEntry, update, options, disabled } = props; + const classes = useStyles(); + + return ( + + + + + + {name} + + + {edgeLength} + + + + + + ); +} + +export default UVContents; 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..fefadd88c --- /dev/null +++ b/client/src/pages/Ingestion/components/Metadata/Model/index.tsx @@ -0,0 +1,271 @@ +/** + * Metadata - Model + * + * This component renders the metadata fields specific to model asset. + */ +import { Box, Checkbox } from '@material-ui/core'; +import { makeStyles } from '@material-ui/core/styles'; +import React, { useState } from 'react'; +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 { withDefaultValueNumber } from '../../../../../utils/shared'; +import BoundingBoxInput from './BoundingBoxInput'; +import ObjectSelectModal from './ObjectSelectModal'; +import RelatedObjectsList from './RelatedObjectsList'; +import UVContents from './UVContents'; + +const useStyles = makeStyles(({ palette, typography }) => ({ + container: { + marginTop: 20 + }, + notRequiredFields: { + display: 'flex', + flex: 1, + flexDirection: 'column', + marginLeft: 30, + borderRadius: 5, + backgroundColor: palette.secondary.light + }, + noteText: { + marginTop: 10, + fontSize: '0.8em', + fontWeight: typography.fontWeightLight, + fontStyle: 'italic', + textAlign: 'center' + } +})); + +interface ModelProps { + readonly metadataIndex: number; +} + +function Model(props: ModelProps): React.ReactElement { + const { metadataIndex } = props; + const classes = useStyles(); + const metadata = useMetadataStore(state => state.metadatas[metadataIndex]); + const { model } = metadata; + const [updateMetadataField, getFieldErrors] = useMetadataStore(state => [state.updateMetadataField, state.getFieldErrors]); + const [getEntries, getInitialEntry] = useVocabularyStore(state => [state.getEntries, state.getInitialEntry]); + + const [modalOpen, setModalOpen] = useState(false); + + const errors = getFieldErrors(metadata); + + const onIdentifersChange = (identifiers: StateIdentifier[]): void => { + updateMetadataField(metadataIndex, 'identifiers', identifiers, MetadataType.model); + }; + + const setCheckboxField = ({ target }): void => { + const { name, checked } = target; + updateMetadataField(metadataIndex, name, checked, MetadataType.model); + }; + + const setIdField = ({ target }): void => { + const { name, value } = target; + let idFieldValue: number | null = null; + + if (value) { + idFieldValue = Number.parseInt(value, 10); + } + + updateMetadataField(metadataIndex, name, idFieldValue, MetadataType.model); + }; + + + const setDateField = (name: string, value?: string | null): void => { + if (value) { + const date = new Date(value); + updateMetadataField(metadataIndex, name, date, MetadataType.model); + } + }; + + const updateUVMapsVariant = (uvMapId: number, mapType: number) => { + const { uvMaps } = model; + const updatedUVMaps = uvMaps.map(uvMap => { + if (uvMapId === uvMap.id) { + return { + ...uvMap, + mapType + }; + } + return uvMap; + }); + updateMetadataField(metadataIndex, 'uvMaps', updatedUVMaps, MetadataType.model); + }; + + const openSourceObjectModal = () => { + setModalOpen(true); + }; + + const onRemoveSourceObject = (idSystemObject: number): void => { + const { sourceObjects } = model; + const updatedSourceObjects = sourceObjects.filter(sourceObject => sourceObject.idSystemObject !== idSystemObject); + updateMetadataField(metadataIndex, 'sourceObjects', updatedSourceObjects, MetadataType.model); + }; + + const onModalClose = () => { + setModalOpen(false); + }; + + const onSelectedObjects = (newSourceObjects: StateRelatedObject[]) => { + updateMetadataField(metadataIndex, 'sourceObjects', newSourceObjects, MetadataType.model); + onModalClose(); + }; + + const noteLabelProps = { style: { fontStyle: 'italic' } }; + const noteFieldProps = { alignItems: 'center', style: { paddingBottom: 0 } }; + const rowFieldProps = { alignItems: 'center', justifyContent: 'space-between' }; + + return ( + + + + + + + + setDateField('dateCaptured', value)} /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +} + +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..7b042b730 --- /dev/null +++ b/client/src/pages/Ingestion/components/Metadata/Other/index.tsx @@ -0,0 +1,45 @@ +/** + * Metadata - Other + * + * This component renders the metadata fields for other asset types. + */ +import { Box } from '@material-ui/core'; +import React from 'react'; +import { AssetIdentifiers } from '../../../../../components'; +import { StateIdentifier, useMetadataStore } from '../../../../../store'; +import { MetadataType } from '../../../../../store/metadata'; + +interface OtherProps { + readonly metadataIndex: number; +} + +function Other(props: OtherProps): React.ReactElement { + const { metadataIndex } = props; + const metadata = useMetadataStore(state => state.metadatas[metadataIndex]); + const { other } = metadata; + const updateMetadataField = useMetadataStore(state => state.updateMetadataField); + + const onIdentifersChange = (identifiers: StateIdentifier[]): void => { + updateMetadataField(metadataIndex, 'identifiers', identifiers, MetadataType.other); + }; + + const setCheckboxField = ({ target }): void => { + const { name, checked } = target; + updateMetadataField(metadataIndex, name, checked, MetadataType.other); + }; + + return ( + + + + ); +} + +export default Other; \ No newline at end of file diff --git a/client/src/pages/Ingestion/components/Metadata/Photogrammetry/AssetContents.tsx b/client/src/pages/Ingestion/components/Metadata/Photogrammetry/AssetContents.tsx index fb37cb2c1..abd5a8ced 100644 --- a/client/src/pages/Ingestion/components/Metadata/Photogrammetry/AssetContents.tsx +++ b/client/src/pages/Ingestion/components/Metadata/Photogrammetry/AssetContents.tsx @@ -1,14 +1,23 @@ -import { Box, Typography, Select, MenuItem } from '@material-ui/core'; -import { makeStyles } from '@material-ui/core/styles'; +/** + * 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'; +import { AiFillFolder } from 'react-icons/ai'; import { FieldType } from '../../../../../components'; import { StateFolder, VocabularyOption } from '../../../../../store'; +import { palette } from '../../../../../theme'; +import { ViewableProps } from '../../../../../types/repository'; -const useStyles = makeStyles(({ palette, typography }) => ({ +export 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: { @@ -16,28 +25,36 @@ const useStyles = makeStyles(({ palette, typography }) => ({ flex: 1, alignItems: 'center', justifyContent: 'center', - color: palette.primary.contrastText + color: palette.primary.dark }, emptyFolders: { - marginTop: 10, + margin: '10px 0px', color: palette.grey[600], textAlign: 'center' }, contentText: { - color: palette.primary.dark + color: palette.primary.dark, + margin: `0px ${spacing(1)}px`, + wordBreak: 'break-word' }, 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, + } }, })); -interface AssetContentsProps { +interface AssetContentsProps extends ViewableProps { initialEntry: number | null; folders: StateFolder[]; options: VocabularyOption[]; @@ -45,41 +62,28 @@ interface AssetContentsProps { } function AssetContents(props: AssetContentsProps): React.ReactElement { - const { folders, options, initialEntry, onUpdate } = props; - const classes = useStyles(); + const { folders, options, initialEntry, onUpdate, viewMode = false, disabled = false } = props; return ( - - - - Folder Name - - - Variant Type - - + + - {!folders.length && No folders detected} - {folders.map(({ id, name, variantType }, index: number) => { + + {folders.map(({ id, name, variantType }: StateFolder, index: number) => { const update = ({ target }) => onUpdate(id, target.value); return ( - - - {name} - - - - - + } + initialEntry={initialEntry} + options={options} + update={update} + /> ); })} @@ -87,4 +91,81 @@ 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; + disabled: boolean; +} + +function Content(props: ContentProps): React.ReactElement { + const { fieldName, value, name, icon, initialEntry, update, options, disabled } = props; + const classes = useStyles(); + + return ( + + + + {icon} + + {name} + + + + + + ); +} + export default AssetContents; diff --git a/client/src/pages/Ingestion/components/Metadata/Photogrammetry/Description.tsx b/client/src/pages/Ingestion/components/Metadata/Photogrammetry/Description.tsx index 7aab06756..f55d3ca85 100644 --- a/client/src/pages/Ingestion/components/Metadata/Photogrammetry/Description.tsx +++ b/client/src/pages/Ingestion/components/Metadata/Photogrammetry/Description.tsx @@ -1,7 +1,13 @@ -import { makeStyles } from '@material-ui/core/styles'; +/** + * 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'; import { FieldType } from '../../../../../components'; +import { ViewableProps } from '../../../../../types/repository'; const useStyles = makeStyles(({ palette, typography }) => ({ description: { @@ -10,29 +16,38 @@ const useStyles = makeStyles(({ palette, typography }) => ({ padding: 10, resize: 'none', overflow: 'scroll', - border: `1px 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, borderRadius: 5, + fontWeight: typography.fontWeightRegular, fontFamily: typography.fontFamily } })); -interface DescriptionProps { +interface DescriptionProps extends ViewableProps { value: string; onChange: (event: React.ChangeEvent) => void; } function Description(props: DescriptionProps): React.ReactElement { - const { value, onChange } = 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' }; return ( - + ({ - input: { - width: '50%', - outline: 'none', - border: `1px solid ${palette.primary.contrastText}`, - padding: 8, - borderRadius: 5, - fontFamily: typography.fontFamily - } -})); - -interface IdInputFieldProps { - label: string; - value: number | null; - name: string; - onChange: (event: React.ChangeEvent) => void; -} - -function IdInputField(props: IdInputFieldProps): React.ReactElement { - const { label, name, value, onChange } = props; - const classes = useStyles(); - - const rowFieldProps = { alignItems: 'center', justifyContent: 'space-between' }; - - return ( - - - - ); -} - -export default IdInputField; \ No newline at end of file diff --git a/client/src/pages/Ingestion/components/Metadata/Photogrammetry/IdentifierList.tsx b/client/src/pages/Ingestion/components/Metadata/Photogrammetry/IdentifierList.tsx deleted file mode 100644 index 2685b482b..000000000 --- a/client/src/pages/Ingestion/components/Metadata/Photogrammetry/IdentifierList.tsx +++ /dev/null @@ -1,133 +0,0 @@ -import { Box, Button, Checkbox, MenuItem, Select } from '@material-ui/core'; -import { 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 }) => ({ - container: { - marginTop: 20 - }, - assetIdentifier: { - display: 'flex', - alignItems: 'center', - marginBottom: 10, - }, - systemCreatedText: { - marginLeft: spacing(2), - fontStyle: 'italic', - color: palette.primary.contrastText - }, - identifierInput: { - width: '75%', - border: 'none', - outline: 'none', - padding: '0px 2px', - paddingBottom: 5, - backgroundColor: 'transparent', - fontSize: typography.body1.fontSize, - fontFamily: typography.fontFamily, - borderBottom: `1px solid ${palette.grey[300]}`, - '&:focus': { - outline: 'none', - }, - '&::placeholder': { - fontStyle: 'italic' - }, - '&::-moz-placeholder': { - fontStyle: 'italic' - } - }, - identifierSelect: { - minWidth: 180, - padding: '0px 10px', - marginLeft: 20, - background: palette.background.paper, - border: `1px solid ${palette.primary.contrastText}`, - borderRadius: 5, - fontFamily: typography.fontFamily - }, - identifierOption: { - marginLeft: 20, - cursor: 'pointer' - }, - addIdentifier: { - color: palette.background.paper, - width: 80 - } -})); - -interface IdentifierListProps { - identifiers: StateIdentifier[] - onAdd: (initialEntry: number | null) => void; - onUpdate: (id: number, fieldName: string, fieldValue: number | string | boolean) => void; - onRemove: (id: number) => void; - identifierTypes: VocabularyOption[]; -} - -function IdentifierList(props: IdentifierListProps): React.ReactElement { - const { identifiers, onAdd, onUpdate, identifierTypes, onRemove } = props; - const classes = useStyles(); - - return ( - - - - {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 ( - - - - - - - ); - })} - - - - - ); -} - -export default IdentifierList; \ No newline at end of file diff --git a/client/src/pages/Ingestion/components/Metadata/Photogrammetry/index.tsx b/client/src/pages/Ingestion/components/Metadata/Photogrammetry/index.tsx index 8c2b59332..6ed062198 100644 --- a/client/src/pages/Ingestion/components/Metadata/Photogrammetry/index.tsx +++ b/client/src/pages/Ingestion/components/Metadata/Photogrammetry/index.tsx @@ -1,144 +1,70 @@ /* eslint-disable react-hooks/exhaustive-deps */ -import { Box, Checkbox, Typography } from '@material-ui/core'; +/** + * Metadata - Photogrammetry + * + * This component renders the metadata fields specific to photogrammetry asset. + */ +import { Box, Checkbox } from '@material-ui/core'; import { makeStyles, withStyles } from '@material-ui/core/styles'; import React from 'react'; -import { FieldType } from '../../../../../components'; -import DateFnsUtils from '@date-io/date-fns'; -import { MuiPickersUtilsProvider, KeyboardDatePicker } from '@material-ui/pickers'; -import { Colors } from '../../../../../theme'; -import { StateMetadata, useVocabulary, useMetadata, StateIdentifier } from '../../../../../store'; +import { AssetIdentifiers, DateInputField, FieldType, InputField, SelectField } from '../../../../../components'; +import { MetadataType, StateIdentifier, StateMetadata, useMetadataStore, useVocabularyStore } from '../../../../../store'; import { eVocabularySetID } from '../../../../../types/server'; -import Description from './Description'; -import IdentifierList from './IdentifierList'; -import SelectField from './SelectField'; -import IdInputField from './IdInputField'; -import lodash from 'lodash'; +import { withDefaultValueNumber } from '../../../../../utils/shared'; import AssetContents from './AssetContents'; +import Description from './Description'; -const useStyles = makeStyles(({ palette, typography, spacing }) => ({ +const useStyles = makeStyles(() => ({ 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 - }, - divider: { - display: 'flex', - height: 20, - width: 30 - }, - date: { - width: '50%', - background: palette.background.paper, - border: `1px solid ${palette.primary.contrastText}`, - padding: '1px 8px', - color: Colors.defaults.white, - borderRadius: 5, - fontFamily: typography.fontFamily - } })); -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; + readonly metadataIndex: number; } 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, updateMetadataField] = useMetadataStore(state => [state.getFieldErrors, state.updateMetadataField]); + const metadata: StateMetadata = useMetadataStore(state => state.metadatas[metadataIndex]); const errors = getFieldErrors(metadata); const { photogrammetry } = metadata; - const { getEntries, getInitialEntry } = useVocabulary(); + const [getEntries, getInitialEntry] = useVocabularyStore(state => [state.getEntries, state.getInitialEntry]); const setField = ({ target }): void => { const { name, value } = target; - updatePhotogrammetryField(metadataIndex, name, value); + updateMetadataField(metadataIndex, name, value, MetadataType.photogrammetry); }; const setIdField = ({ target }): void => { const { name, value } = target; let idFieldValue: number | null = null; + if (value) { idFieldValue = Number.parseInt(value, 10); } - updatePhotogrammetryField(metadataIndex, name, idFieldValue); + updateMetadataField(metadataIndex, name, idFieldValue, MetadataType.photogrammetry); }; - const setDateField = (name: string, value: string | null | undefined): void => { + const setDateField = (name: string, value?: string | null): void => { if (value) { const date = new Date(value); - updatePhotogrammetryField(metadataIndex, name, date); + updateMetadataField(metadataIndex, name, date, MetadataType.photogrammetry); } }; const setCheckboxField = ({ target }): void => { const { name, checked } = target; - - updatePhotogrammetryField(metadataIndex, name, checked); + updateMetadataField(metadataIndex, name, checked, MetadataType.photogrammetry); }; - 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 => { + updateMetadataField(metadataIndex, 'identifiers', identifiers, MetadataType.photogrammetry); }; const updateFolderVariant = (folderId: number, variantType: number) => { @@ -152,37 +78,25 @@ function Photogrammetry(props: PhotogrammetryProps): React.ReactElement { } return folder; }); - updatePhotogrammetryField(metadataIndex, 'folders', updatedFolders); + updateMetadataField(metadataIndex, 'folders', updatedFolders, MetadataType.photogrammetry); }; const rowFieldProps = { alignItems: 'center', justifyContent: 'space-between' }; return ( - - - - - System will create an identifier - - - - + - + - - setDateField('dateCaptured', value)} - /> - + setDateField('dateCaptured', value)} /> - - - + + - - + + - + ({ + 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/Scene/ReferenceModels.tsx b/client/src/pages/Ingestion/components/Metadata/Scene/ReferenceModels.tsx new file mode 100644 index 000000000..9f13877c3 --- /dev/null +++ b/client/src/pages/Ingestion/components/Metadata/Scene/ReferenceModels.tsx @@ -0,0 +1,178 @@ +/** + * ReferenceModels + * + * This component renders the reference model list for Scene metadata ingestion 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 } from '../../../../../components'; +import { ReferenceModelAction, StateReferenceModel } from '../../../../../store'; +import { getDetailsUrlForObject } from '../../../../../utils/repository'; +import { formatBytes } from '../../../../../utils/upload'; + +const useStyles = makeStyles(({ palette }) => ({ + container: { + display: 'flex', + width: '52vw', + flexDirection: 'column', + borderRadius: 5, + padding: 10, + backgroundColor: palette.primary.light + }, + list: { + padding: 10, + paddingBottom: 0, + marginBottom: 10, + borderRadius: 5, + backgroundColor: palette.secondary.light + }, + header: { + fontSize: '0.8em', + color: palette.primary.dark + }, + label: { + fontSize: '0.8em', + color: palette.primary.dark + }, + labelUnderline: { + textDecoration: 'underline', + cursor: 'pointer' + }, + empty: { + textAlign: 'center', + margin: '15px 0px', + fontSize: '0.8em', + color: palette.primary.dark + } +})); + +const mockReferenceModels: StateReferenceModel[] = [ + { + idSystemObject: 1, + name: 'Armstrong1.obj (mock)', + 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 + }, + { + idSystemObject: 1, + name: 'Armstrong2.obj (mock)', + 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 + } +]; + +function ReferenceModels(): React.ReactElement { + const classes = useStyles(); + const hasModels = !!mockReferenceModels.length; + + return ( + +
+ {!hasModels && } + {hasModels && ( + + {mockReferenceModels.map((referenceModel: StateReferenceModel, index: number) => )} + + )} + + ); +} + +function Header(): React.ReactElement { + const classes = useStyles(); + + return ( + + + Reference Models(s) + + + Geometry File Size + + + UV Resolution + + + Bounding Box + + + Action + + + ); +} + +interface ItemProps { + referenceModel: StateReferenceModel; +} + +function Item(props: ItemProps): React.ReactElement { + const { referenceModel } = props; + 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 ( + + + + {name} + + + + {formatBytes(fileSize)} + + + {resolution} + + + {boundingBox} + + + {action.toString()} + + + ); +} + +function Empty(): React.ReactElement { + const classes = useStyles(); + + return No reference model(s) found; +} + +export default ReferenceModels; 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..c9f3683f5 --- /dev/null +++ b/client/src/pages/Ingestion/components/Metadata/Scene/index.tsx @@ -0,0 +1,55 @@ +/** + * Metadata - Scene + * + * This component renders the metadata fields specific to scene asset. + */ +import { Box } from '@material-ui/core'; +import { makeStyles } from '@material-ui/core/styles'; +import React from 'react'; +import { AssetIdentifiers } from '../../../../../components'; +import { StateIdentifier, useMetadataStore } from '../../../../../store'; +import { MetadataType } from '../../../../../store/metadata'; +import ReferenceModels from './ReferenceModels'; + +const useStyles = makeStyles(() => ({ + container: { + marginTop: 20 + }, +})); + +interface SceneProps { + readonly metadataIndex: number; +} + +function Scene(props: SceneProps): React.ReactElement { + const { metadataIndex } = props; + const classes = useStyles(); + const metadata = useMetadataStore(state => state.metadatas[metadataIndex]); + const { scene } = metadata; + const updateMetadataField = useMetadataStore(state => state.updateMetadataField); + + const onIdentifersChange = (identifiers: StateIdentifier[]): void => { + updateMetadataField(metadataIndex, 'identifiers', identifiers, MetadataType.scene); + }; + + const setCheckboxField = ({ target }): void => { + const { name, checked } = target; + updateMetadataField(metadataIndex, name, checked, MetadataType.scene); + }; + + return ( + + + + + ); +} + +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 434a1e970..5e8e47e42 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'; @@ -7,22 +12,41 @@ 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, + modelFieldsSchema, + otherFieldsSchema, + photogrammetryFieldsSchema, + sceneFieldsSchema, + StateItem, + StateMetadata, + StateProject, + useItemStore, + useMetadataStore, + useProjectStore, + useVocabularyStore +} from '../../../../store'; import useIngest from '../../hooks/useIngest'; +import Model from './Model'; +import Other from './Other'; import Photogrammetry from './Photogrammetry'; +import Scene from './Scene'; const useStyles = makeStyles(({ palette }) => ({ 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' + width: '52vw', + padding: 20, + paddingBottom: 0 }, breadcrumbs: { marginBottom: 10, @@ -42,11 +66,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 { ingestPhotogrammetryData, ingestionComplete } = useIngest(); - const getAssetType = useVocabulary(state => state.getAssetType); + 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 { ingestionStart, ingestionComplete } = useIngest(); + const getAssetType = useVocabularyStore(state => state.getAssetType); const metadataLength = metadatas.length; const query = qs.parse(search) as QueryParams; @@ -62,54 +86,34 @@ function Metadata(): React.ReactElement { const assetType = getAssetType(Number.parseInt(type, 10)); const onPrevious = () => { + toast.dismiss(); 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 }); - } + const hasError: boolean = validateFields(metadata.photogrammetry, photogrammetryFieldsSchema); + if (hasError) return; + } - if (description.trim() === '') { - toast.warn('Description cannot be empty', { autoClose: false }); - hasError = true; - } + if (assetType.model) { + const hasError: boolean = validateFields(metadata.model, modelFieldsSchema); + if (hasError) return; + } - for (const fieldValue of Object.values(photogrammetry)) { - if (fieldValue) { - hasError = true; - } - } + if (assetType.scene) { + const hasError: boolean = validateFields(metadata.scene, sceneFieldsSchema); + if (hasError) return; + } + if (assetType.other) { + const hasError: boolean = validateFields(metadata.other, otherFieldsSchema); if (hasError) return; } if (isLast) { setIngestionLoading(true); - const success: boolean = await ingestPhotogrammetryData(); + const success: boolean = await ingestionStart(); setIngestionLoading(false); if (success) { @@ -121,8 +125,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); } }; @@ -132,11 +136,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/pages/Ingestion/components/SubjectItem/ItemList.tsx b/client/src/pages/Ingestion/components/SubjectItem/ItemList.tsx index 48f46ebe2..ddb46e2ca 100644 --- a/client/src/pages/Ingestion/components/SubjectItem/ItemList.tsx +++ b/client/src/pages/Ingestion/components/SubjectItem/ItemList.tsx @@ -1,12 +1,19 @@ -import React from 'react'; -import { TableContainer, Table, TableCell, TableHead, TableRow, TableBody, Checkbox } from '@material-ui/core'; +/** + * 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'; -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 { MdCheckBox, MdCheckBoxOutlineBlank } from 'react-icons/md'; +import { RiCheckboxBlankCircleLine, RiRecordCircleFill } from 'react-icons/ri'; +import { defaultItem, StateItem, useItemStore } 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 +21,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 +39,16 @@ 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', + fontWeight: typography.fontWeightRegular, fontFamily: typography.fontFamily, '&:focus': { outline: 'none', @@ -51,7 +64,7 @@ const useStyles = makeStyles(({ palette, spacing, typography }) => ({ 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 @@ -143,23 +156,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..67ca7a77f 100644 --- a/client/src/pages/Ingestion/components/SubjectItem/ProjectList.tsx +++ b/client/src/pages/Ingestion/components/SubjectItem/ProjectList.tsx @@ -1,20 +1,26 @@ +/** + * 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'; import React from 'react'; -import { useProject } from '../../../../store'; +import { useProjectStore } from '../../../../store'; const useStyles = makeStyles(({ palette }) => ({ projectSelect: { width: '100%', padding: '0px 10px', - backgroundColor: palette.background.paper + backgroundColor: palette.background.paper, + fontSize: '0.8em' } })); 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/SearchList.tsx b/client/src/pages/Ingestion/components/SubjectItem/SearchList.tsx index 586a4ac5b..6fb0b5089 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'; @@ -11,7 +16,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 +30,10 @@ const useStyles = makeStyles(({ palette }) => ({ searchButton: { height: 35, width: 60, - color: palette.background.paper + color: palette.background.paper, + [breakpoints.down('lg')]: { + height: 30 + } } })); @@ -77,8 +85,6 @@ function SearchList(): React.ReactElement { /> ({ +const useStyles = makeStyles(({ palette, spacing, breakpoints }) => ({ container: { maxHeight: '20vh', backgroundColor: palette.background.paper @@ -12,8 +17,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' @@ -34,7 +43,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']; @@ -56,7 +65,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..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'; @@ -5,14 +10,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 +55,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..6dbb7217c 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'; @@ -5,8 +10,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 useIngest from '../../hooks/useIngest'; +import { useItemStore, useMetadataStore, useProjectStore, useSubjectStore, useVocabularyStore } from '../../../../store'; import ItemList from './ItemList'; import ProjectList from './ProjectList'; import SearchList from './SearchList'; @@ -16,14 +20,17 @@ const useStyles = makeStyles(({ palette }) => ({ container: { display: 'flex', flex: 1, - flexDirection: 'column' + flexDirection: 'column', + overflow: 'auto', + maxHeight: 'calc(100vh - 60px)' }, content: { display: 'flex', flex: 1, - width: '50vw', + width: '52vw', flexDirection: 'column', - padding: '40px 0px 0px 40px' + padding: 20, + paddingBottom: 0 }, filesLabel: { color: palette.primary.dark, @@ -44,14 +51,13 @@ 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] = useMetadata(state => [state.metadatas, state.updateMetadataFolders]); + 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(); - const { ingestionReset } = useIngest(); useEffect(() => { if (subjects.length > 0) { @@ -73,15 +79,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; @@ -125,8 +122,9 @@ 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}`); + toast.dismiss(); history.push(nextRoute); }; @@ -173,8 +171,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/FileList.tsx b/client/src/pages/Ingestion/components/Uploads/FileList.tsx index 351753480..610700206 100644 --- a/client/src/pages/Ingestion/components/Uploads/FileList.tsx +++ b/client/src/pages/Ingestion/components/Uploads/FileList.tsx @@ -1,19 +1,24 @@ +/** + * FileList + * + * This component renders file list used in UploadList and UploadCompleteList components. + */ 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/FileListItem.tsx b/client/src/pages/Ingestion/components/Uploads/FileListItem.tsx index b21f068b1..8a8f9605e 100644 --- a/client/src/pages/Ingestion/components/Uploads/FileListItem.tsx +++ b/client/src/pages/Ingestion/components/Uploads/FileListItem.tsx @@ -1,17 +1,23 @@ +/** + * 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'; +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', @@ -22,7 +28,11 @@ const useStyles = makeStyles(({ palette, typography }) => ({ borderRadius: 5, width: '100%', zIndex: 10, - overflow: 'hidden' + overflow: 'hidden', + [breakpoints.down('lg')]: { + minHeight: 50, + marginTop: 5, + } }, item: { display: 'flex', @@ -33,10 +43,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 +75,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 +156,10 @@ function FileListItem(props: FileListItemProps): React.ReactElement { if (!complete) { options = ( - {!uploading && !failed && } - {uploading && !failed && } + {!uploading && !failed && } + {uploading && !failed && } {failed && } - + ); } @@ -147,8 +167,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..9717e09f4 100644 --- a/client/src/pages/Ingestion/components/Uploads/UploadCompleteList.tsx +++ b/client/src/pages/Ingestion/components/Uploads/UploadCompleteList.tsx @@ -1,56 +1,24 @@ /* 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 { makeStyles } from '@material-ui/core/styles'; 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'; 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) - }, -})); +import lodash from 'lodash'; function UploadListComplete(): React.ReactElement { - const classes = useStyles(); + const classes = useUploadListStyles(); - const { completed, loadCompleted } = useUpload(); + const { completed, loadCompleted } = useUploadStore(); const { data, loading, error } = useQuery(GetUploadedAssetVersionDocument); useEffect(() => { @@ -59,13 +27,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/pages/Ingestion/components/Uploads/UploadFilesPicker.tsx b/client/src/pages/Ingestion/components/Uploads/UploadFilesPicker.tsx index 9e397ff91..7c9904d75 100644 --- a/client/src/pages/Ingestion/components/Uploads/UploadFilesPicker.tsx +++ b/client/src/pages/Ingestion/components/Uploads/UploadFilesPicker.tsx @@ -1,10 +1,15 @@ +/** + * 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'; 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: { @@ -13,9 +18,10 @@ const useStyles = makeStyles(({ palette, typography, spacing }) => ({ alignItems: 'center', justifyContent: 'center', height: '20vh', - width: '51vw', + width: '52vw', border: `1px dashed ${palette.primary.main}`, borderRadius: 10, + padding: 10, backgroundColor: palette.primary.light }, icon: { @@ -36,7 +42,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 0c575c164..048a32520 100644 --- a/client/src/pages/Ingestion/components/Uploads/UploadList.tsx +++ b/client/src/pages/Ingestion/components/Uploads/UploadList.tsx @@ -1,56 +1,56 @@ +/** + * 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'; import { FieldType } from '../../../../components'; -import { useUpload } from '../../../../store'; +import { useUploadStore } from '../../../../store'; +import { scrollBarProperties } from '../../../../utils/shared'; import FileList from './FileList'; import UploadListHeader from './UploadListHeader'; -const useStyles = makeStyles(({ palette, spacing }) => ({ +export const useUploadListStyles = makeStyles(({ palette, breakpoints }) => ({ container: { display: 'flex', flex: 1, flexDirection: 'column', marginTop: 20, maxHeight: 'auto', - width: '50vw', + width: '52vw', }, list: { display: 'flex', flexDirection: 'column', alignItems: 'center', - minHeight: 80, - maxHeight: '20vh', + minHeight: '16vh', + maxHeight: '16vh', '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', } }, listDetail: { textAlign: 'center', color: palette.grey[500], fontStyle: 'italic', - marginTop: spacing(4) + marginTop: '8%' }, })); function UploadList(): React.ReactElement { - const classes = useStyles(); - const { pending } = useUpload(); + const classes = useUploadListStyles(); + const { pending } = useUploadStore(); 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..2b7d7f76e 100644 --- a/client/src/pages/Ingestion/components/Uploads/UploadListHeader.tsx +++ b/client/src/pages/Ingestion/components/Uploads/UploadListHeader.tsx @@ -1,8 +1,13 @@ +/** + * 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'; -const useStyles = makeStyles(({ palette, typography }) => ({ +const useStyles = makeStyles(({ palette, typography, breakpoints }) => ({ header: { display: 'flex', alignItems: 'center', @@ -10,6 +15,9 @@ const useStyles = makeStyles(({ palette, typography }) => ({ width: '100%', borderRadius: 5, background: palette.background.paper, + [breakpoints.down('lg')]: { + height: 35, + } }, fileDetails: { display: 'flex', @@ -43,13 +51,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..88e7ac9ca 100644 --- a/client/src/pages/Ingestion/components/Uploads/index.tsx +++ b/client/src/pages/Ingestion/components/Uploads/index.tsx @@ -1,28 +1,38 @@ /* 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, { 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 { 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'; -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 }) => ({ 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: 20, + paddingBottom: 0, }, fileDrop: { display: 'flex', @@ -54,31 +64,21 @@ 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 } = useUpload(); - const { updateMetadataSteps } = useMetadata(); - const { updateVocabularyEntries } = useVocabulary(); - - const fetchVocabularyEntries = async () => { - setLoadingVocabulary(true); - await updateVocabularyEntries(); - setLoadingVocabulary(false); - }; - - useEffect(() => { - fetchVocabularyEntries(); - }, []); + const [completed, discardFiles] = useUploadStore(state => [state.completed, state.discardFiles]); + const updateMetadataSteps = useMetadataStore(state => state.updateMetadataSteps); - 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; @@ -88,7 +88,7 @@ function Uploads(): React.ReactElement { toast.warn('Please select valid combination of files'); return; } - + toast.dismiss(); history.push(nextStep); } catch { setGettingAssetDetails(false); @@ -107,34 +107,68 @@ function Uploads(): React.ReactElement { } }; - let content: React.ReactNode = ; + return ( + + + + + + + + + ); +} + +function AliveUploadComponents(): React.ReactElement { + const [onProgressEvent, onSetCancelledEvent, onFailedEvent, onCompleteEvent] = useUploadStore(state => [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); + }; - if (!loadingVocabulary) { - content = ( - - - - - - ); - } + 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 ( - - - - {content} - - - - + + + + + ); } diff --git a/client/src/pages/Ingestion/hooks/useIngest.ts b/client/src/pages/Ingestion/hooks/useIngest.ts index 78a6ca881..5d12fc4ef 100644 --- a/client/src/pages/Ingestion/hooks/useIngest.ts +++ b/client/src/pages/Ingestion/hooks/useIngest.ts @@ -1,74 +1,101 @@ -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'; +/** + * Ingest Hook + * + * This custom hooks provides easy access ingestion functionality. + */ 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 { isNewItem, parseFileId } from '../../../store/utils'; +import { toast } from 'react-toastify'; +import { HOME_ROUTES, INGESTION_ROUTES_TYPE, resolveSubRoute } from '../../../constants/routes'; +import { apolloClient } from '../../../graphql'; +import { + defaultItem, + isNewItem, + parseFileId, + StateFolder, + StateIdentifier, + StateItem, + StateProject, + StateUVMap, + useItemStore, + useMetadataStore, + useProjectStore, + useSubjectStore, + useUploadStore, + useVocabularyStore +} from '../../../store'; +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; } 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.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(); - 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 { @@ -86,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, @@ -147,22 +141,121 @@ 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, + sourceObjects, + 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), + sourceObjects, + roughness, + metalness, + pointCount, + faceCount, + isWatertight, + hasNormals, + hasVertexColor, + hasUVSpace, + boundingBoxP1X, + boundingBoxP1Y, + boundingBoxP1Z, + boundingBoxP2X, + boundingBoxP2Y, + boundingBoxP2Z, + directory + }; + + ingestModel.push(modelData); + } + + if (isScene) { + const { identifiers, systemCreated, referenceModels } = scene; + + const ingestIdentifiers: IngestIdentifierInput[] = getIngestIdentifiers(identifiers); + + const sceneData: IngestSceneInput = { + idAssetVersion: parseFileId(file.id), + identifiers: ingestIdentifiers, + systemCreated, + referenceModels + }; + + ingestScene.push(sceneData); } + + if (isOther) { + const { identifiers, systemCreated } = other; + + const ingestIdentifiers: IngestIdentifierInput[] = getIngestIdentifiers(identifiers); + + const otherData: IngestOtherInput = { + idAssetVersion: parseFileId(file.id), + identifiers: ingestIdentifiers, + systemCreated + }; + + 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'] }); @@ -200,8 +293,60 @@ 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, edgeLength, mapType } = uvMap; + + const uvMapData: IngestUvMapInput = { + name, + edgeLength, + mapType: nonNullValue('mapType', mapType) + }; + + ingestUVMaps.push(uvMapData); + }); + + return ingestUVMaps; + }; + return { - ingestPhotogrammetryData, + ingestionStart, ingestionComplete, ingestionReset }; diff --git a/client/src/pages/Ingestion/index.tsx b/client/src/pages/Ingestion/index.tsx index 4ad9a8946..85c596b50 100644 --- a/client/src/pages/Ingestion/index.tsx +++ b/client/src/pages/Ingestion/index.tsx @@ -1,16 +1,22 @@ +/** + * 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'; 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 +28,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([]); @@ -38,10 +44,11 @@ function Ingestion(): React.ReactElement { }); 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}?fileId=${id}&type=${type}`, + route, enabled: false }); }); @@ -59,7 +66,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; 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 8a85233f2..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'; @@ -7,9 +12,9 @@ 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 { useUser } from '../../store'; +import { useUserStore } from '../../store'; import { actionOnKeyPress } from '../../utils/shared'; import useLoginForm, { ILoginForm } from './hooks/useLoginForm'; @@ -92,7 +97,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(); @@ -141,6 +146,7 @@ function Login(): React.ReactElement { {({ handleSubmit, handleChange, values, isSubmitting, submitForm }) => (
actionOnKeyPress(key, 'Enter', submitForm)} loading={isSubmitting} > diff --git a/client/src/pages/Repository/components/DetailsView/DetailsHeader.tsx b/client/src/pages/Repository/components/DetailsView/DetailsHeader.tsx new file mode 100644 index 000000000..b1a52fcfb --- /dev/null +++ b/client/src/pages/Repository/components/DetailsView/DetailsHeader.tsx @@ -0,0 +1,77 @@ +/** + * DetailsHeader + * + * This component renders repository details header for the DetailsView component. + */ +import { Box, Typography } from '@material-ui/core'; +import { fade, makeStyles } from '@material-ui/core/styles'; +import React from 'react'; +import { DebounceInput } from 'react-debounce-input'; +import { Helmet } from 'react-helmet'; +import { BreadcrumbsView } from '../../../../components'; +import { GetSystemObjectDetailsResult, RepositoryPath } from '../../../../types/graphql'; +import { eSystemObjectType } from '../../../../types/server'; +import { getTermForSystemObjectType, isFieldUpdated } from '../../../../utils/repository'; +import { getHeaderTitle } from '../../../../utils/shared'; + +const useStyles = makeStyles(({ palette }) => ({ + header: { + color: palette.primary.dark + }, + name: { + minWidth: 180, + height: 20, + padding: '5px 8px', + borderRadius: 5, + marginRight: 20, + color: palette.primary.dark, + 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 | null; + disabled: boolean; + onNameUpdate: (event: React.ChangeEvent) => void; +} + +function DetailsHeader(props: DetailsHeaderProps): React.ReactElement { + 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)}`); + + return ( + + + {title} + + + {getTermForSystemObjectType(objectType)} + + + + + + {!!path.length && } + + + ); +} + +export default DetailsHeader; \ No newline at end of file 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..c739b3543 --- /dev/null +++ b/client/src/pages/Repository/components/DetailsView/DetailsTab/ActorDetails.tsx @@ -0,0 +1,59 @@ +/* eslint-disable react-hooks/exhaustive-deps */ +/** + * ActorDetails + * + * This component renders details tab for Actor specific details used in DetailsTab component. + */ +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 { + const { data, loading, disabled, onUpdateDetail, objectType } = props; + + const [details, setDetails] = useState({}); + + useEffect(() => { + if (data && !loading) { + const { Actor } = data.getDetailsTabDataForObject; + setDetails({ + OrganizationName: Actor?.OrganizationName, + }); + } + }, [data, loading]); + + useEffect(() => { + onUpdateDetail(objectType, details); + }, [details]); + + if (!data || loading) { + return ; + } + + const onSetField = (event: React.ChangeEvent) => { + const { name, value } = event.target; + setDetails(details => ({ ...details, [name]: value })); + }; + + const actorData = data.getDetailsTabDataForObject?.Actor; + + return ( + + + + ); +} + +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..74545485e --- /dev/null +++ b/client/src/pages/Repository/components/DetailsView/DetailsTab/AssetDetails.tsx @@ -0,0 +1,86 @@ +/* eslint-disable react-hooks/exhaustive-deps */ +/** + * AssetDetails + * + * This component renders details tab for Asset specific details used in DetailsTab component. + */ +import { Box } from '@material-ui/core'; +import React, { useEffect, useState } from 'react'; +import { InputField, Loader, SelectField } from '../../../../../components'; +import { useVocabularyStore } from '../../../../../store'; +import { AssetDetailFields } from '../../../../../types/graphql'; +import { eVocabularySetID } from '../../../../../types/server'; +import { isFieldUpdated } from '../../../../../utils/repository'; +import { withDefaultValueNumber } from '../../../../../utils/shared'; +import { DetailComponentProps } from './index'; + +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]); + + useEffect(() => { + if (data && !loading) { + const { Asset } = data.getDetailsTabDataForObject; + setDetails({ + FilePath: Asset?.FilePath, + AssetType: Asset?.AssetType + }); + } + }, [data, loading]); + + useEffect(() => { + onUpdateDetail(objectType, details); + }, [details]); + + if (!data || loading) { + return ; + } + + 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); + } + + setDetails(details => ({ ...details, [name]: idFieldValue })); + }; + + const assetData = data.getDetailsTabDataForObject?.Asset; + + return ( + + + + + ); +} + +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 new file mode 100644 index 000000000..6ec66cac0 --- /dev/null +++ b/client/src/pages/Repository/components/DetailsView/DetailsTab/AssetDetailsTable.tsx @@ -0,0 +1,122 @@ +/** + * 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 { EmptyTable, NewTabLink } 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 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..fd05a5dd1 --- /dev/null +++ b/client/src/pages/Repository/components/DetailsView/DetailsTab/AssetVersionDetails.tsx @@ -0,0 +1,111 @@ +/* eslint-disable react-hooks/exhaustive-deps */ +/** + * AssetVersionDetails + * + * This component renders details tab for AssetVersion specific details used in DetailsTab component. + */ +import { Box, makeStyles, Typography } from '@material-ui/core'; +import React, { useEffect, useState } from 'react'; +import { CheckboxField, FieldType, Loader } from '../../../../../components'; +import { AssetVersionDetailFields } from '../../../../../types/graphql'; +import { isFieldUpdated } from '../../../../../utils/repository'; +import { formatBytes } from '../../../../../utils/upload'; +import { DetailComponentProps } from './index'; + +export const useStyles = makeStyles(({ palette }) => ({ + value: { + fontSize: '0.8em', + color: palette.primary.dark + } +})); + +function AssetVersionDetails(props: DetailComponentProps): React.ReactElement { + const classes = useStyles(); + const { data, loading, onUpdateDetail, objectType, disabled } = props; + + const [details, setDetails] = useState({}); + + useEffect(() => { + if (data && !loading) { + const { AssetVersion } = data.getDetailsTabDataForObject; + setDetails({ + Version: AssetVersion?.Version, + Creator: AssetVersion?.Creator, + DateCreated: AssetVersion?.DateCreated, + StorageSize: AssetVersion?.StorageSize, + Ingested: AssetVersion?.Ingested, + }); + } + }, [data, loading]); + + useEffect(() => { + onUpdateDetail(objectType, details); + }, [details]); + + if (!data || loading) { + return ; + } + + const setCheckboxField = ({ target }): void => { + const { name, checked } = target; + setDetails(details => ({ ...details, [name]: checked })); + }; + + const rowFieldProps = { alignItems: 'center', justifyContent: 'space-between', style: { borderRadius: 0 } }; + + const assetVersionData = data.getDetailsTabDataForObject?.AssetVersion; + + return ( + + + {details.Version} + + + {details.Creator} + + + {details.DateCreated} + + + {formatBytes(details.StorageSize ?? 0)} + + + + + ); +} + +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 new file mode 100644 index 000000000..495764e24 --- /dev/null +++ b/client/src/pages/Repository/components/DetailsView/DetailsTab/AssetVersionsTable.tsx @@ -0,0 +1,92 @@ +/** + * 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 { EmptyTable, NewTabLink } from '../../../../../components'; +import { StateDetailVersion } from '../../../../../store'; +import { getDetailsUrlForObject } from '../../../../../utils/repository'; +import { formatBytes } from '../../../../../utils/upload'; +import { useObjectVersions } from '../../../hooks/useDetailsView'; +import { 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 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..15cbb5924 --- /dev/null +++ b/client/src/pages/Repository/components/DetailsView/DetailsTab/CaptureDataDetails.tsx @@ -0,0 +1,265 @@ +/* eslint-disable react-hooks/exhaustive-deps */ +/** + * CaptureDataDetails + * + * This component renders details tab for CaptureData specific details used in DetailsTab component. + */ +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 } 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'; +import { DetailComponentProps } from './index'; + +function CaptureDataDetails(props: DetailComponentProps): React.ReactElement { + const { data, loading, disabled, onUpdateDetail, objectType } = 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; + setDetails({ + captureMethod: CaptureData?.captureMethod, + description: CaptureData?.description, + dateCaptured: CaptureData?.dateCaptured, + datasetType: CaptureData?.datasetType, + folders: CaptureData?.folders || [], + datasetFieldId: CaptureData?.datasetFieldId, + itemPositionType: CaptureData?.itemPositionType, + itemPositionFieldId: CaptureData?.itemPositionFieldId, + itemArrangementFieldId: CaptureData?.itemArrangementFieldId, + focusType: CaptureData?.focusType, + lightsourceType: CaptureData?.lightsourceType, + backgroundRemovalMethod: CaptureData?.backgroundRemovalMethod, + clusterType: CaptureData?.clusterType, + clusterGeometryFieldId: CaptureData?.clusterGeometryFieldId, + cameraSettingUniform: CaptureData?.cameraSettingUniform, + }); + } + }, [data, loading]); + + if (!data || loading) { + return ; + } + + const updateFolderVariant = () => { + alert('TODO: KARAN: Update Folder Variant'); + }; + + const onSetField = (event: React.ChangeEvent) => { + const { name, value } = event.target; + setDetails(details => ({ ...details, [name]: value })); + }; + + 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 } }; + const captureDataData = data.getDetailsTabDataForObject?.CaptureData; + + return ( + + + + + + + + setDateField('dateCaptured', value)} + /> + + + + + + + + + + + + + + + + + + + + + + + + + ); +} + +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..63076665f --- /dev/null +++ b/client/src/pages/Repository/components/DetailsView/DetailsTab/IntermediaryFileDetails.tsx @@ -0,0 +1,20 @@ +/** + * IntermediaryFileDetails + * + * This component renders details tab for IntermediaryFile specific details used in DetailsTab component. + */ +import React from 'react'; +import { Loader } from '../../../../../components'; +import { DetailComponentProps } from './index'; + +function IntermediaryFileDetails(props: DetailComponentProps): React.ReactElement { + const { data, loading } = props; + + if (!data || loading) { + return ; + } + + return
; +} + +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..a1ee91e8f --- /dev/null +++ b/client/src/pages/Repository/components/DetailsView/DetailsTab/ItemDetails.tsx @@ -0,0 +1,86 @@ +/* eslint-disable react-hooks/exhaustive-deps */ +/** + * ItemDetails + * + * This component renders details tab for Item specific details used in DetailsTab component. + */ +import { Box } from '@material-ui/core'; +import React, { useEffect, useState } from 'react'; +import { CheckboxField, Loader } from '../../../../../components'; +import { SubjectDetailFields } from '../../../../../types/graphql'; +import { isFieldUpdated } from '../../../../../utils/repository'; +import { DetailComponentProps } from './index'; +import { SubjectFields } from './SubjectDetails'; + +export interface ItemDetailFields extends SubjectDetailFields { + EntireSubject?: boolean | null | undefined; +} + +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; + setDetails({ + EntireSubject: Item?.EntireSubject, + 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]); + + if (!data || loading) { + return ; + } + + const onSetField = (event: React.ChangeEvent) => { + const { name, value } = event.target; + let numberValue: number | null = null; + + if (value) { + numberValue = Number.parseInt(value, 10); + } + + setDetails(details => ({ ...details, [name]: numberValue })); + }; + + const setCheckboxField = ({ target }): void => { + const { name, checked } = target; + setDetails(details => ({ ...details, [name]: checked })); + }; + + const itemData = data.getDetailsTabDataForObject?.Item; + + return ( + + + + + ); +} + +export default ItemDetails; 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..003b2581d --- /dev/null +++ b/client/src/pages/Repository/components/DetailsView/DetailsTab/ModelDetails.tsx @@ -0,0 +1,359 @@ +/* eslint-disable react-hooks/exhaustive-deps */ +/** + * ModelDetails + * + * This component renders details tab for Model specific details used in DetailsTab component. + */ +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 { 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'; +import UVContents from '../../../../Ingestion/components/Metadata/Model/UVContents'; +import { DetailComponentProps } from './index'; + +export const useStyles = makeStyles(({ palette }) => ({ + value: { + fontSize: '0.8em', + color: palette.primary.dark + } +})); + +function ModelDetails(props: DetailComponentProps): React.ReactElement { + const classes = useStyles(); + 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; + setDetails({ + size: Model?.size, + 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: Model?.uvMaps || [], + boundingBoxP1X: Model?.boundingBoxP1X, + boundingBoxP1Y: Model?.boundingBoxP1Y, + boundingBoxP1Z: Model?.boundingBoxP1Z, + boundingBoxP2X: Model?.boundingBoxP2X, + boundingBoxP2Y: Model?.boundingBoxP2Y, + boundingBoxP2Z: Model?.boundingBoxP2Z, + countPoint: Model?.countPoint, + countFace: Model?.countFace, + countColorChannel: Model?.countColorChannel, + countTextureCoorinateChannel: Model?.countTextureCoorinateChannel, + hasBones: Model?.hasBones, + hasFaceNormals: Model?.hasFaceNormals, + hasTangents: Model?.hasTangents, + hasTextureCoordinates: Model?.hasTextureCoordinates, + hasVertexNormals: Model?.hasVertexNormals, + hasVertexColor: Model?.hasVertexColor, + isManifold: Model?.isManifold, + isWatertight: Model?.isWatertight, + }); + } + }, [data, loading]); + + if (!data || loading) { + return ; + } + + 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 } }; + + const modelData = data.getDetailsTabDataForObject?.Model; + + return ( + + + + {formatBytes(details?.size ?? 0)} + + + setDateField('dateCaptured', value)} + /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +} + +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..b83b97f2f --- /dev/null +++ b/client/src/pages/Repository/components/DetailsView/DetailsTab/ProjectDetails.tsx @@ -0,0 +1,54 @@ +/* eslint-disable react-hooks/exhaustive-deps */ +/** + * ProjectDetails + * + * This component renders details tab for Actor specific details used in DetailsTab component. + */ +import React, { useEffect, useState } from 'react'; +import { Loader } from '../../../../../components'; +import { ProjectDetailFields } from '../../../../../types/graphql'; +import { isFieldUpdated } from '../../../../../utils/repository'; +import Description from '../../../../Ingestion/components/Metadata/Photogrammetry/Description'; +import { DetailComponentProps } from './index'; + +function ProjectDetails(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 { Project } = data.getDetailsTabDataForObject; + setDetails({ + Description: Project?.Description + }); + } + }, [data, loading]); + + if (!data || loading) { + return ; + } + + const onSetField = (event: React.ChangeEvent) => { + const { value } = event.target; + setDetails(details => ({ ...details, Description: value })); + }; + + 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 new file mode 100644 index 000000000..558635dae --- /dev/null +++ b/client/src/pages/Repository/components/DetailsView/DetailsTab/ProjectDocumentationDetails.tsx @@ -0,0 +1,53 @@ +/* eslint-disable react-hooks/exhaustive-deps */ +/** + * ProjectDocumentationDetails + * + * This component renders details tab for Actor specific details used in DetailsTab component. + */ +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'; + +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; + setDetails({ + Description: ProjectDocumentation?.Description + }); + } + }, [data, loading]); + + if (!data || loading) { + return ; + } + + const onSetField = (event: React.ChangeEvent) => { + const { value } = event.target; + setDetails(details => ({ ...details, Description: value })); + }; + + 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/SceneDetails.tsx b/client/src/pages/Repository/components/DetailsView/DetailsTab/SceneDetails.tsx new file mode 100644 index 000000000..9847b5d92 --- /dev/null +++ b/client/src/pages/Repository/components/DetailsView/DetailsTab/SceneDetails.tsx @@ -0,0 +1,119 @@ +/* eslint-disable react-hooks/exhaustive-deps */ +/** + * SceneDetails + * + * This component renders details tab for Scene specific details used in DetailsTab component. + */ +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'; + +export const useStyles = makeStyles(({ palette }) => ({ + value: { + fontSize: '0.8em', + color: palette.primary.dark + } +})); + +function SceneDetails(props: DetailComponentProps): React.ReactElement { + 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 ; + } + + 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/StakeholderDetails.tsx b/client/src/pages/Repository/components/DetailsView/DetailsTab/StakeholderDetails.tsx new file mode 100644 index 000000000..540dade02 --- /dev/null +++ b/client/src/pages/Repository/components/DetailsView/DetailsTab/StakeholderDetails.tsx @@ -0,0 +1,103 @@ +/* eslint-disable react-hooks/exhaustive-deps */ +/** + * StakeholderDetails + * + * This component renders details tab for Stakeholder specific details used in DetailsTab component. + */ +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 { + const { data, loading, disabled, onUpdateDetail, objectType } = props; + + const [details, setDetails] = useState({}); + + useEffect(() => { + onUpdateDetail(objectType, details); + }, [details]); + + 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 ; + } + + const onSetField = (event: React.ChangeEvent) => { + const { name, value } = event.target; + setDetails(details => ({ ...details, [name]: value })); + }; + + const stakeholderData = data.getDetailsTabDataForObject?.Stakeholder; + + return ( + + + + + + + + ); +} + +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..47804faeb --- /dev/null +++ b/client/src/pages/Repository/components/DetailsView/DetailsTab/SubjectDetails.tsx @@ -0,0 +1,234 @@ +/* eslint-disable react-hooks/exhaustive-deps */ +/** + * SubjectDetails + * + * This component renders details tab for Subject specific details used in DetailsTab component. + */ +import { Box } from '@material-ui/core'; +import React, { useEffect, useState } from 'react'; +import { DebounceNumberInput, FieldType, InputField, Loader } from '../../../../../components'; +import { ItemDetailFields, SubjectDetailFields } from '../../../../../types/graphql'; +import { isFieldUpdated } from '../../../../../utils/repository'; +import { DetailComponentProps } from './index'; + +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; + setDetails({ + 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]); + + if (!data || loading) { + return ; + } + + const onSetField = (event: React.ChangeEvent) => { + const { name, value } = event.target; + let numberValue: number | null = null; + + if (value) { + numberValue = Number.parseInt(value, 10); + } + + setDetails(details => ({ ...details, [name]: numberValue })); + }; + + 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, + TS0, + TS1, + TS2, + R0, + R1, + R2, + R3, + disabled, + onChange + } = props; + + const details = { + Latitude, + Longitude, + Altitude, + TS0, + TS1, + TS2, + R0, + R1, + R2, + R3 + }; + + return ( + + + + + + + + ); +} + +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, originalFields } = props; + + const rowFieldProps = { justifyContent: 'space-between', style: { borderRadius: 0 } }; + + const details = { + TS0, + TS1, + TS2 + }; + + return ( + + + + + + + + + + ); +} + +interface RotationQuaternionInputProps { + R0?: number | null; + 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, originalFields } = props; + + const rowFieldProps = { justifyContent: 'space-between', style: { borderRadius: 0 } }; + + const details = { + R0, + R1, + R2, + R3, + }; + + return ( + + + + + + + + + + + ); +} + +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..0d059d6dc --- /dev/null +++ b/client/src/pages/Repository/components/DetailsView/DetailsTab/UnitDetails.tsx @@ -0,0 +1,69 @@ +/* eslint-disable react-hooks/exhaustive-deps */ +/** + * UnitDetails + * + * This component renders details tab for Unit specific details used in DetailsTab component. + */ +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 { + const { data, loading, disabled, onUpdateDetail, objectType } = props; + const [details, setDetails] = useState({}); + + useEffect(() => { + onUpdateDetail(objectType, details); + }, [details]); + + useEffect(() => { + if (data && !loading) { + const { Unit } = data.getDetailsTabDataForObject; + setDetails({ + Abbreviation: Unit?.Abbreviation, + ARKPrefix: Unit?.ARKPrefix + }); + } + }, [data, loading]); + + if (!data || loading) { + return ; + } + + const onSetField = (event: React.ChangeEvent) => { + const { name, value } = event.target; + setDetails(details => ({ ...details, [name]: value })); + }; + + const unitData = data.getDetailsTabDataForObject?.Unit; + + return ( + + + + + ); +} + +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 new file mode 100644 index 000000000..8f7f82afb --- /dev/null +++ b/client/src/pages/Repository/components/DetailsView/DetailsTab/index.tsx @@ -0,0 +1,337 @@ +/* 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 { + 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'; +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: { + backgroundColor: fade(palette.primary.main, 0.25) + }, + tabpanel: { + backgroundColor: fade(palette.primary.main, 0.25) + } +})); + +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; + objectType: eSystemObjectType; + sourceObjects: StateRelatedObject[]; + 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, onUpdateDetail } = 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; + + const RelatedTab = (index: number) => ( + + + + + ); + + const sharedProps = { + onUpdateDetail, + objectType, + disabled + }; + + const detailsProps = { + ...detailsQueryResult, + ...sharedProps + }; + + switch (objectType) { + case eSystemObjectType.eUnit: + tabs = ['Details', 'Related']; + tabPanels = ( + + + + + {RelatedTab(1)} + + ); + break; + case eSystemObjectType.eProject: + tabs = ['Details', 'Related']; + tabPanels = ( + + + + + {RelatedTab(1)} + + ); + break; + case eSystemObjectType.eSubject: + tabs = ['Assets', 'Details', 'Related']; + tabPanels = ( + + + + + + + + {RelatedTab(2)} + + ); + break; + case eSystemObjectType.eItem: + tabs = ['Assets', 'Details', 'Related']; + tabPanels = ( + + + + + + + + {RelatedTab(2)} + + ); + break; + case eSystemObjectType.eCaptureData: + tabs = ['Assets', 'Details', 'Related']; + tabPanels = ( + + + + + + + + {RelatedTab(2)} + + ); + break; + case eSystemObjectType.eModel: + tabs = ['Assets', 'Details', 'Related']; + tabPanels = ( + + + + + + + + {RelatedTab(2)} + + ); + break; + case eSystemObjectType.eScene: + tabs = ['Assets', 'Details', 'Related']; + tabPanels = ( + + + + + + + + {RelatedTab(2)} + + ); + break; + case eSystemObjectType.eIntermediaryFile: + tabs = ['Assets', 'Details', 'Related']; + tabPanels = ( + + + + + + + + {RelatedTab(2)} + + ); + break; + case eSystemObjectType.eProjectDocumentation: + tabs = ['Assets', 'Details', 'Related']; + tabPanels = ( + + + + + + + + {RelatedTab(2)} + + ); + break; + case eSystemObjectType.eAsset: + tabs = ['Versions', 'Details', 'Related']; + tabPanels = ( + + + + + + + + {RelatedTab(2)} + + ); + break; + case eSystemObjectType.eAssetVersion: + tabs = ['Details', 'Related']; + tabPanels = ( + + + + + {RelatedTab(1)} + + ); + break; + case eSystemObjectType.eActor: + tabs = ['Details', 'Related']; + tabPanels = ( + + + + + {RelatedTab(1)} + + ); + break; + case eSystemObjectType.eStakeholder: + tabs = ['Details', 'Related']; + tabPanels = ( + + + + + {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 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..db8d08c0c --- /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 { makeStyles } from '@material-ui/core/styles'; +import React from 'react'; +import DefaultThumbnail from '../../../../assets/images/default-thumbnail.png'; + +const useStyles = makeStyles(() => ({ + thumbnail: { + height: 200, + width: 200, + marginTop: 50, + borderRadius: 10 + } +})); + +interface DetailsThumbnailProps { + thumbnail?: string | null; +} + +function DetailsThumbnail(props: DetailsThumbnailProps): React.ReactElement { + const { thumbnail } = props; + const classes = useStyles(); + + return ( + + asset thumbnail + + ); +} + +export default DetailsThumbnail; \ No newline at end of file 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..c17f07448 --- /dev/null +++ b/client/src/pages/Repository/components/DetailsView/ObjectDetails.tsx @@ -0,0 +1,102 @@ +/** + * Object Details + * + * This component renders object details for the Repository Details UI. + */ +import { Box, Checkbox, Typography } from '@material-ui/core'; +import { makeStyles } from '@material-ui/core/styles'; +import React from 'react'; +import { NewTabLink } from '../../../../components'; +import { GetSystemObjectDetailsResult, RepositoryPath } from '../../../../types/graphql'; +import { getDetailsUrlForObject, getUpdatedCheckboxProps, isFieldUpdated } from '../../../../utils/repository'; +import { withDefaultValueBoolean } from '../../../../utils/shared'; + +const useStyles = makeStyles(({ palette, typography }) => ({ + detail: { + display: 'flex', + minHeight: 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 { + unit?: RepositoryPath | null; + project?: RepositoryPath | null; + subject?: RepositoryPath | null; + item?: RepositoryPath | null; + 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, originalFields, onRetiredUpdate } = props; + + const isRetiredUpdated: boolean = isFieldUpdated({ retired }, originalFields, '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/ObjectNotFoundView.tsx b/client/src/pages/Repository/components/DetailsView/ObjectNotFoundView.tsx new file mode 100644 index 000000000..946104031 --- /dev/null +++ b/client/src/pages/Repository/components/DetailsView/ObjectNotFoundView.tsx @@ -0,0 +1,74 @@ +/** + * ObjectNotFoundView + * + * This component renders when the queried object is not present in the system. + */ +import { Box, Button, Typography } from '@material-ui/core'; +import { makeStyles } from '@material-ui/core/styles'; +import React from 'react'; +import { IoIosInformationCircleOutline, IoMdArrowBack } from 'react-icons/io'; +import { useHistory } from 'react-router'; +import { Progress } from '../../../../components'; +import { palette } from '../../../../theme'; + +const useStyles = makeStyles(({ palette }) => ({ + container: { + display: 'flex', + flex: 1, + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'center', + padding: 10, + marginBottom: 20, + borderRadius: 10, + backgroundColor: palette.primary.light + }, + title: { + margin: '10px 0px 5px 0px', + }, + subtitle: { + color: palette.grey[600], + marginBottom: 10 + }, + button: { + color: palette.primary.main, + fontSize: '0.8em' + } +})); + +interface ObjectNotFoundViewProps { + loading: boolean; +} + +function ObjectNotFoundView(props: ObjectNotFoundViewProps): React.ReactElement { + const { loading } = props; + const classes = useStyles(); + const history = useHistory(); + + const startIcon: React.ReactNode = ; + + const onGoBack = (): void => { + history.goBack(); + }; + + let content: React.ReactNode = ; + + if (!loading) { + content = ( + + + Not found + Object you selected was not found in the system + + + ); + } + + 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 new file mode 100644 index 000000000..4cc025f22 --- /dev/null +++ b/client/src/pages/Repository/components/DetailsView/index.tsx @@ -0,0 +1,294 @@ +/** + * DetailsView + * + * This component renders repository details view for the Repository UI. + */ +import { Box } from '@material-ui/core'; +import { makeStyles } from '@material-ui/core/styles'; +import React, { useEffect, useState } from 'react'; +import { useParams } from 'react-router'; +import { toast } from 'react-toastify'; +import { LoadingButton } from '../../../../components'; +import IdentifierList from '../../../../components/shared/IdentifierList'; +import { parseIdentifiersToState, useVocabularyStore } from '../../../../store'; +import { + ActorDetailFieldsInput, + AssetDetailFieldsInput, + AssetVersionDetailFieldsInput, + CaptureDataDetailFieldsInput, + ItemDetailFieldsInput, + ModelDetailFieldsInput, + ProjectDetailFieldsInput, + ProjectDocumentationDetailFieldsInput, + SceneDetailFieldsInput, + StakeholderDetailFieldsInput, + SubjectDetailFieldsInput, + UnitDetailFieldsInput, + 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'; +import DetailsTab, { UpdateDataFields } from './DetailsTab'; +import DetailsThumbnail from './DetailsThumbnail'; +import ObjectDetails from './ObjectDetails'; +import ObjectNotFoundView from './ObjectNotFoundView'; + +const useStyles = makeStyles(({ palette, breakpoints }) => ({ + container: { + display: 'flex', + flex: 1, + flexDirection: 'column', + maxHeight: 'calc(100vh - 140px)', + padding: 20, + marginBottom: 20, + borderRadius: 10, + overflowY: 'scroll', + backgroundColor: palette.primary.light, + [breakpoints.down('lg')]: { + maxHeight: 'calc(100vh - 120px)', + padding: 10 + } + }, + updateButton: { + height: 35, + width: 100, + marginTop: 10, + color: palette.background.paper, + [breakpoints.down('lg')]: { + height: 30 + } + } +})); + +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); + const { data, loading } = useObjectDetails(idSystemObject); + const [updatedData, setUpdatedData] = useState({}); + + 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 { + idObject, + objectType, + identifiers, + allowed, + publishedState, + thumbnail, + unit, + project, + subject, + item, + objectAncestors, + sourceObjects, + derivedObjects + } = data.getSystemObjectDetails; + + const disabled: boolean = !allowed; + + 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 = () => { + 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, + Name: details.name, + Retired: details.retired + }; + + 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 } = 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 (error) { + console.log(JSON.stringify(error)); + 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 ( + + + + + + + + + + + + + + + + + + + Update + + + + + ); +} + +export default DetailsView; 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..62f10f648 --- /dev/null +++ b/client/src/pages/Repository/components/RepositoryFilterView/FilterDate.tsx @@ -0,0 +1,104 @@ +/** + * 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'; +import { KeyboardDatePicker, MuiPickersUtilsProvider } from '@material-ui/pickers'; +import React from 'react'; +import { AiOutlineCalendar } from 'react-icons/ai'; +import { useRepositoryStore } from '../../../../store'; +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 [fromDate, toDate, updateFilterValue] = useRepositoryStore(state => [state.fromDate, state.toDate, state.updateFilterValue]); + + const onDate = (name: string, date: string | null | undefined) => { + if (date) { + updateFilterValue(name, new Date(date)); + } + }; + + 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, + keyboardIcon, + InputProps + }; + + return ( + + {label} + + onDate('fromDate', value)} + variant='inline' + margin='normal' + + /> + to + onDate('toDate', 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..7c4b84fba --- /dev/null +++ b/client/src/pages/Repository/components/RepositoryFilterView/FilterSelect.tsx @@ -0,0 +1,82 @@ +/** + * 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'; +import { useRepositoryStore } from '../../../../store'; +import { FilterOption } from './RepositoryFilterOptions'; + +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; + options: FilterOption[]; + multiple?: boolean; +} + +function FilterSelect(props: FilterSelectProps): React.ReactElement { + const { label, name, multiple, options } = props; + const classes = useStyles(); + + const [value, updateFilterValue] = useRepositoryStore(state => [state[name], state.updateFilterValue]); + + const onChange = ({ target }) => { + let { value } = target; + + if (multiple) { + value = value.sort(); + } + + updateFilterValue(name, value); + }; + + 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/RepositoryFilterOptions.ts b/client/src/pages/Repository/components/RepositoryFilterView/RepositoryFilterOptions.ts new file mode 100644 index 000000000..0a859a4de --- /dev/null +++ b/client/src/pages/Repository/components/RepositoryFilterView/RepositoryFilterOptions.ts @@ -0,0 +1,180 @@ +/** + * RepositoryFilterOptions + * + * Default options for repository filter view. + */ +import { GetFilterViewDataQuery, Vocabulary } from '../../../../types/graphql'; +import { eMetadata, eSystemObjectType, eVocabularySetID } from '../../../../types/server'; +import { getTermForSystemObjectType } from '../../../../utils/repository'; +import lodash from 'lodash'; + +export type FilterOption = { + label: string; + 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 = { + chipsOptions: ChipOption[]; + unitsOptions: FilterOption[]; + projectsOptions: FilterOption[]; + repositoryRootTypesOptions: FilterOption[]; + objectToDisplayOptions: FilterOption[]; + metadataToDisplayOptions: FilterOption[]; + captureMethodOptions: FilterOption[]; + variantTypeOptions: FilterOption[]; + modelPurposeOptions: FilterOption[]; + fileTypeOptions: FilterOption[]; + hasOptions: FilterOption[]; + missingOptions: FilterOption[]; +}; + +export const metadataToDisplayOptions: FilterOption[] = [ + { label: 'Unit', value: eMetadata.eHierarchyUnit }, + { label: 'Project', value: eMetadata.eHierarchyProject }, + { label: 'Subject', value: eMetadata.eHierarchySubject }, + { label: 'Item', value: eMetadata.eHierarchyItem }, + { label: 'Identifier', value: eMetadata.eCommonIdentifier }, + { label: 'Name', value: eMetadata.eCommonName }, + { label: 'Description', value: eMetadata.eCommonDescription }, + { label: 'Date Created', value: eMetadata.eCommonDateCreated }, + { label: 'Unit ARK Prefix', value: eMetadata.eUnitARKPrefix }, + { label: 'Subject Identifier', value: eMetadata.eSubjectIdentifierPreferred }, + { label: 'Item Entire Subject', value: eMetadata.eItemEntireSubject }, + { label: 'Capture Method', value: eMetadata.eCDCaptureMethod }, + { label: 'Capture Dataset Type', value: eMetadata.eCDDatasetType }, + { label: 'Capture Dataset Field ID', value: eMetadata.eCDDatasetFieldID }, + { label: 'Capture Item Position Type', value: eMetadata.eCDItemPositionType }, + { label: 'Capture Item Position Field ID', value: eMetadata.eCDItemPositionFieldID }, + { label: 'Capture Item Arrangement Field ID', value: eMetadata.eCDItemArrangementFieldID }, + { label: 'Capture Focus Type', value: eMetadata.eCDFocusType }, + { label: 'Capture Light Source Type', value: eMetadata.eCDLightSourceType }, + { label: 'Capture Background Removal Method', value: eMetadata.eCDBackgroundRemovalMethod }, + { label: 'Capture Cluster Type', value: eMetadata.eCDClusterType }, + { label: 'Capture Cluster Geometry Field ID', value: eMetadata.eCDClusterGeometryFieldID }, + { label: 'Capture Camera Settings Uniform', value: eMetadata.eCDCameraSettingsUniform }, + { label: 'Capture Variant Type', value: eMetadata.eCDVariantType }, + { label: 'Model Creation Method', value: eMetadata.eModelCreationMethod }, + { label: 'Model Master', value: eMetadata.eModelMaster }, + { label: 'Model Authoritative', value: eMetadata.eModelAuthoritative }, + { label: 'Model Modality', value: eMetadata.eModelModality }, + { label: 'Model Units', value: eMetadata.eModelUnits }, + { label: 'Model Purpose', value: eMetadata.eModelPurpose }, + { label: 'Model File Type', value: eMetadata.eModelFileType }, + { label: 'Model Roughness', value: eMetadata.eModelRoughness }, + { label: 'Model Metalness', value: eMetadata.eModelMetalness }, + { label: 'Model Point Count', value: eMetadata.eModelPointCount }, + { label: 'Model Face Count', value: eMetadata.eModelFaceCount }, + { label: 'Model Is Watertight', value: eMetadata.eModelIsWatertight }, + { label: 'Model Has Normals', value: eMetadata.eModelHasNormals }, + { label: 'Model Has VertexColor', value: eMetadata.eModelHasVertexColor }, + { label: 'Model Has UV Space', value: eMetadata.eModelHasUVSpace }, + { label: 'Model BoundingBoxP1X', value: eMetadata.eModelBoundingBoxP1X }, + { label: 'Model BoundingBoxP1Y', value: eMetadata.eModelBoundingBoxP1Y }, + { label: 'Model BoundingBoxP1Z', value: eMetadata.eModelBoundingBoxP1Z }, + { label: 'Model BoundingBoxP2X', value: eMetadata.eModelBoundingBoxP2X }, + { label: 'Model BoundingBoxP2Y', value: eMetadata.eModelBoundingBoxP2Y }, + { label: 'Model BoundingBoxP2Z', value: eMetadata.eModelBoundingBoxP2Z }, + { label: 'Model UV Map Edge Length', value: eMetadata.eModelUVMapEdgeLength }, + { label: 'Model Channel Position', value: eMetadata.eModelChannelPosition }, + { label: 'Model Channel Width', value: eMetadata.eModelChannelWidth }, + { label: 'Model UV Map Type', value: eMetadata.eModelUVMapType }, + { label: 'Scene Is Oriented', value: eMetadata.eSceneIsOriented }, + { label: 'Scene Has Been QCd', value: eMetadata.eSceneHasBeenQCd }, + { label: 'Asset File Name', value: eMetadata.eAssetFileName }, + { label: 'Asset File Path', value: eMetadata.eAssetFilePath }, + { label: 'Asset Type', value: eMetadata.eAssetType }, + { label: 'Asset Creator', value: eMetadata.eAVUserCreator }, + { label: 'Asset Storage Hash', value: eMetadata.eAVStorageHash }, + { label: 'Asset Storage Size', value: eMetadata.eAVStorageSize }, + { label: 'Asset Ingested', value: eMetadata.eAVIngested }, + { label: 'Bulk Asset Ingest', value: eMetadata.eAVBulkIngest }, + { label: 'Organization Name', value: eMetadata.eCommonOrganizationName }, + { label: 'Stakeholder Email Address', value: eMetadata.eStakeholderEmailAddress }, + { label: 'Stakeholder Phone Number Mobile', value: eMetadata.eStakeholderPhoneNumberMobile }, + { label: 'Stakeholder Phone Number Office', value: eMetadata.eStakeholderPhoneNumberOffice }, + { label: 'Stakeholder Mailing Address', value: eMetadata.eStakeholderMailingAddress }, +]; + +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 }, + { 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 }, + ]; + + const chipsOptions: ChipOption[] = []; + let unitsOptions: FilterOption[] = []; + let projectsOptions: FilterOption[] = []; + + const getFilterViewData = data?.getFilterViewData; + + if (getFilterViewData?.units && getFilterViewData.units.length) { + unitsOptions = sortOptionsAlphabetically(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 = sortOptionsAlphabetically(getFilterViewData?.projects.map(({ Name, SystemObject }) => ({ label: Name, value: SystemObject?.idSystemObject ?? 0 }))); + chipsOptions.push(...filterOptionToChipOption(projects, projectsOptions, eSystemObjectType.eProject)); + } + + const repositoryRootTypesOptions: FilterOption[] = systemObjectTypes; + const objectToDisplayOptions: FilterOption[] = systemObjectTypes; + 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.eModelFileType)); + const hasOptions: FilterOption[] = systemObjectTypes; + const missingOptions: FilterOption[] = systemObjectTypes; + + return { + chipsOptions, + unitsOptions, + projectsOptions, + repositoryRootTypesOptions, + objectToDisplayOptions, + metadataToDisplayOptions, + captureMethodOptions, + variantTypeOptions, + modelPurposeOptions, + fileTypeOptions, + hasOptions, + missingOptions + }; +} + +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 })); +} + +function sortOptionsAlphabetically(options: FilterOption[]): FilterOption[] { + return lodash.orderBy(options, [({ label }: FilterOption) => label.toLowerCase().trim()], ['asc']); +} \ 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 d5ae6c197..48d06ddc7 100644 --- a/client/src/pages/Repository/components/RepositoryFilterView/index.tsx +++ b/client/src/pages/Repository/components/RepositoryFilterView/index.tsx @@ -1,139 +1,240 @@ -import React from 'react'; -import { Box, Typography } from '@material-ui/core'; -import { makeStyles } from '@material-ui/core/styles'; -import { RepositoryFilter } from '../../index'; -import { DebounceInput } from 'react-debounce-input'; -import { motion } from 'framer-motion'; -import { eSystemObjectType } from '../../../../types/server'; -import { RepositoryIcon } from '../../../../components'; +/** + * 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, { 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 { getDetailsUrlForObject, getTermForSystemObjectType } from '../../../../utils/repository'; +import FilterDate from './FilterDate'; +import FilterSelect from './FilterSelect'; +import { ChipOption, getRepositoryFilterOptions } from './RepositoryFilterOptions'; const useStyles = makeStyles(({ palette, typography, breakpoints }) => ({ container: { display: 'flex', - flexDirection: 'column', + height: (isExpanded: boolean) => isExpanded ? 235 : 35, background: palette.primary.light, - borderRadius: 10, - padding: 20, + borderRadius: 5, + padding: 10, marginBottom: 10, + transition: '250ms height ease', [breakpoints.down('lg')]: { - padding: 10, - borderRadius: 5 + height: (isExpanded: boolean) => isExpanded ? 215 : 30 } }, - 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, + flexDirection: 'column' }, - filter: { + defaultFilter: { + color: palette.primary.dark, + fontWeight: typography.fontWeightRegular + }, + 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: 35, + padding: '0px 10px', + borderRadius: 10, + marginLeft: 5, + transition: 'all 250ms linear', [breakpoints.down('lg')]: { - minWidth: 100, - width: 100 + height: 20, + 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 + caption: { + marginTop: 4, + marginLeft: 4, + fontSize: '0.75em', + color: palette.primary.dark, + fontStyle: 'italic', + fontWeight: typography.fontWeightLight }, - filterText: { + textArea: { + width: 280, + padding: '5px 8px', + borderRadius: 5, + color: palette.primary.dark, + backgroundColor: Colors.defaults.white, + border: `0.5px solid ${palette.primary.contrastText}`, + fontSize: '0.8em', + cursor: 'pointer' + }, + chip: { marginLeft: 10, - [breakpoints.down('lg')]: { - fontSize: 10 - } + color: palette.primary.dark + }, + selectContainer: { + display: 'flex', + flexDirection: 'column', + marginRight: 20 + }, + options: { + display: 'flex', + alignItems: (isExpanded: boolean) => isExpanded ? 'flex-end' : 'center', + justifyContent: 'center' } })); -interface RepositoryFilterViewProps { - filter: RepositoryFilter; - onChange: (field: string, value: string | boolean) => void; -} +const StyledChip = withStyles(({ palette }) => ({ + outlined: { + height: 30, + fontSize: '0.75em', + border: `0.5px solid ${palette.primary.contrastText}` + } +}))(Chip); -function RepositoryFilterView(props: RepositoryFilterViewProps): React.ReactElement { - const { filter, onChange } = props; - const classes = useStyles(); +function RepositoryFilterView(): React.ReactElement { + 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 CheckboxFilters = [ - { - value: filter.units, - name: 'units', - type: eSystemObjectType.eUnit - }, - { - value: filter.projects, - name: 'projects', - type: eSystemObjectType.eProject + const { + chipsOptions, + unitsOptions, + projectsOptions, + repositoryRootTypesOptions, + objectToDisplayOptions, + metadataToDisplayOptions, + captureMethodOptions, + variantTypeOptions, + modelPurposeOptions, + fileTypeOptions, + hasOptions, + missingOptions + } = getRepositoryFilterOptions({ data, units, projects, getEntries }); + + const onCopyLink = (): void => { + if ('clipboard' in navigator) { + navigator.clipboard.writeText(window.location.href); + 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 = ( + + + Unit: All + + + + {chipsOptions.map((chip: ChipOption, index: number) => { + const { id, type, name } = chip; + const label: string = `${getTermForSystemObjectType(type)}: ${name}`; - const iconProps = { objectType: type, textColor, backgroundColor }; + const onClick = () => { + window.open(getDetailsUrlForObject(id), '_blank'); + }; return ( - onChange(name, !value)} - whileTap={{ scale: 0.95 }} - > - - - {name.toUpperCase()} - - + label={label} + size='small' + deleteIcon={} + className={classes.chip} + onClick={onClick} + onDelete={() => removeUnitsOrProjects(id, type)} + variant='outlined' + /> ); })} ); + + let expandIcon: React.ReactNode = ( + + ); + + if (isExpanded) { + content = ( + + {content} + + + + + + + + + + + + + + + + + + + + + + + + + + ); + + expandIcon = ( + + ); + } + + if (!data || loading) { + content = ; + } + + return ( + + + {content} + + + + + {expandIcon} + + + + ); } -export default RepositoryFilterView; +export default memo(RepositoryFilterView); 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..c91429c95 --- /dev/null +++ b/client/src/pages/Repository/components/RepositoryTreeView/MetadataView.tsx @@ -0,0 +1,77 @@ +/** + * 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'; +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, + } + }, + text: { + fontSize: '0.8em', + [breakpoints.down('lg')]: { + fontSize: '0.9em', + } + } +})); + +interface MetadataViewProps { + header: boolean; + treeColumns: TreeViewColumn[]; + options?: React.ReactNode; +} + +function MetadataView(props: MetadataViewProps): React.ReactElement { + const { header, treeColumns, options = null } = 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 ( +
+ {options} + {renderTreeColumns(treeColumns)} +
+ ); +} + +export default React.memo(MetadataView, lodash.isEqual); diff --git a/client/src/pages/Repository/components/RepositoryTreeView/RepositoryTreeHeader.tsx b/client/src/pages/Repository/components/RepositoryTreeView/RepositoryTreeHeader.tsx index d008b9be3..ab90514d7 100644 --- a/client/src/pages/Repository/components/RepositoryTreeView/RepositoryTreeHeader.tsx +++ b/client/src/pages/Repository/components/RepositoryTreeView/RepositoryTreeHeader.tsx @@ -1,9 +1,15 @@ -import React from 'react'; +/** + * RepositoryTreeHeader + * + * This component renders header for RepositoryTreeView. + */ 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 { useControlStore } from '../../../../store'; import { eMetadata } from '../../../../types/server'; +import { getTreeViewColumns, getTreeWidth } from '../../../../utils/repository'; +import MetadataView from './MetadataView'; const useStyles = makeStyles(({ palette, typography, breakpoints }) => ({ container: { @@ -11,7 +17,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, @@ -33,31 +39,39 @@ const useStyles = makeStyles(({ palette, typography, breakpoints }) => ({ } }, treeViewText: { - paddingLeft: 20, left: 20, + height: 20, + paddingLeft: 20, width: '60%', backgroundColor: palette.primary.light, + [breakpoints.down('lg')]: { + paddingLeft: 10, + left: 10, + } } })); + 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); + const width = getTreeWidth(treeColumns.length, sideBarExpanded, fullWidth); return ( - Tree view + ); } -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/RepositoryTreeNode.tsx b/client/src/pages/Repository/components/RepositoryTreeView/RepositoryTreeNode.tsx deleted file mode 100644 index fed653e9a..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) { - getObjectChildren(); - } - }, [loadData, 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..e036b5539 100644 --- a/client/src/pages/Repository/components/RepositoryTreeView/StyledTreeItem.tsx +++ b/client/src/pages/Repository/components/RepositoryTreeView/StyledTreeItem.tsx @@ -1,53 +1,26 @@ -import { Box, Collapse, Tooltip } from '@material-ui/core'; -import { fade, withStyles, Theme, makeStyles } from '@material-ui/core/styles'; +/** + * 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'; -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) => ({ 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 @@ -67,9 +40,10 @@ 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', + padding: '4px 8px' }, backgroundColor: 'transparent !important', '&:hover': { @@ -87,120 +61,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; +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 new file mode 100644 index 000000000..8f6dbfa50 --- /dev/null +++ b/client/src/pages/Repository/components/RepositoryTreeView/TreeLabel.tsx @@ -0,0 +1,150 @@ +/** + * TreeLabel + * + * 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 { NewTabLink, Progress } from '../../../../components'; +import { palette } from '../../../../theme'; +import { getDetailsUrlForObject, getTermForSystemObjectType } from '../../../../utils/repository'; +import MetadataView, { TreeViewColumn } from './MetadataView'; +import { RiExternalLinkFill } from 'react-icons/ri'; + +const useStyles = makeStyles(({ breakpoints }) => ({ + container: { + display: 'flex', + }, + label: { + display: 'flex', + flex: 0.9, + alignItems: 'center', + position: 'sticky', + left: 45, + [breakpoints.down('lg')]: { + left: 30 + } + }, + labelText: { + display: 'flex', + alignItems: 'center', + justifyContent: 'space-between', + width: '60%', + fontSize: '0.8em', + backgroundColor: ({ color }: TreeLabelProps) => color, + zIndex: 10, + [breakpoints.down('lg')]: { + fontSize: '0.9em', + } + }, + options: { + display: 'flex', + alignItems: 'center', + position: 'sticky', + left: 0, + width: 120 + }, + option: { + display: 'flex', + alignItems: 'center' + } +})); + +interface TreeLabelProps { + idSystemObject: number; + label?: React.ReactNode; + 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 { idSystemObject, 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} +
+
+ + + + +
+ } + /> +
+ ); +} + +const useLabelStyle = makeStyles(({ breakpoints, palette, typography }) => ({ + container: { + display: 'flex', + alignItems: 'center', + height: 40, + [breakpoints.down('lg')]: { + height: 30, + } + }, + emptyText: { + fontSize: '0.8em', + fontWeight: typography.fontWeightLight, + color: palette.grey[500], + [breakpoints.down('lg')]: { + fontSize: '0.7em', + } + }, + stickyItem: { + position: 'sticky', + left: 0, + } +})); + +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 React.memo(TreeLabel, lodash.isEqual); \ 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 1afbc30b3..7d8a5269e 100644 --- a/client/src/pages/Repository/components/RepositoryTreeView/index.tsx +++ b/client/src/pages/Repository/components/RepositoryTreeView/index.tsx @@ -1,27 +1,34 @@ -import { Box, Typography } from '@material-ui/core'; +/** + * 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'; import { TreeView } from '@material-ui/lab'; -import lodash from 'lodash'; -import React, { useState } from 'react'; -import { BsChevronDown, BsChevronRight } from 'react-icons/bs'; +import React, { useCallback, useEffect } from 'react'; import { Loader } from '../../../../components'; -import { getSortedTreeEntries, getSystemObjectTypesForFilter, getTreeWidth } from '../../../../utils/repository'; -import { useGetRootObjects } from '../../hooks/useRepository'; -import { RepositoryFilter } from '../../index'; +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'; -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, - maxHeight: '72vh', - maxWidth: '83.5vw', + 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: '71vh', - maxWidth: '80.5vw' + maxHeight: ({ isExpanded, isModal }: StyleProps) => getTreeViewStyleHeight(isExpanded, isModal, 'lg'), + maxWidth: ({ sideBarExpanded }: StyleProps) => getTreeViewStyleWidth(sideBarExpanded, 'lg') } }, tree: { @@ -29,67 +36,147 @@ const useStyles = makeStyles(({ palette, breakpoints }) => ({ flexDirection: 'column', flex: 1 }, - fullView: { - display: 'flex', - flex: 1, - maxWidth: '83.5vw', - alignItems: 'center', - justifyContent: 'center', - color: palette.primary.dark + fullWidth: { + maxWidth: '95.5vw' } })); +type StyleProps = { + sideBarExpanded: boolean; + isExpanded: boolean; + isModal: boolean; +}; + interface RepositoryTreeViewProps { - filter: RepositoryFilter; + isModal?: boolean; + selectedItems?: StateRelatedObject[]; + onSelect?: (item: StateRelatedObject) => void; + onUnSelect?: (id: number) => void; } function RepositoryTreeView(props: RepositoryTreeViewProps): React.ReactElement { - const { filter } = props; - const classes = useStyles(); - const [expandedNodes, setExpandedNodes] = useState(new Map() as ExpandedNodeMap); + const { isModal = false, selectedItems = [], onSelect, onUnSelect } = props; - const objectTypes = getSystemObjectTypesForFilter(filter); - const { getRootObjectsData, getRootObjectsLoading, getRootObjectsError } = useGetRootObjects(objectTypes, filter); + const [loading, isExpanded] = useRepositoryStore(useCallback(state => [state.loading, state.isExpanded], [])); + const sideBarExpanded = useControlStore(state => state.sideBarExpanded); - const noFilter = !filter.units && !filter.projects; + const classes = useStyles({ isExpanded, sideBarExpanded, isModal }); - const entries = getSortedTreeEntries(getRootObjectsData?.getObjectChildren?.entries ?? []); - const metadataColumns = getRootObjectsData?.getObjectChildren?.metadataColumns ?? []; - const width = getTreeWidth(metadataColumns.length); + const [tree, initializeTree, getChildren] = useRepositoryStore(state => [state.tree, state.initializeTree, state.getChildren]); + const metadataColumns = useRepositoryStore(state => state.metadataToDisplay); - let content: React.ReactNode = null; + useEffect(() => { + initializeTree(); + }, [initializeTree]); - if (!getRootObjectsLoading && !getRootObjectsError) { - content = renderTreeNodes(expandedNodes, filter, entries, metadataColumns); - } else if (!noFilter) { - content = ; - } + const onNodeToggle = useCallback(async (_, nodeIds: string[]) => { + if (!nodeIds.length) return; + const [nodeId] = nodeIds.slice(); + const alreadyLoaded = tree.has(nodeId); + + if (!alreadyLoaded) { + getChildren(nodeId); + } + }, [tree, getChildren]); + + 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) { + childNodesContent = renderTree(childNodes); + } else { + childNodesContent = ; + } + } + + 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: StateRelatedObject = { + idSystemObject, + name, + objectType, + identifier: '' + }; + onSelect(repositoryItem); + } + }; + + const unSelect = (event: React.MouseEvent) => { + if (onUnSelect) { + event.stopPropagation(); + onUnSelect(idSystemObject); + } + }; - const onNodeToggle = (_, nodeIds: string[]) => { - const keyValueArray: [string, undefined][] = lodash.map(nodeIds, (id: string) => [id, undefined]); - const updatedMap: ExpandedNodeMap = new Map(keyValueArray); - setExpandedNodes(updatedMap); + const label: React.ReactNode = ( + + ); + + return ( + + {childNodesContent} + + ); + }); }; - return ( - + let content: React.ReactNode = ; + + if (!loading) { + const treeColumns = getTreeViewColumns(metadataColumns, false); + const width = getTreeWidth(treeColumns.length, sideBarExpanded, isModal); + const children = tree.get(treeRootKey); + + content = ( } + defaultExpandIcon={} + onNodeToggle={onNodeToggle} style={{ width }} - defaultCollapseIcon={} - defaultExpandIcon={} > - - {noFilter && ( - - Please select a valid filter - - )} - {content} + + {renderTree(children)} - + ); + } + + const fullWidthStyles = isModal ? { maxWidth: '98vw' } : {}; + + return ( +
+ {content} +
); } -export default RepositoryTreeView; \ No newline at end of file +export default React.memo(RepositoryTreeView); 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/useDetailsView.ts b/client/src/pages/Repository/hooks/useDetailsView.ts new file mode 100644 index 000000000..2b00eb2c8 --- /dev/null +++ b/client/src/pages/Repository/hooks/useDetailsView.ts @@ -0,0 +1,72 @@ +import { useQuery, FetchResult } from '@apollo/client'; +import { apolloClient } from '../../../graphql'; +import { + GetAssetDetailsForSystemObjectDocument, + GetAssetDetailsForSystemObjectQueryResult, + GetSystemObjectDetailsDocument, + GetSystemObjectDetailsQueryResult, + GetVersionsForSystemObjectDocument, + GetVersionsForSystemObjectQueryResult, + GetDetailsTabDataForObjectDocument, + GetDetailsTabDataForObjectQueryResult, + UpdateObjectDetailsDocument, + UpdateObjectDetailsDataInput, + UpdateObjectDetailsMutation +} from '../../../types/graphql'; +import { eSystemObjectType } from '../../../types/server'; + +export function useObjectDetails(idSystemObject: number): GetSystemObjectDetailsQueryResult { + return useQuery(GetSystemObjectDetailsDocument, { + variables: { + input: { + idSystemObject + } + } + }); +} + +export function useObjectAssets(idSystemObject: number): GetAssetDetailsForSystemObjectQueryResult { + return useQuery(GetAssetDetailsForSystemObjectDocument, { + variables: { + input: { + idSystemObject + } + } + }); +} + +export function useObjectVersions(idSystemObject: number): GetVersionsForSystemObjectQueryResult { + return useQuery(GetVersionsForSystemObjectDocument, { + variables: { + input: { + idSystemObject + } + } + }); +} + +export function useDetailsTabData(idSystemObject: number, objectType: eSystemObjectType): GetDetailsTabDataForObjectQueryResult { + return useQuery(GetDetailsTabDataForObjectDocument, { + variables: { + input: { + idSystemObject, + objectType + } + } + }); +} + +export function updateDetailsTabData(idSystemObject: number, idObject: number, objectType: eSystemObjectType, data: UpdateObjectDetailsDataInput): Promise> { + return apolloClient.mutate({ + mutation: UpdateObjectDetailsDocument, + variables: { + input: { + idSystemObject, + idObject, + objectType, + data + } + }, + refetchQueries: ['getSystemObjectDetails', 'getDetailsTabDataForObject'] + }); +} diff --git a/client/src/pages/Repository/hooks/useRepository.ts b/client/src/pages/Repository/hooks/useRepository.ts index 55e3db62f..ae3805004 100644 --- a/client/src/pages/Repository/hooks/useRepository.ts +++ b/client/src/pages/Repository/hooks/useRepository.ts @@ -1,72 +1,57 @@ -import { ApolloError, useLazyQuery, useQuery } from '@apollo/client'; -import { RepositoryFilter } from '../index'; -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) - } +/** + * Repository hook + * + * This custom hook provides reusable functions for getting repository tree data. + */ +import { ApolloQueryResult } from '@apollo/client'; +import { RepositoryFilter } from '..'; +import { apolloClient } from '../../../graphql'; +import { GetObjectChildrenDocument, GetObjectChildrenQuery } from '../../../types/graphql'; + +function getObjectChildrenForRoot(filter: RepositoryFilter): Promise> { + return apolloClient.query({ + query: GetObjectChildrenDocument, + variables: { + input: { + idRoot: 0, + objectTypes: filter.repositoryRootType, + metadataColumns: filter.metadataToDisplay, + objectsToDisplay: filter.objectsToDisplay, + search: filter.search, + units: filter.units, + projects: filter.projects, + has: filter.has, + missing: filter.missing, + captureMethod: filter.captureMethod, + variantType: filter.variantType, + modelPurpose: filter.modelPurpose, + modelFileType: filter.modelFileType, } } - ); - - 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, filter: RepositoryFilter): Promise> { + return apolloClient.query({ + query: GetObjectChildrenDocument, + variables: { + input: { + idRoot, + objectTypes: [], + metadataColumns: filter.metadataToDisplay, + objectsToDisplay: filter.objectsToDisplay, + search: filter.search, + units: filter.units, + projects: filter.projects, + has: filter.has, + missing: filter.missing, + captureMethod: filter.captureMethod, + variantType: filter.variantType, + modelPurpose: filter.modelPurpose, + modelFileType: filter.modelFileType, } } - ); - - 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 { getObjectChildrenForRoot, getObjectChildren }; diff --git a/client/src/pages/Repository/index.tsx b/client/src/pages/Repository/index.tsx index b5cff4d9f..5b097f915 100644 --- a/client/src/pages/Repository/index.tsx +++ b/client/src/pages/Repository/index.tsx @@ -1,64 +1,144 @@ -import React, { useEffect, useState } from 'react'; +/* eslint-disable react-hooks/exhaustive-deps */ +/** + * 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 } from 'react'; +import { Redirect, useHistory, useLocation } from 'react-router'; +import { PrivateRoute } from '../../components'; +import { HOME_ROUTES, REPOSITORY_ROUTE, resolveRoute, resolveSubRoute } from '../../constants'; +import { useControlStore, useRepositoryStore } from '../../store'; +import { eMetadata, eSystemObjectType } from '../../types/server'; +import { generateRepositoryUrl, parseRepositoryUrl } from '../../utils/repository'; +import DetailsView from './components/DetailsView'; import RepositoryFilterView from './components/RepositoryFilterView'; import RepositoryTreeView from './components/RepositoryTreeView'; -import { useHistory, useLocation } from 'react-router'; -import { generateRepositoryUrl, parseRepositoryUrl } from '../../utils/repository'; const useStyles = makeStyles(({ breakpoints }) => ({ container: { display: 'flex', flex: 1, - maxWidth: '100vw', + maxWidth: (sideBarExpanded: boolean) => sideBarExpanded ? '85vw' : '93vw', flexDirection: 'column', - padding: 40, + padding: 20, + paddingBottom: 0, + paddingRight: 0, [breakpoints.down('lg')]: { - padding: 20, + paddingRight: 20, + maxWidth: (sideBarExpanded: boolean) => sideBarExpanded ? '85vw' : '92vw', } } })); export type RepositoryFilter = { - units: boolean; - projects: boolean; + search: string; + repositoryRootType: eSystemObjectType[]; + objectsToDisplay: eSystemObjectType[]; + metadataToDisplay: eMetadata[]; + units: number[]; + projects: number[]; + has: eSystemObjectType[]; + missing: eSystemObjectType[]; + captureMethod: number[]; + variantType: number[]; + modelPurpose: number[]; + modelFileType: number[]; }; function Repository(): React.ReactElement { - const classes = useStyles(); + const sideBarExpanded = useControlStore(state => state.sideBarExpanded); + const classes = useStyles(sideBarExpanded); + + return ( + + + + + + + + + + ); +} + +function TreeViewPage(): React.ReactElement { const history = useHistory(); - const { search } = useLocation(); + const location = useLocation(); + const { + search, + repositoryRootType, + objectsToDisplay, + metadataToDisplay, + units, + projects, + has, + missing, + captureMethod, + variantType, + modelPurpose, + modelFileType, + updateRepositoryFilter + } = useRepositoryStore(); - const queries = parseRepositoryUrl(search); + const queries: RepositoryFilter = parseRepositoryUrl(location.search); - const initialFilterState: RepositoryFilter = { - units: true, - projects: false - }; + const filterState: RepositoryFilter = React.useMemo(() => ({ + search, + repositoryRootType, + objectsToDisplay, + metadataToDisplay, + units, + projects, + has, + missing, + captureMethod, + variantType, + modelPurpose, + modelFileType, + }), [ + search, + repositoryRootType, + objectsToDisplay, + metadataToDisplay, + units, + projects, + has, + missing, + captureMethod, + variantType, + modelPurpose, + modelFileType, + ]); - const defaultFilterState = Object.keys(queries).length ? queries : initialFilterState; + const initialFilterState = Object.keys(queries).length ? queries : filterState; - const [filter, setFilter] = useState(defaultFilterState); + useEffect(() => { + updateRepositoryFilter(initialFilterState); + }, [updateRepositoryFilter]); useEffect(() => { - const route = generateRepositoryUrl(filter); + const route = generateRepositoryUrl(filterState); 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 }), - })); - }; + }, [filterState, history]); return ( - - - - + + + + ); } diff --git a/client/src/pages/index.ts b/client/src/pages/index.ts index 50ea9faef..67d6dfdef 100644 --- a/client/src/pages/index.ts +++ b/client/src/pages/index.ts @@ -1,9 +1,8 @@ /** * Pages - * User facing pages are organized here + * User facing pages are organized here. */ import Home from './Home'; import Login from './Login'; -import About from './About'; -export { Home, Login, About }; +export { Home, Login }; diff --git a/client/src/store/control.ts b/client/src/store/control.ts new file mode 100644 index 000000000..1e141a6b3 --- /dev/null +++ b/client/src/store/control.ts @@ -0,0 +1,18 @@ +/** + * Control Store + * + * This store manages state for root level sidebar. + */ +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 31ff43e9b..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'; @@ -5,4 +10,6 @@ export * from './subject'; export * from './project'; export * from './item'; export * from './metadata'; +export * from './repository'; export * from './utils'; +export * from './control'; diff --git a/client/src/store/item.ts b/client/src/store/item.ts index 70673744f..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'; @@ -25,7 +30,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/index.ts similarity index 53% rename from client/src/store/metadata.ts rename to client/src/store/metadata/index.ts index cc67b2f08..127e0152e 100644 --- a/client/src/store/metadata.ts +++ b/client/src/store/metadata/index.ts @@ -1,127 +1,80 @@ +/** + * 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'; -import create, { SetState, GetState } from 'zustand'; -import { apolloClient } from '../graphql'; +import * as yup from 'yup'; +import create, { GetState, SetState } from 'zustand'; +import { apolloClient } from '../../graphql'; import { AreCameraSettingsUniformDocument, AssetVersionContent, + GetAssetVersionsDetailsDocument, GetAssetVersionsDetailsQuery, GetContentsForAssetVersionsDocument, - IngestFolder, 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 { parseFileId, parseItemToState, parseProjectToState, parseSubjectUnitIdentifierToState } from './utils'; -import { useVocabulary } from './vocabulary'; - -type MetadataInfo = { - metadata: StateMetadata; - metadataIndex: number; - isLast: boolean; -}; - -type FieldErrors = { - photogrammetry: { - dateCaptured: boolean; - datasetType: boolean; - }; -}; - -export type MetadataFieldValue = string | number | boolean | null | Date | StateIdentifier[] | StateFolder[]; - -type MetadataUpdate = { - valid: boolean; - selectedFiles: boolean; -}; - -export type StateIdentifier = { - id: number; - identifier: string; - identifierType: number | null; - selected: boolean; -}; - -export type StateFolder = { - id: number; - name: string; - variantType: number | null; -}; - -export type PhotogrammetryFields = { - systemCreated: boolean; - identifiers: StateIdentifier[]; - folders: StateFolder[]; - description: string; - dateCaptured: Date; - datasetType: number | null; - 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; - directory: string; -}; - -export const defaultPhotogrammetryFields: PhotogrammetryFields = { - systemCreated: true, - identifiers: [], - folders: [], - description: '', - dateCaptured: new Date(), - datasetType: null, - datasetFieldId: null, - itemPositionType: null, - itemPositionFieldId: null, - itemArrangementFieldId: null, - focusType: null, - lightsourceType: null, - backgroundRemovalMethod: null, - clusterType: null, - clusterGeometryFieldId: null, - cameraSettingUniform: false, - directory: '' -}; - -export type StateMetadata = { - photogrammetry: PhotogrammetryFields; - file: IngestionFile; -}; +} from '../../types/graphql'; +import { eVocabularySetID } from '../../types/server'; +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, parseFoldersToState, parseIdentifiersToState, parseItemToState, parseProjectToState, parseSubjectUnitIdentifierToState, parseUVMapsToState } from '../utils'; +import { useVocabularyStore } from '../vocabulary'; +import { defaultModelFields, defaultOtherFields, defaultPhotogrammetryFields, defaultSceneFields, ValidateFieldsSchema } from './metadata.defaults'; +import { + FieldErrors, + MetadataFieldValue, + MetadataInfo, + MetadataType, + MetadataUpdate, + ModelFields, + OtherFields, + PhotogrammetryFields, + SceneFields, + StateFolder, + StateIdentifier, + StateMetadata, + ValidateFields +} from './metadata.types'; 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; getMetadataInfo: (id: FileId) => MetadataInfo; updateMetadataSteps: () => Promise; - updatePhotogrammetryField: (metadataIndex: number, name: string, value: MetadataFieldValue) => void; + updateMetadataField: (metadataIndex: Readonly, name: string, value: MetadataFieldValue, metadataType: MetadataType) => void; updateMetadataFolders: () => Promise; updateCameraSettings: (metadatas: StateMetadata[]) => Promise; 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 }), + getSelectedIdentifiers: (identifiers: StateIdentifier[]): StateIdentifier[] | undefined => lodash.filter(identifiers, { selected: true }), getFieldErrors: (metadata: StateMetadata): FieldErrors => { - const { getAssetType } = useVocabulary.getState(); + const { getAssetType } = useVocabularyStore.getState(); const errors: FieldErrors = { photogrammetry: { dateCaptured: false, datasetType: false + }, + model: { + dateCaptured: false, + creationMethod: false, + modality: false, + units: false, + purpose: false, + modelFileType: false } }; @@ -132,11 +85,40 @@ export const useMetadata = create((set: SetState, if (assetType.photogrammetry) { errors.photogrammetry.dateCaptured = metadata.photogrammetry.dateCaptured.toString() === 'Invalid Date'; - errors.photogrammetry.datasetType = metadata.photogrammetry.datasetType === null; + errors.photogrammetry.datasetType = lodash.isNull(metadata.photogrammetry.datasetType); + } + + if (assetType.model) { + errors.model.dateCaptured = metadata.model.dateCaptured.toString() === 'Invalid Date'; + errors.model.creationMethod = lodash.isNull(metadata.model.creationMethod); + errors.model.modality = lodash.isNull(metadata.model.modality); + errors.model.units = lodash.isNull(metadata.model.units); + errors.model.purpose = lodash.isNull(metadata.model.purpose); + errors.model.modelFileType = lodash.isNull(metadata.model.modelFileType); } return errors; }, + validateFields: (fields: ValidateFields, schema: ValidateFieldsSchema): boolean => { + let hasError: boolean = false; + const options: yup.ValidateOptions = { + abortEarly: false + }; + + toast.dismiss(); + + try { + schema.validateSync(fields, options); + } catch (error) { + hasError = true; + + 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); @@ -155,19 +137,20 @@ 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 { isAuthenticated } = useUserStore.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); if (!selectedFiles.length) { return { valid: false, - selectedFiles: false + selectedFiles: false, + error: false }; } @@ -180,13 +163,40 @@ export const useMetadata = create((set: SetState, selected: false }; - const defaultVocabularyFields = { + const defaultIdentifierField = [defaultIdentifier]; + + const defaultPhotogrammetry: PhotogrammetryFields = { ...defaultPhotogrammetryFields, datasetType: getInitialEntry(eVocabularySetID.eCaptureDataDatasetType), - identifiers: [defaultIdentifier] + identifiers: defaultIdentifierField + }; + + const defaultModel: ModelFields = { + ...defaultModelFields, + identifiers: defaultIdentifierField, + creationMethod: getInitialEntry(eVocabularySetID.eModelCreationMethod), + modality: getInitialEntry(eVocabularySetID.eModelModality), + units: getInitialEntry(eVocabularySetID.eModelUnits), + purpose: getInitialEntry(eVocabularySetID.eModelPurpose), + modelFileType: getInitialEntry(eVocabularySetID.eModelFileType) + }; + + const defaultScene: SceneFields = { + ...defaultSceneFields, + identifiers: defaultIdentifierField + }; + + const defaultOther: OtherFields = { + ...defaultOtherFields, + identifiers: defaultIdentifierField }; try { + if (!(await isAuthenticated())) { + toast.error('user is not authenticated, please login'); + return { valid: true, selectedFiles: true, error: true }; + } + const assetVersionDetailsQuery: ApolloQueryResult = await apolloClient.query({ query: GetAssetVersionsDetailsDocument, variables: { @@ -209,7 +219,15 @@ export const useMetadata = create((set: SetState, const metadatas: StateMetadata[] = []; for (let index = 0; index < Details.length; index++) { - const { idAssetVersion, SubjectUnitIdentifier: foundSubjectUnitIdentifier, Project: foundProject, Item: foundItem, CaptureDataPhoto } = Details[index]; + const { + idAssetVersion, + SubjectUnitIdentifier: foundSubjectUnitIdentifier, + Project: foundProject, + Item: foundItem, + CaptureDataPhoto, + Model, + Scene + } = Details[index]; if (foundSubjectUnitIdentifier) { const subject: StateSubject = parseSubjectUnitIdentifierToState(foundSubjectUnitIdentifier); @@ -226,7 +244,6 @@ export const useMetadata = create((set: SetState, items.push(item); } - let metadataStep: StateMetadata; const file = completed.find((file: IngestionFile) => parseFileId(file.id) === idAssetVersion); if (!file) { @@ -234,53 +251,82 @@ export const useMetadata = create((set: SetState, throw new Error(); } + let metadataStep: StateMetadata = { + file, + photogrammetry: defaultPhotogrammetry, + model: defaultModel, + scene: defaultScene, + other: defaultOther + }; + if (CaptureDataPhoto) { const { identifiers, folders } = CaptureDataPhoto; - const parsedIdentifiers: StateIdentifier[] = identifiers.map( - ({ identifier, identifierType }, index): StateIdentifier => ({ - id: index, - identifier, - identifierType, - selected: true - }) - ); - - const stateIdentifiers = parsedIdentifiers.length ? parsedIdentifiers : defaultVocabularyFields.identifiers; + const stateIdentifiers: StateIdentifier[] = parseIdentifiersToState(identifiers, defaultIdentifierField); metadataStep = { - file, + ...metadataStep, photogrammetry: { - ...defaultVocabularyFields, + ...metadataStep.photogrammetry, ...(CaptureDataPhoto && { ...CaptureDataPhoto, dateCaptured: new Date(CaptureDataPhoto.dateCaptured), - folders: getStateFolders(folders), + folders: parseFoldersToState(folders), identifiers: stateIdentifiers }) } }; metadatas.push(metadataStep); - } else { + } else if (Model) { + const { identifiers, uvMaps } = Model; + const stateIdentifiers: StateIdentifier[] = parseIdentifiersToState(identifiers, defaultIdentifierField); + metadataStep = { - file, - photogrammetry: { - ...defaultVocabularyFields + ...metadataStep, + model: { + ...metadataStep.model, + ...(Model && { + ...Model, + dateCaptured: new Date(Model.dateCaptured), + identifiers: stateIdentifiers, + uvMaps: parseUVMapsToState(uvMaps) + }) } }; metadatas.push(metadataStep); + } else if (Scene) { + const { identifiers } = Scene; + const stateIdentifiers: StateIdentifier[] = parseIdentifiersToState(identifiers, defaultIdentifierField); + + metadataStep = { + ...metadataStep, + scene: { + ...metadataStep.scene, + ...(Scene && { + ...Scene, + identifiers: stateIdentifiers + }) + } + }; + metadatas.push(metadataStep); + } else { + metadatas.push(metadataStep); } } - addSubjects(lodash.uniqBy(subjects, 'arkId')); + const uniqueSubjects = lodash.uniqBy(subjects, 'arkId'); + const uniqueItems = lodash.uniqBy(items, 'name'); + + addSubjects(uniqueSubjects); addProjects(projects); - addItems(lodash.uniqBy(items, 'name')); + addItems(uniqueItems); set({ metadatas }); return { valid: true, - selectedFiles: true + selectedFiles: true, + error: false }; } } catch { @@ -289,17 +335,23 @@ export const useMetadata = create((set: SetState, return { valid: false, - selectedFiles: true + selectedFiles: true, + error: false }; }, - updatePhotogrammetryField: (metadataIndex: number, name: string, value: MetadataFieldValue) => { + updateMetadataField: (metadataIndex: Readonly, 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 { ...metadata, - photogrammetry: { - ...metadata.photogrammetry, + [metadataType]: { + ...metadata[metadataType], [name]: value } }; @@ -310,17 +362,8 @@ export const useMetadata = create((set: SetState, set({ metadatas: updatedMetadatas }); }, - getStateFolders: (folders: IngestFolder[]): StateFolder[] => { - const stateFolders: StateFolder[] = folders.map(({ name, variantType }, index: number) => ({ - id: index, - name, - variantType - })); - - 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 +421,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(); @@ -418,3 +461,7 @@ export const useMetadata = create((set: SetState, set({ metadatas: [] }); } })); + +export * from './metadata.defaults'; +export * from './metadata.types'; + diff --git a/client/src/store/metadata/metadata.defaults.ts b/client/src/store/metadata/metadata.defaults.ts new file mode 100644 index 000000000..d586dffd9 --- /dev/null +++ b/client/src/store/metadata/metadata.defaults.ts @@ -0,0 +1,196 @@ +/** + * Metadata Store Defaults + * + * Default field definitions for the metadata store. + */ +import lodash from 'lodash'; +import * as yup from 'yup'; +import { ModelFields, OtherFields, PhotogrammetryFields, SceneFields, StateIdentifier } from './metadata.types'; + +const identifierWhenSelectedValidation = { + is: true, + then: yup.string().trim().required('Enter a valid identifier'), + otherwise: yup.string().trim() +}; + +const identifierSchema = yup.object().shape({ + id: yup.number().required(), + identifier: yup.string().trim().when('selected', identifierWhenSelectedValidation), + 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 identifiersWhenValidation = { + is: false, + then: yup.array().of(identifierSchema).test(identifierValidation) +}; + +export const defaultPhotogrammetryFields: PhotogrammetryFields = { + systemCreated: true, + identifiers: [], + folders: [], + description: '', + dateCaptured: new Date(), + datasetType: null, + datasetFieldId: null, + itemPositionType: null, + itemPositionFieldId: null, + itemArrangementFieldId: null, + focusType: null, + lightsourceType: null, + backgroundRemovalMethod: null, + clusterType: null, + clusterGeometryFieldId: null, + cameraSettingUniform: false, + directory: '' +}; + +export type PhotogrammetrySchemaType = typeof photogrammetryFieldsSchema; + +export const photogrammetryFieldsSchema = yup.object().shape({ + systemCreated: yup.boolean().required(), + 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(), + datasetType: yup.number().typeError('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() +}); + +const uvMapSchema = yup.object().shape({ + id: yup.number().required(), + name: yup.string().required(), + mapType: yup.number().nullable(true) +}); + +const sourceObjectSchema = yup.object().shape({ + idSystemObject: yup.number().required(), + name: yup.string().required(), + identifier: yup.string().required(), + objectType: yup.number().required() +}); + +export const defaultModelFields: ModelFields = { + systemCreated: true, + identifiers: [], + uvMaps: [], + sourceObjects: [], + dateCaptured: new Date(), + creationMethod: null, + master: false, + authoritative: false, + modality: null, + units: null, + purpose: null, + modelFileType: null, + roughness: null, + metalness: null, + pointCount: null, + faceCount: null, + isWatertight: null, + hasNormals: null, + hasVertexColor: null, + hasUVSpace: null, + boundingBoxP1X: null, + boundingBoxP1Y: null, + boundingBoxP1Z: null, + boundingBoxP2X: null, + boundingBoxP2Y: null, + boundingBoxP2Z: null, + directory: '' +}; + +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), + sourceObjects: yup.array().of(sourceObjectSchema), + dateCaptured: yup.date().typeError('Date Captured is required'), + creationMethod: yup.number().typeError('Creation method is 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'), + modelFileType: yup.number().typeError('Model File Type is required'), + roughness: yup.number().nullable(true), + metalness: yup.number().nullable(true), + pointCount: yup.number().nullable(true), + faceCount: yup.number().nullable(true), + isWatertight: yup.boolean().nullable(true), + hasNormals: yup.boolean().nullable(true), + hasVertexColor: yup.boolean().nullable(true), + hasUVSpace: yup.boolean().nullable(true), + boundingBoxP1X: yup.number().nullable(true), + boundingBoxP1Y: yup.number().nullable(true), + boundingBoxP1Z: yup.number().nullable(true), + boundingBoxP2X: yup.number().nullable(true), + boundingBoxP2Y: yup.number().nullable(true), + boundingBoxP2Z: yup.number().nullable(true), + directory: yup.string() +}); + +export const defaultSceneFields: SceneFields = { + systemCreated: true, + identifiers: [], + referenceModels: [] +}; + +export type SceneSchemaType = typeof sceneFieldsSchema; + +export const referenceModelSchema = yup.object().shape({ + idSystemObject: yup.number().required(), + name: yup.string().required(), + fileSize: yup.number().required(), + resolution: yup.number().nullable(true), + boundingBoxP1X: yup.number().nullable(true), + boundingBoxP1Y: yup.number().nullable(true), + boundingBoxP1Z: yup.number().nullable(true), + boundingBoxP2X: yup.number().nullable(true), + boundingBoxP2Y: yup.number().nullable(true), + boundingBoxP2Z: yup.number().nullable(true), + action: yup.number().required() +}); + +export const sceneFieldsSchema = yup.object().shape({ + systemCreated: yup.boolean().required(), + identifiers: yup.array().of(identifierSchema).when('systemCreated', identifiersWhenValidation), + referenceModels: yup.array().of(referenceModelSchema) +}); + +export const defaultOtherFields: OtherFields = { + systemCreated: true, + identifiers: [] +}; + +export type OtherSchemaType = typeof otherFieldsSchema; + +export const otherFieldsSchema = yup.object().shape({ + systemCreated: yup.boolean().required(), + identifiers: yup.array().of(identifierSchema).when('systemCreated', identifiersWhenValidation) +}); + +export type ValidateFieldsSchema = PhotogrammetrySchemaType | ModelSchemaType | SceneSchemaType | OtherSchemaType; diff --git a/client/src/store/metadata/metadata.types.ts b/client/src/store/metadata/metadata.types.ts new file mode 100644 index 000000000..f26e4358f --- /dev/null +++ b/client/src/store/metadata/metadata.types.ts @@ -0,0 +1,144 @@ +/** + * Metadata Store Types + * + * Type definitions for the metadata store. + */ +import { AssetDetail, DetailVersion, ReferenceModel, ReferenceModelAction, RelatedObject } from '../../types/graphql'; +import { IngestionFile } from '../upload'; + +export type StateMetadata = { + photogrammetry: PhotogrammetryFields; + model: ModelFields; + scene: SceneFields; + other: OtherFields; + file: IngestionFile; +}; + +export enum MetadataType { + photogrammetry = 'photogrammetry', + model = 'model', + scene = 'scene', + other = 'other' +} + +export type MetadataInfo = { + metadata: StateMetadata; + readonly metadataIndex: number; + isLast: boolean; +}; + +export type FieldErrors = { + photogrammetry: { + dateCaptured: boolean; + datasetType: boolean; + }; + model: { + dateCaptured: boolean; + creationMethod: boolean; + modality: boolean; + units: boolean; + purpose: boolean; + modelFileType: boolean; + }; +}; + +export type MetadataFieldValue = string | number | boolean | null | Date | StateIdentifier[] | StateFolder[] | StateUVMap[] | StateRelatedObject[]; + +export type MetadataUpdate = { + valid: boolean; + selectedFiles: boolean; + error: boolean; +}; + +export type StateIdentifier = { + id: number; + identifier: string; + identifierType: number | null; + selected: boolean; +}; + +export type StateFolder = { + id: number; + name: string; + variantType: number | null; +}; + +export type PhotogrammetryFields = { + systemCreated: boolean; + identifiers: StateIdentifier[]; + folders: StateFolder[]; + description: string; + dateCaptured: Date; + datasetType: number | null; + 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; + directory: string; +}; + +export type StateUVMap = { + id: number; + name: string; + edgeLength: number; + mapType: number | null; +}; + +export type ModelFields = { + systemCreated: boolean; + identifiers: StateIdentifier[]; + uvMaps: StateUVMap[]; + sourceObjects: StateRelatedObject[]; + dateCaptured: Date; + creationMethod: number | null; + master: boolean; + authoritative: boolean; + modality: number | null; + units: number | null; + purpose: number | null; + modelFileType: number | null; + 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; + directory: string; +}; + +export type SceneFields = { + systemCreated: boolean; + identifiers: StateIdentifier[]; + referenceModels: StateReferenceModel[]; +}; + +export type OtherFields = { + systemCreated: boolean; + identifiers: StateIdentifier[]; +}; + +export type StateRelatedObject = RelatedObject; + +export type StateReferenceModel = ReferenceModel; + +export type StateAssetDetail = AssetDetail; + +export type StateDetailVersion = DetailVersion; + +export type ValidateFields = PhotogrammetryFields | ModelFields | SceneFields | OtherFields; + +export { ReferenceModelAction }; diff --git a/client/src/store/project.ts b/client/src/store/project.ts index 927ae84fe..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'; @@ -18,10 +23,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 new file mode 100644 index 000000000..adec0867b --- /dev/null +++ b/client/src/store/repository.ts @@ -0,0 +1,183 @@ +/** + * Repository Store + * + * This store manages state for Repository filter and tree view. + */ +import create, { GetState, SetState } from 'zustand'; +import { RepositoryFilter } from '../pages/Repository'; +import { getObjectChildren, getObjectChildrenForRoot } from '../pages/Repository/hooks/useRepository'; +import { NavigationResultEntry } from '../types/graphql'; +import { eMetadata, eSystemObjectType } from '../types/server'; +import { parseRepositoryTreeNodeId, validateArray } from '../utils/repository'; + +type RepositoryStore = { + isExpanded: boolean; + search: string; + tree: Map; + loading: boolean; + updateSearch: (value: string) => void; + toggleFilter: () => void; + repositoryRootType: eSystemObjectType[]; + objectsToDisplay: number[]; + metadataToDisplay: eMetadata[]; + units: number[]; + projects: number[]; + has: eSystemObjectType[]; + missing: eSystemObjectType[]; + captureMethod: number[]; + variantType: number[]; + modelPurpose: number[]; + modelFileType: number[]; + fromDate: Date | null; + toDate: Date | null; + getFilterState: () => RepositoryFilter; + removeUnitsOrProjects: (id: number, type: eSystemObjectType) => void; + updateFilterValue: (name: string, value: number | number[] | Date) => void; + initializeTree: () => Promise; + getChildren: (nodeId: string) => Promise; + updateRepositoryFilter: (filter: RepositoryFilter) => void; +}; + +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], + objectsToDisplay: [], + metadataToDisplay: [eMetadata.eHierarchyUnit, eMetadata.eHierarchySubject, eMetadata.eHierarchyItem], + units: [], + projects: [], + has: [], + missing: [], + captureMethod: [], + variantType: [], + modelPurpose: [], + modelFileType: [], + fromDate: null, + toDate: null, + updateFilterValue: (name: string, value: number | number[] | Date): void => { + const { initializeTree } = get(); + set({ [name]: value, loading: true }); + initializeTree(); + }, + updateSearch: (value: string): void => { + set({ search: value }); + }, + toggleFilter: (): void => { + const { isExpanded } = get(); + set({ isExpanded: !isExpanded }); + }, + initializeTree: async (): Promise => { + const { getFilterState } = get(); + const filter = getFilterState(); + const { data, error } = await getObjectChildrenForRoot(filter); + + 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, getFilterState } = get(); + const filter = getFilterState(); + const { idSystemObject } = parseRepositoryTreeNodeId(nodeId); + const { data, error } = await getObjectChildren(idSystemObject, filter); + + if (data && !error) { + const { getObjectChildren } = data; + const { entries } = getObjectChildren; + const updatedTree: Map = new Map(tree); + updatedTree.set(nodeId, entries); + set({ tree: updatedTree }); + } + }, + removeUnitsOrProjects: (id: number, type: eSystemObjectType): void => { + 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 }); + }, + updateRepositoryFilter: (filter: RepositoryFilter): void => { + const { + repositoryRootType, + objectsToDisplay, + metadataToDisplay, + units, + projects, + has, + missing, + captureMethod, + variantType, + modelPurpose, + modelFileType + } = get(); + + const stateValues: RepositoryFilter = { + ...filter, + repositoryRootType: validateArray(filter.repositoryRootType, repositoryRootType), + objectsToDisplay: validateArray(filter.objectsToDisplay, objectsToDisplay), + metadataToDisplay: validateArray(filter.metadataToDisplay, metadataToDisplay), + units: validateArray(filter.units, units), + projects: validateArray(filter.projects, projects), + has: validateArray(filter.has, has), + missing: validateArray(filter.missing, missing), + captureMethod: validateArray(filter.captureMethod, captureMethod), + variantType: validateArray(filter.variantType, variantType), + modelPurpose: validateArray(filter.modelPurpose, modelPurpose), + modelFileType: validateArray(filter.modelFileType, modelFileType), + }; + + set(stateValues); + }, + getFilterState: (): RepositoryFilter => { + const { + search, + repositoryRootType, + objectsToDisplay, + metadataToDisplay, + units, + projects, + has, + missing, + captureMethod, + variantType, + modelPurpose, + modelFileType + } = get(); + + return { + search, + repositoryRootType, + objectsToDisplay, + metadataToDisplay, + units, + projects, + has, + missing, + captureMethod, + variantType, + modelPurpose, + modelFileType + }; + } +})); diff --git a/client/src/store/subject.ts b/client/src/store/subject.ts index caab4ca91..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'; @@ -12,8 +17,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 +36,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 +66,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 a34f187fd..b8dc9b2c7 100644 --- a/client/src/store/upload.ts +++ b/client/src/store/upload.ts @@ -1,13 +1,19 @@ +/** + * 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'; 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'; import { parseFileId } from './utils'; +import { UploadEvents, UploadEventType, UploadCompleteEvent, UploadProgressEvent, UploadSetCancelEvent, UploadFailedEvent } from '../utils/events'; export type FileId = string; @@ -47,10 +53,14 @@ 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; }; -export const useUpload = create((set: SetState, get: GetState) => ({ +export const useUploadStore = create((set: SetState, get: GetState) => ({ completed: [], pending: [], loading: true, @@ -65,7 +75,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) { @@ -175,22 +185,20 @@ export const useUpload = create((set: SetState, get: 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({ @@ -208,18 +216,16 @@ export const useUpload = create((set: SetState, get: 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 }) { @@ -227,12 +233,8 @@ export const useUpload = create((set: SetState, get: 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); } } } @@ -297,8 +299,56 @@ export const useUpload = create((set: SetState, get: 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 => { - 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/store/user.ts b/client/src/store/user.ts index bd6a29534..4e41880f7 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'; @@ -6,13 +11,17 @@ import API, { AuthResponseType } from '../api'; type UserStore = { user: User | null; + isAuthenticated: () => Promise; initialize: () => Promise; login: (email: string, password: string) => Promise; logout: () => Promise; }; -export const useUser = create((set: SetState, get: GetState) => ({ +export const useUserStore = create((set: SetState, get: GetState) => ({ user: null, + isAuthenticated: async (): Promise => { + return !!(await getAuthenticatedUser()); + }, initialize: async () => { const { user } = get(); if (!user) { @@ -30,7 +39,14 @@ export const useUser = create((set: SetState, get: GetStat }; } - 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; diff --git a/client/src/store/utils.ts b/client/src/store/utils.ts index 8739ad86e..7c67dda10 100644 --- a/client/src/store/utils.ts +++ b/client/src/store/utils.ts @@ -1,8 +1,14 @@ -import { Item, Project, SubjectUnitIdentifier, AssetVersion, Vocabulary } from '../types/graphql'; -import { StateSubject } from './subject'; +/** + * Utils + * + * These are store specific utilities. + */ +import { AssetVersion, IngestFolder, IngestIdentifier, IngestUvMap, Item, Project, SubjectUnitIdentifier, Vocabulary } from '../types/graphql'; import { StateItem } from './item'; +import { StateFolder, StateIdentifier, StateUVMap } from './metadata'; import { StateProject } from './project'; -import { IngestionFile, FileUploadStatus, FileId } from './upload'; +import { StateSubject } from './subject'; +import { FileId, FileUploadStatus, IngestionFile } from './upload'; export function parseFileId(id: FileId): number { return Number.parseInt(id, 10); @@ -63,3 +69,39 @@ export function parseAssetVersionToState(assetVersion: AssetVersion, vocabulary: cancel: null }; } + +export function parseIdentifiersToState(identifiers: IngestIdentifier[], defaultIdentifierField: StateIdentifier[]): StateIdentifier[] { + const parsedIdentifiers = identifiers.map( + ({ identifier, identifierType }: IngestIdentifier, index: number): StateIdentifier => ({ + 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 uvMaps: StateUVMap[] = folders.map(({ name, edgeLength, mapType }: IngestUvMap, index: number) => ({ + id: index, + name, + edgeLength, + mapType + })); + + return uvMaps; +} diff --git a/client/src/store/vocabulary.ts b/client/src/store/vocabulary.ts index 58d4c1c66..b3f0ee3e5 100644 --- a/client/src/store/vocabulary.ts +++ b/client/src/store/vocabulary.ts @@ -1,15 +1,23 @@ -import create, { SetState, GetState } from 'zustand'; +/** + * Vocabulary Store + * + * This store manages state for vocabularies used in Ingestion flow. + */ +import lodash from 'lodash'; +import create, { GetState, SetState } from 'zustand'; import { apolloClient } from '../graphql'; import { GetVocabularyEntriesDocument, Vocabulary } from '../types/graphql'; import { eVocabularySetID } from '../types/server'; -import lodash from 'lodash'; +import { multiIncludes } from '../utils/shared'; export type VocabularyOption = Pick; export type StateVocabulary = Map; type AssetType = { photogrammetry: boolean; - bagit: boolean; + scene: boolean; + model: boolean; + other: boolean; }; type VocabularyStore = { @@ -17,10 +25,11 @@ type VocabularyStore = { updateVocabularyEntries: () => Promise; getEntries: (eVocabularySetID: eVocabularySetID) => VocabularyOption[]; getInitialEntry: (eVocabularySetID: eVocabularySetID) => number | null; + getVocabularyTerm: (eVocabularySetID: eVocabularySetID, idVocabulary: number) => string | null; 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 = { @@ -34,7 +43,14 @@ export const useVocabulary = create((set: SetState((set: SetState { + const { vocabularies } = get(); + const vocabularyEntry = vocabularies.get(eVocabularySetID); + + if (vocabularyEntry && vocabularyEntry.length) { + for (let i = 0; i < vocabularyEntry.length; i++) { + const vocabulary = vocabularyEntry[i]; + if (vocabulary.idVocabulary === idVocabulary) return vocabulary.Term; + } + } + + return null; + }, getAssetType: (idVocabulary: number): AssetType => { const { vocabularies } = get(); const vocabularyEntry = vocabularies.get(eVocabularySetID.eAssetAssetType); const assetType: AssetType = { photogrammetry: false, - bagit: false + scene: false, + model: false, + other: false }; if (vocabularyEntry) { const foundVocabulary = lodash.find(vocabularyEntry, option => option.idVocabulary === idVocabulary); if (foundVocabulary) { - assetType.photogrammetry = foundVocabulary.Term.toLowerCase().includes('photogrammetry'); - assetType.bagit = foundVocabulary.Term.toLowerCase().includes('bulk'); + const { Term } = foundVocabulary; + const term = Term.toLowerCase(); + + assetType.photogrammetry = term.includes('photogrammetry'); + assetType.scene = term.includes('scene'); + assetType.model = term.includes('model'); + assetType.other = !multiIncludes(term, ['photogrammetry', 'scene', 'model']); } } diff --git a/client/src/theme/colors.ts b/client/src/theme/colors.ts index c5b08039d..7e90bcea1 100644 --- a/client/src/theme/colors.ts +++ b/client/src/theme/colors.ts @@ -1,12 +1,17 @@ +/** + * Colors + * + * Custom colors used in client. + */ import { eSystemObjectType } from '../types/server'; -type ColorMap = { [key: string]: string }; +type ColorMap = Record; type ColorType = { defaults: ColorMap; sidebarOptions: ColorMap; upload: ColorMap; - repository: { [key in number | string]: ColorMap }; + repository: Record; }; const Colors: ColorType = { @@ -54,6 +59,46 @@ const Colors: ColorType = { dark: '#a5d6a7', regular: '#edf7ed', light: '#f6fbf6' + }, + [eSystemObjectType.eModel]: { + dark: '#a5d6a7', + regular: '#edf7ed', + light: '#f6fbf6' + }, + [eSystemObjectType.eScene]: { + dark: '#a5d6a7', + regular: '#edf7ed', + light: '#f6fbf6' + }, + [eSystemObjectType.eIntermediaryFile]: { + dark: '#a5d6a7', + regular: '#edf7ed', + light: '#f6fbf6' + }, + [eSystemObjectType.eProjectDocumentation]: { + dark: '#a5d6a7', + regular: '#edf7ed', + light: '#f6fbf6' + }, + [eSystemObjectType.eAsset]: { + dark: '#a5d6a7', + regular: '#edf7ed', + light: '#f6fbf6' + }, + [eSystemObjectType.eAssetVersion]: { + dark: '#a5d6a7', + regular: '#edf7ed', + light: '#f6fbf6' + }, + [eSystemObjectType.eActor]: { + dark: '#a5d6a7', + regular: '#edf7ed', + light: '#f6fbf6' + }, + [eSystemObjectType.eStakeholder]: { + dark: '#a5d6a7', + regular: '#edf7ed', + light: '#f6fbf6' } } }; 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 86dfb3710..21981dbfa 100644 --- a/client/src/theme/typography.ts +++ b/client/src/theme/typography.ts @@ -1,3 +1,10 @@ +/** + * 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'; @@ -5,43 +12,48 @@ function pxToRem(value: number): string { return `${value / 16}rem`; } -// 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) } } diff --git a/client/src/types/graphql.tsx b/client/src/types/graphql.tsx index 630dd210e..6e502f81c 100644 --- a/client/src/types/graphql.tsx +++ b/client/src/types/graphql.tsx @@ -21,11 +21,14 @@ export type Query = { areCameraSettingsUniform: AreCameraSettingsUniformResult; getAccessPolicy: GetAccessPolicyResult; getAsset: GetAssetResult; + getAssetDetailsForSystemObject: GetAssetDetailsForSystemObjectResult; getAssetVersionsDetails: GetAssetVersionsDetailsResult; getCaptureData: GetCaptureDataResult; getCaptureDataPhoto: GetCaptureDataPhotoResult; getContentsForAssetVersions: GetContentsForAssetVersionsResult; getCurrentUser: GetCurrentUserResult; + getDetailsTabDataForObject: GetDetailsTabDataForObjectResult; + getFilterViewData: GetFilterViewDataResult; getIngestionItemsForSubjects: GetIngestionItemsForSubjectsResult; getIngestionProjectsForSubjects: GetIngestionProjectsForSubjectsResult; getIntermediaryFile: GetIntermediaryFileResult; @@ -38,148 +41,144 @@ export type Query = { getProject: GetProjectResult; getProjectDocumentation: GetProjectDocumentationResult; getScene: GetSceneResult; + getSourceObjectIdentifer: GetSourceObjectIdentiferResult; getSubject: GetSubjectResult; getSubjectsForUnit: GetSubjectsForUnitResult; + getSystemObjectDetails: GetSystemObjectDetailsResult; getUnit: GetUnitResult; getUploadedAssetVersion: GetUploadedAssetVersionResult; getUser: GetUserResult; + getVersionsForSystemObject: GetVersionsForSystemObjectResult; getVocabulary: GetVocabularyResult; getVocabularyEntries: GetVocabularyEntriesResult; getWorkflow: GetWorkflowResult; 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; }; @@ -193,7 +192,6 @@ export type GetAccessPolicyResult = { AccessPolicy?: Maybe; }; - export type AccessAction = { __typename?: 'AccessAction'; idAccessAction: Scalars['Int']; @@ -242,7 +240,6 @@ export type AccessRole = { AccessAction?: Maybe>>; }; - export type Mutation = { __typename?: 'Mutation'; createCaptureData: CreateCaptureDataResult; @@ -258,74 +255,65 @@ export type Mutation = { createVocabularySet: CreateVocabularySetResult; discardUploadedAssetVersions: DiscardUploadedAssetVersionsResult; ingestData: IngestDataResult; + updateObjectDetails: UpdateObjectDetailsResult; 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']; @@ -397,17 +385,84 @@ export type IngestPhotogrammetry = { identifiers: Array; }; +export type IngestUvMap = { + __typename?: 'IngestUVMap'; + name: Scalars['String']; + edgeLength: Scalars['Int']; + mapType: Scalars['Int']; +}; + +export enum RelatedObjectType { + Source = 'Source', + Derived = 'Derived' +} + +export type RelatedObject = { + __typename?: 'RelatedObject'; + idSystemObject: Scalars['Int']; + name: Scalars['String']; + identifier?: Maybe; + objectType: 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; + sourceObjects: 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 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 = { @@ -418,6 +473,7 @@ export type GetAssetVersionDetailResult = { Item?: Maybe; CaptureDataPhoto?: Maybe; Model?: Maybe; + Scene?: Maybe; }; export type GetAssetVersionsDetailsResult = { @@ -462,6 +518,7 @@ export type Asset = { FileName: Scalars['String']; FilePath: Scalars['String']; idAssetGroup?: Maybe; + idVAssetType?: Maybe; idSystemObject?: Maybe; StorageKey?: Maybe; AssetGroup?: Maybe; @@ -495,6 +552,7 @@ export type AssetGroup = { }; export type CreateCaptureDataInput = { + Name: Scalars['String']; idVCaptureMethod: Scalars['Int']; DateCaptured: Scalars['DateTime']; Description: Scalars['String']; @@ -555,6 +613,7 @@ export type CaptureData = { VCaptureMethod?: Maybe; CaptureDataFile?: Maybe>>; CaptureDataGroup?: Maybe>>; + CaptureDataPhoto?: Maybe>>; SystemObject?: Maybe; }; @@ -649,16 +708,75 @@ export type IngestPhotogrammetryInput = { identifiers: Array; }; +export type IngestUvMapInput = { + name: Scalars['String']; + edgeLength: Scalars['Int']; + mapType: Scalars['Int']; +}; + +export type RelatedObjectInput = { + idSystemObject: Scalars['Int']; + name: Scalars['String']; + identifier?: Maybe; + objectType: 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; + sourceObjects: 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 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; }; export type IngestDataInput = { @@ -666,6 +784,9 @@ export type IngestDataInput = { project: IngestProjectInput; item: IngestItemInput; photogrammetry: Array; + model: Array; + scene: Array; + other: Array; }; export type IngestDataResult = { @@ -713,11 +834,13 @@ export type LicenseAssignment = { }; export type CreateModelInput = { + Name: Scalars['String']; Authoritative: Scalars['Boolean']; idVCreationMethod: Scalars['Int']; idVModality: Scalars['Int']; idVPurpose: Scalars['Int']; idVUnits: Scalars['Int']; + idVFileType: Scalars['Int']; Master: Scalars['Boolean']; idAssetThumbnail?: Maybe; }; @@ -739,49 +862,97 @@ export type GetModelResult = { export type Model = { __typename?: 'Model'; idModel: Scalars['Int']; - Authoritative: Scalars['Boolean']; + Name: Scalars['String']; DateCreated: Scalars['DateTime']; - idAssetThumbnail?: Maybe; + Master: Scalars['Boolean']; + Authoritative: Scalars['Boolean']; idVCreationMethod: Scalars['Int']; idVModality: Scalars['Int']; idVPurpose: Scalars['Int']; idVUnits: Scalars['Int']; - Master: Scalars['Boolean']; - AssetThumbnail?: Maybe; + idVFileType: Scalars['Int']; + idAssetThumbnail?: Maybe; + idModelMetrics?: Maybe; + ModelConstellation?: Maybe; VCreationMethod?: Maybe; VModality?: Maybe; VPurpose?: Maybe; VUnits?: Maybe; - ModelGeometryFile?: Maybe>>; + VFileType?: Maybe; + AssetThumbnail?: Maybe; + ModelMetrics?: Maybe; + ModelObject?: Maybe>>; ModelProcessingAction?: Maybe>>; ModelSceneXref?: Maybe>>; SystemObject?: Maybe; }; -export type ModelGeometryFile = { - __typename?: 'ModelGeometryFile'; - idModelGeometryFile: Scalars['Int']; - idAsset: Scalars['Int']; +export type ModelMaterial = { + __typename?: 'ModelMaterial'; + idModelMaterial: Scalars['Int']; + idModelObject: Scalars['Int']; + Name?: Maybe; + ModelObject: ModelObject; +}; + +export type ModelMaterialChannel = { + __typename?: 'ModelMaterialChannel'; + idModelMaterialChannel: Scalars['Int']; + idModelMaterial: Scalars['Int']; + idVMaterialType?: Maybe; + MaterialTypeOther?: Maybe; + idModelMaterialUVMap?: Maybe; + ChannelPosition?: Maybe; + ChannelWidth?: Maybe; + Scalar1?: Maybe; + Scalar2?: Maybe; + Scalar3?: Maybe; + Scalar4?: Maybe; + ModelMaterial: ModelMaterial; + VMaterialType?: Maybe; + ModelMaterialUVMap?: Maybe; +}; + +export type ModelMaterialUvMap = { + __typename?: 'ModelMaterialUVMap'; + idModelMaterialUVMap: Scalars['Int']; idModel: Scalars['Int']; - idVModelFileType: Scalars['Int']; + idAsset: Scalars['Int']; + UVMapEdgeLength: Scalars['Int']; + Model: Model; + Asset: Asset; +}; + +export type ModelMetrics = { + __typename?: 'ModelMetrics'; + idModelMetrics: Scalars['Int']; BoundingBoxP1X?: Maybe; BoundingBoxP1Y?: Maybe; BoundingBoxP1Z?: Maybe; BoundingBoxP2X?: Maybe; BoundingBoxP2Y?: Maybe; BoundingBoxP2Z?: Maybe; - FaceCount?: Maybe; - HasNormals?: Maybe; - HasUVSpace?: Maybe; + CountPoint?: Maybe; + CountFace?: Maybe; + CountColorChannel?: Maybe; + CountTextureCoorinateChannel?: Maybe; + HasBones?: Maybe; + HasFaceNormals?: Maybe; + HasTangents?: Maybe; + HasTextureCoordinates?: Maybe; + HasVertexNormals?: Maybe; HasVertexColor?: Maybe; + IsManifold?: Maybe; IsWatertight?: Maybe; - Metalness?: Maybe; - PointCount?: Maybe; - Roughness?: Maybe; - Asset?: Maybe; - Model?: Maybe; - VModelFileType?: Maybe; - ModelUVMapFile?: Maybe>>; +}; + +export type ModelObject = { + __typename?: 'ModelObject'; + idModelObject: Scalars['Int']; + idModel: Scalars['Int']; + idModelMetrics?: Maybe; + Model: Model; + ModelMetrics?: Maybe; }; export type ModelProcessingAction = { @@ -823,26 +994,15 @@ export type ModelSceneXref = { Scene?: Maybe; }; -export type ModelUvMapChannel = { - __typename?: 'ModelUVMapChannel'; - idModelUVMapChannel: Scalars['Int']; - ChannelPosition: Scalars['Int']; - ChannelWidth: Scalars['Int']; - idModelUVMapFile: Scalars['Int']; - idVUVMapType: Scalars['Int']; - ModelUVMapFile?: Maybe; - VUVMapType?: Maybe; -}; - -export type ModelUvMapFile = { - __typename?: 'ModelUVMapFile'; - idModelUVMapFile: Scalars['Int']; - idAsset: Scalars['Int']; - idModelGeometryFile: Scalars['Int']; - UVMapEdgeLength: Scalars['Int']; - Asset?: Maybe; - ModelGeometryFile?: Maybe; - ModelUVMapChannel?: Maybe>>; +export type ModelConstellation = { + __typename?: 'ModelConstellation'; + Model: Model; + ModelObjects?: Maybe>>; + ModelMaterials?: Maybe>>; + ModelMaterialChannels?: Maybe>>; + ModelMaterialUVMaps?: Maybe>>; + ModelMetric?: Maybe; + ModelObjectMetrics?: Maybe>>; }; export type PaginationInput = { @@ -855,7 +1015,17 @@ export type PaginationInput = { export type GetObjectChildrenInput = { idRoot: Scalars['Int']; objectTypes: Array; + objectsToDisplay: Array; metadataColumns: Array; + search: Scalars['String']; + units: Array; + projects: Array; + has: Array; + missing: Array; + captureMethod: Array; + variantType: Array; + modelPurpose: Array; + modelFileType: Array; }; export type NavigationResultEntry = { @@ -875,6 +1045,12 @@ export type GetObjectChildrenResult = { metadataColumns: Array; }; +export type GetFilterViewDataResult = { + __typename?: 'GetFilterViewDataResult'; + units: Array; + projects: Array; +}; + export type CreateSceneInput = { Name: Scalars['String']; HasBeenQCd: Scalars['Boolean']; @@ -936,124 +1112,520 @@ export type IntermediaryFile = { SystemObject?: Maybe; }; -export type SystemObject = { - __typename?: 'SystemObject'; +export type UpdateObjectDetailsInput = { idSystemObject: Scalars['Int']; - Retired: Scalars['Boolean']; - idActor?: Maybe; - idAsset?: Maybe; - idAssetVersion?: Maybe; - idCaptureData?: Maybe; - idIntermediaryFile?: Maybe; - idItem?: Maybe; - idModel?: Maybe; - idProject?: Maybe; - idProjectDocumentation?: Maybe; - idScene?: Maybe; - idStakeholder?: Maybe; - idSubject?: Maybe; - idUnit?: Maybe; - idWorkflow?: Maybe; - idWorkflowStep?: Maybe; - Actor?: Maybe; - Asset?: Maybe; - AssetVersion?: Maybe; - CaptureData?: Maybe; - IntermediaryFile?: Maybe; - Item?: Maybe; - Model?: Maybe; - Project?: Maybe; - ProjectDocumentation?: Maybe; - Scene?: Maybe; - Stakeholder?: Maybe; - Subject?: Maybe; - Unit?: Maybe; - Workflow?: Maybe; - WorkflowStep?: Maybe; - AccessContextObject?: Maybe>>; - Identifier?: Maybe>>; - LicenseAssignment?: Maybe>>; - Metadata?: Maybe>>; - SystemObjectVersion?: Maybe>>; - SystemObjectDerived?: Maybe>>; - SystemObjectMaster?: Maybe>>; - UserPersonalizationSystemObject?: Maybe>>; - WorkflowStepXref?: Maybe>>; + idObject: Scalars['Int']; + objectType: Scalars['Int']; + data: UpdateObjectDetailsDataInput; }; -export type SystemObjectVersion = { - __typename?: 'SystemObjectVersion'; - idSystemObjectVersion: Scalars['Int']; - idSystemObject: Scalars['Int']; - PublishedState: Scalars['Int']; - SystemObject?: Maybe; +export type UnitDetailFieldsInput = { + Abbreviation?: Maybe; + ARKPrefix?: Maybe; }; -export type Identifier = { - __typename?: 'Identifier'; - idIdentifier: Scalars['Int']; - IdentifierValue: Scalars['String']; - idSystemObject?: Maybe; - idVIdentifierType?: Maybe; - SystemObject?: Maybe; - VIdentifierType?: Maybe; +export type ProjectDetailFieldsInput = { + Description?: Maybe; }; -export type Metadata = { - __typename?: 'Metadata'; - idMetadata: Scalars['Int']; - Name: Scalars['String']; - idAssetValue?: Maybe; - idSystemObject?: Maybe; - idUser?: Maybe; - idVMetadataSource?: Maybe; - ValueExtended?: Maybe; - ValueShort?: Maybe; - AssetValue?: Maybe; - SystemObject?: Maybe; - User?: Maybe; - VMetadataSource?: 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 CreateUnitInput = { - Name: Scalars['String']; - Abbreviation: Scalars['String']; - ARKPrefix: Scalars['String']; +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 CreateUnitResult = { - __typename?: 'CreateUnitResult'; - Unit?: 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 CreateProjectInput = { - Name: Scalars['String']; - Description: Scalars['String']; +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; + HasBeenQCd?: Maybe; + IsOriented?: Maybe; +}; + +export type ProjectDocumentationDetailFieldsInput = { + Description?: Maybe; }; -export type CreateProjectResult = { - __typename?: 'CreateProjectResult'; - Project?: Maybe; +export type AssetDetailFieldsInput = { + FilePath?: Maybe; + AssetType?: Maybe; }; -export type CreateSubjectInput = { - idUnit: Scalars['Int']; - Name: Scalars['String']; - idAssetThumbnail?: Maybe; - idGeoLocation?: Maybe; - idIdentifierPreferred?: Maybe; +export type AssetVersionDetailFieldsInput = { + Creator?: Maybe; + DateCreated?: Maybe; + Ingested?: Maybe; + Version?: Maybe; + StorageSize?: Maybe; }; -export type CreateSubjectResult = { - __typename?: 'CreateSubjectResult'; - Subject?: Maybe; +export type ActorDetailFieldsInput = { + OrganizationName?: Maybe; }; -export type CreateItemInput = { - Name: Scalars['String']; - EntireSubject: Scalars['Boolean']; - idAssetThumbnail?: Maybe; - idGeoLocation?: Maybe; +export type StakeholderDetailFieldsInput = { + OrganizationName?: Maybe; + MailingAddress?: Maybe; + EmailAddress?: Maybe; + PhoneNumberMobile?: Maybe; + PhoneNumberOffice?: Maybe; +}; + +export type UpdateObjectDetailsDataInput = { + Name?: Maybe; + Retired?: Maybe; + 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; + boundingBoxP1X?: Maybe; + boundingBoxP1Y?: Maybe; + boundingBoxP1Z?: Maybe; + boundingBoxP2X?: Maybe; + boundingBoxP2Y?: Maybe; + boundingBoxP2Z?: Maybe; + countPoint?: Maybe; + countFace?: Maybe; + countColorChannel?: Maybe; + countTextureCoorinateChannel?: Maybe; + hasBones?: Maybe; + hasFaceNormals?: Maybe; + hasTangents?: Maybe; + hasTextureCoordinates?: Maybe; + hasVertexNormals?: Maybe; + hasVertexColor?: Maybe; + isManifold?: Maybe; + isWatertight?: Maybe; +}; + +export type SceneDetailFields = { + __typename?: 'SceneDetailFields'; + Links: Array; + AssetType?: Maybe; + Tours?: Maybe; + Annotation?: Maybe; + HasBeenQCd?: Maybe; + IsOriented?: 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; +}; + +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'; + idObject: Scalars['Int']; + name: Scalars['String']; + retired: Scalars['Boolean']; + objectType: Scalars['Int']; + allowed: Scalars['Boolean']; + publishedState: Scalars['String']; + thumbnail?: Maybe; + identifiers: Array; + objectAncestors: Array>; + sourceObjects: Array; + derivedObjects: Array; + unit?: Maybe; + project?: Maybe; + subject?: Maybe; + item?: 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 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']; + Retired: Scalars['Boolean']; + idActor?: Maybe; + idAsset?: Maybe; + idAssetVersion?: Maybe; + idCaptureData?: Maybe; + idIntermediaryFile?: Maybe; + idItem?: Maybe; + idModel?: Maybe; + idProject?: Maybe; + idProjectDocumentation?: Maybe; + idScene?: Maybe; + idStakeholder?: Maybe; + idSubject?: Maybe; + idUnit?: Maybe; + idWorkflow?: Maybe; + idWorkflowStep?: Maybe; + Actor?: Maybe; + Asset?: Maybe; + AssetVersion?: Maybe; + CaptureData?: Maybe; + IntermediaryFile?: Maybe; + Item?: Maybe; + Model?: Maybe; + Project?: Maybe; + ProjectDocumentation?: Maybe; + Scene?: Maybe; + Stakeholder?: Maybe; + Subject?: Maybe; + Unit?: Maybe; + Workflow?: Maybe; + WorkflowStep?: Maybe; + AccessContextObject?: Maybe>>; + Identifier?: Maybe>>; + LicenseAssignment?: Maybe>>; + Metadata?: Maybe>>; + SystemObjectVersion?: Maybe>>; + SystemObjectDerived?: Maybe>>; + SystemObjectMaster?: Maybe>>; + UserPersonalizationSystemObject?: Maybe>>; + WorkflowStepXref?: Maybe>>; +}; + +export type SystemObjectVersion = { + __typename?: 'SystemObjectVersion'; + idSystemObjectVersion: Scalars['Int']; + idSystemObject: Scalars['Int']; + PublishedState: Scalars['Int']; + SystemObject?: Maybe; +}; + +export type Identifier = { + __typename?: 'Identifier'; + idIdentifier: Scalars['Int']; + IdentifierValue: Scalars['String']; + idSystemObject?: Maybe; + idVIdentifierType?: Maybe; + SystemObject?: Maybe; + VIdentifierType?: Maybe; +}; + +export type Metadata = { + __typename?: 'Metadata'; + idMetadata: Scalars['Int']; + Name: Scalars['String']; + idAssetValue?: Maybe; + idSystemObject?: Maybe; + idUser?: Maybe; + idVMetadataSource?: Maybe; + ValueExtended?: Maybe; + ValueShort?: Maybe; + AssetValue?: Maybe; + SystemObject?: Maybe; + User?: Maybe; + VMetadataSource?: Maybe; +}; + +export type CreateUnitInput = { + Name: Scalars['String']; + Abbreviation: Scalars['String']; + ARKPrefix: Scalars['String']; +}; + +export type CreateUnitResult = { + __typename?: 'CreateUnitResult'; + Unit?: Maybe; +}; + +export type CreateProjectInput = { + Name: Scalars['String']; + Description: Scalars['String']; +}; + +export type CreateProjectResult = { + __typename?: 'CreateProjectResult'; + Project?: Maybe; +}; + +export type CreateSubjectInput = { + idUnit: Scalars['Int']; + Name: Scalars['String']; + idAssetThumbnail?: Maybe; + idGeoLocation?: Maybe; + idIdentifierPreferred?: Maybe; +}; + +export type CreateSubjectResult = { + __typename?: 'CreateSubjectResult'; + Subject?: Maybe; +}; + +export type CreateItemInput = { + Name: Scalars['String']; + EntireSubject: Scalars['Boolean']; + idAssetThumbnail?: Maybe; + idGeoLocation?: Maybe; }; export type CreateItemResult = { @@ -1448,960 +2020,668 @@ 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 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 - )> - } - )> +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 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 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' + | 'modelFileType' + | 'dateCaptured' + | 'boundingBoxP1X' + | 'boundingBoxP1Y' + | 'boundingBoxP1Z' + | 'boundingBoxP2X' + | 'boundingBoxP2Y' + | 'boundingBoxP2Z' + | 'countPoint' + | 'countFace' + | 'countColorChannel' + | 'countTextureCoorinateChannel' + | 'hasBones' + | 'hasFaceNormals' + | 'hasTangents' + | 'hasTextureCoordinates' + | 'hasVertexNormals' + | 'hasVertexColor' + | 'isManifold' + | 'isWatertight' + > & { 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 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 GetVersionsForSystemObjectQueryVariables = Exact<{ + input: GetVersionsForSystemObjectInput; +}>; + +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 - } - } - `; + 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' - * }, - * }); - */ -export function useDiscardUploadedAssetVersionsMutation(baseOptions?: Apollo.MutationHookOptions) { + * __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); } export type DiscardUploadedAssetVersionsMutationHookResult = ReturnType; export type DiscardUploadedAssetVersionsMutationResult = Apollo.MutationResult; 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 +2689,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 +2723,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 +2757,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 +2789,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,67 +2823,99 @@ 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); } 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) { - 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 +2923,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 +2957,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 +2991,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 +3025,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 +3062,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 +3096,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 +3130,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 +3165,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 +3200,117 @@ 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 + 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__ -* -* 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 +3321,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 +3358,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 +3402,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 +3437,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 +3472,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 +3505,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 +3540,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); } @@ -3229,39 +3574,84 @@ 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) { - 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 +3662,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 +3697,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); } @@ -3341,34 +3731,386 @@ 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 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 + modelFileType + dateCaptured + uvMaps { + name + edgeLength + mapType + } + boundingBoxP1X + boundingBoxP1Y + boundingBoxP1Z + boundingBoxP2X + boundingBoxP2Y + boundingBoxP2Z + countPoint + countFace + countColorChannel + countTextureCoorinateChannel + hasBones + hasFaceNormals + hasTangents + hasTextureCoordinates + hasVertexNormals + hasVertexColor + isManifold + isWatertight + } + 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__ + * + * 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) { + 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 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 + } + } + } +`; + +/** + * __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 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) { - 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,67 +4121,71 @@ 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' - * }, - * }); - */ -export function useGetIngestionProjectsForSubjectsQuery(baseOptions?: Apollo.QueryHookOptions) { + * __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); } -export function useGetIngestionProjectsForSubjectsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { +export function useGetIngestionProjectsForSubjectsLazyQuery( + baseOptions?: Apollo.LazyQueryHookOptions +) { return Apollo.useLazyQuery(GetIngestionProjectsForSubjectsDocument, baseOptions); } export type GetIngestionProjectsForSubjectsQueryHookResult = ReturnType; export type GetIngestionProjectsForSubjectsLazyQueryHookResult = 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 +4196,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 +4232,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 +4289,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 +4324,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 +4359,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 +4394,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 +4430,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 +4465,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 +4504,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 +4546,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 +4584,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 +4619,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 +4658,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); } @@ -3945,4 +4691,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/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 diff --git a/client/src/types/server.ts b/client/src/types/server.ts index de384f633..83da29d00 100644 --- a/client/src/types/server.ts +++ b/client/src/types/server.ts @@ -11,9 +11,9 @@ export enum eVocabularySetID { eModelModality, eModelUnits, eModelPurpose, - eModelGeometryFileModelFileType, + eModelFileType, eModelProcessingActionStepActionMethod, - eModelUVMapChannelUVMapType, + eModelMaterialChannelMaterialType, eIdentifierIdentifierType, eIdentifierIdentifierTypeActor, eMetadataMetadataSource, @@ -55,9 +55,70 @@ export enum eVocabularyID { } export enum eMetadata { - eUnitAbbreviation, - eSubjectIdentifier, - eItemName + eCommonName, + eCommonDescription, + eCommonIdentifier, + eCommonDateCreated, + eCommonOrganizationName, + eHierarchyUnit, + eHierarchyProject, + eHierarchySubject, + eHierarchyItem, + eUnitARKPrefix, + eSubjectIdentifierPreferred, + eItemEntireSubject, + eCDCaptureMethod, + eCDDatasetType, + eCDDatasetFieldID, + eCDItemPositionType, + eCDItemPositionFieldID, + eCDItemArrangementFieldID, + eCDFocusType, + eCDLightSourceType, + eCDBackgroundRemovalMethod, + eCDClusterType, + eCDClusterGeometryFieldID, + eCDCameraSettingsUniform, + eCDVariantType, + eModelCreationMethod, + eModelMaster, + eModelAuthoritative, + eModelModality, + eModelUnits, + eModelPurpose, + eModelFileType, + eModelRoughness, + eModelMetalness, + eModelPointCount, + eModelFaceCount, + eModelIsWatertight, + eModelHasNormals, + eModelHasVertexColor, + eModelHasUVSpace, + eModelBoundingBoxP1X, + eModelBoundingBoxP1Y, + eModelBoundingBoxP1Z, + eModelBoundingBoxP2X, + eModelBoundingBoxP2Y, + eModelBoundingBoxP2Z, + eModelUVMapEdgeLength, + eModelChannelPosition, + eModelChannelWidth, + eModelUVMapType, + eSceneIsOriented, + eSceneHasBeenQCd, + eAssetFileName, + eAssetFilePath, + eAssetType, + eAVUserCreator, + eAVStorageHash, + eAVStorageSize, + eAVIngested, + eAVBulkIngest, + eStakeholderEmailAddress, + eStakeholderPhoneNumberMobile, + eStakeholderPhoneNumberOffice, + eStakeholderMailingAddress, } export enum eSystemObjectType { 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 }; diff --git a/client/src/utils/repository.ts b/client/src/utils/repository.ts deleted file mode 100644 index 84c433d97..000000000 --- a/client/src/utils/repository.ts +++ /dev/null @@ -1,143 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -import lodash from 'lodash'; -import * as qs from 'query-string'; -import { HOME_ROUTES } from '../constants'; -import { RepositoryFilter } from '../pages/Repository'; -import { TreeViewColumn } from '../pages/Repository/components/RepositoryTreeView/StyledTreeItem'; -import { RepositoryColorVariant } from '../theme/colors'; -import { NavigationResultEntry } from '../types/graphql'; -import { eMetadata, eSystemObjectType } from '../types/server'; - -export function getSystemObjectTypesForFilter(filter: RepositoryFilter): eSystemObjectType[] { - const objectTypes: eSystemObjectType[] = []; - - if (filter.units) { - objectTypes.push(eSystemObjectType.eUnit); - } - - if (filter.projects) { - objectTypes.push(eSystemObjectType.eProject); - } - - return objectTypes; -} - -export function getTermForSystemObjectType(objectType: eSystemObjectType): string { - switch (objectType) { - 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'; - default: - return 'unknown'; - } -} - -export function getRepositoryTreeNodeId(idSystemObject: number, idObject: number, objectType: eSystemObjectType): string { - return `${idSystemObject}-${eSystemObjectType[objectType]}-${idObject}`; -} - -export function getSortedTreeEntries(entries: NavigationResultEntry[]): NavigationResultEntry[] { - return lodash.orderBy(entries, [(entry: NavigationResultEntry) => entry.name.toLowerCase()], 'asc'); -} - -export function trimmedMetadataField(value: string, start: number, end: number): string { - const { length } = value; - if (length < 30) return value; - return `${value.substring(0, start)}...${value.substring(length - end, length)}`; -} - -export function parseRepositoryUrl(search: string): any { - return qs.parse(search, { - parseBooleans: true, - parseNumbers: true, - arrayFormat: 'comma' - }); -} - -export function generateRepositoryUrl(filter: RepositoryFilter): string { - const validate = (value: unknown) => { - if (lodash.isBoolean(value)) { - return value === true; - } - - return true; - }; - - const queryResult = lodash.pickBy(filter, validate); - return `${HOME_ROUTES.REPOSITORY}?${qs.stringify(queryResult)}`; -} - -export function getTreeWidth(columnSize: number): string { - const width = 50 + columnSize * 10; - if (width <= 80) { - return '83.5vw'; - } - - return `${width}vw`; -} - -export function getTreeColorVariant(index: number): RepositoryColorVariant { - return index % 2 ? RepositoryColorVariant.light : RepositoryColorVariant.regular; -} - -export function getTreeViewColumns(metadataColumns: eMetadata[], isHeader: boolean, values?: string[]): TreeViewColumn[] { - const treeColumns: TreeViewColumn[] = []; - const MIN_SIZE = 10; - - metadataColumns.forEach((metadataColumn, index: number) => { - const treeColumn: TreeViewColumn = { - metadataColumn, - label: values ? values[index] : 'Unknown', - size: MIN_SIZE - }; - - switch (metadataColumn) { - case eMetadata.eUnitAbbreviation: - if (isHeader) treeColumn.label = 'Unit'; - break; - - case eMetadata.eSubjectIdentifier: - if (isHeader) treeColumn.label = 'SubjectId'; - treeColumn.size = MIN_SIZE * 2; - break; - - case eMetadata.eItemName: - if (isHeader) treeColumn.label = 'Item name'; - break; - - default: - break; - } - - treeColumns.push(treeColumn); - }); - - return treeColumns; -} - -export function computeMetadataViewWidth(treeColumns: TreeViewColumn[]): string { - return `${treeColumns.reduce((prev, current) => prev + current.size, 0)}vw`; -} diff --git a/client/src/utils/repository.tsx b/client/src/utils/repository.tsx new file mode 100644 index 000000000..9af8e68a2 --- /dev/null +++ b/client/src/utils/repository.tsx @@ -0,0 +1,252 @@ +/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/** + * Repository utilities + * + * 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'; +import React from 'react'; +import { AiOutlineFileText } from 'react-icons/ai'; +import { RepositoryIcon, RepositoryIconProps } from '../components'; +import { RepositoryFilter } from '../pages/Repository'; +import { TreeViewColumn } from '../pages/Repository/components/RepositoryTreeView/MetadataView'; +import { metadataToDisplayOptions } from '../pages/Repository/components/RepositoryFilterView/RepositoryFilterOptions'; +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'; + +export function getSystemObjectTypesForFilter(filter: RepositoryFilter): eSystemObjectType[] { + const objectTypes: eSystemObjectType[] = []; + + if (filter.units) { + objectTypes.push(eSystemObjectType.eUnit); + } + + if (filter.projects) { + objectTypes.push(eSystemObjectType.eProject); + } + + return objectTypes; +} + +export function getTermForSystemObjectType(objectType: eSystemObjectType): string { + switch (objectType) { + 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'; + default: return 'Unknown'; + } +} + +export function getRepositoryTreeNodeId(idSystemObject: number, objectType: eSystemObjectType, idObject: number): string { + 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'); +} + +export function trimmedMetadataField(value: string, start: number, end: number): string { + const { length } = value; + if (length < 30) return value; + return `${value.substring(0, start)}...${value.substring(length - end, length)}`; +} + +export function parseRepositoryUrl(search: string): any { + return qs.parse(search, { + parseBooleans: true, + parseNumbers: true, + arrayFormat: 'comma' + }); +} + +export function generateRepositoryUrl(filter: RepositoryFilter): string { + return `?${qs.stringify(filter, { + arrayFormat: 'comma', + skipEmptyString: true + })}`; +} + +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'; + return '93vw'; + } else { + if (sideBarExpanded) return '81.5vw'; + return '91vw'; + } + } + + return `${computedWidth}vw`; +} + +export function getTreeColorVariant(index: number): RepositoryColorVariant { + return index % 2 ? RepositoryColorVariant.light : RepositoryColorVariant.regular; +} + +// cached data, computed once +let metadataTitleMap: Map | null = null; + +export function getTreeViewColumns(metadataColumns: eMetadata[], isHeader: boolean, values?: string[]): TreeViewColumn[] { + const treeColumns: TreeViewColumn[] = []; + const MIN_SIZE = 5; + + if (!metadataTitleMap) { + metadataTitleMap = new Map(); + for (const filterOption of metadataToDisplayOptions) + metadataTitleMap.set(filterOption.value, filterOption.label); + } + + metadataColumns.forEach((metadataColumn, index: number) => { + const treeColumn: TreeViewColumn = { + metadataColumn, + label: values ? values[index] : 'Unknown', + size: MIN_SIZE + }; + + if (isHeader) + treeColumn.label = metadataTitleMap ? (metadataTitleMap.get(metadataColumn) || 'Unknown') : 'Unknown'; + + switch (metadataColumn) { + case eMetadata.eHierarchySubject: treeColumn.size = MIN_SIZE * 3; break; + case eMetadata.eHierarchyItem: treeColumn.size = MIN_SIZE * 3; break; + default: break; + } + + treeColumns.push(treeColumn); + }); + + return treeColumns; +} + +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: RepositoryIconProps = { objectType, backgroundColor, textColor, overrideText: undefined }; + + switch (objectType) { + default: break; + case eSystemObjectType.eIntermediaryFile: iconProps.overrideText = 'IF'; break; + case eSystemObjectType.eProjectDocumentation: iconProps.overrideText = 'PD'; break; + case eSystemObjectType.eActor: iconProps.overrideText = 'AC'; break; + case eSystemObjectType.eStakeholder: iconProps.overrideText = 'ST'; break; + + case eSystemObjectType.eAsset: + case eSystemObjectType.eAssetVersion: + return { icon: , color }; + } + return { icon: , color }; +} + +export function sortEntriesAlphabetically(entries: NavigationResultEntry[]): NavigationResultEntry[] { + return lodash.orderBy(entries, [entry => entry.name.toLowerCase().trim()], ['asc']); +} + +export function isRepositoryItemSelected(nodeId: string, sourceObjects: StateRelatedObject[]): boolean { + const { idSystemObject } = parseRepositoryTreeNodeId(nodeId); + const idSystemObjects: number[] = sourceObjects.map(({ idSystemObject }) => idSystemObject); + + return idSystemObjects.includes(idSystemObject); +} + +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'; +} + +export function validateArray(value: T[], defaultValue: T[]): T[] { + const result: T[] = []; + + if (!value) { + result.push(...defaultValue); + } else if (Array.isArray(value)) { + result.push(...value); + } else { + result.push(value); + } + + return result; +} + +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' + }; +} diff --git a/client/src/utils/shared.ts b/client/src/utils/shared.ts index 41d9e99d7..797eaf145 100644 --- a/client/src/utils/shared.ts +++ b/client/src/utils/shared.ts @@ -1,5 +1,67 @@ +/** + * Shared utilities + * + * Shared utilities for components, functionality. + */ +import { CSSProperties } from '@material-ui/core/styles/withStyles'; +import { Colors, palette } from '../theme'; + +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) { + return value; + } + + if (defaultValue === null) throw new Error('Default value is null'); + + 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(); } }; + +export const multiIncludes = (text: string, values: string[]): boolean => { + const expression = new RegExp(values.join('|')); + return expression.test(text); +}; + +export const scrollBarProperties = (vertical: boolean, horizontal: boolean, backgroundColor: string): CSSProperties => ({ + scrollBehavior: 'smooth', + '&::-webkit-scrollbar': { + '-webkit-appearance': 'none', + maxWidth: 8 + }, + '&::-webkit-scrollbar:vertical': vertical ? { width: 12 } : null, + '&::-webkit-scrollbar:horizontal': horizontal ? { height: 12 } : null, + '&::-webkit-scrollbar-thumb': { + borderRadius: 10, + backgroundColor + } +}); + +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 +}; + +export function getHeaderTitle(title?: string): string { + if (title) return `${title} - Packrat`; + return 'Packrat'; +} \ No newline at end of file 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 { 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 eff1099aa..000000000 --- a/common/package.json +++ /dev/null @@ -1,36 +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 && concurrently 'yarn build:watch' 'yarn server:start'", - "server:start": "nodemon -e '*.ts' --watch build 'node build/index.js'", - "build": "tsc --build", - "build:watch": "tsc --watch", - "clean": "rm -rf node_modules/ build/", - "postinstall": "echo \"postinstall common\"", - "test": "jest --passWithNoTests" - } -} \ 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.deploy.yml b/docker-compose.deploy.yml new file mode 100644 index 000000000..6af1e1f23 --- /dev/null +++ b/docker-compose.deploy.yml @@ -0,0 +1,128 @@ +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_SOLR_HOST=$PACKRAT_SOLR_HOST + + 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_SOLR_HOST=$PACKRAT_SOLR_HOST + + 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/ + + packrat-solr-dev: + container_name: packrat-solr-dev + image: "packrat-solr-dev:${IMAGE_TAG}" + restart: always + build: + context: . + dockerfile: ./docker/solr.Dockerfile + target: solr + ports: + - $PACKRAT_SOLR_PORT:8983 + environment: + - PACKRAT_SOLR_HOST=packrat-solr-dev + + packrat-solr-prod: + container_name: packrat-solr-prod + image: "packrat-solr-prod:${IMAGE_TAG}" + restart: always + build: + context: . + dockerfile: ./docker/solr.Dockerfile + target: solr + ports: + - $PACKRAT_SOLR_PORT:8983 + environment: + - PACKRAT_SOLR_HOST=packrat-solr-prod + +networks: + default: + ipam: + config: + - subnet: 192.168.4.0/24 diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 979eb9e24..6d5c7236d 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -25,15 +25,15 @@ 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 - ./client:/app/client - - ./common:/app/common packrat-server: container_name: packrat-server @@ -43,15 +43,23 @@ 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 + - EDAN_SERVER=$EDAN_SERVER + - EDAN_APPID=$EDAN_APPID + - OCFL_STORAGE_ROOT=$OCFL_STORAGE_ROOT + - OCFL_STAGING_ROOT=$OCFL_STAGING_ROOT + - PACKRAT_SOLR_HOST=$PACKRAT_SOLR_HOST volumes: - ./node_modules:/app/node_modules - ./package.json:/app/package.json - ./server:/app/server - - ./common:/app/common depends_on: - db @@ -65,10 +73,22 @@ services: target: db ports: - $PACKRAT_DB_PORT:3306 - env_file: - - .env + environment: + - MYSQL_ROOT_PASSWORD=$MYSQL_ROOT_PASSWORD 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 + networks: default: ipam: diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 8eccae08d..795aa1870 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,19 @@ 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 + - EDAN_SERVER=$EDAN_SERVER + - EDAN_APPID=$EDAN_APPID + - OCFL_STORAGE_ROOT=$OCFL_STORAGE_ROOT + - OCFL_STAGING_ROOT=$OCFL_STAGING_ROOT + - PACKRAT_SOLR_HOST=$PACKRAT_SOLR_HOST depends_on: - db @@ -49,8 +59,20 @@ services: restart: always ports: - $PACKRAT_DB_PORT:3306 - env_file: - - .env + environment: + - MYSQL_ROOT_PASSWORD=$MYSQL_ROOT_PASSWORD + + packrat-solr: + container_name: packrat-solr + image: packrat-solr + restart: always + build: + context: . + dockerfile: ./docker/solr.Dockerfile + target: solr + ports: + - $PACKRAT_SOLR_PORT:8983 + networks: default: ipam: diff --git a/docker/dev.Dockerfile b/docker/dev.Dockerfile index cef2b38c3..eeb21b3fe 100644 --- a/docker/dev.Dockerfile +++ b/docker/dev.Dockerfile @@ -1,4 +1,4 @@ -FROM node:12-alpine AS base +FROM node:12.18.4-alpine AS base # Add a work directory WORKDIR /app # Copy package.json for caching @@ -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" ] @@ -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..7e0fd2e23 100644 --- a/docker/prod.Dockerfile +++ b/docker/prod.Dockerfile @@ -1,4 +1,4 @@ -FROM node:12-alpine AS base +FROM node:12.18.4-alpine AS base # Add a work directory WORKDIR /app # Copy package.json for caching @@ -10,16 +10,16 @@ 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-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:12-alpine AS server +FROM node:12.18.4-alpine AS server # Add a work directory WORKDIR /app # Copy from server-builder @@ -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/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..d32808bd5 --- /dev/null +++ b/e2e/package.json @@ -0,0 +1,37 @@ +{ + "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" + }, + "volta": { + "node": "12.18.4", + "yarn": "1.22.4" + } +} 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 new file mode 100644 index 000000000..6a1c125e1 --- /dev/null +++ b/e2e/utils.ts @@ -0,0 +1,58 @@ +import playwright, { ChromiumBrowser, ChromiumBrowserContext, LaunchOptions, Page } from 'playwright'; +import { Selectors } from '../client/src/config'; + +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(); + } + + public login = async (): Promise => { + if (!this.page) { + throw new Error('E2E tests: page was not initialized'); + } + + 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); + await this.page.click(ID(Selectors.AUTH.LOGIN_BUTTON)); + await this.page.waitForNavigation({ + timeout: 20000, + waitUntil: 'domcontentloaded' + }); + }; +} + +const ID = (selector: string): string => `#${selector}`; + +export { E2ETestUtils as default, Selectors, ID }; 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/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 1accd4cb9..a0335808d 100644 --- a/package.json +++ b/package.json @@ -25,23 +25,30 @@ "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", "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", - "build": "lerna run build", + "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", + "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/", "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" + "test": "yarn workspaces run test", + "test:e2e": "cd e2e && yarn test" }, "dependencies": { "@typescript-eslint/eslint-plugin": "4.1.0", @@ -74,8 +81,11 @@ ], "packages": [ "client", - "server", - "common" + "server" ] + }, + "volta": { + "node": "12.18.4", + "yarn": "1.22.4" } } diff --git a/scripts/cleanup.sh b/scripts/cleanup.sh new file mode 100644 index 000000000..9946f0934 --- /dev/null +++ b/scripts/cleanup.sh @@ -0,0 +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 new file mode 100644 index 000000000..ba38e053d --- /dev/null +++ b/scripts/deploy.sh @@ -0,0 +1,47 @@ +# This script helps with deployment of Packrat system +# usage: ./scripts/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 packrat-solr-$1 diff --git a/scripts/devbox/db.sh b/scripts/devbox/db.sh new file mode 100755 index 000000000..32e9f6cbc --- /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 "Done" \ No newline at end of file diff --git a/scripts/devbox/network.sh b/scripts/devbox/network.sh new file mode 100755 index 000000000..2667e9b8d --- /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 "Done" \ No newline at end of file diff --git a/scripts/devbox/up.sh b/scripts/devbox/up.sh new file mode 100755 index 000000000..a48180165 --- /dev/null +++ b/scripts/devbox/up.sh @@ -0,0 +1,8 @@ +# 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 + +echo "Done" \ No newline at end of file diff --git a/scripts/initdb.sh b/scripts/initdb.sh new file mode 100755 index 000000000..69e1727ed --- /dev/null +++ b/scripts/initdb.sh @@ -0,0 +1,46 @@ +# This script helps with deployment of Packrat system's DB +# 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}" ]] + 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" diff --git a/scripts/proxy/nginx.conf b/scripts/proxy/nginx.conf new file mode 100644 index 000000000..cd9e32265 --- /dev/null +++ b/scripts/proxy/nginx.conf @@ -0,0 +1,98 @@ +# 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 8080; + server_name $hostname; + + 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; + } + } + + # 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/refresh.sh b/scripts/proxy/refresh.sh new file mode 100755 index 000000000..13b85efa8 --- /dev/null +++ b/scripts/proxy/refresh.sh @@ -0,0 +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 --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 diff --git a/scripts/reload.sh b/scripts/reload.sh new file mode 100644 index 000000000..ea289e85f --- /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 packrat-solr-$1 diff --git a/server/cache/SystemObjectCache.ts b/server/cache/SystemObjectCache.ts index 0875bee01..6bbfe2859 100644 --- a/server/cache/SystemObjectCache.ts +++ b/server/cache/SystemObjectCache.ts @@ -1,16 +1,16 @@ 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'; 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 { @@ -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/cache/VocabularyCache.ts b/server/cache/VocabularyCache.ts index 52200eb9d..4a0f385a0 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, @@ -16,9 +20,9 @@ export enum eVocabularySetID { eModelModality, eModelUnits, eModelPurpose, - eModelGeometryFileModelFileType, + eModelFileType, eModelProcessingActionStepActionMethod, - eModelUVMapChannelUVMapType, + eModelMaterialChannelMaterialType, eIdentifierIdentifierType, eIdentifierIdentifierTypeActor, eMetadataMetadataSource, @@ -126,9 +130,9 @@ export class VocabularyCache { case 'Model.Modality': eVocabSetEnum = eVocabularySetID.eModelModality; break; case 'Model.Units': eVocabSetEnum = eVocabularySetID.eModelUnits; break; case 'Model.Purpose': eVocabSetEnum = eVocabularySetID.eModelPurpose; break; - case 'ModelGeometryFile.ModelFileType': eVocabSetEnum = eVocabularySetID.eModelGeometryFileModelFileType; break; + case 'Model.FileType': eVocabSetEnum = eVocabularySetID.eModelFileType; break; case 'ModelProcessingActionStep.ActionMethod': eVocabSetEnum = eVocabularySetID.eModelProcessingActionStepActionMethod; break; - case 'ModelUVMapChannel.UVMapType': eVocabSetEnum = eVocabularySetID.eModelUVMapChannelUVMapType; break; + case 'ModelMaterialChannel.MaterialType': eVocabSetEnum = eVocabularySetID.eModelMaterialChannelMaterialType; break; case 'Identifier.IdentifierType': eVocabSetEnum = eVocabularySetID.eIdentifierIdentifierType; break; case 'Identifier.IdentifierTypeActor': eVocabSetEnum = eVocabularySetID.eIdentifierIdentifierTypeActor; break; case 'Metadata.MetadataSource': eVocabSetEnum = eVocabularySetID.eMetadataMetadataSource; break; diff --git a/server/config/index.ts b/server/config/index.ts index 5970d2eec..4103e2133 100644 --- a/server/config/index.ts +++ b/server/config/index.ts @@ -17,7 +17,9 @@ enum COLLECTION_TYPE { } enum NAVIGATION_TYPE { - DB = 'db' + DEFAULT, + DB = 'db', + SOLR = 'solr' } type ConfigType = { @@ -78,7 +80,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/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..6ea64558c --- /dev/null +++ b/server/config/solr/data/packrat/conf/schema.xml @@ -0,0 +1,562 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + --> + --> + --> + --> + --> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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/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..aa926affe 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; @@ -148,7 +158,7 @@ export class Asset extends DBC.DBObject implements AssetBase, SystemO // Subject: as a thumbnail // Item: as a thumbnail // CaptureData: as a thumbnail, and as an asset representing all or part of a CaptureData set (explicitly connected to CaptureDataFile) - // Model: as a thumbnail, and as an asset representing all or part of a Model (explicitly connected to ModelGeometryFile and ModelMVMapFile) + // Model: as a thumbnail, and as an asset representing all or part of a Model (implicity connected via SystemObjectXref, and explicitly connected to ModelMaterialUVMap) // Scene: as a thumbnail, and as an asset representing all or part of a Scene // IntermediaryFile: as an asset representing all or part of an IntermediaryFile // ProjectDocumentation: as an asset representing all or part of a ProjectDocumentation 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/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..a98800bb0 100644 --- a/server/db/api/CaptureDataPhoto.ts +++ b/server/db/api/CaptureDataPhoto.ts @@ -111,4 +111,25 @@ 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; + } + } + + 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 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/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/Model.ts b/server/db/api/Model.ts index 537a478ff..8ad5e62b6 100644 --- a/server/db/api/Model.ts +++ b/server/db/api/Model.ts @@ -1,136 +1,200 @@ -/* 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 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 { ModelObject, ModelMaterial, ModelMaterialChannel, ModelMaterialUVMap, ModelMetrics, SystemObject, SystemObjectBased } from '..'; +import * as DBC from '../connection'; +import * as LOG from '../../utils/logger'; + +export class ModelConstellation { + model: Model; + modelObjects: ModelObject[] | null; + modelMaterials: ModelMaterial[] | null; + modelMaterialChannels: ModelMaterialChannel[] | null; + modelMaterialUVMaps: ModelMaterialUVMap[] | null; + modelMetric: ModelMetrics | null; + modelObjectMetrics: ModelMetrics[] | null; + + constructor(model: Model, + modelObjects: ModelObject[] | null, modelMaterials: ModelMaterial[] | null, + modelMaterialChannels: ModelMaterialChannel[] | null, modelMaterialUVMaps: ModelMaterialUVMap[] | null, + modelMetric: ModelMetrics | null, modelObjectMetrics: ModelMetrics[] | null) { + this.model = model; + this.modelObjects = modelObjects; + this.modelMaterials = modelMaterials; + this.modelMaterialChannels = modelMaterialChannels; + this.modelMaterialUVMaps = modelMaterialUVMaps; + this.modelMetric = modelMetric; + this.modelObjectMetrics = modelObjectMetrics; + } + + 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 modelObjects: ModelObject[] | null = await ModelObject.fetchFromModel(idModel); + const modelMaterials: ModelMaterial[] | null = await ModelMaterial.fetchFromModelObjects(modelObjects || /* istanbul ignore next */ []); + const modelMaterialChannels: ModelMaterialChannel[] | null = await ModelMaterialChannel.fetchFromModelMaterials(modelMaterials || []); + const modelMaterialUVMaps: ModelMaterialUVMap[] | null = await ModelMaterialUVMap.fetchFromModel(idModel); + const modelMetric: ModelMetrics | null = model.idModelMetrics ? await ModelMetrics.fetch(model.idModelMetrics) : null; + const modelObjectMetrics: ModelMetrics[] | null = await ModelMetrics.fetchFromModelObjects(modelObjects || /* istanbul ignore next */ []); + + return new ModelConstellation(model, modelObjects, modelMaterials, modelMaterialChannels, + modelMaterialUVMaps, modelMetric, modelObjectMetrics); + } +} + +export class Model extends DBC.DBObject implements ModelBase, SystemObjectBased { + idModel!: number; + Name!: string; + DateCreated!: Date; + Master!: boolean; + Authoritative!: boolean; + idVCreationMethod!: number; + idVModality!: number; + idVPurpose!: number; + idVUnits!: number; + idVFileType!: number; + idAssetThumbnail!: number | null; + idModelMetrics!: number | null; + + private idAssetThumbnailOrig!: number | null; + private idModelMetricsOrig!: number | null; + + constructor(input: ModelBase) { + super(input); + } + + protected updateCachedValues(): void { + this.idAssetThumbnailOrig = this.idAssetThumbnail; + this.idModelMetricsOrig = this.idModelMetrics; + } + + protected async createWorker(): Promise { + try { + const { Name, DateCreated, Master, Authoritative, idVCreationMethod, idVModality, idVUnits, idVPurpose, + idVFileType, idAssetThumbnail, idModelMetrics } = 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, idVFileType: this.idVFileType, + idAssetThumbnail: this.idAssetThumbnail, idModelMetrics: this.idModelMetrics } = + await DBC.DBConnection.prisma.model.create({ + data: { + Name, + DateCreated, + Master, + Authoritative, + Vocabulary_Model_idVCreationMethodToVocabulary: { connect: { idVocabulary: idVCreationMethod }, }, + Vocabulary_Model_idVModalityToVocabulary: { connect: { idVocabulary: idVModality }, }, + Vocabulary_Model_idVPurposeToVocabulary: { connect: { idVocabulary: idVPurpose }, }, + Vocabulary_Model_idVUnitsToVocabulary: { connect: { idVocabulary: idVUnits }, }, + Vocabulary_Model_idVFileTypeToVocabulary: { connect: { idVocabulary: idVFileType }, }, + Asset: idAssetThumbnail ? { connect: { idAsset: idAssetThumbnail }, } : undefined, + ModelMetrics: idModelMetrics ? { connect: { idModelMetrics }, } : 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, Master, Authoritative, idVCreationMethod, idVModality, idVUnits, idVPurpose, + idVFileType, idAssetThumbnail, idModelMetrics, idAssetThumbnailOrig, idModelMetricsOrig } = this; + const retValue: boolean = await DBC.DBConnection.prisma.model.update({ + where: { idModel, }, + data: { + Name, + DateCreated, + Master, + Authoritative, + Vocabulary_Model_idVCreationMethodToVocabulary: { connect: { idVocabulary: idVCreationMethod }, }, + Vocabulary_Model_idVModalityToVocabulary: { connect: { idVocabulary: idVModality }, }, + Vocabulary_Model_idVUnitsToVocabulary: { connect: { idVocabulary: idVUnits }, }, + Vocabulary_Model_idVPurposeToVocabulary: { connect: { idVocabulary: idVPurpose }, }, + Vocabulary_Model_idVFileTypeToVocabulary: { connect: { idVocabulary: idVFileType }, }, + Asset: idAssetThumbnail ? { connect: { idAsset: idAssetThumbnail }, } : idAssetThumbnailOrig ? { disconnect: true, } : undefined, + ModelMetrics: idModelMetrics ? { connect: { idModelMetrics }, } : idModelMetricsOrig ? { 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/ModelGeometryFile.ts b/server/db/api/ModelGeometryFile.ts deleted file mode 100644 index 0eec2466e..000000000 --- a/server/db/api/ModelGeometryFile.ts +++ /dev/null @@ -1,101 +0,0 @@ -/* eslint-disable camelcase */ -import { ModelGeometryFile as ModelGeometryFileBase } from '@prisma/client'; -import * as DBC from '../connection'; -import * as LOG from '../../utils/logger'; - -export class ModelGeometryFile extends DBC.DBObject implements ModelGeometryFileBase { - idModelGeometryFile!: number; - BoundingBoxP1X!: number | null; - BoundingBoxP1Y!: number | null; - BoundingBoxP1Z!: number | null; - BoundingBoxP2X!: number | null; - BoundingBoxP2Y!: number | null; - BoundingBoxP2Z!: number | null; - FaceCount!: number | null; - HasNormals!: boolean | null; - HasUVSpace!: boolean | null; - HasVertexColor!: boolean | null; - idAsset!: number; - idModel!: number; - idVModelFileType!: number; - IsWatertight!: boolean | null; - Metalness!: number | null; - PointCount!: number | null; - Roughness!: number | null; - - constructor(input: ModelGeometryFileBase) { - super(input); - } - - protected updateCachedValues(): void { } - - protected async createWorker(): Promise { - try { - const { idModel, idAsset, idVModelFileType, Roughness, Metalness, PointCount, FaceCount, IsWatertight, HasNormals, HasVertexColor, HasUVSpace, - BoundingBoxP1X, BoundingBoxP1Y, BoundingBoxP1Z, BoundingBoxP2X, BoundingBoxP2Y, BoundingBoxP2Z } = this; - ({ idModelGeometryFile: this.idModelGeometryFile, idModel: this.idModel, idAsset: this.idAsset, - idVModelFileType: this.idVModelFileType, Roughness: this.Roughness, Metalness: this.Metalness, - PointCount: this.PointCount, FaceCount: this.FaceCount, IsWatertight: this.IsWatertight, - HasNormals: this.HasNormals, HasVertexColor: this.HasVertexColor, HasUVSpace: this.HasUVSpace, - BoundingBoxP1X: this.BoundingBoxP1X, BoundingBoxP1Y: this.BoundingBoxP1Y, BoundingBoxP1Z: this.BoundingBoxP1Z, - BoundingBoxP2X: this.BoundingBoxP2X, BoundingBoxP2Y: this.BoundingBoxP2Y, BoundingBoxP2Z: this.BoundingBoxP2Z } = - await DBC.DBConnection.prisma.modelGeometryFile.create({ - data: { - Model: { connect: { idModel }, }, - Asset: { connect: { idAsset }, }, - Vocabulary: { connect: { idVocabulary: idVModelFileType }, }, - Roughness, Metalness, PointCount, FaceCount, IsWatertight, HasNormals, HasVertexColor, HasUVSpace, - BoundingBoxP1X, BoundingBoxP1Y, BoundingBoxP1Z, BoundingBoxP2X, BoundingBoxP2Y, BoundingBoxP2Z, - }, - })); - return true; - } catch (error) /* istanbul ignore next */ { - LOG.logger.error('DBAPI.ModelGeometryFile.create', error); - return false; - } - } - - protected async updateWorker(): Promise { - try { - const { idModelGeometryFile, idModel, idAsset, idVModelFileType, Roughness, Metalness, PointCount, FaceCount, IsWatertight, HasNormals, HasVertexColor, HasUVSpace, - BoundingBoxP1X, BoundingBoxP1Y, BoundingBoxP1Z, BoundingBoxP2X, BoundingBoxP2Y, BoundingBoxP2Z } = this; - return await DBC.DBConnection.prisma.modelGeometryFile.update({ - where: { idModelGeometryFile, }, - data: { - Model: { connect: { idModel }, }, - Asset: { connect: { idAsset }, }, - Vocabulary: { connect: { idVocabulary: idVModelFileType }, }, - Roughness, Metalness, PointCount, FaceCount, IsWatertight, HasNormals, HasVertexColor, HasUVSpace, - BoundingBoxP1X, BoundingBoxP1Y, BoundingBoxP1Z, BoundingBoxP2X, BoundingBoxP2Y, BoundingBoxP2Z, - }, - }) ? true : /* istanbul ignore next */ false; - } catch (error) /* istanbul ignore next */ { - LOG.logger.error('DBAPI.ModelGeometryFile.update', error); - return false; - } - } - - static async fetch(idModelGeometryFile: number): Promise { - if (!idModelGeometryFile) - return null; - try { - return DBC.CopyObject( - await DBC.DBConnection.prisma.modelGeometryFile.findOne({ where: { idModelGeometryFile, }, }), ModelGeometryFile); - } catch (error) /* istanbul ignore next */ { - LOG.logger.error('DBAPI.ModelGeometryFile.fetch', error); - return null; - } - } - - static async fetchFromModel(idModel: number): Promise { - if (!idModel) - return null; - try { - return DBC.CopyArray( - await DBC.DBConnection.prisma.modelGeometryFile.findMany({ where: { idModel } }), ModelGeometryFile); - } catch (error) /* istanbul ignore next */ { - LOG.logger.error('DBAPI.ModelGeometryFile.fetchFromModel', error); - return null; - } - } -} diff --git a/server/db/api/ModelMaterial.ts b/server/db/api/ModelMaterial.ts new file mode 100644 index 000000000..f90160d4f --- /dev/null +++ b/server/db/api/ModelMaterial.ts @@ -0,0 +1,83 @@ +/* eslint-disable camelcase */ +import { ModelMaterial as ModelMaterialBase, ModelObject, join } from '@prisma/client'; +import * as DBC from '../connection'; +import * as LOG from '../../utils/logger'; + +export class ModelMaterial extends DBC.DBObject implements ModelMaterialBase { + idModelMaterial!: number; + idModelObject!: number; + Name!: string | null; + + constructor(input: ModelMaterialBase) { + super(input); + } + + protected updateCachedValues(): void { } + + protected async createWorker(): Promise { + try { + const { idModelObject, Name } = this; + ({ idModelMaterial: this.idModelMaterial, idModelObject: this.idModelObject, Name: this.Name } = + await DBC.DBConnection.prisma.modelMaterial.create({ + data: { + ModelObject: { connect: { idModelObject }, }, + Name, + }, + })); + return true; + } catch (error) /* istanbul ignore next */ { + LOG.logger.error('DBAPI.ModelMaterial.create', error); + return false; + } + } + + protected async updateWorker(): Promise { + try { + const { idModelMaterial, idModelObject, Name } = this; + const retValue: boolean = await DBC.DBConnection.prisma.modelMaterial.update({ + where: { idModelMaterial, }, + data: { + ModelObject: { connect: { idModelObject }, }, + Name, + }, + }) ? true : /* istanbul ignore next */ false; + return retValue; + } catch (error) /* istanbul ignore next */ { + LOG.logger.error('DBAPI.ModelMaterial.update', error); + return false; + } + } + + static async fetch(idModelMaterial: number): Promise { + if (!idModelMaterial) + return null; + try { + return DBC.CopyObject( + await DBC.DBConnection.prisma.modelMaterial.findOne({ where: { idModelMaterial, }, }), ModelMaterial); + } catch (error) /* istanbul ignore next */ { + LOG.logger.error('DBAPI.ModelMaterial.fetch', error); + return null; + } + } + + static async fetchFromModelObjects(modelObjects: ModelObject[]): Promise { + if (modelObjects.length == 0) + return null; + try { + const idModelObjects: number[] = []; + for (const modelObject of modelObjects) + idModelObjects.push(modelObject.idModelObject); + + return DBC.CopyArray( + await DBC.DBConnection.prisma.$queryRaw` + SELECT DISTINCT * + FROM ModelMaterial + WHERE idModelObject IN (${join(idModelObjects)})`, + ModelMaterial + ); + } catch (error) /* istanbul ignore next */ { + LOG.logger.error('DBAPI.ModelMaterial.fetchFromModelObjects', error); + return null; + } + } +} diff --git a/server/db/api/ModelMaterialChannel.ts b/server/db/api/ModelMaterialChannel.ts new file mode 100644 index 000000000..5d199ddc3 --- /dev/null +++ b/server/db/api/ModelMaterialChannel.ts @@ -0,0 +1,130 @@ +/* eslint-disable camelcase */ +import { ModelMaterialChannel as ModelMaterialChannelBase, join } from '@prisma/client'; +import { ModelMaterial } from '..'; +import * as DBC from '../connection'; +import * as LOG from '../../utils/logger'; + +export class ModelMaterialChannel extends DBC.DBObject implements ModelMaterialChannelBase { + idModelMaterialChannel!: number; + idModelMaterial!: number; + idVMaterialType!: number | null; + MaterialTypeOther!: string | null; + idModelMaterialUVMap!: number | null; + ChannelPosition!: number | null; + ChannelWidth!: number | null; + Scalar1!: number | null; + Scalar2!: number | null; + Scalar3!: number | null; + Scalar4!: number | null; + + private idVMaterialTypeOrig!: number | null; + private idModelMaterialUVMapOrig!: number | null; + + constructor(input: ModelMaterialChannelBase) { + super(input); + } + + protected updateCachedValues(): void { + this.idVMaterialTypeOrig = this.idVMaterialType; + this.idModelMaterialUVMapOrig = this.idModelMaterialUVMap; + } + + protected async createWorker(): Promise { + try { + const { idModelMaterial, idVMaterialType, MaterialTypeOther, idModelMaterialUVMap, ChannelPosition, ChannelWidth, + Scalar1, Scalar2, Scalar3, Scalar4 } = this; + ({ idModelMaterialChannel: this.idModelMaterialChannel, idModelMaterial: this.idModelMaterial, idVMaterialType: this.idVMaterialType, + MaterialTypeOther: this.MaterialTypeOther, idModelMaterialUVMap: this.idModelMaterialUVMap, ChannelPosition: this.ChannelPosition, + ChannelWidth: this.ChannelWidth, Scalar1: this.Scalar1, Scalar2: this.Scalar2, Scalar3: this.Scalar3, Scalar4: this.Scalar4 } = + await DBC.DBConnection.prisma.modelMaterialChannel.create({ + data: { + ModelMaterial: { connect: { idModelMaterial }, }, + Vocabulary: idVMaterialType ? { connect: { idVocabulary: idVMaterialType }, } : undefined, + MaterialTypeOther, + ModelMaterialUVMap: idModelMaterialUVMap ? { connect: { idModelMaterialUVMap }, } : undefined, + ChannelPosition, + ChannelWidth, + Scalar1, + Scalar2, + Scalar3, + Scalar4, + }, + })); + return true; + } catch (error) /* istanbul ignore next */ { + LOG.logger.error('DBAPI.ModelMaterialChannel.create', error); + return false; + } + } + + protected async updateWorker(): Promise { + try { + const { idModelMaterialChannel, idModelMaterial, idVMaterialType, MaterialTypeOther, idModelMaterialUVMap, ChannelPosition, ChannelWidth, + Scalar1, Scalar2, Scalar3, Scalar4, idVMaterialTypeOrig, idModelMaterialUVMapOrig } = this; + const retValue: boolean = await DBC.DBConnection.prisma.modelMaterialChannel.update({ + where: { idModelMaterialChannel, }, + data: { + ModelMaterial: { connect: { idModelMaterial }, }, + Vocabulary: idVMaterialType ? { connect: { idVocabulary: idVMaterialType }, } : idVMaterialTypeOrig ? { disconnect: true, } : undefined, + MaterialTypeOther, + ModelMaterialUVMap: idModelMaterialUVMap ? { connect: { idModelMaterialUVMap }, } : idModelMaterialUVMapOrig ? { disconnect: true, } : undefined, + ChannelPosition, + ChannelWidth, + Scalar1, + Scalar2, + Scalar3, + Scalar4, + }, + }) ? true : /* istanbul ignore next */ false; + return retValue; + } catch (error) /* istanbul ignore next */ { + LOG.logger.error('DBAPI.ModelMaterialChannel.update', error); + return false; + } + } + + static async fetch(idModelMaterialChannel: number): Promise { + if (!idModelMaterialChannel) + return null; + try { + return DBC.CopyObject( + await DBC.DBConnection.prisma.modelMaterialChannel.findOne({ where: { idModelMaterialChannel, }, }), ModelMaterialChannel); + } catch (error) /* istanbul ignore next */ { + LOG.logger.error('DBAPI.ModelMaterialChannel.fetch', error); + return null; + } + } + + static async fetchFromModelMaterial(idModelMaterial: number): Promise { + if (!idModelMaterial) + return null; + try { + return DBC.CopyArray( + await DBC.DBConnection.prisma.modelMaterialChannel.findMany({ where: { idModelMaterial } }), ModelMaterialChannel); + } catch (error) /* istanbul ignore next */ { + LOG.logger.error('DBAPI.ModelMaterialChannel.fetchFromModelMaterial', error); + return null; + } + } + + static async fetchFromModelMaterials(modelMaterials: ModelMaterial[]): Promise { + if (modelMaterials.length == 0) + return null; + try { + const idModelMaterials: number[] = []; + for (const modelMaterial of modelMaterials) + idModelMaterials.push(modelMaterial.idModelMaterial); + + return DBC.CopyArray( + await DBC.DBConnection.prisma.$queryRaw` + SELECT DISTINCT * + FROM ModelMaterialChannel + WHERE idModelMaterial IN (${join(idModelMaterials)})`, + ModelMaterialChannel + ); + } catch (error) /* istanbul ignore next */ { + LOG.logger.error('DBAPI.ModelMaterialChannel.fetchFromModelMaterials', error); + return null; + } + } +} diff --git a/server/db/api/ModelMaterialUVMap.ts b/server/db/api/ModelMaterialUVMap.ts new file mode 100644 index 000000000..5fb4ef401 --- /dev/null +++ b/server/db/api/ModelMaterialUVMap.ts @@ -0,0 +1,99 @@ +/* eslint-disable camelcase */ +import { ModelMaterialUVMap as ModelMaterialUVMapBase, join } from '@prisma/client'; +import { Model } from '..'; +import * as DBC from '../connection'; +import * as LOG from '../../utils/logger'; + +export class ModelMaterialUVMap extends DBC.DBObject implements ModelMaterialUVMapBase { + idModelMaterialUVMap!: number; + idModel!: number; + idAsset!: number; + UVMapEdgeLength!: number; + + constructor(input: ModelMaterialUVMapBase) { + super(input); + } + + protected updateCachedValues(): void { } + + protected async createWorker(): Promise { + try { + const { idModel, idAsset, UVMapEdgeLength } = this; + ({ idModelMaterialUVMap: this.idModelMaterialUVMap, idModel: this.idModel, idAsset: this.idAsset, UVMapEdgeLength: this.UVMapEdgeLength } = + await DBC.DBConnection.prisma.modelMaterialUVMap.create({ + data: { + Model: { connect: { idModel }, }, + Asset: { connect: { idAsset }, }, + UVMapEdgeLength, + }, + })); + return true; + } catch (error) /* istanbul ignore next */ { + LOG.logger.error('DBAPI.ModelMaterialUVMap.create', error); + return false; + } + } + + protected async updateWorker(): Promise { + try { + const { idModelMaterialUVMap, idModel, idAsset, UVMapEdgeLength } = this; + const retValue: boolean = await DBC.DBConnection.prisma.modelMaterialUVMap.update({ + where: { idModelMaterialUVMap, }, + data: { + Model: { connect: { idModel }, }, + Asset: { connect: { idAsset }, }, + UVMapEdgeLength, + }, + }) ? true : /* istanbul ignore next */ false; + return retValue; + } catch (error) /* istanbul ignore next */ { + LOG.logger.error('DBAPI.ModelMaterialUVMap.update', error); + return false; + } + } + + static async fetch(idModelMaterialUVMap: number): Promise { + if (!idModelMaterialUVMap) + return null; + try { + return DBC.CopyObject( + await DBC.DBConnection.prisma.modelMaterialUVMap.findOne({ where: { idModelMaterialUVMap, }, }), ModelMaterialUVMap); + } catch (error) /* istanbul ignore next */ { + LOG.logger.error('DBAPI.ModelMaterialUVMap.fetch', error); + return null; + } + } + + static async fetchFromModel(idModel: number): Promise { + if (!idModel) + return null; + try { + return DBC.CopyArray( + await DBC.DBConnection.prisma.modelMaterialUVMap.findMany({ where: { idModel } }), ModelMaterialUVMap); + } catch (error) /* istanbul ignore next */ { + LOG.logger.error('DBAPI.ModelMaterialUVMap.fetchFromModel', error); + return null; + } + } + + static async fetchFromModels(models: Model[]): Promise { + if (models.length == 0) + return null; + try { + const idModel: number[] = []; + for (const model of models) + idModel.push(model.idModel); + + return DBC.CopyArray( + await DBC.DBConnection.prisma.$queryRaw` + SELECT DISTINCT * + FROM ModelMaterialUVMap + WHERE idModel IN (${join(idModel)})`, + ModelMaterialUVMap + ); + } catch (error) /* istanbul ignore next */ { + LOG.logger.error('DBAPI.ModelMaterialUVMap.fetchFromModels', error); + return null; + } + } +} diff --git a/server/db/api/ModelMetrics.ts b/server/db/api/ModelMetrics.ts new file mode 100644 index 000000000..c4d3a8677 --- /dev/null +++ b/server/db/api/ModelMetrics.ts @@ -0,0 +1,149 @@ +/* eslint-disable camelcase, @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any */ +import { ModelMetrics as ModelMetricsBase, join } from '@prisma/client'; +import { ModelObject } from '..'; +import * as DBC from '../connection'; +import * as LOG from '../../utils/logger'; + +export class ModelMetrics extends DBC.DBObject implements ModelMetricsBase { + idModelMetrics!: number; + BoundingBoxP1X!: number | null; + BoundingBoxP1Y!: number | null; + BoundingBoxP1Z!: number | null; + BoundingBoxP2X!: number | null; + BoundingBoxP2Y!: number | null; + BoundingBoxP2Z!: number | null; + CountPoint!: number | null; + CountFace!: number | null; + CountColorChannel!: number | null; + CountTextureCoorinateChannel!: number | null; + HasBones!: boolean | null; + HasFaceNormals!: boolean | null; + HasTangents!: boolean | null; + HasTextureCoordinates!: boolean | null; + HasVertexNormals!: boolean | null; + HasVertexColor!: boolean | null; + IsManifold!: boolean | null; + IsWatertight!: boolean | null; + + constructor(input: ModelMetricsBase) { + super(input); + } + + protected updateCachedValues(): void { } + + // BoundingBoxP1X, BoundingBoxP1Y, BoundingBoxP1Z, BoundingBoxP2X, BoundingBoxP2Y, BoundingBoxP2Z, CountPoint, CountFace, CountColorChannel, CountTextureCoorinateChannel, HasBones, HasFaceNormals, HasTangents, HasTextureCoordinates, HasVertexNormals, HasVertexColor, IsManifold, IsWatertight + protected async createWorker(): Promise { + try { + const { BoundingBoxP1X, BoundingBoxP1Y, BoundingBoxP1Z, BoundingBoxP2X, BoundingBoxP2Y, BoundingBoxP2Z, CountPoint, + CountFace, CountColorChannel, CountTextureCoorinateChannel, HasBones, HasFaceNormals, HasTangents, HasTextureCoordinates, + HasVertexNormals, HasVertexColor, IsManifold, IsWatertight } = this; + ({ idModelMetrics: this.idModelMetrics, BoundingBoxP1X: this.BoundingBoxP1X, BoundingBoxP1Y: this.BoundingBoxP1Y, BoundingBoxP1Z: this.BoundingBoxP1Z, + BoundingBoxP2X: this.BoundingBoxP2X, BoundingBoxP2Y: this.BoundingBoxP2Y, BoundingBoxP2Z: this.BoundingBoxP2Z, + CountPoint: this.CountPoint, CountFace: this.CountFace, CountColorChannel: this.CountColorChannel, + CountTextureCoorinateChannel: this.CountTextureCoorinateChannel, HasBones: this.HasBones, HasFaceNormals: this.HasFaceNormals, + HasTangents: this.HasTangents, HasTextureCoordinates: this.HasTextureCoordinates, HasVertexNormals: this.HasVertexNormals, + HasVertexColor: this.HasVertexColor, IsManifold: this.IsManifold, IsWatertight: this.IsWatertight } = + await DBC.DBConnection.prisma.modelMetrics.create({ + data: { BoundingBoxP1X, BoundingBoxP1Y, BoundingBoxP1Z, BoundingBoxP2X, BoundingBoxP2Y, BoundingBoxP2Z, + CountPoint, CountFace, CountColorChannel, CountTextureCoorinateChannel, HasBones, HasFaceNormals, + HasTangents, HasTextureCoordinates, HasVertexNormals, HasVertexColor, IsManifold, IsWatertight + }, + })); + return true; + } catch (error) /* istanbul ignore next */ { + LOG.logger.error('DBAPI.ModelMetrics.create', error); + return false; + } + } + + protected async updateWorker(): Promise { + try { + const { idModelMetrics, BoundingBoxP1X, BoundingBoxP1Y, BoundingBoxP1Z, BoundingBoxP2X, BoundingBoxP2Y, BoundingBoxP2Z, + CountPoint, CountFace, CountColorChannel, CountTextureCoorinateChannel, HasBones, HasFaceNormals, HasTangents, + HasTextureCoordinates, HasVertexNormals, HasVertexColor, IsManifold, IsWatertight } = this; + const retValue: boolean = await DBC.DBConnection.prisma.modelMetrics.update({ + where: { idModelMetrics, }, + data: { BoundingBoxP1X, BoundingBoxP1Y, BoundingBoxP1Z, BoundingBoxP2X, BoundingBoxP2Y, BoundingBoxP2Z, + CountPoint, CountFace, CountColorChannel, CountTextureCoorinateChannel, HasBones, HasFaceNormals, + HasTangents, HasTextureCoordinates, HasVertexNormals, HasVertexColor, IsManifold, IsWatertight + }, + }) ? true : /* istanbul ignore next */ false; + return retValue; + } catch (error) /* istanbul ignore next */ { + LOG.logger.error('DBAPI.ModelMetrics.update', error); + return false; + } + } + + static async fetch(idModelMetrics: number): Promise { + if (!idModelMetrics) + return null; + try { + return DBC.CopyObject( + await DBC.DBConnection.prisma.modelMetrics.findOne({ where: { idModelMetrics, }, }), ModelMetrics); + } catch (error) /* istanbul ignore next */ { + LOG.logger.error('DBAPI.ModelMetrics.fetch', error); + return null; + } + } + + static safeNumber(value: any): number | null { + if (value == null) + return null; + return parseInt(value); + } + + static safeBoolean(value: any): boolean | null { + if (value == null) + return null; + return value ? true : false; + } + + static async fetchFromModelObjects(modelObjects: ModelObject[]): Promise { + if (modelObjects.length == 0) + return null; + try { + const idModelObjects: number[] = []; + for (const modelObject of modelObjects) + idModelObjects.push(modelObject.idModelObject); + + const modelMetricsBaseList: ModelMetricsBase[] | null = + // return DBC.CopyArray( + await DBC.DBConnection.prisma.$queryRaw` + SELECT DISTINCT * + FROM ModelMetrics + WHERE idModelMetrics IN + (SELECT idModelMetrics FROM ModelObject WHERE idModelObject IN (${join(idModelObjects)}))`; // , ModelMetrics); + + const modelMetricsList: ModelMetrics[] = []; + for (const modelMetricsBase of modelMetricsBaseList) { + const modelMetrics = new ModelMetrics({ + idModelMetrics: modelMetricsBase.idModelMetrics, + BoundingBoxP1X: ModelMetrics.safeNumber(modelMetricsBase.BoundingBoxP1X), + BoundingBoxP1Y: ModelMetrics.safeNumber(modelMetricsBase.BoundingBoxP1Y), + BoundingBoxP1Z: ModelMetrics.safeNumber(modelMetricsBase.BoundingBoxP1Z), + BoundingBoxP2X: ModelMetrics.safeNumber(modelMetricsBase.BoundingBoxP2X), + BoundingBoxP2Y: ModelMetrics.safeNumber(modelMetricsBase.BoundingBoxP2Y), + BoundingBoxP2Z: ModelMetrics.safeNumber(modelMetricsBase.BoundingBoxP2Z), + CountPoint: ModelMetrics.safeNumber(modelMetricsBase.CountPoint), + CountFace: ModelMetrics.safeNumber(modelMetricsBase.CountFace), + CountColorChannel: ModelMetrics.safeNumber(modelMetricsBase.CountColorChannel), + CountTextureCoorinateChannel: ModelMetrics.safeNumber(modelMetricsBase.CountTextureCoorinateChannel), + HasBones: ModelMetrics.safeBoolean(modelMetricsBase.HasBones), + HasFaceNormals: ModelMetrics.safeBoolean(modelMetricsBase.HasFaceNormals), + HasTangents: ModelMetrics.safeBoolean(modelMetricsBase.HasTangents), + HasTextureCoordinates: ModelMetrics.safeBoolean(modelMetricsBase.HasTextureCoordinates), + HasVertexNormals: ModelMetrics.safeBoolean(modelMetricsBase.HasVertexNormals), + HasVertexColor: ModelMetrics.safeBoolean(modelMetricsBase.HasVertexColor), + IsManifold: ModelMetrics.safeBoolean(modelMetricsBase.IsManifold), + IsWatertight: ModelMetrics.safeBoolean(modelMetricsBase.IsWatertight), + }); + modelMetricsList.push(modelMetrics); + } + return modelMetricsList; + } catch (error) /* istanbul ignore next */ { + LOG.logger.error('DBAPI.ModelMetrics.fetchFromModelObjects', error); + return null; + } + } +} diff --git a/server/db/api/ModelObject.ts b/server/db/api/ModelObject.ts new file mode 100644 index 000000000..6adcea6fe --- /dev/null +++ b/server/db/api/ModelObject.ts @@ -0,0 +1,78 @@ +/* eslint-disable camelcase */ +import { ModelObject as ModelObjectBase } from '@prisma/client'; +import * as DBC from '../connection'; +import * as LOG from '../../utils/logger'; + +export class ModelObject extends DBC.DBObject implements ModelObjectBase { + idModelObject!: number; + idModel!: number; + idModelMetrics!: number | null; + + private idModelMetricsOrig!: number | null; + + constructor(input: ModelObjectBase) { + super(input); + } + + protected updateCachedValues(): void { + this.idModelMetricsOrig = this.idModelMetrics; + } + + protected async createWorker(): Promise { + try { + const { idModel, idModelMetrics } = this; + ({ idModelObject: this.idModelObject, idModel: this.idModel, idModelMetrics: this.idModelMetrics } = + await DBC.DBConnection.prisma.modelObject.create({ + data: { + Model: { connect: { idModel }, }, + ModelMetrics: idModelMetrics ? { connect: { idModelMetrics }, } : undefined, + }, + })); + return true; + } catch (error) /* istanbul ignore next */ { + LOG.logger.error('DBAPI.ModelObject.create', error); + return false; + } + } + + protected async updateWorker(): Promise { + try { + const { idModelObject, idModel, idModelMetrics, idModelMetricsOrig } = this; + const retValue: boolean = await DBC.DBConnection.prisma.modelObject.update({ + where: { idModelObject, }, + data: { + Model: { connect: { idModel }, }, + ModelMetrics: idModelMetrics ? { connect: { idModelMetrics }, } : idModelMetricsOrig ? { disconnect: true, } : undefined, + }, + }) ? true : /* istanbul ignore next */ false; + return retValue; + } catch (error) /* istanbul ignore next */ { + LOG.logger.error('DBAPI.ModelObject.update', error); + return false; + } + } + + static async fetch(idModelObject: number): Promise { + if (!idModelObject) + return null; + try { + return DBC.CopyObject( + await DBC.DBConnection.prisma.modelObject.findOne({ where: { idModelObject, }, }), ModelObject); + } catch (error) /* istanbul ignore next */ { + LOG.logger.error('DBAPI.ModelObject.fetch', error); + return null; + } + } + + static async fetchFromModel(idModel: number): Promise { + if (!idModel) + return null; + try { + return DBC.CopyArray( + await DBC.DBConnection.prisma.modelObject.findMany({ where: { idModel } }), ModelObject); + } catch (error) /* istanbul ignore next */ { + LOG.logger.error('DBAPI.ModelObject.fetchFromModel', error); + return null; + } + } +} diff --git a/server/db/api/ModelUVMapChannel.ts b/server/db/api/ModelUVMapChannel.ts deleted file mode 100644 index 5c1bbc90c..000000000 --- a/server/db/api/ModelUVMapChannel.ts +++ /dev/null @@ -1,78 +0,0 @@ -/* eslint-disable camelcase */ -import { ModelUVMapChannel as ModelUVMapChannelBase } from '@prisma/client'; -import * as DBC from '../connection'; -import * as LOG from '../../utils/logger'; - -export class ModelUVMapChannel extends DBC.DBObject implements ModelUVMapChannelBase { - idModelUVMapChannel!: number; - ChannelPosition!: number; - ChannelWidth!: number; - idModelUVMapFile!: number; - idVUVMapType!: number; - - constructor(input: ModelUVMapChannelBase) { - super(input); - } - - protected updateCachedValues(): void { } - - protected async createWorker(): Promise { - try { - const { idModelUVMapFile, ChannelPosition, ChannelWidth, idVUVMapType } = this; - ({ idModelUVMapChannel: this.idModelUVMapChannel, idModelUVMapFile: this.idModelUVMapFile, - ChannelPosition: this.ChannelPosition, ChannelWidth: this.ChannelWidth, idVUVMapType: this.idVUVMapType } = - await DBC.DBConnection.prisma.modelUVMapChannel.create({ - data: { - ModelUVMapFile: { connect: { idModelUVMapFile }, }, - ChannelPosition, ChannelWidth, - Vocabulary: { connect: { idVocabulary: idVUVMapType }, }, - }, - })); - return true; - } catch (error) /* istanbul ignore next */ { - LOG.logger.error('DBAPI.ModelUVMapChannel.create', error); - return false; - } - } - - protected async updateWorker(): Promise { - try { - const { idModelUVMapChannel, idModelUVMapFile, ChannelPosition, ChannelWidth, idVUVMapType } = this; - return await DBC.DBConnection.prisma.modelUVMapChannel.update({ - where: { idModelUVMapChannel, }, - data: { - ModelUVMapFile: { connect: { idModelUVMapFile }, }, - ChannelPosition, ChannelWidth, - Vocabulary: { connect: { idVocabulary: idVUVMapType }, }, - }, - }) ? true : /* istanbul ignore next */ false; - } catch (error) /* istanbul ignore next */ { - LOG.logger.error('DBAPI.ModelUVMapChannel.update', error); - return false; - } - } - - static async fetch(idModelUVMapChannel: number): Promise { - if (!idModelUVMapChannel) - return null; - try { - return DBC.CopyObject( - await DBC.DBConnection.prisma.modelUVMapChannel.findOne({ where: { idModelUVMapChannel, }, }), ModelUVMapChannel); - } catch (error) /* istanbul ignore next */ { - LOG.logger.error('DBAPI.ModelUVMapChannel.fetch', error); - return null; - } - } - - static async fetchFromModelUVMapFile(idModelUVMapFile: number): Promise { - if (!idModelUVMapFile) - return null; - try { - return DBC.CopyArray( - await DBC.DBConnection.prisma.modelUVMapChannel.findMany({ where: { idModelUVMapFile } }), ModelUVMapChannel); - } catch (error) /* istanbul ignore next */ { - LOG.logger.error('DBAPI.ModelUVMapChannel.fetchFromModelUVMapFile', error); - return null; - } - } -} diff --git a/server/db/api/ModelUVMapFile.ts b/server/db/api/ModelUVMapFile.ts deleted file mode 100644 index 72682bc3c..000000000 --- a/server/db/api/ModelUVMapFile.ts +++ /dev/null @@ -1,77 +0,0 @@ -/* eslint-disable camelcase */ -import { ModelUVMapFile as ModelUVMapFileBase } from '@prisma/client'; -import * as DBC from '../connection'; -import * as LOG from '../../utils/logger'; - -export class ModelUVMapFile extends DBC.DBObject implements ModelUVMapFileBase { - idModelUVMapFile!: number; - idAsset!: number; - idModelGeometryFile!: number; - UVMapEdgeLength!: number; - - constructor(input: ModelUVMapFileBase) { - super(input); - } - - protected updateCachedValues(): void { } - - protected async createWorker(): Promise { - try { - const { idModelGeometryFile, idAsset, UVMapEdgeLength } = this; - ({ idModelUVMapFile: this.idModelUVMapFile, idModelGeometryFile: this.idModelGeometryFile, - idAsset: this.idAsset, UVMapEdgeLength: this.UVMapEdgeLength } = - await DBC.DBConnection.prisma.modelUVMapFile.create({ - data: { - ModelGeometryFile: { connect: { idModelGeometryFile }, }, - Asset: { connect: { idAsset }, }, - UVMapEdgeLength, - }, - })); - return true; - } catch (error) /* istanbul ignore next */ { - LOG.logger.error('DBAPI.ModelUVMapFile.create', error); - return false; - } - } - - protected async updateWorker(): Promise { - try { - const { idModelUVMapFile, idModelGeometryFile, idAsset, UVMapEdgeLength } = this; - return await DBC.DBConnection.prisma.modelUVMapFile.update({ - where: { idModelUVMapFile, }, - data: { - ModelGeometryFile: { connect: { idModelGeometryFile }, }, - Asset: { connect: { idAsset }, }, - UVMapEdgeLength, - }, - }) ? true : /* istanbul ignore next */ false; - } catch (error) /* istanbul ignore next */ { - LOG.logger.error('DBAPI.ModelUVMapFile.update', error); - return false; - } - } - - static async fetch(idModelUVMapFile: number): Promise { - if (!idModelUVMapFile) - return null; - try { - return DBC.CopyObject( - await DBC.DBConnection.prisma.modelUVMapFile.findOne({ where: { idModelUVMapFile, }, }), ModelUVMapFile); - } catch (error) /* istanbul ignore next */ { - LOG.logger.error('DBAPI.ModelUVMapFile.fetch', error); - return null; - } - } - - static async fetchFromModelGeometryFile(idModelGeometryFile: number): Promise { - if (!idModelGeometryFile) - return null; - try { - return DBC.CopyArray( - await DBC.DBConnection.prisma.modelUVMapFile.findMany({ where: { idModelGeometryFile } }), ModelUVMapFile); - } catch (error) /* istanbul ignore next */ { - LOG.logger.error('DBAPI.ModelUVMapFile.fetchFromModelGeometryFile', error); - 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/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/api/SystemObjectPairs.ts b/server/db/api/SystemObjectPairs.ts index 38487f10e..79ff25336 100644 --- a/server/db/api/SystemObjectPairs.ts +++ b/server/db/api/SystemObjectPairs.ts @@ -1,481 +1,522 @@ -/* 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 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; + 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/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/db/api/composite/ObjectGraph.ts b/server/db/api/composite/ObjectGraph.ts index 92654a4af..388229c02 100644 --- a/server/db/api/composite/ObjectGraph.ts +++ b/server/db/api/composite/ObjectGraph.ts @@ -3,11 +3,12 @@ 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 = { - idSystemObject: number, - idObject: number, - eType: eSystemObjectType +export type SystemObjectIDType = { + idSystemObject: number; + idObject: number; + eObjectType: eSystemObjectType; }; export enum eObjectGraphMode { @@ -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 { @@ -98,10 +102,14 @@ export class ObjectGraph { if (this.pushCount++ >= this.maxPushCount) return true; + // short-circuit if we're building an ObjectGraphDatabase and we've already processed this object + if (this.objectGraphDatabase && this.objectGraphDatabase.alreadyProcssed(idSystemObject, relatedType)) + return true; + const sourceType: SystemObjectIDType = { idSystemObject, idObject: 0, - eType: eSystemObjectType.eUnknown + eObjectType: eSystemObjectType.eUnknown }; const SOP: SystemObjectPairs | null = await SystemObjectPairs.fetch(idSystemObject); @@ -140,21 +148,33 @@ 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)}`); + + this.systemObjectProcessed.set(idSystemObject, sourceType); + this.systemObjectAdded.set(idSystemObject, sourceType); + + // record relationship + if (this.objectGraphDatabase) { + if (relatedType) { + if (eMode == eObjectGraphMode.eAncestors) + await this.objectGraphDatabase.recordRelationship(sourceType, relatedType); + else + await this.objectGraphDatabase.recordRelationship(relatedType, sourceType); + } + } + /* const valid: string = (this.validHierarchy ? '' : ' INVALID HIERARCHY') + (this.noCycles ? '' : ' CYCLE'); - const sourceDesc: string = `${eSystemObjectType[sourceType.eType]} ${sourceType.idObject}/${sourceType.idSystemObject}`; - const relatedDesc: string = (relatedType) ? `${eSystemObjectType[relatedType.eType]} ${relatedType.idObject}/${relatedType.idSystemObject}` : 'root'; + const sourceDesc: string = `${eSystemObjectType[sourceType.eObjectType]} ${sourceType.idObject}/${sourceType.idSystemObject}`; + const relatedDesc: string = (relatedType) ? `${eSystemObjectType[relatedType.eObjectType]} ${relatedType.idObject}/${relatedType.idSystemObject}` : 'root'; const traverseType: string = (eMode == eObjectGraphMode.eAncestors) ? '^^' : 'vv'; const prefix: string = `OA [${this.pushCount.toString().padStart(3, '0')} ${traverseType}]: `; if (eMode == eObjectGraphMode.eAncestors) - LOG.logger.info(`${prefix}${sourceDesc} -> ${relatedDesc}${valid}`); + LOG.logger.info(`${prefix}${sourceDesc} -> ${relatedDesc}${valid}$`); else - LOG.logger.info(`${prefix}${relatedDesc} -> ${sourceDesc}${valid}`); + LOG.logger.info(`${prefix}${relatedDesc} -> ${sourceDesc}${valid}$`); */ - this.systemObjectProcessed.set(idSystemObject, sourceType); - this.systemObjectAdded.set(idSystemObject, sourceType); // gather using master/derived systemobjectxref's const SORelated: SystemObject[] | null = (eMode == eObjectGraphMode.eAncestors) @@ -204,17 +224,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; } @@ -245,9 +265,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 @@ -286,12 +306,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; } @@ -321,15 +341,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; } @@ -362,14 +382,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; } @@ -391,17 +411,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; } @@ -435,20 +455,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; } @@ -481,15 +501,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; } @@ -526,12 +546,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; } @@ -561,17 +581,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; } @@ -606,14 +626,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; } @@ -635,16 +655,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; } @@ -682,13 +702,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 new file mode 100644 index 000000000..244293451 --- /dev/null +++ b/server/db/api/composite/ObjectGraphDataEntry.ts @@ -0,0 +1,163 @@ +// 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; + modelFileType: number | null = null; +} + +export class ObjectGraphDataEntryHierarchy { + idSystemObject: number = 0; + retired: boolean = false; + eObjectType: eSystemObjectType | null = null; + idObject: number = 0; + + parents: number[] = []; // array of SystemObject.idSystemObject + children: number[] = []; // array of SystemObject.idSystemObject + + 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 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(); + 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.idSystemObject, child); + // LOG.logger.info(`${JSON.stringify(this.systemObjectIDType)} children: ${this.childMap.size}`); + } + + recordParent(parent: SystemObjectIDType): void { + 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; + + 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 (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); + 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.modelFileType) { + if (!this.childrenModelFileTypes.has(objectGraphState.modelFileType)) { + this.childrenModelFileTypes.set(objectGraphState.modelFileType, true); + retValue = true; + } + } + } + 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()]; + + // 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; + 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 new file mode 100644 index 000000000..2fb9cfd54 --- /dev/null +++ b/server/db/api/composite/ObjectGraphDatabase.ts @@ -0,0 +1,353 @@ +import { Unit, Project, Subject, Item, SystemObjectIDType, Actor, Asset, AssetVersion, CaptureData, CaptureDataFile, IntermediaryFile, + Model, 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 SystemObject.idSystemObject to graph entry details + + // used by ObjectGraph + async recordRelationship(parent: SystemObjectIDType, child: SystemObjectIDType): Promise { + // 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); + + 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.idSystemObject, 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.idSystemObject, childData); + } + + parentData.recordChild(child); + childData.recordParent(parent); + } + + alreadyProcssed(idSystemObject: number, relatedType: SystemObjectIDType | null): boolean { + const sourceFound: boolean = this.objectMap.has(idSystemObject); + const relatedFound: boolean = !relatedType || this.objectMap.has(relatedType.idSystemObject); + return sourceFound && relatedFound; + } + + 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; + 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; + LOG.logger.info('ObjectGraphDatabase.fetch completed successfully'); + return true; + } + + /* #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; + } + // 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()) { + LOG.logger.error(`GraphDatabase.${functionName} unable to compute ObjectGraph for ${JSON.stringify(oID)}`); + return false; + } + + 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; + } + + 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; + for (const unit of units) { + if (!await this.computeGraphDataFromObject(unit.idUnit, eSystemObjectType.eUnit, 'computeGraphDataFromUnits')) + continue; + } + return true; + } + + 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; + for (const project of projects) { + if (!await this.computeGraphDataFromObject(project.idProject, eSystemObjectType.eProject, 'computeGraphDataFromProjects')) + continue; + } + return true; + } + + 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; + for (const Subject of Subjects) { + if (!await this.computeGraphDataFromObject(Subject.idSubject, eSystemObjectType.eSubject, 'computeGraphDataFromSubjects')) + continue; + } + return true; + } + + 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; + for (const Item of Items) { + if (!await this.computeGraphDataFromObject(Item.idItem, eSystemObjectType.eItem, 'computeGraphDataFromItems')) + continue; + } + return true; + } + + 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; + for (const CaptureData of CaptureDatas) { + if (!await this.computeGraphDataFromObject(CaptureData.idCaptureData, eSystemObjectType.eCaptureData, 'computeGraphDataFromCaptureDatas')) + continue; + } + return true; + } + + 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; + for (const Model of Models) { + if (!await this.computeGraphDataFromObject(Model.idModel, eSystemObjectType.eModel, 'computeGraphDataFromModels')) + continue; + } + return true; + } + + 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; + for (const Scene of Scenes) { + if (!await this.computeGraphDataFromObject(Scene.idScene, eSystemObjectType.eScene, 'computeGraphDataFromScenes')) + continue; + } + return true; + } + + 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; + for (const IntermediaryFile of IntermediaryFiles) { + if (!await this.computeGraphDataFromObject(IntermediaryFile.idIntermediaryFile, eSystemObjectType.eIntermediaryFile, 'computeGraphDataFromIntermediaryFiles')) + continue; + } + return true; + } + + 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; + for (const ProjectDocumentation of ProjectDocumentations) { + if (!await this.computeGraphDataFromObject(ProjectDocumentation.idProjectDocumentation, + eSystemObjectType.eProjectDocumentation, 'computeGraphDataFromProjectDocumentations')) + continue; + } + return true; + } + + 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; + for (const Asset of Assets) { + if (!await this.computeGraphDataFromObject(Asset.idAsset, eSystemObjectType.eAsset, 'computeGraphDataFromAssets')) + continue; + } + return true; + } + + 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; + for (const AssetVersion of AssetVersions) { + if (!await this.computeGraphDataFromObject(AssetVersion.idAssetVersion, eSystemObjectType.eAssetVersion, 'computeGraphDataFromAssetVersions')) + continue; + } + return true; + } + + 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; + for (const Actor of Actors) { + if (!await this.computeGraphDataFromObject(Actor.idActor, eSystemObjectType.eActor, 'computeGraphDataFromActors')) + continue; + } + return true; + } + + 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; + 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 { + 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 + // determine if applicable computed values (unit, project, subject, item) have been applied to child; if not: + // apply computed value + // 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 (recurse: extract state, apply, ascend) + + let retValue: boolean = true; + for (const objectGraphDataEntry of this.objectMap.values()) { + const objectGraphState = await this.extractState(objectGraphDataEntry.systemObjectIDType); + retValue = this.applyGraphState(objectGraphDataEntry, objectGraphState) && retValue; + } + return retValue; + } + + private async applyGraphState(objectGraphDataEntry: ObjectGraphDataEntry, objectGraphState: ObjectGraphState): Promise { + // Apply extracted state to the current object. + objectGraphDataEntry.applyGraphState(objectGraphState, eApplyGraphStateDirection.eSelf); + let retValue: boolean = true; + retValue = (await this.applyGraphStateRecursive(objectGraphDataEntry, objectGraphState, eApplyGraphStateDirection.eChild, 32)) && retValue; + retValue = (await this.applyGraphStateRecursive(objectGraphDataEntry, objectGraphState, eApplyGraphStateDirection.eParent, 32)) && retValue; + return retValue; + } + + private async applyGraphStateRecursive(objectGraphDataEntry: ObjectGraphDataEntry, objectGraphState: ObjectGraphState, + eDirection: eApplyGraphStateDirection, depth: number): Promise { + if (eDirection == eApplyGraphStateDirection.eSelf) + return false; + if (depth <= 0) + return false; + + const relationMap: Map | undefined = + eDirection == eApplyGraphStateDirection.eChild ? objectGraphDataEntry.childMap : objectGraphDataEntry.parentMap; + if (!relationMap) + return true; + + 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: + 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 CaptureData from ${systemObjectIDType}`); + } break; + + case eSystemObjectType.eModel: { + const model: Model | null = await Model.fetch(systemObjectIDType.idObject); + if (model) { + objectGraphState.modelPurpose = model.idVPurpose; + objectGraphState.modelFileType = model.idVFileType; + } else + LOG.logger.error(`ObjectGraphDatabase.applyGraphData() Unable to load Model from ${systemObjectIDType}`); + } break; + } + + return objectGraphState; + } + /* #endregion */ +} diff --git a/server/db/connection/DBObject.ts b/server/db/connection/DBObject.ts index a36410609..7e824f7ec 100644 --- a/server/db/connection/DBObject.ts +++ b/server/db/connection/DBObject.ts @@ -1,6 +1,6 @@ export abstract class DBObject { - protected abstract async createWorker(): Promise; - protected abstract async updateWorker(): Promise; + protected abstract createWorker(): Promise; + protected abstract updateWorker(): Promise; protected abstract updateCachedValues(): void; constructor(input: T) { diff --git a/server/db/index.ts b/server/db/index.ts index 0ee6df60d..73df4a945 100644 --- a/server/db/index.ts +++ b/server/db/index.ts @@ -29,12 +29,14 @@ export * from './api/License'; export * from './api/LicenseAssignment'; export * from './api/Metadata'; export * from './api/Model'; -export * from './api/ModelGeometryFile'; +export * from './api/ModelMaterial'; +export * from './api/ModelMaterialChannel'; +export * from './api/ModelMaterialUVMap'; +export * from './api/ModelMetrics'; +export * from './api/ModelObject'; export * from './api/ModelProcessingAction'; export * from './api/ModelProcessingActionStep'; export * from './api/ModelSceneXref'; -export * from './api/ModelUVMapChannel'; -export * from './api/ModelUVMapFile'; export * from './api/Project'; export * from './api/ProjectDocumentation'; export * from './api/Scene'; @@ -57,4 +59,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/db/prisma/schema.prisma b/server/db/prisma/schema.prisma index f247d9eac..8d8008a60 100644 --- a/server/db/prisma/schema.prisma +++ b/server/db/prisma/schema.prisma @@ -86,16 +86,16 @@ model Actor { } model Asset { - idAsset Int @default(autoincrement()) @id + idAsset Int @default(autoincrement()) @id FileName String FilePath String idAssetGroup Int? idVAssetType Int idSystemObject Int? - StorageKey String? @unique - AssetGroup AssetGroup? @relation(fields: [idAssetGroup], references: [idAssetGroup]) - SystemObject_Asset_idSystemObjectToSystemObject SystemObject? @relation("Asset_idSystemObjectToSystemObject", fields: [idSystemObject], references: [idSystemObject]) - Vocabulary Vocabulary @relation(fields: [idVAssetType], references: [idVocabulary]) + StorageKey String? @unique + AssetGroup AssetGroup? @relation(fields: [idAssetGroup], references: [idAssetGroup]) + SystemObject_Asset_idSystemObjectToSystemObject SystemObject? @relation("Asset_idSystemObjectToSystemObject", fields: [idSystemObject], references: [idSystemObject]) + Vocabulary Vocabulary @relation(fields: [idVAssetType], references: [idVocabulary]) AssetVersion AssetVersion[] CaptureData CaptureData[] CaptureDataFile CaptureDataFile[] @@ -103,11 +103,10 @@ model Asset { Item Item[] Metadata Metadata[] Model Model[] - ModelGeometryFile ModelGeometryFile[] - ModelUVMapFile ModelUVMapFile[] + ModelMaterialUVMap ModelMaterialUVMap[] Scene Scene[] Subject Subject[] - SystemObject_AssetToSystemObject_idAsset SystemObject? @relation("AssetToSystemObject_idAsset") + SystemObject_AssetToSystemObject_idAsset SystemObject? @relation("AssetToSystemObject_idAsset") @@index([idAssetGroup], name: "Asset_idAssetGroup") @@index([idSystemObject], name: "fk_asset_systemobject1") @@ -144,6 +143,7 @@ model AssetVersion { model CaptureData { idCaptureData Int @default(autoincrement()) @id + Name String idVCaptureMethod Int DateCaptured DateTime Description String @@ -355,58 +355,117 @@ model Metadata { model Model { idModel Int @default(autoincrement()) @id + Name String DateCreated DateTime - idVCreationMethod Int Master Boolean Authoritative Boolean + idVCreationMethod Int idVModality Int idVUnits Int idVPurpose Int + idVFileType Int idAssetThumbnail Int? + idModelMetrics Int? Asset Asset? @relation(fields: [idAssetThumbnail], references: [idAsset]) + ModelMetrics ModelMetrics? @relation(fields: [idModelMetrics], references: [idModelMetrics]) Vocabulary_Model_idVCreationMethodToVocabulary Vocabulary @relation("Model_idVCreationMethodToVocabulary", fields: [idVCreationMethod], references: [idVocabulary]) + Vocabulary_Model_idVFileTypeToVocabulary Vocabulary @relation("Model_idVFileTypeToVocabulary", fields: [idVFileType], references: [idVocabulary]) Vocabulary_Model_idVModalityToVocabulary Vocabulary @relation("Model_idVModalityToVocabulary", fields: [idVModality], references: [idVocabulary]) Vocabulary_Model_idVPurposeToVocabulary Vocabulary @relation("Model_idVPurposeToVocabulary", fields: [idVPurpose], references: [idVocabulary]) Vocabulary_Model_idVUnitsToVocabulary Vocabulary @relation("Model_idVUnitsToVocabulary", fields: [idVUnits], references: [idVocabulary]) - ModelGeometryFile ModelGeometryFile[] + ModelMaterialUVMap ModelMaterialUVMap[] + ModelObject ModelObject[] ModelProcessingAction ModelProcessingAction[] ModelSceneXref ModelSceneXref[] SystemObject SystemObject? @@index([idAssetThumbnail], name: "fk_model_asset1") + @@index([idModelMetrics], name: "fk_model_modelmetrics1") @@index([idVCreationMethod], name: "fk_model_vocabulary1") @@index([idVModality], name: "fk_model_vocabulary2") @@index([idVUnits], name: "fk_model_vocabulary3") @@index([idVPurpose], name: "fk_model_vocabulary4") -} + @@index([idVFileType], name: "fk_model_vocabulary5") +} + +model ModelMaterial { + idModelMaterial Int @default(autoincrement()) @id + idModelObject Int + Name String? + ModelObject ModelObject @relation(fields: [idModelObject], references: [idModelObject]) + ModelMaterialChannel ModelMaterialChannel[] + + @@index([idModelObject], name: "Model_idModelObject") +} + +model ModelMaterialChannel { + idModelMaterialChannel Int @default(autoincrement()) @id + idModelMaterial Int + idVMaterialType Int? + MaterialTypeOther String? + idModelMaterialUVMap Int? + ChannelPosition Int? + ChannelWidth Int? + Scalar1 Float? + Scalar2 Float? + Scalar3 Float? + Scalar4 Float? + ModelMaterial ModelMaterial @relation(fields: [idModelMaterial], references: [idModelMaterial]) + ModelMaterialUVMap ModelMaterialUVMap? @relation(fields: [idModelMaterialUVMap], references: [idModelMaterialUVMap]) + Vocabulary Vocabulary? @relation(fields: [idVMaterialType], references: [idVocabulary]) + + @@index([idModelMaterial], name: "ModelMaterialChannel_idModelMaterial") + @@index([idModelMaterialUVMap], name: "fk_modelmaterialchannel_modelmaterialuvmap1") + @@index([idVMaterialType], name: "fk_modelmaterialchannel_vocabulary1") +} + +model ModelMaterialUVMap { + idModelMaterialUVMap Int @default(autoincrement()) @id + idModel Int + idAsset Int + UVMapEdgeLength Int + Asset Asset @relation(fields: [idAsset], references: [idAsset]) + Model Model @relation(fields: [idModel], references: [idModel]) + ModelMaterialChannel ModelMaterialChannel[] -model ModelGeometryFile { - idModelGeometryFile Int @default(autoincrement()) @id - idModel Int - idAsset Int - idVModelFileType Int - Roughness Float? - Metalness Float? - PointCount Int? - FaceCount Int? - IsWatertight Boolean? - HasNormals Boolean? - HasVertexColor Boolean? - HasUVSpace Boolean? - BoundingBoxP1X Float? - BoundingBoxP1Y Float? - BoundingBoxP1Z Float? - BoundingBoxP2X Float? - BoundingBoxP2Y Float? - BoundingBoxP2Z Float? - Asset Asset @relation(fields: [idAsset], references: [idAsset]) - Model Model @relation(fields: [idModel], references: [idModel]) - Vocabulary Vocabulary @relation(fields: [idVModelFileType], references: [idVocabulary]) - ModelUVMapFile ModelUVMapFile[] - - @@index([idAsset], name: "ModelGeometryFile_idAsset") - @@index([idModel], name: "ModelGeometryFile_idModel") - @@index([idVModelFileType], name: "fk_modelgeometryfile_vocabulary1") + @@index([idAsset], name: "ModelUVMapFile_idAsset") + @@index([idModel], name: "ModelUVMapFile_idModel") +} + +model ModelMetrics { + idModelMetrics Int @default(autoincrement()) @id + BoundingBoxP1X Float? + BoundingBoxP1Y Float? + BoundingBoxP1Z Float? + BoundingBoxP2X Float? + BoundingBoxP2Y Float? + BoundingBoxP2Z Float? + CountPoint Int? + CountFace Int? + CountColorChannel Int? + CountTextureCoorinateChannel Int? + HasBones Boolean? + HasFaceNormals Boolean? + HasTangents Boolean? + HasTextureCoordinates Boolean? + HasVertexNormals Boolean? + HasVertexColor Boolean? + IsManifold Boolean? + IsWatertight Boolean? + Model Model[] + ModelObject ModelObject[] +} + +model ModelObject { + idModelObject Int @default(autoincrement()) @id + idModel Int + idModelMetrics Int? + Model Model @relation(fields: [idModel], references: [idModel]) + ModelMetrics ModelMetrics? @relation(fields: [idModelMetrics], references: [idModelMetrics]) + ModelMaterial ModelMaterial[] + + @@index([idModel], name: "ModelObject_idModel") + @@index([idModelMetrics], name: "fk_modelobject_modelmetrics1") } model ModelProcessingAction { @@ -454,32 +513,6 @@ model ModelSceneXref { @@index([idScene], name: "ModelSceneXref_idScene") } -model ModelUVMapChannel { - idModelUVMapChannel Int @default(autoincrement()) @id - idModelUVMapFile Int - ChannelPosition Int - ChannelWidth Int - idVUVMapType Int - ModelUVMapFile ModelUVMapFile @relation(fields: [idModelUVMapFile], references: [idModelUVMapFile]) - Vocabulary Vocabulary @relation(fields: [idVUVMapType], references: [idVocabulary]) - - @@index([idModelUVMapFile], name: "ModelUVMapChannel_idModelUVMapFile") - @@index([idVUVMapType], name: "fk_modeluvmapchannel_vocabulary1") -} - -model ModelUVMapFile { - idModelUVMapFile Int @default(autoincrement()) @id - idModelGeometryFile Int - idAsset Int - UVMapEdgeLength Int - Asset Asset @relation(fields: [idAsset], references: [idAsset]) - ModelGeometryFile ModelGeometryFile @relation(fields: [idModelGeometryFile], references: [idModelGeometryFile]) - ModelUVMapChannel ModelUVMapChannel[] - - @@index([idAsset], name: "ModelUVMapFile_idAsset") - @@index([idModelGeometryFile], name: "ModelUVMapFile_idModelGeometryFile") -} - model Project { idProject Int @default(autoincrement()) @id Name String @@ -703,12 +736,12 @@ model Vocabulary { JobTask JobTask[] Metadata Metadata[] Model_Model_idVCreationMethodToVocabulary Model[] @relation("Model_idVCreationMethodToVocabulary") + Model_Model_idVFileTypeToVocabulary Model[] @relation("Model_idVFileTypeToVocabulary") Model_Model_idVModalityToVocabulary Model[] @relation("Model_idVModalityToVocabulary") Model_Model_idVPurposeToVocabulary Model[] @relation("Model_idVPurposeToVocabulary") Model_Model_idVUnitsToVocabulary Model[] @relation("Model_idVUnitsToVocabulary") - ModelGeometryFile ModelGeometryFile[] + ModelMaterialChannel ModelMaterialChannel[] ModelProcessingActionStep ModelProcessingActionStep[] - ModelUVMapChannel ModelUVMapChannel[] WorkflowStep WorkflowStep[] @@index([idVocabularySet, SortOrder], name: "Vocabulary_idVocabulySet_SortOrder") diff --git a/server/db/sql/models/Packrat.mwb b/server/db/sql/models/Packrat.mwb index ae217f237..f4890d2a2 100644 Binary files a/server/db/sql/models/Packrat.mwb and b/server/db/sql/models/Packrat.mwb differ diff --git a/server/db/sql/scripts/Packrat.DATA.sql b/server/db/sql/scripts/Packrat.DATA.sql index 9b75d442c..d3c01c47f 100644 --- a/server/db/sql/scripts/Packrat.DATA.sql +++ b/server/db/sql/scripts/Packrat.DATA.sql @@ -13,9 +13,9 @@ INSERT INTO VocabularySet (Name, SystemMaintained) VALUES ('Model.CreationMethod INSERT INTO VocabularySet (Name, SystemMaintained) VALUES ('Model.Modality', 1); INSERT INTO VocabularySet (Name, SystemMaintained) VALUES ('Model.Units', 1); INSERT INTO VocabularySet (Name, SystemMaintained) VALUES ('Model.Purpose', 1); -INSERT INTO VocabularySet (Name, SystemMaintained) VALUES ('ModelGeometryFile.ModelFileType', 1); +INSERT INTO VocabularySet (Name, SystemMaintained) VALUES ('Model.FileType', 1); INSERT INTO VocabularySet (Name, SystemMaintained) VALUES ('ModelProcessingActionStep.ActionMethod', 1); -INSERT INTO VocabularySet (Name, SystemMaintained) VALUES ('ModelUVMapChannel.UVMapType', 1); +INSERT INTO VocabularySet (Name, SystemMaintained) VALUES ('ModelMaterialChannel.MaterialType', 1); INSERT INTO VocabularySet (Name, SystemMaintained) VALUES ('Identifier.IdentifierType', 1); INSERT INTO VocabularySet (Name, SystemMaintained) VALUES ('Identifier.IdentifierTypeActor', 1); INSERT INTO VocabularySet (Name, SystemMaintained) VALUES ('Metadata.MetadataSource', 1); @@ -1113,7 +1113,8 @@ 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 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'); diff --git a/server/db/sql/scripts/Packrat.SCHEMA.sql b/server/db/sql/scripts/Packrat.SCHEMA.sql index f9f1dee64..14585cd5a 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, @@ -258,39 +259,83 @@ 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, `Authoritative` boolean NOT NULL, + `idVCreationMethod` int(11) NOT NULL, `idVModality` int(11) NOT NULL, `idVUnits` int(11) NOT NULL, `idVPurpose` int(11) NOT NULL, + `idVFileType` int(11) NOT NULL, `idAssetThumbnail` int(11) DEFAULT NULL, + `idModelMetrics` int(11) NULL, PRIMARY KEY (`idModel`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -CREATE TABLE IF NOT EXISTS `ModelGeometryFile` ( - `idModelGeometryFile` int(11) NOT NULL AUTO_INCREMENT, +CREATE TABLE IF NOT EXISTS `ModelMaterial` ( + `idModelMaterial` int(11) NOT NULL AUTO_INCREMENT, + `idModelObject` int(11) NOT NULL, + `Name` varchar(255) NULL, + PRIMARY KEY (`idModelMaterial`), + KEY `Model_idModelObject` (`idModelObject`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `ModelMaterialChannel` ( + `idModelMaterialChannel` int(11) NOT NULL AUTO_INCREMENT, + `idModelMaterial` int(11) NOT NULL, + `idVMaterialType` int(11) NULL, + `MaterialTypeOther` varchar(255) NULL, + `idModelMaterialUVMap` int(11) NULL, + `ChannelPosition` int(11) NULL, + `ChannelWidth` int(11) NULL, + `Scalar1` double DEFAULT NULL, + `Scalar2` double DEFAULT NULL, + `Scalar3` double DEFAULT NULL, + `Scalar4` double DEFAULT NULL, + PRIMARY KEY (`idModelMaterialChannel`), + KEY `ModelMaterialChannel_idModelMaterial` (`idModelMaterial`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `ModelMaterialUVMap` ( + `idModelMaterialUVMap` int(11) NOT NULL AUTO_INCREMENT, `idModel` int(11) NOT NULL, `idAsset` int(11) NOT NULL, - `idVModelFileType` int(11) NOT NULL, - `Roughness` double DEFAULT NULL, - `Metalness` double DEFAULT NULL, - `PointCount` int(11) DEFAULT NULL, - `FaceCount` int(11) DEFAULT NULL, - `IsWatertight` boolean DEFAULT NULL, - `HasNormals` boolean DEFAULT NULL, - `HasVertexColor` boolean DEFAULT NULL, - `HasUVSpace` boolean DEFAULT NULL, + `UVMapEdgeLength` int(11) NOT NULL, + PRIMARY KEY (`idModelMaterialUVMap`), + KEY `ModelUVMapFile_idModel` (`idModel`), + KEY `ModelUVMapFile_idAsset` (`idAsset`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `ModelMetrics` ( + `idModelMetrics` int(11) NOT NULL AUTO_INCREMENT, `BoundingBoxP1X` double DEFAULT NULL, `BoundingBoxP1Y` double DEFAULT NULL, `BoundingBoxP1Z` double DEFAULT NULL, `BoundingBoxP2X` double DEFAULT NULL, `BoundingBoxP2Y` double DEFAULT NULL, `BoundingBoxP2Z` double DEFAULT NULL, - PRIMARY KEY (`idModelGeometryFile`), - KEY `ModelGeometryFile_idModel` (`idModel`), - KEY `ModelGeometryFile_idAsset` (`idAsset`) + `CountPoint` int(11) DEFAULT NULL, + `CountFace` int(11) DEFAULT NULL, + `CountColorChannel` int(11) DEFAULT NULL, + `CountTextureCoorinateChannel` int(11) DEFAULT NULL, + `HasBones` boolean DEFAULT NULL, + `HasFaceNormals` boolean DEFAULT NULL, + `HasTangents` boolean DEFAULT NULL, + `HasTextureCoordinates` boolean DEFAULT NULL, + `HasVertexNormals` boolean DEFAULT NULL, + `HasVertexColor` boolean DEFAULT NULL, + `IsManifold` boolean DEFAULT NULL, + `IsWatertight` boolean DEFAULT NULL, + PRIMARY KEY (`idModelMetrics`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `ModelObject` ( + `idModelObject` int(11) NOT NULL AUTO_INCREMENT, + `idModel` int(11) NOT NULL, + `idModelMetrics` int(11) NULL, + PRIMARY KEY (`idModelObject`), + KEY `ModelObject_idModel` (`idModel`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE IF NOT EXISTS `ModelProcessingAction` ( @@ -329,26 +374,6 @@ CREATE TABLE IF NOT EXISTS `ModelSceneXref` ( KEY `ModelSceneXref_idScene` (`idScene`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -CREATE TABLE IF NOT EXISTS `ModelUVMapChannel` ( - `idModelUVMapChannel` int(11) NOT NULL AUTO_INCREMENT, - `idModelUVMapFile` int(11) NOT NULL, - `ChannelPosition` int(11) NOT NULL, - `ChannelWidth` int(11) NOT NULL, - `idVUVMapType` int(11) NOT NULL, - PRIMARY KEY (`idModelUVMapChannel`), - KEY `ModelUVMapChannel_idModelUVMapFile` (`idModelUVMapFile`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8; - -CREATE TABLE IF NOT EXISTS `ModelUVMapFile` ( - `idModelUVMapFile` int(11) NOT NULL AUTO_INCREMENT, - `idModelGeometryFile` int(11) NOT NULL, - `idAsset` int(11) NOT NULL, - `UVMapEdgeLength` int(11) NOT NULL, - PRIMARY KEY (`idModelUVMapFile`), - KEY `ModelUVMapFile_idModelGeometryFile` (`idModelGeometryFile`), - KEY `ModelUVMapFile_idAsset` (`idAsset`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8; - CREATE TABLE IF NOT EXISTS `Project` ( `idProject` int(11) NOT NULL AUTO_INCREMENT, `Name` varchar(128) NOT NULL, @@ -817,26 +842,67 @@ ADD CONSTRAINT `fk_model_vocabulary4` REFERENCES `Packrat`.`Vocabulary` (`idVocabulary`) ON DELETE NO ACTION ON UPDATE NO ACTION, +ADD CONSTRAINT `fk_model_vocabulary5` + FOREIGN KEY (`idVFileType`) + REFERENCES `Packrat`.`Vocabulary` (`idVocabulary`) + ON DELETE NO ACTION + ON UPDATE NO ACTION, ADD CONSTRAINT `fk_model_asset1` FOREIGN KEY (`idAssetThumbnail`) REFERENCES `Packrat`.`Asset` (`idAsset`) ON DELETE NO ACTION + ON UPDATE NO ACTION, +ADD CONSTRAINT `fk_model_modelmetrics1` + FOREIGN KEY (`idModelMetrics`) + REFERENCES `Packrat`.`ModelMetrics` (`idModelMetrics`) + ON DELETE NO ACTION ON UPDATE NO ACTION; -ALTER TABLE `Packrat`.`ModelGeometryFile` -ADD CONSTRAINT `fk_modelgeometryfile_model1` +ALTER TABLE `Packrat`.`ModelMaterial` +ADD CONSTRAINT `fk_modelmaterial_modelobject1` + FOREIGN KEY (`idModelObject`) + REFERENCES `Packrat`.`ModelObject` (`idModelObject`) + ON DELETE NO ACTION + ON UPDATE NO ACTION; + +ALTER TABLE `Packrat`.`ModelMaterialChannel` +ADD CONSTRAINT `fk_modelmaterialchannel_modelmaterial1` + FOREIGN KEY (`idModelMaterial`) + REFERENCES `Packrat`.`ModelMaterial` (`idModelMaterial`) + ON DELETE NO ACTION + ON UPDATE NO ACTION, +ADD CONSTRAINT `fk_modelmaterialchannel_modelmaterialuvmap1` + FOREIGN KEY (`idModelMaterialUVMap`) + REFERENCES `Packrat`.`ModelMaterialUVMap` (`idModelMaterialUVMap`) + ON DELETE NO ACTION + ON UPDATE NO ACTION, +ADD CONSTRAINT `fk_modelmaterialchannel_vocabulary1` + FOREIGN KEY (`idVMaterialType`) + REFERENCES `Packrat`.`Vocabulary` (`idVocabulary`) + ON DELETE NO ACTION + ON UPDATE NO ACTION; + +ALTER TABLE `Packrat`.`ModelMaterialUVMap` +ADD CONSTRAINT `fk_modelmaterialuvmap_model1` FOREIGN KEY (`idModel`) REFERENCES `Packrat`.`Model` (`idModel`) ON DELETE NO ACTION ON UPDATE NO ACTION, -ADD CONSTRAINT `fk_modelgeometryfile_asset1` +ADD CONSTRAINT `fk_modelmaterialuvmap_asset1` FOREIGN KEY (`idAsset`) REFERENCES `Packrat`.`Asset` (`idAsset`) ON DELETE NO ACTION + ON UPDATE NO ACTION; + +ALTER TABLE `Packrat`.`ModelObject` +ADD CONSTRAINT `fk_modelobject_model1` + FOREIGN KEY (`idModel`) + REFERENCES `Packrat`.`Model` (`idModel`) + ON DELETE NO ACTION ON UPDATE NO ACTION, -ADD CONSTRAINT `fk_modelgeometryfile_vocabulary1` - FOREIGN KEY (`idVModelFileType`) - REFERENCES `Packrat`.`Vocabulary` (`idVocabulary`) +ADD CONSTRAINT `fk_modelobject_modelmetrics1` + FOREIGN KEY (`idModelMetrics`) + REFERENCES `Packrat`.`ModelMetrics` (`idModelMetrics`) ON DELETE NO ACTION ON UPDATE NO ACTION; @@ -876,30 +942,6 @@ ADD CONSTRAINT `fk_modelscenexref_scene1` ON DELETE NO ACTION ON UPDATE NO ACTION; -ALTER TABLE `Packrat`.`ModelUVMapChannel` -ADD CONSTRAINT `fk_modeluvmapchannel_modeluvmapfile1` - FOREIGN KEY (`idModelUVMapFile`) - REFERENCES `Packrat`.`ModelUVMapFile` (`idModelUVMapFile`) - ON DELETE NO ACTION - ON UPDATE NO ACTION, -ADD CONSTRAINT `fk_modeluvmapchannel_vocabulary1` - FOREIGN KEY (`idVUVMapType`) - REFERENCES `Packrat`.`Vocabulary` (`idVocabulary`) - ON DELETE NO ACTION - ON UPDATE NO ACTION; - -ALTER TABLE `Packrat`.`ModelUVMapFile` -ADD CONSTRAINT `fk_modeluvmapfile_modelgeometryfile1` - FOREIGN KEY (`idModelGeometryFile`) - REFERENCES `Packrat`.`ModelGeometryFile` (`idModelGeometryFile`) - ON DELETE NO ACTION - ON UPDATE NO ACTION, -ADD CONSTRAINT `fk_modeluvmapfile_asset1` - FOREIGN KEY (`idAsset`) - REFERENCES `Packrat`.`Asset` (`idAsset`) - ON DELETE NO ACTION - ON UPDATE NO ACTION; - ALTER TABLE `Packrat`.`ProjectDocumentation` ADD CONSTRAINT `fk_projectdocumentation_project1` FOREIGN KEY (`idProject`) diff --git a/server/graphql/api/index.ts b/server/graphql/api/index.ts index 920b4e03f..0d7ad68b2 100644 --- a/server/graphql/api/index.ts +++ b/server/graphql/api/index.ts @@ -90,7 +90,20 @@ import { DiscardUploadedAssetVersionsInput, DiscardUploadedAssetVersionsResult, GetObjectChildrenInput, - GetObjectChildrenResult + GetObjectChildrenResult, + GetSourceObjectIdentiferInput, + GetSourceObjectIdentiferResult, + GetSystemObjectDetailsInput, + GetSystemObjectDetailsResult, + GetAssetDetailsForSystemObjectInput, + GetAssetDetailsForSystemObjectResult, + GetVersionsForSystemObjectInput, + GetVersionsForSystemObjectResult, + GetDetailsTabDataForObjectInput, + GetDetailsTabDataForObjectResult, + GetFilterViewDataResult, + UpdateObjectDetailsInput, + UpdateObjectDetailsResult } from '../../types/graphql'; // Queries @@ -123,6 +136,12 @@ 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'; +import getSystemObjectDetails from './queries/systemobject/getSystemObjectDetails'; +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'; @@ -139,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'; @@ -185,7 +205,14 @@ const allQueries = { getProjectDocumentation, getIntermediaryFile, discardUploadedAssetVersions, - getObjectChildren + getObjectChildren, + getSourceObjectIdentifer, + getSystemObjectDetails, + getAssetDetailsForSystemObject, + getVersionsForSystemObject, + getDetailsTabDataForObject, + getFilterViewData, + updateObjectDetails }; type GraphQLRequest = { @@ -516,6 +543,76 @@ class GraphQLApi { }); } + async getSourceObjectIdentifer(input: GetSourceObjectIdentiferInput, context?: Context): Promise { + const operationName = 'getSourceObjectIdentifer'; + const variables = { input }; + return this.graphqlRequest({ + operationName, + variables, + context + }); + } + + async getSystemObjectDetails(input: GetSystemObjectDetailsInput, context?: Context): Promise { + const operationName = 'getSystemObjectDetails'; + const variables = { input }; + return this.graphqlRequest({ + operationName, + variables, + context + }); + } + + 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 getDetailsTabDataForObject(input: GetDetailsTabDataForObjectInput, context?: Context): Promise { + const operationName = 'getDetailsTabDataForObject'; + const variables = { input }; + return this.graphqlRequest({ + operationName, + variables, + context + }); + } + + async getFilterViewData(context?: Context): Promise { + const operationName = 'getFilterViewData'; + const variables = {}; + return this.graphqlRequest({ + operationName, + variables, + context + }); + } + + 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/asset/getAssetVersionsDetails.ts b/server/graphql/api/queries/asset/getAssetVersionsDetails.ts index 3e769447e..1461491a6 100644 --- a/server/graphql/api/queries/asset/getAssetVersionsDetails.ts +++ b/server/graphql/api/queries/asset/getAssetVersionsDetails.ts @@ -50,15 +50,47 @@ const getAssetVersionsDetails = gql` } Model { idAssetVersion + systemCreated + master authoritative - dateCreated creationMethod modality purpose units - master + 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 + } + } } } } 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 { 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/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/getDetailsTabDataForObject.ts b/server/graphql/api/queries/systemobject/getDetailsTabDataForObject.ts new file mode 100644 index 000000000..10e4641ce --- /dev/null +++ b/server/graphql/api/queries/systemobject/getDetailsTabDataForObject.ts @@ -0,0 +1,131 @@ +import { gql } from 'apollo-server-express'; + +const getDetailsTabDataForObject = 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 + modelFileType + dateCaptured + uvMaps { + name + edgeLength + mapType + } + boundingBoxP1X + boundingBoxP1Y + boundingBoxP1Z + boundingBoxP2X + boundingBoxP2Y + boundingBoxP2Z + countPoint + countFace + countColorChannel + countTextureCoorinateChannel + hasBones + hasFaceNormals + hasTangents + hasTextureCoordinates + hasVertexNormals + hasVertexColor + isManifold + isWatertight + } + 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 + } + } + } +`; + +export default getDetailsTabDataForObject; 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/api/queries/systemobject/getSystemObjectDetails.ts b/server/graphql/api/queries/systemobject/getSystemObjectDetails.ts new file mode 100644 index 000000000..e4fde0009 --- /dev/null +++ b/server/graphql/api/queries/systemobject/getSystemObjectDetails.ts @@ -0,0 +1,58 @@ +import { gql } from 'apollo-server-express'; + +const getSystemObjectDetails = 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 + } + } + } +`; + +export default getSystemObjectDetails; 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 d5328340f..59502cd2f 100644 --- a/server/graphql/schema.graphql +++ b/server/graphql/schema.graphql @@ -2,11 +2,14 @@ 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! @@ -19,11 +22,14 @@ 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! + 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! @@ -99,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! } @@ -161,16 +168,79 @@ type IngestPhotogrammetry { identifiers: [IngestIdentifier!]! } +type IngestUVMap { + name: String! + edgeLength: Int! + mapType: Int! +} + +enum RelatedObjectType { + Source + Derived +} + +type RelatedObject { + idSystemObject: Int! + name: String! + identifier: String + objectType: 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!]! + 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 +} + +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 { @@ -180,6 +250,7 @@ type GetAssetVersionDetailResult { Item: Item CaptureDataPhoto: IngestPhotogrammetry Model: IngestModel + Scene: IngestScene } type GetAssetVersionsDetailsResult { @@ -218,6 +289,7 @@ type Asset { FileName: String! FilePath: String! idAssetGroup: Int + idVAssetType: Int idSystemObject: Int StorageKey: String AssetGroup: AssetGroup @@ -249,6 +321,7 @@ type AssetGroup { } input CreateCaptureDataInput { + Name: String! idVCaptureMethod: Int! DateCaptured: DateTime! Description: String! @@ -304,6 +377,7 @@ type CaptureData { VCaptureMethod: Vocabulary CaptureDataFile: [CaptureDataFile] CaptureDataGroup: [CaptureDataGroup] + CaptureDataPhoto: [CaptureDataPhoto] SystemObject: SystemObject } @@ -395,16 +469,75 @@ input IngestPhotogrammetryInput { identifiers: [IngestIdentifierInput!]! } +input IngestUVMapInput { + name: String! + edgeLength: Int! + mapType: Int! +} + +input RelatedObjectInput { + idSystemObject: Int! + name: String! + identifier: String + objectType: 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!]! + 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! +} + +input IngestSceneInput { + idAssetVersion: Int! + systemCreated: Boolean! + identifiers: [IngestIdentifierInput!]! + referenceModels: [ReferenceModelInput!]! +} + +input IngestOtherInput { + idAssetVersion: Int! + systemCreated: Boolean! + identifiers: [IngestIdentifierInput!]! } input IngestDataInput { @@ -412,6 +545,9 @@ input IngestDataInput { project: IngestProjectInput! item: IngestItemInput! photogrammetry: [IngestPhotogrammetryInput!]! + model: [IngestModelInput!]! + scene: [IngestSceneInput!]! + other: [IngestOtherInput!]! } type IngestDataResult { @@ -454,11 +590,13 @@ type LicenseAssignment { } input CreateModelInput { + Name: String! Authoritative: Boolean! idVCreationMethod: Int! idVModality: Int! idVPurpose: Int! idVUnits: Int! + idVFileType: Int! Master: Boolean! idAssetThumbnail: Int } @@ -477,48 +615,92 @@ type GetModelResult { type Model { idModel: Int! - Authoritative: Boolean! + Name: String! DateCreated: DateTime! - idAssetThumbnail: Int + Master: Boolean! + Authoritative: Boolean! idVCreationMethod: Int! idVModality: Int! idVPurpose: Int! idVUnits: Int! - Master: Boolean! - AssetThumbnail: Asset + idVFileType: Int! + idAssetThumbnail: Int + idModelMetrics: Int + ModelConstellation: ModelConstellation VCreationMethod: Vocabulary VModality: Vocabulary VPurpose: Vocabulary VUnits: Vocabulary - ModelGeometryFile: [ModelGeometryFile] + VFileType: Vocabulary + AssetThumbnail: Asset + ModelMetrics: ModelMetrics + ModelObject: [ModelObject] ModelProcessingAction: [ModelProcessingAction] ModelSceneXref: [ModelSceneXref] SystemObject: SystemObject } -type ModelGeometryFile { - idModelGeometryFile: Int! - idAsset: Int! +type ModelMaterial { + idModelMaterial: Int! + idModelObject: Int! + Name: String + ModelObject: ModelObject! +} + +type ModelMaterialChannel { + idModelMaterialChannel: Int! + idModelMaterial: Int! + idVMaterialType: Int + MaterialTypeOther: String + idModelMaterialUVMap: Int + ChannelPosition: Int + ChannelWidth: Int + Scalar1: Float + Scalar2: Float + Scalar3: Float + Scalar4: Float + ModelMaterial: ModelMaterial! + VMaterialType: Vocabulary + ModelMaterialUVMap: ModelMaterialUVMap +} + +type ModelMaterialUVMap { + idModelMaterialUVMap: Int! idModel: Int! - idVModelFileType: Int! + idAsset: Int! + UVMapEdgeLength: Int! + Model: Model! + Asset: Asset! +} + +type ModelMetrics { + idModelMetrics: Int! BoundingBoxP1X: Float BoundingBoxP1Y: Float BoundingBoxP1Z: Float BoundingBoxP2X: Float BoundingBoxP2Y: Float BoundingBoxP2Z: Float - FaceCount: Int - HasNormals: Boolean - HasUVSpace: Boolean + CountPoint: Int + CountFace: Int + CountColorChannel: Int + CountTextureCoorinateChannel: Int + HasBones: Boolean + HasFaceNormals: Boolean + HasTangents: Boolean + HasTextureCoordinates: Boolean + HasVertexNormals: Boolean HasVertexColor: Boolean + IsManifold: Boolean IsWatertight: Boolean - Metalness: Float - PointCount: Int - Roughness: Float - Asset: Asset - Model: Model - VModelFileType: Vocabulary - ModelUVMapFile: [ModelUVMapFile] +} + +type ModelObject { + idModelObject: Int! + idModel: Int! + idModelMetrics: Int + Model: Model! + ModelMetrics: ModelMetrics } type ModelProcessingAction { @@ -557,24 +739,14 @@ type ModelSceneXref { Scene: Scene } -type ModelUVMapChannel { - 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] +type ModelConstellation { + Model: Model! + ModelObjects: [ModelObject] + ModelMaterials: [ModelMaterial] + ModelMaterialChannels: [ModelMaterialChannel] + ModelMaterialUVMaps: [ModelMaterialUVMap] + ModelMetric: ModelMetrics + ModelObjectMetrics: [ModelMetrics] } input PaginationInput { @@ -587,7 +759,17 @@ input PaginationInput { 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!]! } type NavigationResultEntry { @@ -605,6 +787,11 @@ type GetObjectChildrenResult { metadataColumns: [Int!]! } +type GetFilterViewDataResult { + units: [Unit!]! + projects: [Project!]! +} + input CreateSceneInput { Name: String! HasBeenQCd: Boolean! @@ -660,6 +847,379 @@ type IntermediaryFile { SystemObject: SystemObject } +input UpdateObjectDetailsInput { + idSystemObject: Int! + idObject: 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: 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 +} + +input SceneDetailFieldsInput { + Links: [String!]! + AssetType: Int + Tours: Int + Annotation: Int + HasBeenQCd: Boolean + IsOriented: Boolean +} + +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 { + 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! +} + +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!]! + boundingBoxP1X: Float + boundingBoxP1Y: Float + boundingBoxP1Z: Float + boundingBoxP2X: Float + boundingBoxP2Y: Float + boundingBoxP2Z: Float + countPoint: Int + countFace: Int + countColorChannel: Int + countTextureCoorinateChannel: Int + hasBones: Boolean + hasFaceNormals: Boolean + hasTangents: Boolean + hasTextureCoordinates: Boolean + hasVertexNormals: Boolean + hasVertexColor: Boolean + isManifold: Boolean + isWatertight: Boolean +} + +type SceneDetailFields { + Links: [String!]! + AssetType: Int + Tours: Int + Annotation: Int + HasBeenQCd: Boolean + IsOriented: Boolean +} + +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: 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! +} + +type RepositoryPath { + 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 +} + +input GetSourceObjectIdentiferInput { + idSystemObjects: [Int!]! +} + +type SourceObjectIdentifier { + idSystemObject: Int! + identifier: String +} + +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/asset/queries.graphql b/server/graphql/schema/asset/queries.graphql index 29e04d7cb..e06acaa2a 100644 --- a/server/graphql/schema/asset/queries.graphql +++ b/server/graphql/schema/asset/queries.graphql @@ -40,16 +40,79 @@ type IngestPhotogrammetry { identifiers: [IngestIdentifier!]! } +type IngestUVMap { + name: String! + edgeLength: Int! + mapType: Int! +} + +enum RelatedObjectType { + Source + Derived +} + +type RelatedObject { + idSystemObject: Int! + name: String! + identifier: String + objectType: 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!]! + 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 +} + +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 { @@ -59,6 +122,7 @@ type GetAssetVersionDetailResult { Item: Item CaptureDataPhoto: IngestPhotogrammetry Model: IngestModel + Scene: IngestScene } type GetAssetVersionsDetailsResult { 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/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/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/graphql/schema/ingestion/mutations.graphql b/server/graphql/schema/ingestion/mutations.graphql index 70819cf0e..899c25ebe 100644 --- a/server/graphql/schema/ingestion/mutations.graphql +++ b/server/graphql/schema/ingestion/mutations.graphql @@ -51,16 +51,75 @@ input IngestPhotogrammetryInput { identifiers: [IngestIdentifierInput!]! } +input IngestUVMapInput { + name: String! + edgeLength: Int! + mapType: Int! +} + +input RelatedObjectInput { + idSystemObject: Int! + name: String! + identifier: String + objectType: 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!]! + 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! +} + +input IngestSceneInput { + idAssetVersion: Int! + systemCreated: Boolean! + identifiers: [IngestIdentifierInput!]! + referenceModels: [ReferenceModelInput!]! +} + +input IngestOtherInput { + idAssetVersion: Int! + systemCreated: Boolean! + identifiers: [IngestIdentifierInput!]! } input IngestDataInput { @@ -68,6 +127,9 @@ input IngestDataInput { project: IngestProjectInput! item: IngestItemInput! photogrammetry: [IngestPhotogrammetryInput!]! + model: [IngestModelInput!]! + scene: [IngestSceneInput!]! + other: [IngestOtherInput!]! } type IngestDataResult { 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/graphql/schema/model/mutations.graphql b/server/graphql/schema/model/mutations.graphql index 3348e4e66..2e3fdfaf5 100644 --- a/server/graphql/schema/model/mutations.graphql +++ b/server/graphql/schema/model/mutations.graphql @@ -5,11 +5,13 @@ type Mutation { } input CreateModelInput { + Name: String! Authoritative: Boolean! idVCreationMethod: Int! idVModality: Int! idVPurpose: Int! idVUnits: Int! + idVFileType: Int! Master: Boolean! idAssetThumbnail: Int } diff --git a/server/graphql/schema/model/resolvers/index.ts b/server/graphql/schema/model/resolvers/index.ts index a06835b89..dd33fea59 100644 --- a/server/graphql/schema/model/resolvers/index.ts +++ b/server/graphql/schema/model/resolvers/index.ts @@ -1,9 +1,10 @@ import Model from './types/Model'; -import ModelGeometryFile from './types/ModelGeometryFile'; +import ModelMaterial from './types/ModelMaterial'; +import ModelMaterialChannel from './types/ModelMaterialChannel'; +import ModelMaterialUVMap from './types/ModelMaterialUVMap'; +import ModelObject from './types/ModelObject'; import ModelProcessingAction from './types/ModelProcessingAction'; import ModelProcessingActionStep from './types/ModelProcessingActionStep'; -import ModelUVMapFile from './types/ModelUVMapFile'; -import ModelUVMapChannel from './types/ModelUVMapChannel'; import getModel from './queries/getModel'; import createModel from './mutations/createModel'; @@ -15,11 +16,12 @@ const resolvers = { createModel }, Model, - ModelGeometryFile, + ModelMaterial, + ModelMaterialChannel, + ModelMaterialUVMap, + ModelObject, ModelProcessingAction, ModelProcessingActionStep, - ModelUVMapFile, - ModelUVMapChannel }; export default resolvers; diff --git a/server/graphql/schema/model/resolvers/mutations/createModel.ts b/server/graphql/schema/model/resolvers/mutations/createModel.ts index 1bec325aa..148b2d848 100644 --- a/server/graphql/schema/model/resolvers/mutations/createModel.ts +++ b/server/graphql/schema/model/resolvers/mutations/createModel.ts @@ -4,18 +4,21 @@ 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, idVFileType, Master, idAssetThumbnail } = input; const modelArgs = { idModel: 0, + Name, Authoritative, idVCreationMethod, idVModality, idVPurpose, idVUnits, + idVFileType, Master, idAssetThumbnail: idAssetThumbnail || null, - DateCreated: new Date() + DateCreated: new Date(), + idModelMetrics: 0 }; const Model = new DBAPI.Model(modelArgs); diff --git a/server/graphql/schema/model/resolvers/types/Model.ts b/server/graphql/schema/model/resolvers/types/Model.ts index f06e2e9fa..9b354e6f5 100644 --- a/server/graphql/schema/model/resolvers/types/Model.ts +++ b/server/graphql/schema/model/resolvers/types/Model.ts @@ -5,8 +5,8 @@ import { Parent } from '../../../../../types/resolvers'; import * as DBAPI from '../../../../../db'; const Model = { - AssetThumbnail: async (parent: Parent): Promise => { - return await DBAPI.Asset.fetch(parent.idAssetThumbnail); + ModelConstellation: async (parent: Parent): Promise => { + return await DBAPI.ModelConstellation.fetch(parent.idModel); }, VCreationMethod: async (parent: Parent): Promise => { return await DBAPI.Vocabulary.fetch(parent.idVCreationMethod); @@ -20,8 +20,17 @@ const Model = { VUnits: async (parent: Parent): Promise => { return await DBAPI.Vocabulary.fetch(parent.idVUnits); }, - ModelGeometryFile: async (parent: Parent): Promise => { - return await DBAPI.ModelGeometryFile.fetchFromModel(parent.idModel); + VFileType: async (parent: Parent): Promise => { + return await DBAPI.Vocabulary.fetch(parent.idVFileType); + }, + AssetThumbnail: async (parent: Parent): Promise => { + return await DBAPI.Asset.fetch(parent.idAssetThumbnail); + }, + ModelMetrics: async (parent: Parent): Promise => { + return await DBAPI.ModelMetrics.fetch(parent.idModelMetrics); + }, + ModelObject: async (parent: Parent): Promise => { + return await DBAPI.ModelObject.fetchFromModel(parent.idModel); }, ModelProcessingAction: async (parent: Parent): Promise => { return await DBAPI.ModelProcessingAction.fetchFromModel(parent.idModel); diff --git a/server/graphql/schema/model/resolvers/types/ModelGeometryFile.ts b/server/graphql/schema/model/resolvers/types/ModelGeometryFile.ts deleted file mode 100644 index ff45c08c9..000000000 --- a/server/graphql/schema/model/resolvers/types/ModelGeometryFile.ts +++ /dev/null @@ -1,22 +0,0 @@ -/** - * Type resolver for ModelGeometryFile - */ -import { Parent } from '../../../../../types/resolvers'; -import * as DBAPI from '../../../../../db'; - -const ModelGeometryFile = { - Asset: async (parent: Parent): Promise => { - return await DBAPI.Asset.fetch(parent.idAsset); - }, - Model: async (parent: Parent): Promise => { - return await DBAPI.Model.fetch(parent.idModel); - }, - VModelFileType: async (parent: Parent): Promise => { - return await DBAPI.Vocabulary.fetch(parent.idVModelFileType); - }, - ModelUVMapFile: async (parent: Parent): Promise => { - return await DBAPI.ModelUVMapFile.fetchFromModelGeometryFile(parent.idModelGeometryFile); - } -}; - -export default ModelGeometryFile; diff --git a/server/graphql/schema/model/resolvers/types/ModelMaterial.ts b/server/graphql/schema/model/resolvers/types/ModelMaterial.ts new file mode 100644 index 000000000..6a19f79c7 --- /dev/null +++ b/server/graphql/schema/model/resolvers/types/ModelMaterial.ts @@ -0,0 +1,13 @@ +/** + * Type resolver for ModelMaterial + */ +import { Parent } from '../../../../../types/resolvers'; +import * as DBAPI from '../../../../../db'; + +const ModelMaterial = { + ModelObject: async (parent: Parent): Promise => { + return await DBAPI.ModelObject.fetch(parent.idModelObject); + } +}; + +export default ModelMaterial; diff --git a/server/graphql/schema/model/resolvers/types/ModelMaterialChannel.ts b/server/graphql/schema/model/resolvers/types/ModelMaterialChannel.ts new file mode 100644 index 000000000..bf157dda2 --- /dev/null +++ b/server/graphql/schema/model/resolvers/types/ModelMaterialChannel.ts @@ -0,0 +1,19 @@ +/** + * Type resolver for ModelMaterialChannel + */ +import { Parent } from '../../../../../types/resolvers'; +import * as DBAPI from '../../../../../db'; + +const ModelMaterialChannel = { + ModelMaterial: async (parent: Parent): Promise => { + return await DBAPI.ModelMaterial.fetch(parent.idModelMaterial); + }, + VMaterialType: async (parent: Parent): Promise => { + return await DBAPI.Vocabulary.fetch(parent.idVMaterialType); + }, + ModelMaterialUVMap: async (parent: Parent): Promise => { + return await DBAPI.ModelMaterialUVMap.fetch(parent.idModelMaterialUVMap); + }, +}; + +export default ModelMaterialChannel; diff --git a/server/graphql/schema/model/resolvers/types/ModelMaterialUVMap.ts b/server/graphql/schema/model/resolvers/types/ModelMaterialUVMap.ts new file mode 100644 index 000000000..a60f91315 --- /dev/null +++ b/server/graphql/schema/model/resolvers/types/ModelMaterialUVMap.ts @@ -0,0 +1,16 @@ +/** + * Type resolver for ModelMaterialUVMap + */ +import { Parent } from '../../../../../types/resolvers'; +import * as DBAPI from '../../../../../db'; + +const ModelMaterialUVMap = { + Model: async (parent: Parent): Promise => { + return await DBAPI.Model.fetch(parent.idModel); + }, + Asset: async (parent: Parent): Promise => { + return await DBAPI.Asset.fetch(parent.idAsset); + }, +}; + +export default ModelMaterialUVMap; \ No newline at end of file diff --git a/server/graphql/schema/model/resolvers/types/ModelObject.ts b/server/graphql/schema/model/resolvers/types/ModelObject.ts new file mode 100644 index 000000000..228e83b11 --- /dev/null +++ b/server/graphql/schema/model/resolvers/types/ModelObject.ts @@ -0,0 +1,16 @@ +/** + * Type resolver for ModelObject + */ +import { Parent } from '../../../../../types/resolvers'; +import * as DBAPI from '../../../../../db'; + +const ModelObject = { + Model: async (parent: Parent): Promise => { + return await DBAPI.Model.fetch(parent.idModel); + }, + ModelMetrics: async (parent: Parent): Promise => { + return await DBAPI.ModelMetrics.fetch(parent.idModelMetrics); + }, +}; + +export default ModelObject; \ No newline at end of file diff --git a/server/graphql/schema/model/resolvers/types/ModelUVMapChannel.ts b/server/graphql/schema/model/resolvers/types/ModelUVMapChannel.ts deleted file mode 100644 index bf39184c5..000000000 --- a/server/graphql/schema/model/resolvers/types/ModelUVMapChannel.ts +++ /dev/null @@ -1,16 +0,0 @@ -/** - * Type resolver for ModelUVMapChannel - */ -import { Parent } from '../../../../../types/resolvers'; -import * as DBAPI from '../../../../../db'; - -const ModelUVMapChannel = { - ModelUVMapFile: async (parent: Parent): Promise => { - return await DBAPI.ModelUVMapFile.fetch(parent.idModelUVMapFile); - }, - VUVMapType: async (parent: Parent): Promise => { - return await DBAPI.Vocabulary.fetch(parent.idVUVMapType); - } -}; - -export default ModelUVMapChannel; diff --git a/server/graphql/schema/model/resolvers/types/ModelUVMapFile.ts b/server/graphql/schema/model/resolvers/types/ModelUVMapFile.ts deleted file mode 100644 index ce26077c8..000000000 --- a/server/graphql/schema/model/resolvers/types/ModelUVMapFile.ts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Type resolver for ModelUVMapFile - */ -import { Parent } from '../../../../../types/resolvers'; -import * as DBAPI from '../../../../../db'; - -const ModelUVMapFile = { - Asset: async (parent: Parent): Promise => { - return await DBAPI.Asset.fetch(parent.idAsset); - }, - ModelGeometryFile: async (parent: Parent): Promise => { - return await DBAPI.ModelGeometryFile.fetch(parent.idModelGeometryFile); - }, - ModelUVMapChannel: async (parent: Parent): Promise => { - return await DBAPI.ModelUVMapChannel.fetchFromModelUVMapFile(parent.idModelUVMapFile); - } -}; - -export default ModelUVMapFile; diff --git a/server/graphql/schema/model/types.graphql b/server/graphql/schema/model/types.graphql index c391d3322..c09fb9a95 100644 --- a/server/graphql/schema/model/types.graphql +++ b/server/graphql/schema/model/types.graphql @@ -2,48 +2,92 @@ scalar DateTime type Model { idModel: Int! - Authoritative: Boolean! + Name: String! DateCreated: DateTime! - idAssetThumbnail: Int + Master: Boolean! + Authoritative: Boolean! idVCreationMethod: Int! idVModality: Int! idVPurpose: Int! idVUnits: Int! - Master: Boolean! - AssetThumbnail: Asset + idVFileType: Int! + idAssetThumbnail: Int + idModelMetrics: Int + ModelConstellation: ModelConstellation VCreationMethod: Vocabulary VModality: Vocabulary VPurpose: Vocabulary VUnits: Vocabulary - ModelGeometryFile: [ModelGeometryFile] + VFileType: Vocabulary + AssetThumbnail: Asset + ModelMetrics: ModelMetrics + ModelObject: [ModelObject] ModelProcessingAction: [ModelProcessingAction] ModelSceneXref: [ModelSceneXref] SystemObject: SystemObject } -type ModelGeometryFile { - idModelGeometryFile: Int! - idAsset: Int! +type ModelMaterial { + idModelMaterial: Int! + idModelObject: Int! + Name: String + ModelObject: ModelObject! +} + +type ModelMaterialChannel { + idModelMaterialChannel: Int! + idModelMaterial: Int! + idVMaterialType: Int + MaterialTypeOther: String + idModelMaterialUVMap: Int + ChannelPosition: Int + ChannelWidth: Int + Scalar1: Float + Scalar2: Float + Scalar3: Float + Scalar4: Float + ModelMaterial: ModelMaterial! + VMaterialType: Vocabulary + ModelMaterialUVMap: ModelMaterialUVMap +} + +type ModelMaterialUVMap { + idModelMaterialUVMap: Int! idModel: Int! - idVModelFileType: Int! + idAsset: Int! + UVMapEdgeLength: Int! + Model: Model! + Asset: Asset! +} + +type ModelMetrics { + idModelMetrics: Int! BoundingBoxP1X: Float BoundingBoxP1Y: Float BoundingBoxP1Z: Float BoundingBoxP2X: Float BoundingBoxP2Y: Float BoundingBoxP2Z: Float - FaceCount: Int - HasNormals: Boolean - HasUVSpace: Boolean + CountPoint: Int + CountFace: Int + CountColorChannel: Int + CountTextureCoorinateChannel: Int + HasBones: Boolean + HasFaceNormals: Boolean + HasTangents: Boolean + HasTextureCoordinates: Boolean + HasVertexNormals: Boolean HasVertexColor: Boolean + IsManifold: Boolean IsWatertight: Boolean - Metalness: Float - PointCount: Int - Roughness: Float - Asset: Asset - Model: Model - VModelFileType: Vocabulary - ModelUVMapFile: [ModelUVMapFile] +} + +type ModelObject { + idModelObject: Int! + idModel: Int! + idModelMetrics: Int + Model: Model! + ModelMetrics: ModelMetrics } type ModelProcessingAction { @@ -82,22 +126,12 @@ type ModelSceneXref { Scene: Scene } -type ModelUVMapChannel { - 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] -} +type ModelConstellation { + Model: Model! + ModelObjects: [ModelObject] + ModelMaterials: [ModelMaterial] + ModelMaterialChannels: [ModelMaterialChannel] + ModelMaterialUVMaps: [ModelMaterialUVMap] + ModelMetric: ModelMetrics + ModelObjectMetrics: [ModelMetrics] +} \ No newline at end of file diff --git a/server/graphql/schema/repository/queries.graphql b/server/graphql/schema/repository/queries.graphql index 753b75ec7..8572da69c 100644 --- a/server/graphql/schema/repository/queries.graphql +++ b/server/graphql/schema/repository/queries.graphql @@ -1,11 +1,22 @@ type Query { getObjectChildren(input: GetObjectChildrenInput!): GetObjectChildrenResult! + getFilterViewData: GetFilterViewDataResult! } 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!]! } type NavigationResultEntry { @@ -22,3 +33,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/graphql/schema/repository/resolvers/queries/getObjectChildren.ts b/server/graphql/schema/repository/resolvers/queries/getObjectChildren.ts index d1cd036f1..25e58168e 100644 --- a/server/graphql/schema/repository/resolvers/queries/getObjectChildren.ts +++ b/server/graphql/schema/repository/resolvers/queries/getObjectChildren.ts @@ -4,7 +4,21 @@ import { NavigationFactory, INavigation, NavigationFilter, NavigationResult } fr import * as LOG from '../../../../../utils/logger'; export default async function getObjectChildren(_: Parent, args: QueryGetObjectChildrenArgs): Promise { - const { idRoot, objectTypes, metadataColumns } = args.input; + const { + idRoot, + objectTypes, + metadataColumns, + search, + objectsToDisplay, + units, + projects, + has, + missing, + captureMethod, + variantType, + modelPurpose, + modelFileType, + } = args.input; const navigation: INavigation | null = await NavigationFactory.getInstance(); if (!navigation) { @@ -22,7 +36,19 @@ export default async function getObjectChildren(_: Parent, args: QueryGetObjectC const filter: NavigationFilter = { idRoot, objectTypes, - metadataColumns + metadataColumns, + search, + objectsToDisplay, + units, + projects, + has, + missing, + captureMethod, + variantType, + modelPurpose, + modelFileType, + rows: 100, + cursorMark: '' }; const result: NavigationResult = await navigation.getObjectChildren(filter); diff --git a/server/graphql/schema/systemobject/mutations.graphql b/server/graphql/schema/systemobject/mutations.graphql new file mode 100644 index 000000000..7bc6fe5ff --- /dev/null +++ b/server/graphql/schema/systemobject/mutations.graphql @@ -0,0 +1,152 @@ +scalar DateTime + +type Mutation { + updateObjectDetails(input: UpdateObjectDetailsInput!): UpdateObjectDetailsResult! +} + +input UpdateObjectDetailsInput { + idSystemObject: Int! + idObject: 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: 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 +} + +input SceneDetailFieldsInput { + Links: [String!]! + AssetType: Int + Tours: Int + Annotation: Int + HasBeenQCd: Boolean + IsOriented: Boolean +} + +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 { + 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! +} diff --git a/server/graphql/schema/systemobject/queries.graphql b/server/graphql/schema/systemobject/queries.graphql new file mode 100644 index 000000000..ec1315a32 --- /dev/null +++ b/server/graphql/schema/systemobject/queries.graphql @@ -0,0 +1,233 @@ +scalar DateTime + +type Query { + getSystemObjectDetails(input: GetSystemObjectDetailsInput!): GetSystemObjectDetailsResult! + getSourceObjectIdentifer(input: GetSourceObjectIdentiferInput!): GetSourceObjectIdentiferResult! + getAssetDetailsForSystemObject(input: GetAssetDetailsForSystemObjectInput!): GetAssetDetailsForSystemObjectResult! + getVersionsForSystemObject(input: GetVersionsForSystemObjectInput!): GetVersionsForSystemObjectResult! + getDetailsTabDataForObject(input: GetDetailsTabDataForObjectInput!): GetDetailsTabDataForObjectResult! +} + +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!]! + boundingBoxP1X: Float + boundingBoxP1Y: Float + boundingBoxP1Z: Float + boundingBoxP2X: Float + boundingBoxP2Y: Float + boundingBoxP2Z: Float + countPoint: Int + countFace: Int + countColorChannel: Int + countTextureCoorinateChannel: Int + hasBones: Boolean + hasFaceNormals: Boolean + hasTangents: Boolean + hasTextureCoordinates: Boolean + hasVertexNormals: Boolean + hasVertexColor: Boolean + isManifold: Boolean + isWatertight: Boolean +} + +type SceneDetailFields { + Links: [String!]! + AssetType: Int + Tours: Int + Annotation: Int + HasBeenQCd: Boolean + IsOriented: Boolean +} + +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: 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! +} + +type RepositoryPath { + 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 +} + +input GetSourceObjectIdentiferInput { + idSystemObjects: [Int!]! +} + +type SourceObjectIdentifier { + idSystemObject: Int! + identifier: String +} + +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 ae5023938..1564079a0 100644 --- a/server/graphql/schema/systemobject/resolvers/index.ts +++ b/server/graphql/schema/systemobject/resolvers/index.ts @@ -2,8 +2,24 @@ 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'; +import getAssetDetailsForSystemObject from './queries/getAssetDetailsForSystemObject'; +import getVersionsForSystemObject from './queries/getVersionsForSystemObject'; +import getDetailsTabDataForObject from './queries/getDetailsTabDataForObject'; +import updateObjectDetails from './mutations/updateObjectDetails'; const resolvers = { + Query: { + getSystemObjectDetails, + getSourceObjectIdentifer, + getAssetDetailsForSystemObject, + 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..160a12a37 --- /dev/null +++ b/server/graphql/schema/systemobject/resolvers/mutations/updateObjectDetails.ts @@ -0,0 +1,383 @@ +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 { idSystemObject, idObject, objectType, data } = input; + + 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); + /** + * TODO: KARAN: add an error property and handle errors + */ + if (SO) { + if (data.Retired) { + await SO.retireObject(); + } else { + await SO.reinstateObject(); + } + } + + switch (objectType) { + case eSystemObjectType.eUnit: { + const Unit = await DBAPI.Unit.fetch(idObject); + + 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(); + } + break; + } + case eSystemObjectType.eProject: { + const Project = await DBAPI.Project.fetch(idObject); + + if (Project) { + Project.Name = data.Name; + if (data.Project) { + const { Description } = data.Project; + Project.Description = maybe(Description); + } + + await Project.update(); + } + break; + } + 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: { + 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 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: 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; + if (modelFileType) Model.idVFileType = modelFileType; + 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; + } + } + + /* + // TODO: do we want to update the asset name and metrics? I don't think so... + const modelMetrics = await DBAPI.ModelMetrics.fetch(Model.idModelMetrics); + if (modelMetrics) { + const Asset = await DBAPI.Asset.fetch(MGF.idAsset); + if (Asset) { + Asset.FileName = data.Name; + await Asset.update(); + } + + 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: { + 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: { + 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: { + const ProjectDocumentation = await DBAPI.ProjectDocumentation.fetch(idObject); + + if (ProjectDocumentation) { + ProjectDocumentation.Name = data.Name; + + if (data.ProjectDocumentation) { + const { Description } = data.ProjectDocumentation; + if (Description) ProjectDocumentation.Description = Description; + } + + await ProjectDocumentation.update(); + } + break; + } + case eSystemObjectType.eAsset: { + const Asset = await DBAPI.Asset.fetch(idObject); + + 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(); + } + break; + } + case eSystemObjectType.eAssetVersion: { + 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: { + 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(); + } + break; + } + case eSystemObjectType.eStakeholder: { + const Stakeholder = await DBAPI.Stakeholder.fetch(idObject); + + 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(); + } + break; + } + default: + break; + } + + return { success: true }; +} 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/getDetailsTabDataForObject.ts b/server/graphql/schema/systemobject/resolvers/queries/getDetailsTabDataForObject.ts new file mode 100644 index 000000000..e6d29ed81 --- /dev/null +++ b/server/graphql/schema/systemobject/resolvers/queries/getDetailsTabDataForObject.ts @@ -0,0 +1,253 @@ +import * as DBAPI from '../../../../../db'; +import { eSystemObjectType } from '../../../../../db'; +import { + AssetVersionDetailFields, + AssetDetailFields, + SubjectDetailFields, + ItemDetailFields, + GetDetailsTabDataForObjectResult, + QueryGetDetailsTabDataForObjectArgs, + CaptureDataDetailFields, + ModelDetailFields, + SceneDetailFields +} 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) { + 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) { + 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: + if (systemObject?.idCaptureData) { + result.CaptureData = await getCaptureDataDetailFields(systemObject.idCaptureData); + } + break; + case eSystemObjectType.eModel: + if (systemObject?.idModel) { + result.Model = await getModelDetailFields(systemObject.idModel); + } + break; + case eSystemObjectType.eScene: + 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); + break; + case eSystemObjectType.eProjectDocumentation: + if (systemObject?.idProjectDocumentation) result.ProjectDocumentation = await DBAPI.ProjectDocumentation.fetch(systemObject.idProjectDocumentation); + break; + 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) { + 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; + case eSystemObjectType.eStakeholder: + if (systemObject?.idStakeholder) result.Stakeholder = await DBAPI.Stakeholder.fetch(systemObject.idStakeholder); + break; + default: + break; + } + + 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 modelConstellation = await DBAPI.ModelConstellation.fetch(idModel); + if (!modelConstellation) + return fields; + + const model = modelConstellation.model; + fields = { + ...fields, + master: model?.Master, + authoritative: model?.Authoritative, + creationMethod: model?.idVCreationMethod, + modality: model?.idVModality, + purpose: model?.idVPurpose, + units: model?.idVUnits, + dateCaptured: model?.DateCreated.toISOString(), + modelFileType: model?.idVFileType, + }; + + // TODO: fetch all assets associated with Model and ModelMaterialUVMap's; add up storage size + if (model?.idAssetThumbnail) { + const AssetVersion = await DBAPI.AssetVersion.fetchFromAsset(model.idAssetThumbnail); + if (AssetVersion && AssetVersion[0]) { + const [AV] = AssetVersion; + fields = { + ...fields, + size: AV.StorageSize + }; + } + } + + // TODO: fetch Material Channels, etc. + const modelMetrics = modelConstellation.modelMetric; + if (modelMetrics) { + fields = { + ...fields, + boundingBoxP1X: modelMetrics.BoundingBoxP1X, + boundingBoxP1Y: modelMetrics.BoundingBoxP1Y, + boundingBoxP1Z: modelMetrics.BoundingBoxP1Z, + boundingBoxP2X: modelMetrics.BoundingBoxP2X, + boundingBoxP2Y: modelMetrics.BoundingBoxP2Y, + boundingBoxP2Z: modelMetrics.BoundingBoxP2Z, + countPoint: modelMetrics.CountPoint, + countFace: modelMetrics.CountFace, + countColorChannel: modelMetrics.CountColorChannel, + countTextureCoorinateChannel: modelMetrics.CountTextureCoorinateChannel, + hasBones: modelMetrics.HasBones, + hasFaceNormals: modelMetrics.HasFaceNormals, + hasTangents: modelMetrics.HasTangents, + hasTextureCoordinates: modelMetrics.HasTextureCoordinates, + hasVertexNormals: modelMetrics.HasVertexNormals, + hasVertexColor: modelMetrics.HasVertexColor, + isManifold: modelMetrics.IsManifold, + isWatertight: modelMetrics.IsWatertight, + }; + } + + return fields; +} \ No newline at end of file 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/graphql/schema/systemobject/resolvers/queries/getSystemObjectDetails.ts b/server/graphql/schema/systemobject/resolvers/queries/getSystemObjectDetails.ts new file mode 100644 index 000000000..0664343c5 --- /dev/null +++ b/server/graphql/schema/systemobject/resolvers/queries/getSystemObjectDetails.ts @@ -0,0 +1,390 @@ +import * as CACHE from '../../../../../cache'; +import * as DBAPI from '../../../../../db'; +import { eObjectGraphMode, eSystemObjectType } from '../../../../../db'; +import { + GetSystemObjectDetailsResult, + IngestIdentifier, + QueryGetSystemObjectDetailsArgs, + RelatedObject, + RelatedObjectType, + RepositoryPath, + SystemObject +} 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 { 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); + const derivedObjects: RelatedObject[] = await getRelatedObjects(idSystemObject, RelatedObjectType.Derived); + const publishedState: string = await getPublishedState(idSystemObject); + const identifiers = await getIngestIdentifiers(idSystemObject); + + if (!oID) { + 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 found for ID: ${idSystemObject}`; + LOGGER.logger.error(message); + throw new Error(message); + } + + const idObject: number = oID.idObject; + const name: string = await resolveNameForObjectType(systemObject, oID.eObjectType); + + return { + idObject, + name, + retired: systemObject.Retired, + objectType: oID.eObjectType, + allowed: true, // TODO: True until Access control is implemented (Post MVP) + publishedState, + thumbnail: null, + unit, + project, + subject, + item, + objectAncestors, + identifiers, + sourceObjects, + derivedObjects + }; +} + +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 = []; + + if (type === RelatedObjectType.Source) { + relatedSystemObjects = await DBAPI.SystemObject.fetchMasterFromXref(idSystemObject); + } else if (type === RelatedObjectType.Derived) { + relatedSystemObjects = await DBAPI.SystemObject.fetchDerivedFromXref(idSystemObject); + } + + if (!relatedSystemObjects) return []; + + const relatedObjects: RelatedObject[] = []; + + 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}`; + LOGGER.logger.error(message); + throw new Error(message); + } + + const sourceObject: RelatedObject = { + idSystemObject: relatedSystemObject.idSystemObject, + name: await resolveNameForObjectType(relatedSystemObject, oID.eObjectType), + identifier: identifier?.[0]?.IdentifierValue ?? null, + objectType: oID.eObjectType + }; + + relatedObjects.push(sourceObject); + } + + return relatedObjects; +} + +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 + })); +} + +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 { + unit, + project, + subject, + item, + objectAncestors: [] + }; + } + + const objectAncestors: RepositoryPath[][] = []; + + 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[]; + +async function objectToRepositoryPath(objects: Objects, objectType: eSystemObjectType): Promise { + const paths: RepositoryPath[] = []; + 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); + + const path: RepositoryPath = { + idSystemObject: SystemObject?.idSystemObject ?? 0, + name: await resolveNameForObjectType(SystemObject, objectType), + objectType + }; + paths.push(path); + } + + return paths; +} + +async function resolveNameForObjectType(systemObject: SystemObject | null, objectType: eSystemObjectType): Promise { + if (!systemObject) return unknownName; + + 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: + 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 Model = await DBAPI.Model.fetch(systemObject.idModel); + if (Model) + return Model.Name; + } + + 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: + 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: + 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: + if (systemObject.idActor) { + const Actor = await DBAPI.Actor.fetch(systemObject.idActor); + if (Actor) { + return Actor?.IndividualName ?? unknownName; + } + } + + 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; + } +} 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/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/index.ts b/server/index.ts index 270ae15e3..d573a4423 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,16 @@ 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'}`); +}); + +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/NavigationDB.ts b/server/navigation/impl/NavigationDB.ts index d8ec267f6..c2229c340 100644 --- a/server/navigation/impl/NavigationDB.ts +++ b/server/navigation/impl/NavigationDB.ts @@ -129,11 +129,11 @@ export class NavigationDB implements NAV.INavigation { const metadata: string[] = []; for (const metadataColumn of metadataColumns) { switch (metadataColumn) { - case NAV.eMetadata.eUnitAbbreviation: metadata.push(unit.Abbreviation || ''); break; /* istanbul ignore next */ + case NAV.eMetadata.eHierarchyUnit: metadata.push(unit.Abbreviation || ''); break; /* istanbul ignore next */ default: - case NAV.eMetadata.eItemName: - case NAV.eMetadata.eSubjectIdentifier: + case NAV.eMetadata.eHierarchyItem: + case NAV.eMetadata.eHierarchySubject: metadata.push(''); break; } @@ -178,7 +178,7 @@ export class NavigationDB implements NAV.INavigation { const metadata: string[] = []; for (const metadataColumn of metadataColumns) { switch (metadataColumn) { - case NAV.eMetadata.eUnitAbbreviation: { + case NAV.eMetadata.eHierarchyUnit: { 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) { @@ -189,8 +189,8 @@ export class NavigationDB implements NAV.INavigation { } break; /* istanbul ignore next */ default: - case NAV.eMetadata.eItemName: - case NAV.eMetadata.eSubjectIdentifier: + case NAV.eMetadata.eHierarchyItem: + case NAV.eMetadata.eHierarchySubject: metadata.push(''); break; } @@ -231,12 +231,12 @@ export class NavigationDB implements NAV.INavigation { const metadata: string[] = []; for (const metadataColumn of metadataColumns) { switch (metadataColumn) { - case NAV.eMetadata.eUnitAbbreviation: { + case NAV.eMetadata.eHierarchyUnit: { 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: { + case NAV.eMetadata.eHierarchySubject: { const identifier: DBAPI.Identifier | null = (subject.idIdentifierPreferred) ? await DBAPI.Identifier.fetch(subject.idIdentifierPreferred) : null; @@ -244,7 +244,7 @@ export class NavigationDB implements NAV.INavigation { } break; /* istanbul ignore next */ default: - case NAV.eMetadata.eItemName: + case NAV.eMetadata.eHierarchyItem: metadata.push(''); break; } @@ -320,19 +320,19 @@ export class NavigationDB implements NAV.INavigation { const metadata: string[] = []; for (const metadataColumn of metadataColumns) { switch (metadataColumn) { - case NAV.eMetadata.eUnitAbbreviation: { + case NAV.eMetadata.eHierarchyUnit: { 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: { + case NAV.eMetadata.eHierarchySubject: { 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: + case NAV.eMetadata.eHierarchyItem: metadata.push(`Item ${item.Name}`); break; @@ -351,7 +351,7 @@ export class NavigationDB implements NAV.INavigation { const metadata: string[] = []; for (const metadataColumn of metadataColumns) { switch (metadataColumn) { - case NAV.eMetadata.eUnitAbbreviation: { /* istanbul ignore else */ + case NAV.eMetadata.eHierarchyUnit: { /* 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 */ ''); @@ -359,7 +359,7 @@ export class NavigationDB implements NAV.INavigation { metadata.push(''); } break; - case NAV.eMetadata.eSubjectIdentifier: { /* istanbul ignore else */ + case NAV.eMetadata.eHierarchySubject: { /* 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) @@ -369,7 +369,7 @@ export class NavigationDB implements NAV.INavigation { metadata.push(''); } break; - case NAV.eMetadata.eItemName: + case NAV.eMetadata.eHierarchyItem: metadata.push(`Item ${item.Name}`); break; diff --git a/server/navigation/impl/NavigationSolr/NavigationSolr.ts b/server/navigation/impl/NavigationSolr/NavigationSolr.ts new file mode 100644 index 000000000..23ef963c4 --- /dev/null +++ b/server/navigation/impl/NavigationSolr/NavigationSolr.ts @@ -0,0 +1,356 @@ +/* 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 { SolrClient } from './SolrClient'; +import { Vocabulary } from '../../../types/graphql'; +import { eMetadata } from '../../interface'; + +interface SolrQueryResult { + result: any; + error: any; +} + +export class NavigationSolr implements NAV.INavigation { + private _solrClient: SolrClient; + + constructor() { + this._solrClient = new SolrClient(null, null, null); + } + + // #region INavigation interface + async getObjectChildren(filter: NAV.NavigationFilter): Promise { + const SQ: solr.Query = await this.computeSolrQuery(filter); + return await this.executeSolrQuery(filter, SQ); + } + // #endregion + + // #region Compute Query + private async computeSolrQuery(filter: NAV.NavigationFilter): Promise { + let SQ: solr.Query = this._solrClient._client.query(); + + // search: string; // search string from the user -- for now, only apply to root-level queries, as well as queries of units, projects, and subjects + if (filter.search) { // if we have a search string, + if (!filter.idRoot) // apply it to root-level queries (i.e. with no specified filter root ID) + SQ = SQ.q(`_text_:*${filter.search}*`); + else { + const oID = await CACHE.SystemObjectCache.getObjectFromSystem(filter.idRoot); + if (oID && + (oID.eObjectType == eSystemObjectType.eUnit || + oID.eObjectType == eSystemObjectType.eProject || + oID.eObjectType == eSystemObjectType.eSubject)) + SQ = SQ.q(`_text_:*${filter.search}*`); // if we do have a root ID, apply it to the children of units, projects, and subjects + else + SQ = SQ.q('*:*'); // if we do have a root ID, do not apply it to the children of items, and all the rest + } + } else + SQ = SQ.q('*:*'); + + // idRoot: number; // idSystemObject of item for which we should get children; 0 means get everything + if (filter.idRoot) { + SQ = SQ.matchFilter('HierarchyParentID', filter.idRoot); + // objectsToDisplay: eSystemObjectType[]; // objects to display + SQ = await this.computeFilterParamFromSystemObjectType(SQ, filter.objectsToDisplay, 'CommonObjectType', '||'); + } else { + // objectTypes: eSystemObjectType[]; // empty array means give all appropriate children types + const objectTypes: eSystemObjectType[] = filter.objectTypes; + if (objectTypes.length == 0 && !filter.search) // if we have no root specified, and we have no keyword search, + objectTypes.push(eSystemObjectType.eUnit); // then restrict children types to "Units" + SQ = await this.computeFilterParamFromSystemObjectType(SQ, objectTypes, 'CommonObjectType', '||'); + } + + // units: number[]; // idSystemObject[] for units filter + SQ = await this.computeFilterParamFromNumbers(SQ, filter.units, 'HierarchyUnitID', '||'); + + // projects: number[]; // idSystemObject[] for projects filter + SQ = await this.computeFilterParamFromNumbers(SQ, filter.projects, 'HierarchyProjectID', '||'); + + // has: eSystemObjectType[]; // has system object filter + SQ = await this.computeFilterParamFromSystemObjectType(SQ, filter.has, 'ChildrenObjectTypes', '&&'); + + // missing: eSystemObjectType[]; // missing system object filter + SQ = await this.computeFilterParamFromSystemObjectType(SQ, filter.missing, '!ChildrenObjectTypes', '&&'); // TODO: does ! work here? + + // 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'); + + // metadataColumns: eMetadata[]; // empty array means give no metadata + const filterColumns: string[] = ['idSystemObject', 'CommonObjectType', 'CommonidObject', 'CommonName']; // fetch standard fields // don't need ChildrenID + for (const metadataColumn of filter.metadataColumns) { + const filterColumn: string = eMetadata[metadataColumn]; + if (filterColumn) + filterColumns.push(filterColumn.substring(1)); // strip of "e" prefix (eHierarchyUnit -> HierarchyUnit) + else + LOG.logger.error(`NavigationSolr.computeSolrQuery called with unexpected metadata column ${metadataColumn}`); + } + + if (filterColumns.length > 0) + SQ = SQ.fl(filterColumns); + + SQ = SQ.sort({ CommonOTNumber: 'asc', CommonName: 'asc', idSystemObject: 'asc' }); // sort by the object type enumeration, then by name, then by idSystemObject + SQ = SQ.cursorMark(filter.cursorMark ? filter.cursorMark : '*'); // c.f. https://lucene.apache.org/solr/guide/6_6/pagination-of-results.html#using-cursors + if (filter.rows > 0) + SQ = SQ.rows(filter.rows); + LOG.logger.info(`NavigationSolr.computeSolrQuery ${JSON.stringify(filter)}:\n${this._solrClient.solrUrl()}/select?${SQ.build()}`); + return SQ; + } + + private async computeFilterParamFromSystemObjectType(SQ: solr.Query, systemObjectTypes: eSystemObjectType[], filterSchema: string, operator: string): Promise { + 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.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}:`; + filterParam += `"${filterValue}"`; + } + return SQ.matchFilter(filterSchema, filterParam); + } + + private computeFilterParamFromNumbers(SQ: solr.Query, filterValueList: number[] | 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}:`; + filterParam += `${filterValue}`; + } + return SQ.matchFilter(filterSchema, filterParam); + } + + private async transformSystemObjectTypeArrayToStrings(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); + } + + return (termList.length > 0) ? termList : null; + } + + 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 (termList.length > 0) ? termList : null; + } + // #endregion + + // #region Execute Query + private async executeSolrQuery(filter: NAV.NavigationFilter, SQ: solr.Query): Promise { + let error: string = ''; + const entries: NAV.NavigationResultEntry[] = []; + const queryResult: SolrQueryResult = await this.executeSolrQueryWorker(SQ); + 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 }; + } + + LOG.logger.info(`NavigationSolr.executeSolrQuery: { numFound: ${queryResult.result.response.numFound}, ` + + `start: ${queryResult.result.response.start}, docsCount: ${queryResult.result.response.docs.length}, ` + + `nextCursorMark: ${queryResult.result.nextCursorMark} }`); + // let docNumber: number = 1; + for (const doc of queryResult.result.response.docs) { + if (!doc.idSystemObject || !doc.CommonObjectType || !doc.CommonidObject || !doc.CommonName) { + LOG.logger.error(`NavigationSolr.executeSolrQuery: malformed query response document ${JSON.stringify(doc)}`); + continue; + } + // LOG.logger.info(`NavigationSolr.executeSolrQuery [${docNumber++}]: ${JSON.stringify(doc)}`); + + const entry: NAV.NavigationResultEntry = { + idSystemObject: parseInt(doc.idSystemObject), + name: doc.CommonName || '', + objectType: DBAPI.SystemObjectNameToType(doc.CommonObjectType), + idObject: doc.CommonidObject, + metadata: this.computeMetadata(doc, filter.metadataColumns) + }; + + entries.push(entry); + } + + let cursorMark: string | null = queryResult.result.nextCursorMark ? queryResult.result.nextCursorMark : null; + if (cursorMark == filter.cursorMark) // solr returns the same cursorMark as the initial query when there are no more results; if so, clear out cursorMark + cursorMark = null; + // LOG.logger.info(`NavigationSolr.executeSolrQuery: ${JSON.stringify(queryResult.result)}`); + return { success: true, error: '', entries, metadataColumns: filter.metadataColumns, cursorMark }; + } + + 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 + resolve({ result: obj, error: null }); + }); + request; + }); + } + + private computeMetadata(doc: any, metadataColumns: NAV.eMetadata[]): string[] { + const metadata: string[] = []; + for (const metadataColumn of metadataColumns) { + switch (metadataColumn) { + case NAV.eMetadata.eCommonName: metadata.push(this.computeMetadataFromString(doc.CommonName)); break; + case NAV.eMetadata.eCommonDescription: metadata.push(this.computeMetadataFromString(doc.CommonDescription)); break; + case NAV.eMetadata.eCommonIdentifier: metadata.push(this.computeMetadataFromStringArray(doc.CommonIdentifier)); break; + case NAV.eMetadata.eCommonDateCreated: metadata.push(this.computeMetadataFromDate(doc.CommonDateCreated)); break; + case NAV.eMetadata.eCommonOrganizationName: metadata.push(this.computeMetadataFromString(doc.CommonOrganizationName)); break; + case NAV.eMetadata.eHierarchyUnit: metadata.push(this.computeMetadataFromStringArray(doc.HierarchyUnit)); break; + case NAV.eMetadata.eHierarchyProject: metadata.push(this.computeMetadataFromStringArray(doc.HierarchyProject)); break; + case NAV.eMetadata.eHierarchyItem: metadata.push(this.computeMetadataFromStringArray(doc.HierarchyItem)); break; + case NAV.eMetadata.eHierarchySubject: metadata.push(this.computeMetadataFromStringArray(doc.HierarchySubject)); break; + case NAV.eMetadata.eUnitARKPrefix: metadata.push(this.computeMetadataFromString(doc.UnitARKPrefix)); break; + case NAV.eMetadata.eSubjectIdentifierPreferred: metadata.push(this.computeMetadataFromString(doc.SubjectIdentifierPreferred)); break; + case NAV.eMetadata.eItemEntireSubject: metadata.push(this.computeMetadataFromBoolean(doc.ItemEntireSubject)); break; + case NAV.eMetadata.eCDCaptureMethod: metadata.push(this.computeMetadataFromString(doc.CDCaptureMethod)); break; + case NAV.eMetadata.eCDDatasetType: metadata.push(this.computeMetadataFromString(doc.CDCaptureDatasetType)); break; + case NAV.eMetadata.eCDDatasetFieldID: metadata.push(this.computeMetadataFromNumber(doc.CDCaptureDatasetFieldID)); break; + case NAV.eMetadata.eCDItemPositionType: metadata.push(this.computeMetadataFromString(doc.CDItemPositionType)); break; + case NAV.eMetadata.eCDItemPositionFieldID: metadata.push(this.computeMetadataFromNumber(doc.CDItemPositionFieldID)); break; + case NAV.eMetadata.eCDItemArrangementFieldID: metadata.push(this.computeMetadataFromNumber(doc.CDItemArrangementFieldID)); break; + case NAV.eMetadata.eCDFocusType: metadata.push(this.computeMetadataFromString(doc.CDFocusType)); break; + case NAV.eMetadata.eCDLightSourceType: metadata.push(this.computeMetadataFromString(doc.CDLightSourceType)); break; + case NAV.eMetadata.eCDBackgroundRemovalMethod: metadata.push(this.computeMetadataFromString(doc.CDBackgroundRemovalMethod)); break; + case NAV.eMetadata.eCDClusterType: metadata.push(this.computeMetadataFromString(doc.CDClusterType)); break; + case NAV.eMetadata.eCDClusterGeometryFieldID: metadata.push(this.computeMetadataFromNumber(doc.CDClusterGeometryFieldID)); break; + case NAV.eMetadata.eCDCameraSettingsUniform: metadata.push(this.computeMetadataFromBoolean(doc.CDCameraSettingsUniform)); break; + case NAV.eMetadata.eCDVariantType: metadata.push(this.computeMetadataFromStringArray(doc.CDVariantType)); break; + case NAV.eMetadata.eModelCreationMethod: metadata.push(this.computeMetadataFromString(doc.ModelCreationMethod)); break; + case NAV.eMetadata.eModelMaster: metadata.push(this.computeMetadataFromBoolean(doc.ModelMaster)); break; + case NAV.eMetadata.eModelAuthoritative: metadata.push(this.computeMetadataFromBoolean(doc.ModelAuthoritative)); break; + case NAV.eMetadata.eModelModality: metadata.push(this.computeMetadataFromString(doc.ModelModality)); break; + case NAV.eMetadata.eModelUnits: metadata.push(this.computeMetadataFromString(doc.ModelUnits)); break; + case NAV.eMetadata.eModelPurpose: metadata.push(this.computeMetadataFromString(doc.ModelPurpose)); break; + case NAV.eMetadata.eModelFileType: metadata.push(this.computeMetadataFromStringArray(doc.ModelFileType)); break; + case NAV.eMetadata.eModelRoughness: metadata.push(this.computeMetadataFromNumberArray(doc.ModelRoughness)); break; + case NAV.eMetadata.eModelMetalness: metadata.push(this.computeMetadataFromNumberArray(doc.ModelMetalness)); break; + case NAV.eMetadata.eModelPointCount: metadata.push(this.computeMetadataFromNumberArray(doc.ModelPointCount)); break; + case NAV.eMetadata.eModelFaceCount: metadata.push(this.computeMetadataFromNumberArray(doc.ModelFaceCount)); break; + case NAV.eMetadata.eModelIsWatertight: metadata.push(this.computeMetadataFromBooleanArray(doc.ModelIsWatertight)); break; + case NAV.eMetadata.eModelHasNormals: metadata.push(this.computeMetadataFromBooleanArray(doc.ModelHasNormals)); break; + case NAV.eMetadata.eModelHasVertexColor: metadata.push(this.computeMetadataFromBooleanArray(doc.ModelHasVertexColor)); break; + case NAV.eMetadata.eModelHasUVSpace: metadata.push(this.computeMetadataFromBooleanArray(doc.ModelHasUVSpace)); break; + case NAV.eMetadata.eModelBoundingBoxP1X: metadata.push(this.computeMetadataFromNumberArray(doc.ModelBoundingBoxP1X)); break; + case NAV.eMetadata.eModelBoundingBoxP1Y: metadata.push(this.computeMetadataFromNumberArray(doc.ModelBoundingBoxP1Y)); break; + case NAV.eMetadata.eModelBoundingBoxP1Z: metadata.push(this.computeMetadataFromNumberArray(doc.ModelBoundingBoxP1Z)); break; + case NAV.eMetadata.eModelBoundingBoxP2X: metadata.push(this.computeMetadataFromNumberArray(doc.ModelBoundingBoxP2X)); break; + case NAV.eMetadata.eModelBoundingBoxP2Y: metadata.push(this.computeMetadataFromNumberArray(doc.ModelBoundingBoxP2Y)); break; + case NAV.eMetadata.eModelBoundingBoxP2Z: metadata.push(this.computeMetadataFromNumberArray(doc.ModelBoundingBoxP2Z)); break; + case NAV.eMetadata.eModelUVMapEdgeLength: metadata.push(this.computeMetadataFromNumberArray(doc.ModelUVMapEdgeLength)); break; + case NAV.eMetadata.eModelChannelPosition: metadata.push(this.computeMetadataFromNumberArray(doc.ModelChannelPosition)); break; + case NAV.eMetadata.eModelChannelWidth: metadata.push(this.computeMetadataFromNumberArray(doc.ModelChannelWidth)); break; + case NAV.eMetadata.eModelUVMapType: metadata.push(this.computeMetadataFromStringArray(doc.ModelUVMapType)); break; + case NAV.eMetadata.eSceneIsOriented: metadata.push(this.computeMetadataFromBoolean(doc.SceneIsOriented)); break; + case NAV.eMetadata.eSceneHasBeenQCd: metadata.push(this.computeMetadataFromBoolean(doc.SceneHasBeenQCd)); break; + case NAV.eMetadata.eAssetFileName: metadata.push(this.computeMetadataFromStringArray(doc.AssetFileName)); break; + case NAV.eMetadata.eAssetFilePath: metadata.push(this.computeMetadataFromString(doc.AssetFilePath)); break; + case NAV.eMetadata.eAssetType: metadata.push(this.computeMetadataFromString(doc.AssetType)); break; + case NAV.eMetadata.eAVUserCreator: metadata.push(this.computeMetadataFromString(doc.AVUserCreator)); break; + case NAV.eMetadata.eAVStorageHash: metadata.push(this.computeMetadataFromString(doc.AVStorageHash)); break; + case NAV.eMetadata.eAVStorageSize: metadata.push(this.computeMetadataFromNumber(doc.AVStorageSize)); break; + case NAV.eMetadata.eAVIngested: metadata.push(this.computeMetadataFromBoolean(doc.AVIngested)); break; + case NAV.eMetadata.eAVBulkIngest: metadata.push(this.computeMetadataFromBoolean(doc.AVBulkIngest)); break; + case NAV.eMetadata.eStakeholderEmailAddress: metadata.push(this.computeMetadataFromString(doc.StakeholderEmailAddress)); break; + case NAV.eMetadata.eStakeholderPhoneNumberMobile: metadata.push(this.computeMetadataFromString(doc.StakeholderPhoneNumberMobile)); break; + case NAV.eMetadata.eStakeholderPhoneNumberOffice: metadata.push(this.computeMetadataFromString(doc.StakeholderPhoneNumberOffice)); break; + case NAV.eMetadata.eStakeholderMailingAddress: metadata.push(this.computeMetadataFromString(doc.StakeholderMailingAddress)); break; + break; + } + } + return metadata; + } + + private computeMetadataFromString(value: string | undefined): string { + return value || ''; + } + + private computeMetadataFromNumber(value: number | undefined): string { + return (value == undefined) ? '' : value.toString(); + } + + private computeMetadataFromBoolean(value: boolean | undefined): string { + return (value == undefined) ? '' : value ? 'true' : 'false'; + } + + private computeMetadataFromDate(value: Date | undefined): string { + return (value == undefined) ? '' : value.toISOString().substring(0, 10); + } + + private computeMetadataFromStringArray(values: string[] | undefined): string { + return (values) ? values.sort().join(', ') : ''; + } + + private computeMetadataFromNumberArray(values: number[] | undefined): string { + return (values) ? values.sort().join(', ') : ''; + } + + private computeMetadataFromBooleanArray(values: boolean[] | undefined): string { + if (!values) + return ''; + let hasFalse: boolean = false; + let hasTrue: boolean = false; + + for (const value of values) { + if (value) + hasTrue = true; + else + hasFalse = true; + } + + if (hasFalse && hasTrue) + return 'false, true'; + if (hasFalse) + return 'false'; + if (hasTrue) + return 'true'; /* istanbul ignore next */ + return ''; // should never get here! + } + // #endregion +} diff --git a/server/navigation/impl/NavigationSolr/ReindexSolr.ts b/server/navigation/impl/NavigationSolr/ReindexSolr.ts new file mode 100644 index 000000000..4be6f80c3 --- /dev/null +++ b/server/navigation/impl/NavigationSolr/ReindexSolr.ts @@ -0,0 +1,616 @@ +/* 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, ObjectGraphDataEntry } 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 + 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; + } + + // TODO: test! Integrate potentially with TBD audit interface, providing a path for system object creation and updates to flow through to Solr + async indexObject(idSystemObject: number): Promise { + // Compute full object graph for object + const OG: DBAPI.ObjectGraph = new DBAPI.ObjectGraph(idSystemObject, DBAPI.eObjectGraphMode.eAll, 32, this.objectGraphDatabase); + if (!await OG.fetch()) { + LOG.logger.error('ReindexSolr.indexObject failed fetching ObjectGraph'); + return false; + } + const OGDE: ObjectGraphDataEntry | undefined = this.objectGraphDatabase.objectMap.get(idSystemObject); + if (!OGDE) { + LOG.logger.error('ReindexSolr.indexObject failed fetching ObjectGraphDataEntry from ObjectGraphDatabase'); + return false; + } + + const doc: any = {}; + if (await this.handleObject(doc, OGDE)) { + const solrClient: SolrClient = new SolrClient(null, null, null); + solrClient._client.add([doc], undefined, function (err, obj) { if (err) LOG.logger.error('ReindexSolr.indexObject adding record', err); else obj; }); + solrClient._client.commit(undefined, function (err, obj) { if (err) LOG.logger.error('ReindexSolr.indexObject -> commit()', err); else obj; }); + } else + LOG.logger.error('ReindexSolr.indexObject failed in handleObject'); + + return true; + } + + private async fullIndexWorker(): Promise { + const solrClient: SolrClient = new SolrClient(null, null, null); + if (!(await this.objectGraphDatabase.fetch())) { + LOG.logger.error('ReindexSolr.fullIndex failed on ObjectGraphDatabase.fetch()'); + return false; + } + + let docs: any[] = []; + for (const objectGraphDataEntry of this.objectGraphDatabase.objectMap.values()) { + const doc: any = {}; + if (await this.handleObject(doc, objectGraphDataEntry)) { + docs.push(doc); + + if (docs.length >= 1000) { + 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 = []; + } + } else + LOG.logger.error('ReindexSolr.fullIndex failed in handleObject'); + } + + if (docs.length > 0) { + 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; + } + + private async handleObject(doc: any, objectGraphDataEntry: DBAPI.ObjectGraphDataEntry): Promise { + await this.extractCommonFields(doc, objectGraphDataEntry); + + switch (objectGraphDataEntry.systemObjectIDType.eObjectType) { + case eSystemObjectType.eUnit: return await this.handleUnit(doc, objectGraphDataEntry); + case eSystemObjectType.eProject: return await this.handleProject(doc, objectGraphDataEntry); + case eSystemObjectType.eSubject: return await this.handleSubject(doc, objectGraphDataEntry); + case eSystemObjectType.eItem: return await this.handleItem(doc, objectGraphDataEntry); + case eSystemObjectType.eCaptureData: return await this.handleCaptureData(doc, objectGraphDataEntry); + case eSystemObjectType.eModel: return await this.handleModel(doc, objectGraphDataEntry); + case eSystemObjectType.eScene: return await this.handleScene(doc, objectGraphDataEntry); + case eSystemObjectType.eIntermediaryFile: return await this.handleIntermediaryFile(doc, objectGraphDataEntry); + case eSystemObjectType.eProjectDocumentation: return await this.handleProjectDocumentation(doc, objectGraphDataEntry); + case eSystemObjectType.eAsset: return await this.handleAsset(doc, objectGraphDataEntry); + case eSystemObjectType.eAssetVersion: return await this.handleAssetVersion(doc, objectGraphDataEntry); + case eSystemObjectType.eActor: return await this.handleActor(doc, objectGraphDataEntry); + case eSystemObjectType.eStakeholder: return await this.handleStakeholder(doc, objectGraphDataEntry); + + default: + case eSystemObjectType.eUnknown: return await this.handleUnknown(doc, objectGraphDataEntry); + } + } + + private async extractCommonFields(doc: any, objectGraphDataEntry: DBAPI.ObjectGraphDataEntry): Promise { + const OGDEH: DBAPI.ObjectGraphDataEntryHierarchy = objectGraphDataEntry.extractHierarchy(); + + doc.idSystemObject = OGDEH.idSystemObject; + doc.CommonRetired = OGDEH.retired; + doc.CommonObjectType = DBAPI.SystemObjectTypeToName(OGDEH.eObjectType); + doc.CommonOTNumber = OGDEH.eObjectType; + doc.CommonidObject = OGDEH.idObject; + doc.CommonIdentifier = await this.computeIdentifiers(objectGraphDataEntry.systemObjectIDType.idSystemObject); + + doc.HierarchyParentID = OGDEH.parents.length == 0 ? [0] : OGDEH.parents; + doc.HierarchyChildrenID = OGDEH.children.length == 0 ? [0] : OGDEH.children; + + 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.HierarchyUnit = nameArray; + doc.HierarchyUnitID = 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.HierarchyProject = nameArray; + doc.HierarchyProjectID = 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.HierarchySubject = nameArray; + doc.HierarchySubjectID = 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.HierarchyItem = nameArray; + doc.HierarchyItemID = idArray; + nameArray = []; + idArray = []; + } + + const ChildrenObjectTypes: string[] = []; + 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; + VocabList = []; + + VocabList = await this.computeVocabularyTerms(OGDEH.childrenVariantTypes); + doc.ChildrenVariantTypes = VocabList; + VocabList = []; + + VocabList = await this.computeVocabularyTerms(OGDEH.childrenModelPurposes); + doc.ChildrenModelPurposes = VocabList; + VocabList = []; + + VocabList = await this.computeVocabularyTerms(OGDEH.childrenModelFileTypes); + doc.ChildrenModelFileTypes = 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.CommonName = unit.Name; + doc.UnitAbbreviation = unit.Abbreviation; + doc.UnitARKPrefix = 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.CommonName = project.Name; + doc.CommonDescription = 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.CommonName = subject.Name; + if (subject.idIdentifierPreferred) { + const ID: DBAPI.Identifier | null = await DBAPI.Identifier.fetch(subject.idIdentifierPreferred); + if (ID) + doc.SubjectIdentifierPreferred = 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.CommonName = item.Name; + doc.ItemEntireSubject = 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); + const captureDataPhoto: DBAPI.CaptureDataPhoto | null = (captureDataPhotos && captureDataPhotos.length > 0) ? captureDataPhotos[0] : null; + + doc.CommonName = captureData.Name; + doc.CommonDescription = captureData.Description; + doc.CommonDateCreated = captureData.DateCaptured; + doc.CDCaptureMethod = await this.lookupVocabulary(captureData.idVCaptureMethod); + if (captureDataPhoto) { + doc.CDCaptureDatasetType = await this.lookupVocabulary(captureDataPhoto.idVCaptureDatasetType); + doc.CDCaptureDatasetFieldID = captureDataPhoto.CaptureDatasetFieldID; + doc.CDItemPositionType = await this.lookupVocabulary(captureDataPhoto.idVItemPositionType); + doc.CDItemPositionFieldID = captureDataPhoto.ItemPositionFieldID; + doc.CDItemArrangementFieldID = captureDataPhoto.ItemArrangementFieldID; + doc.CDFocusType = await this.lookupVocabulary(captureDataPhoto.idVFocusType); + doc.CDLightSourceType = await this.lookupVocabulary(captureDataPhoto.idVLightSourceType); + doc.CDBackgroundRemovalMethod = await this.lookupVocabulary(captureDataPhoto.idVBackgroundRemovalMethod); + doc.CDClusterType = await this.lookupVocabulary(captureDataPhoto.idVClusterType); + doc.CDClusterGeometryFieldID = captureDataPhoto.ClusterGeometryFieldID; + doc.CDCameraSettingsUniform = captureDataPhoto.CameraSettingsUniform; + } + + const captureDataFiles: DBAPI.CaptureDataFile[] | null = await DBAPI.CaptureDataFile.fetchFromCaptureData(captureData.idCaptureData); + if (captureDataFiles) { + const variantTypeMap: Map = new Map(); + for (const captureDataFile of captureDataFiles) { + const variantType: string | null = await this.lookupVocabulary(captureDataFile.idVVariantType); + if (variantType) + variantTypeMap.set(variantType, true); + } + if (variantTypeMap.size > 0) + doc.CDVariantType = [...variantTypeMap.keys()]; + } + + 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.CommonName = modelConstellation.model.Name; + doc.CommonDateCreated = modelConstellation.model.DateCreated; + + doc.ModelCreationMethod = await this.computeVocabulary(modelConstellation.model.idVCreationMethod); + doc.ModelMaster = modelConstellation.model.Master; + doc.ModelAuthoritative = modelConstellation.model.Authoritative; + doc.ModelModality = await this.computeVocabulary(modelConstellation.model.idVModality); + doc.ModelUnits = await this.computeVocabulary(modelConstellation.model.idVUnits); + doc.ModelPurpose = await this.computeVocabulary(modelConstellation.model.idVPurpose); + doc.ModelFileType = await this.computeVocabulary(modelConstellation.model.idVFileType); + + const modelMaterialNameMap: Map = new Map(); + const modelMaterialChannelTypeMap: Map = new Map(); + const modelMaterialChannelTypeOtherMap: Map = new Map(); + const modelMaterialChannelPositionMap: Map = new Map(); + const modelMaterialChannelWidthMap: Map = new Map(); + const modelMaterialChannelValuesMap: Map = new Map(); + const modelMaterialUVMapEdgeLengthMap: Map = new Map(); + const modelMetricsBoundingBoxP1XMap: Map = new Map(); + const modelMetricsBoundingBoxP1YMap: Map = new Map(); + const modelMetricsBoundingBoxP1ZMap: Map = new Map(); + const modelMetricsBoundingBoxP2XMap: Map = new Map(); + const modelMetricsBoundingBoxP2YMap: Map = new Map(); + const modelMetricsBoundingBoxP2ZMap: Map = new Map(); + const modelMetricsCountPointMap: Map = new Map(); + const modelMetricsCountFaceMap: Map = new Map(); + const modelMetricsCountColorChannelMap: Map = new Map(); + const modelMetricsCountTextureCoorinateChannelMap: Map = new Map(); + const modelMetricsHasBonesMap: Map = new Map(); + const modelMetricsHasFaceNormalsMap: Map = new Map(); + const modelMetricsHasTangentsMap: Map = new Map(); + const modelMetricsHasTextureCoordinatesMap: Map = new Map(); + const modelMetricsHasVertexNormalsMap: Map = new Map(); + const modelMetricsHasVertexColorMap: Map = new Map(); + const modelMetricsIsManifoldMap: Map = new Map(); + const modelMetricsIsWatertightMap: Map = new Map(); + + if (modelConstellation.modelMaterials) { + for (const modelMaterial of modelConstellation.modelMaterials) { + if (modelMaterial.Name) + modelMaterialNameMap.set(modelMaterial.Name, true); + } + } + + if (modelConstellation.modelMaterialChannels) { + for (const modelMaterialChannel of modelConstellation.modelMaterialChannels) { + if (modelMaterialChannel.idVMaterialType) { + const materialType = await this.computeVocabulary(modelMaterialChannel.idVMaterialType); + if (materialType) + modelMaterialChannelTypeMap.set(materialType, true); + } + if (modelMaterialChannel.MaterialTypeOther) modelMaterialChannelTypeOtherMap.set(modelMaterialChannel.MaterialTypeOther, true); + if (modelMaterialChannel.ChannelPosition) modelMaterialChannelPositionMap.set(modelMaterialChannel.ChannelPosition, true); + if (modelMaterialChannel.ChannelWidth) modelMaterialChannelWidthMap.set(modelMaterialChannel.ChannelWidth, true); + + let channelValue: string = [modelMaterialChannel.Scalar1, modelMaterialChannel.Scalar2, + modelMaterialChannel.Scalar3, modelMaterialChannel.Scalar4].join(', '); + if (channelValue.indexOf(',') >= 0) + channelValue = `(${channelValue})`; + if (channelValue) modelMaterialChannelValuesMap.set(channelValue, true); + } + } + + if (modelConstellation.modelMaterialUVMaps) { + for (const modelMaterialUVMap of modelConstellation.modelMaterialUVMaps) + modelMaterialUVMapEdgeLengthMap.set(modelMaterialUVMap.UVMapEdgeLength, true); + } + + const modelMetricsList: DBAPI.ModelMetrics[] = []; + if (modelConstellation.modelMetric) + modelMetricsList.push(modelConstellation.modelMetric); + if (modelConstellation.modelObjectMetrics) + modelMetricsList.push(...modelConstellation.modelObjectMetrics); + for (const modelMetrics of modelMetricsList) { + if (modelMetrics.BoundingBoxP1X) modelMetricsBoundingBoxP1XMap.set(modelMetrics.BoundingBoxP1X, true); + if (modelMetrics.BoundingBoxP1Y) modelMetricsBoundingBoxP1YMap.set(modelMetrics.BoundingBoxP1Y, true); + if (modelMetrics.BoundingBoxP1Z) modelMetricsBoundingBoxP1ZMap.set(modelMetrics.BoundingBoxP1Z, true); + if (modelMetrics.BoundingBoxP2X) modelMetricsBoundingBoxP2XMap.set(modelMetrics.BoundingBoxP2X, true); + if (modelMetrics.BoundingBoxP2Y) modelMetricsBoundingBoxP2YMap.set(modelMetrics.BoundingBoxP2Y, true); + if (modelMetrics.BoundingBoxP2Z) modelMetricsBoundingBoxP2ZMap.set(modelMetrics.BoundingBoxP2Z, true); + if (modelMetrics.CountPoint) modelMetricsCountPointMap.set(modelMetrics.CountPoint, true); + if (modelMetrics.CountFace) modelMetricsCountFaceMap.set(modelMetrics.CountFace, true); + if (modelMetrics.CountColorChannel) modelMetricsCountColorChannelMap.set(modelMetrics.CountColorChannel, true); + if (modelMetrics.CountTextureCoorinateChannel) modelMetricsCountTextureCoorinateChannelMap.set(modelMetrics.CountTextureCoorinateChannel, true); + if (modelMetrics.HasBones) modelMetricsHasBonesMap.set(modelMetrics.HasBones, true); + if (modelMetrics.HasFaceNormals) modelMetricsHasFaceNormalsMap.set(modelMetrics.HasFaceNormals, true); + if (modelMetrics.HasTangents) modelMetricsHasTangentsMap.set(modelMetrics.HasTangents, true); + if (modelMetrics.HasTextureCoordinates) modelMetricsHasTextureCoordinatesMap.set(modelMetrics.HasTextureCoordinates, true); + if (modelMetrics.HasVertexNormals) modelMetricsHasVertexNormalsMap.set(modelMetrics.HasVertexNormals, true); + if (modelMetrics.HasVertexColor) modelMetricsHasVertexColorMap.set(modelMetrics.HasVertexColor, true); + if (modelMetrics.IsManifold) modelMetricsIsManifoldMap.set(modelMetrics.IsManifold, true); + if (modelMetrics.IsWatertight) modelMetricsIsWatertightMap.set(modelMetrics.IsWatertight, true); + + } + doc.ModelMaterialName = [...modelMaterialNameMap.keys()]; + doc.modelMaterialChannelType = [...modelMaterialChannelTypeMap.keys()]; + doc.modelMaterialChannelTypeOther = [...modelMaterialChannelTypeOtherMap.keys()]; + doc.modelMaterialChannelPosition = [...modelMaterialChannelPositionMap.keys()]; + doc.modelMaterialChannelWidth = [...modelMaterialChannelWidthMap.keys()]; + doc.modelMaterialChannelValues = [...modelMaterialChannelValuesMap.keys()]; + doc.modelMaterialUVMapEdgeLength = [...modelMaterialUVMapEdgeLengthMap.keys()]; + doc.modelMetricsBoundingBoxP1X = [...modelMetricsBoundingBoxP1XMap.keys()]; + doc.modelMetricsBoundingBoxP1Y = [...modelMetricsBoundingBoxP1YMap.keys()]; + doc.modelMetricsBoundingBoxP1Z = [...modelMetricsBoundingBoxP1ZMap.keys()]; + doc.modelMetricsBoundingBoxP2X = [...modelMetricsBoundingBoxP2XMap.keys()]; + doc.modelMetricsBoundingBoxP2Y = [...modelMetricsBoundingBoxP2YMap.keys()]; + doc.modelMetricsBoundingBoxP2Z = [...modelMetricsBoundingBoxP2ZMap.keys()]; + doc.modelMetricsCountPoint = [...modelMetricsCountPointMap.keys()]; + doc.modelMetricsCountFace = [...modelMetricsCountFaceMap.keys()]; + doc.modelMetricsCountColorChannel = [...modelMetricsCountColorChannelMap.keys()]; + doc.modelMetricsCountTextureCoorinateChannel = [...modelMetricsCountTextureCoorinateChannelMap.keys()]; + doc.modelMetricsHasBones = [...modelMetricsHasBonesMap.keys()]; + doc.modelMetricsHasFaceNormals = [...modelMetricsHasFaceNormalsMap.keys()]; + doc.modelMetricsHasTangents = [...modelMetricsHasTangentsMap.keys()]; + doc.modelMetricsHasTextureCoordinates = [...modelMetricsHasTextureCoordinatesMap.keys()]; + doc.modelMetricsHasVertexNormals = [...modelMetricsHasVertexNormalsMap.keys()]; + doc.modelMetricsHasVertexColor = [...modelMetricsHasVertexColorMap.keys()]; + doc.modelMetricsIsManifold = [...modelMetricsIsManifoldMap.keys()]; + doc.modelMetricsIsWatertight = [...modelMetricsIsWatertightMap.keys()]; + + // TODO: should we turn multivalued metrics and bounding boxes into single valued attributes, and combine the multiple values in a meaningful way (e.g. add point and face counts, combine bounding boxes) + 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.CommonName = scene.Name; + doc.SceneIsOriented = scene.IsOriented; + doc.SceneHasBeenQCd = 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.CommonName = `Intermediary File created ${intermediaryFile.DateCreated.toISOString()}`; + doc.CommonDateCreated = 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.CommonName = projectDocumentation.Name; + doc.CommonDescription = 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.CommonName = asset.FileName; + doc.AssetFileName = asset.FileName; + doc.AssetFilePath = 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.CommonName = `Version ${assetVersion.Version}`; + doc.AVUserCreator = user.Name; + doc.AVStorageHash = assetVersion.StorageHash; + doc.AVStorageSize = assetVersion.StorageSize; + doc.AVIngested = assetVersion.Ingested; + doc.AVBulkIngest = 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.CommonName = actor.IndividualName; + doc.CommonOrganizationName = 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.CommonName = stakeholder.IndividualName; + doc.CommonOrganizationName = stakeholder.OrganizationName; + doc.StakeholderEmailAddress = stakeholder.EmailAddress; + doc.StakeholderPhoneNumberMobile = stakeholder.PhoneNumberMobile; + doc.StakeholderPhoneNumberOffice = stakeholder.PhoneNumberOffice; + doc.StakeholderMailingAddress = 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.CommonName = `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/impl/NavigationSolr/SolrClient.ts b/server/navigation/impl/NavigationSolr/SolrClient.ts new file mode 100644 index 000000000..bff0b4cdd --- /dev/null +++ b/server/navigation/impl/NavigationSolr/SolrClient.ts @@ -0,0 +1,28 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import solr from 'solr-client'; + +export class SolrClient { + _client: solr.Client; + private _host: string; + private _port: number; + private _core: string; + + constructor(host: string | null, port: number | null, core: string | null) { + if (!host) { + const { PACKRAT_SOLR_HOST } = process.env; + host = PACKRAT_SOLR_HOST || 'packrat-solr'; + } + if (!port) + port = 8983; + if (!core) + core = 'packrat'; + this._host = host; + this._port = port; + this._core = core; + this._client = solr.createClient({ host, port, core }); + } + + solrUrl(): string { + return `http://${this._host}:${this._port}/solr/${this._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/INavigation.ts b/server/navigation/interface/INavigation.ts index 629875b0f..2e8ecc4ab 100644 --- a/server/navigation/interface/INavigation.ts +++ b/server/navigation/interface/INavigation.ts @@ -1,30 +1,104 @@ import { eSystemObjectType } from '../../db'; export enum eMetadata { - eUnitAbbreviation, - eSubjectIdentifier, - eItemName + eCommonName, + eCommonDescription, + eCommonIdentifier, + eCommonDateCreated, + eCommonOrganizationName, + eHierarchyUnit, + eHierarchyProject, + eHierarchySubject, + eHierarchyItem, + eUnitARKPrefix, + eSubjectIdentifierPreferred, + eItemEntireSubject, + eCDCaptureMethod, + eCDDatasetType, + eCDDatasetFieldID, + eCDItemPositionType, + eCDItemPositionFieldID, + eCDItemArrangementFieldID, + eCDFocusType, + eCDLightSourceType, + eCDBackgroundRemovalMethod, + eCDClusterType, + eCDClusterGeometryFieldID, + eCDCameraSettingsUniform, + eCDVariantType, + eModelCreationMethod, + eModelMaster, + eModelAuthoritative, + eModelModality, + eModelUnits, + eModelPurpose, + eModelFileType, + eModelRoughness, + eModelMetalness, + eModelPointCount, + eModelFaceCount, + eModelIsWatertight, + eModelHasNormals, + eModelHasVertexColor, + eModelHasUVSpace, + eModelBoundingBoxP1X, + eModelBoundingBoxP1Y, + eModelBoundingBoxP1Z, + eModelBoundingBoxP2X, + eModelBoundingBoxP2Y, + eModelBoundingBoxP2Z, + eModelUVMapEdgeLength, + eModelChannelPosition, + eModelChannelWidth, + eModelUVMapType, + eSceneIsOriented, + eSceneHasBeenQCd, + eAssetFileName, + eAssetFilePath, + eAssetType, + eAVUserCreator, + eAVStorageHash, + eAVStorageSize, + eAVIngested, + eAVBulkIngest, + eStakeholderEmailAddress, + eStakeholderPhoneNumberMobile, + eStakeholderPhoneNumberOffice, + eStakeholderMailingAddress, } 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 + 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 + rows: number; // max result row count; a value of 0 means "all" + cursorMark: string; // a non-empty value indicates a cursor position through a set of result values, used to request the next set of values }; 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[]; + cursorMark?: string | null; // when provided, additional results are available by requesting another navigation, using this returned value for the NavigationFilter.cursorMark }; export interface INavigation { diff --git a/server/navigation/interface/NavigationFactory.ts b/server/navigation/interface/NavigationFactory.ts index f3a3d0c2f..04297f105 100644 --- a/server/navigation/interface/NavigationFactory.ts +++ b/server/navigation/interface/NavigationFactory.ts @@ -1,21 +1,29 @@ 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'; export class NavigationFactory { private static instance: INavigation | null = null; - static async getInstance(): Promise { + static async getInstance(eNavType: NAVIGATION_TYPE = NAVIGATION_TYPE.DEFAULT): Promise { + if (eNavType == NAVIGATION_TYPE.DEFAULT) + eNavType = Config.navigation.type; + /* istanbul ignore else */ if (!NavigationFactory.instance) { - switch (Config.navigation.type) { + switch (eNavType) { /* istanbul ignore next */ default: case NAVIGATION_TYPE.DB: { NavigationFactory.instance = new NavigationDB(); break; } + case NAVIGATION_TYPE.SOLR: { + NavigationFactory.instance = new NavigationSolr(); + break; + } } } return NavigationFactory.instance; diff --git a/server/package.json b/server/package.json index 034a55f69..35b8e0a94 100644 --- a/server/package.json +++ b/server/package.json @@ -25,11 +25,10 @@ "main": "build/index.js", "typings": "build/index.d.ts", "scripts": { - "start": "yarn build && 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", - "build": "tsc --build && copyfiles db/**/*.* graphql/**/**.graphql build/", - "build:watch": "tsc --watch", + "build:dev": "tsc --build && copyfiles db/**/*.* graphql/**/**.graphql build/", + "build:prod": "tsc --build && copyfiles db/**/*.* graphql/**/**.graphql build/", "clean": "rm -rf node_modules/ build/", "generate": "graphql-codegen --config ./graphql/codegen.yml", "generate:prisma": "npx prisma generate --schema=./db/prisma/schema.prisma", @@ -41,7 +40,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", @@ -64,7 +62,9 @@ "node-stream-zip": "1.11.3", "passport": "0.4.1", "passport-local": "1.0.0", + "solr-client": "0.7.1", "supertest": "4.0.2", + "ts-node": "9.1.1", "uuid": "8.3.0", "winston": "3.3.3" }, @@ -77,7 +77,12 @@ "@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" + }, + "volta": { + "node": "12.18.4", + "yarn": "1.22.4" } } 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 }; 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/cache/VocabularyCache.test.ts b/server/tests/cache/VocabularyCache.test.ts index 8cfbc5ea0..d2c66ad70 100644 --- a/server/tests/cache/VocabularyCache.test.ts +++ b/server/tests/cache/VocabularyCache.test.ts @@ -163,9 +163,9 @@ function vocabularyCacheTestWorker(eMode: eCacheTestMode): void { case eVocabularySetID.eModelModality: case eVocabularySetID.eModelUnits: case eVocabularySetID.eModelPurpose: - case eVocabularySetID.eModelGeometryFileModelFileType: + case eVocabularySetID.eModelFileType: case eVocabularySetID.eModelProcessingActionStepActionMethod: - case eVocabularySetID.eModelUVMapChannelUVMapType: + case eVocabularySetID.eModelMaterialChannelMaterialType: case eVocabularySetID.eIdentifierIdentifierType: case eVocabularySetID.eIdentifierIdentifierTypeActor: case eVocabularySetID.eMetadataMetadataSource: @@ -307,32 +307,32 @@ function vocabularyCacheTestWorker(eMode: eCacheTestMode): void { await testVocabularyBySetAndTerm(eVocabularySetID.eModelPurpose, 'Web Delivery'); await testVocabularyBySetAndTerm(eVocabularySetID.eModelPurpose, 'Print Delivery'); await testVocabularyBySetAndTerm(eVocabularySetID.eModelPurpose, 'Intermediate Processing Step'); - await testVocabularyBySetAndTerm(eVocabularySetID.eModelGeometryFileModelFileType, 'obj - Alias Wavefront Object'); - await testVocabularyBySetAndTerm(eVocabularySetID.eModelGeometryFileModelFileType, 'ply - Stanford Polygon File Format'); - await testVocabularyBySetAndTerm(eVocabularySetID.eModelGeometryFileModelFileType, 'stl - StereoLithography'); - await testVocabularyBySetAndTerm(eVocabularySetID.eModelGeometryFileModelFileType, 'glb - GL Transmission Format Binary'); - await testVocabularyBySetAndTerm(eVocabularySetID.eModelGeometryFileModelFileType, 'gltf - GL Transmission Format'); - await testVocabularyBySetAndTerm(eVocabularySetID.eModelGeometryFileModelFileType, 'usdz - Universal Scene Description (zipped)'); - await testVocabularyBySetAndTerm(eVocabularySetID.eModelGeometryFileModelFileType, 'x3d'); - await testVocabularyBySetAndTerm(eVocabularySetID.eModelGeometryFileModelFileType, 'wrl - VRML'); - await testVocabularyBySetAndTerm(eVocabularySetID.eModelGeometryFileModelFileType, 'dae - COLLADA'); - await testVocabularyBySetAndTerm(eVocabularySetID.eModelGeometryFileModelFileType, 'fbx - Filmbox'); - await testVocabularyBySetAndTerm(eVocabularySetID.eModelGeometryFileModelFileType, 'ma - Maya'); - await testVocabularyBySetAndTerm(eVocabularySetID.eModelGeometryFileModelFileType, '3ds - 3D Studio'); - await testVocabularyBySetAndTerm(eVocabularySetID.eModelGeometryFileModelFileType, 'ptx'); - await testVocabularyBySetAndTerm(eVocabularySetID.eModelGeometryFileModelFileType, 'pts'); - await testVocabularyBySetAndTerm(eVocabularySetID.eModelUVMapChannelUVMapType, 'Diffuse'); - await testVocabularyBySetAndTerm(eVocabularySetID.eModelUVMapChannelUVMapType, 'Normal: Tangent Space'); - await testVocabularyBySetAndTerm(eVocabularySetID.eModelUVMapChannelUVMapType, 'Normal: Object Space'); - await testVocabularyBySetAndTerm(eVocabularySetID.eModelUVMapChannelUVMapType, 'Ambient Occlusion'); - await testVocabularyBySetAndTerm(eVocabularySetID.eModelUVMapChannelUVMapType, 'Roughness'); - await testVocabularyBySetAndTerm(eVocabularySetID.eModelUVMapChannelUVMapType, 'Metalness'); - await testVocabularyBySetAndTerm(eVocabularySetID.eModelUVMapChannelUVMapType, 'Specular'); - await testVocabularyBySetAndTerm(eVocabularySetID.eModelUVMapChannelUVMapType, 'Transparency'); - await testVocabularyBySetAndTerm(eVocabularySetID.eModelUVMapChannelUVMapType, 'BRDF'); - await testVocabularyBySetAndTerm(eVocabularySetID.eModelUVMapChannelUVMapType, 'Hole Fill'); - await testVocabularyBySetAndTerm(eVocabularySetID.eModelUVMapChannelUVMapType, 'Reflection'); - await testVocabularyBySetAndTerm(eVocabularySetID.eModelUVMapChannelUVMapType, 'Refraction'); + await testVocabularyBySetAndTerm(eVocabularySetID.eModelFileType, 'obj - Alias Wavefront Object'); + await testVocabularyBySetAndTerm(eVocabularySetID.eModelFileType, 'ply - Stanford Polygon File Format'); + await testVocabularyBySetAndTerm(eVocabularySetID.eModelFileType, 'stl - StereoLithography'); + await testVocabularyBySetAndTerm(eVocabularySetID.eModelFileType, 'glb - GL Transmission Format Binary'); + await testVocabularyBySetAndTerm(eVocabularySetID.eModelFileType, 'gltf - GL Transmission Format'); + await testVocabularyBySetAndTerm(eVocabularySetID.eModelFileType, 'usdz - Universal Scene Description (zipped)'); + await testVocabularyBySetAndTerm(eVocabularySetID.eModelFileType, 'x3d'); + await testVocabularyBySetAndTerm(eVocabularySetID.eModelFileType, 'wrl - VRML'); + await testVocabularyBySetAndTerm(eVocabularySetID.eModelFileType, 'dae - COLLADA'); + await testVocabularyBySetAndTerm(eVocabularySetID.eModelFileType, 'fbx - Filmbox'); + await testVocabularyBySetAndTerm(eVocabularySetID.eModelFileType, 'ma - Maya'); + await testVocabularyBySetAndTerm(eVocabularySetID.eModelFileType, '3ds - 3D Studio'); + await testVocabularyBySetAndTerm(eVocabularySetID.eModelFileType, 'ptx'); + await testVocabularyBySetAndTerm(eVocabularySetID.eModelFileType, 'pts'); + await testVocabularyBySetAndTerm(eVocabularySetID.eModelMaterialChannelMaterialType, 'Diffuse'); + await testVocabularyBySetAndTerm(eVocabularySetID.eModelMaterialChannelMaterialType, 'Normal: Tangent Space'); + await testVocabularyBySetAndTerm(eVocabularySetID.eModelMaterialChannelMaterialType, 'Normal: Object Space'); + await testVocabularyBySetAndTerm(eVocabularySetID.eModelMaterialChannelMaterialType, 'Ambient Occlusion'); + await testVocabularyBySetAndTerm(eVocabularySetID.eModelMaterialChannelMaterialType, 'Roughness'); + await testVocabularyBySetAndTerm(eVocabularySetID.eModelMaterialChannelMaterialType, 'Metalness'); + await testVocabularyBySetAndTerm(eVocabularySetID.eModelMaterialChannelMaterialType, 'Specular'); + await testVocabularyBySetAndTerm(eVocabularySetID.eModelMaterialChannelMaterialType, 'Transparency'); + await testVocabularyBySetAndTerm(eVocabularySetID.eModelMaterialChannelMaterialType, 'BRDF'); + await testVocabularyBySetAndTerm(eVocabularySetID.eModelMaterialChannelMaterialType, 'Hole Fill'); + await testVocabularyBySetAndTerm(eVocabularySetID.eModelMaterialChannelMaterialType, 'Reflection'); + await testVocabularyBySetAndTerm(eVocabularySetID.eModelMaterialChannelMaterialType, 'Refraction'); await testVocabularyBySetAndTerm(eVocabularySetID.eIdentifierIdentifierType, 'ARK'); await testVocabularyBySetAndTerm(eVocabularySetID.eIdentifierIdentifierType, 'DOI'); await testVocabularyBySetAndTerm(eVocabularySetID.eIdentifierIdentifierType, 'Unit CMS ID'); diff --git a/server/tests/db/api/Model.util.ts b/server/tests/db/api/Model.util.ts index fa47d6849..f33b4522c 100644 --- a/server/tests/db/api/Model.util.ts +++ b/server/tests/db/api/Model.util.ts @@ -1,5 +1,6 @@ import * as DBAPI from '../../../db'; -import { Model as ModelBase } from '@prisma/client'; +import { Model as ModelBase, ModelMaterial as ModelMaterialBase, ModelMaterialChannel as ModelMaterialChannelBase, + ModelMaterialUVMap as ModelMaterialUVMapBase, ModelMetrics as ModelMetricsBase, ModelObject as ModelObjectBase } from '@prisma/client'; export async function createModelTest(base: ModelBase): Promise { const model: DBAPI.Model = new DBAPI.Model(base); @@ -7,4 +8,44 @@ export async function createModelTest(base: ModelBase): Promise { expect(created).toBeTruthy(); expect(model.idModel).toBeGreaterThan(0); return model; -} \ No newline at end of file +} + +export async function createModelMaterialTest(base: ModelMaterialBase): Promise { + const modelMaterial: DBAPI.ModelMaterial = new DBAPI.ModelMaterial(base); + const created: boolean = await modelMaterial.create(); + expect(created).toBeTruthy(); + expect(modelMaterial.idModelMaterial).toBeGreaterThan(0); + return modelMaterial; +} + +export async function createModelMaterialChannelTest(base: ModelMaterialChannelBase): Promise { + const modelMaterialChannel: DBAPI.ModelMaterialChannel = new DBAPI.ModelMaterialChannel(base); + const created: boolean = await modelMaterialChannel.create(); + expect(created).toBeTruthy(); + expect(modelMaterialChannel.idModelMaterialChannel).toBeGreaterThan(0); + return modelMaterialChannel; +} + +export async function createModelMaterialUVMapTest(base: ModelMaterialUVMapBase): Promise { + const modelMaterialUVMap: DBAPI.ModelMaterialUVMap = new DBAPI.ModelMaterialUVMap(base); + const created: boolean = await modelMaterialUVMap.create(); + expect(created).toBeTruthy(); + expect(modelMaterialUVMap.idModelMaterialUVMap).toBeGreaterThan(0); + return modelMaterialUVMap; +} + +export async function createModelMetricsTest(base: ModelMetricsBase): Promise { + const modelMetrics: DBAPI.ModelMetrics = new DBAPI.ModelMetrics(base); + const created: boolean = await modelMetrics.create(); + expect(created).toBeTruthy(); + expect(modelMetrics.idModelMetrics).toBeGreaterThan(0); + return modelMetrics; +} + +export async function createModelObjectTest(base: ModelObjectBase): Promise { + const modelObject: DBAPI.ModelObject = new DBAPI.ModelObject(base); + const created: boolean = await modelObject.create(); + expect(created).toBeTruthy(); + expect(modelObject.idModelObject).toBeGreaterThan(0); + return modelObject; +} diff --git a/server/tests/db/composite/ObjectGraph.setup.ts b/server/tests/db/composite/ObjectGraph.setup.ts index 560fef6d5..f62779b70 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 }); @@ -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, idVFileType: this.v1.idVocabulary, idAssetThumbnail: this.assetT4.idAsset, idModelMetrics: 0, 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, idVFileType: this.v1.idVocabulary, idAssetThumbnail: null, idModelMetrics: 0, 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, idVFileType: this.v1.idVocabulary, idAssetThumbnail: null, idModelMetrics: 0, 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, idVFileType: this.v1.idVocabulary, idAssetThumbnail: null, idModelMetrics: 0, 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 b9c9187e8..992bcaa36 100644 --- a/server/tests/db/dbcreation.test.ts +++ b/server/tests/db/dbcreation.test.ts @@ -52,14 +52,19 @@ let metadata: DBAPI.Metadata | null; let metadataNull: DBAPI.Metadata | null; let model: DBAPI.Model | null; let modelNulls: DBAPI.Model | null; -let modelGeometryFile: DBAPI.ModelGeometryFile | null; -let modelGeometryFileNulls: DBAPI.ModelGeometryFile | null; +let modelMaterial: DBAPI.ModelMaterial | null; +let modelMaterialChannel: DBAPI.ModelMaterialChannel | null; +let modelMaterialChannelNulls: DBAPI.ModelMaterialChannel | null; +let modelMaterialUVMap: DBAPI.ModelMaterialUVMap | null; +let modelMetrics: DBAPI.ModelMetrics | null; +let modelMetrics2: DBAPI.ModelMetrics | null; +let modelObject: DBAPI.ModelObject | null; +let modelObject2: DBAPI.ModelObject | null; +let modelObject3: DBAPI.ModelObject | null; let modelProcessingAction: DBAPI.ModelProcessingAction | null; let modelProcessingActionStep: DBAPI.ModelProcessingActionStep | null; let modelSceneXref: DBAPI.ModelSceneXref | null; let modelSceneXrefNull: DBAPI.ModelSceneXref | null; -let modelUVMapChannel: DBAPI.ModelUVMapChannel | null; -let modelUVMapFile: DBAPI.ModelUVMapFile | null; let license: DBAPI.License | null; let licenseAssignment: DBAPI.LicenseAssignment | null; let licenseAssignmentNull: DBAPI.LicenseAssignment | null; @@ -102,7 +107,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 +268,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 +279,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 +446,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 +538,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 +556,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, @@ -554,6 +576,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 +589,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', @@ -790,13 +814,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, @@ -826,70 +850,142 @@ describe('DB Creation Test Suite', () => { } }); + test('DB Creation: ModelMetrics', async () => { + modelMetrics = await UTIL.createModelMetricsTest({ + BoundingBoxP1X: 0, BoundingBoxP1Y: 0, BoundingBoxP1Z: 0, BoundingBoxP2X: 1, BoundingBoxP2Y: 1, BoundingBoxP2Z: 1, + CountPoint: 100, CountFace: 50, CountColorChannel: 0, CountTextureCoorinateChannel: 0, HasBones: true, HasFaceNormals: false, + HasTangents: true, HasTextureCoordinates: false, HasVertexNormals: true, HasVertexColor: false, IsManifold: true, IsWatertight: false, + idModelMetrics: 0 + }); + expect(modelMetrics).toBeTruthy(); + + modelMetrics2 = await UTIL.createModelMetricsTest({ + BoundingBoxP1X: 0, BoundingBoxP1Y: 0, BoundingBoxP1Z: 0, BoundingBoxP2X: 2, BoundingBoxP2Y: 2, BoundingBoxP2Z: 2, + CountPoint: null, CountFace: null, CountColorChannel: 0, CountTextureCoorinateChannel: 0, HasBones: false, HasFaceNormals: false, + HasTangents: null, HasTextureCoordinates: null, HasVertexNormals: false, HasVertexColor: true, IsManifold: false, IsWatertight: false, + idModelMetrics: 0 + }); + expect(modelMetrics2).toBeTruthy(); + }); + test('DB Creation: Model', async () => { - if (vocabulary && assetThumbnail) + if (vocabulary && assetThumbnail && modelMetrics) model = await UTIL.createModelTest({ - DateCreated: UTIL.nowCleansed(), - idVCreationMethod: vocabulary.idVocabulary, + Name: 'Test Model', Master: true, Authoritative: true, + DateCreated: UTIL.nowCleansed(), + idVCreationMethod: vocabulary.idVocabulary, idVModality: vocabulary.idVocabulary, idVUnits: vocabulary.idVocabulary, idVPurpose: vocabulary.idVocabulary, + idVFileType: vocabulary.idVocabulary, idAssetThumbnail: assetThumbnail.idAsset, + idModelMetrics: modelMetrics.idModelMetrics, idModel: 0 }); expect(model).toBeTruthy(); }); test('DB Creation: Model With Nulls', async () => { - if (vocabulary) + if (assetThumbnail && vocabulary) modelNulls = await UTIL.createModelTest({ - DateCreated: UTIL.nowCleansed(), - idVCreationMethod: vocabulary.idVocabulary, + Name: 'Test Model with Nulls', Master: true, Authoritative: true, + DateCreated: UTIL.nowCleansed(), + idVCreationMethod: vocabulary.idVocabulary, idVModality: vocabulary.idVocabulary, idVUnits: vocabulary.idVocabulary, idVPurpose: vocabulary.idVocabulary, + idVFileType: vocabulary.idVocabulary, idAssetThumbnail: null, + idModelMetrics: null, idModel: 0 }); expect(modelNulls).toBeTruthy(); }); - test('DB Creation: ModelGeometryFile', async () => { - if (model && assetThumbnail && vocabulary) - modelGeometryFile = new DBAPI.ModelGeometryFile({ + test('DB Creation: ModelObject', async () => { + if (model && modelMetrics) + modelObject = await UTIL.createModelObjectTest({ idModel: model.idModel, - idAsset: assetThumbnail.idAsset, - idVModelFileType: vocabulary.idVocabulary, - Roughness: 0, Metalness: 0, PointCount: 0, FaceCount: 0, IsWatertight: false, HasNormals: false, HasVertexColor: false, HasUVSpace: false, - BoundingBoxP1X: 0, BoundingBoxP1Y: 0, BoundingBoxP1Z: 0, BoundingBoxP2X: 0, BoundingBoxP2Y: 0, BoundingBoxP2Z: 0, - idModelGeometryFile: 0 + idModelMetrics: modelMetrics.idModelMetrics, + idModelObject: 0 }); - expect(modelGeometryFile).toBeTruthy(); - if (modelGeometryFile) { - expect(await modelGeometryFile.create()).toBeTruthy(); - expect(modelGeometryFile.idModelGeometryFile).toBeGreaterThan(0); - } + expect(modelObject).toBeTruthy(); + + if (model && modelMetrics2) + modelObject2 = await UTIL.createModelObjectTest({ + idModel: model.idModel, + idModelMetrics: modelMetrics2.idModelMetrics, + idModelObject: 0 + }); + expect(modelObject2).toBeTruthy(); + + if (model) + modelObject3 = await UTIL.createModelObjectTest({ + idModel: model.idModel, + idModelMetrics: 0, + idModelObject: 0 + }); + expect(modelObject3).toBeTruthy(); + }); - test('DB Creation: ModelGeometryFile With Nulls', async () => { - if (model && assetThumbnail && vocabulary) - modelGeometryFileNulls = new DBAPI.ModelGeometryFile({ + test('DB Creation: ModelMaterial', async () => { + if (modelObject) + modelMaterial = await UTIL.createModelMaterialTest({ + idModelObject: modelObject.idModelObject, + Name: 'Test ModelMaterial', + idModelMaterial: 0 + }); + expect(modelMaterial).toBeTruthy(); + }); + + test('DB Creation: ModelMaterialUVMap', async () => { + if (model && assetThumbnail) + modelMaterialUVMap = await UTIL.createModelMaterialUVMapTest({ idModel: model.idModel, idAsset: assetThumbnail.idAsset, - idVModelFileType: vocabulary.idVocabulary, - Roughness: null, Metalness: null, PointCount: null, FaceCount: null, IsWatertight: null, HasNormals: null, HasVertexColor: null, HasUVSpace: null, - BoundingBoxP1X: null, BoundingBoxP1Y: null, BoundingBoxP1Z: null, BoundingBoxP2X: null, BoundingBoxP2Y: null, BoundingBoxP2Z: null, - idModelGeometryFile: 0 + UVMapEdgeLength: 1000, + idModelMaterialUVMap: 0 }); - expect(modelGeometryFileNulls).toBeTruthy(); - if (modelGeometryFileNulls) { - expect(await modelGeometryFileNulls.create()).toBeTruthy(); - expect(modelGeometryFileNulls.idModelGeometryFile).toBeGreaterThan(0); - } + expect(modelMaterialUVMap).toBeTruthy(); + }); + + test('DB Creation: ModelMaterialChannel', async () => { + if (modelMaterial && modelMaterialUVMap && vocabulary) + modelMaterialChannel = await UTIL.createModelMaterialChannelTest({ + idModelMaterial: modelMaterial.idModelMaterial, + idVMaterialType: vocabulary.idVocabulary, + MaterialTypeOther: 'Model Material Type', + idModelMaterialUVMap: modelMaterialUVMap.idModelMaterialUVMap, + ChannelPosition: 0, + ChannelWidth: 1, + Scalar1: null, + Scalar2: null, + Scalar3: null, + Scalar4: null, + idModelMaterialChannel: 0 + }); + expect(modelMaterialChannel).toBeTruthy(); + + if (modelMaterial && modelMaterialUVMap && vocabulary) + modelMaterialChannelNulls = await UTIL.createModelMaterialChannelTest({ + idModelMaterial: modelMaterial.idModelMaterial, + idVMaterialType: null, + MaterialTypeOther: 'Model Material Type', + idModelMaterialUVMap: null, + ChannelPosition: 0, + ChannelWidth: 1, + Scalar1: null, + Scalar2: null, + Scalar3: null, + Scalar4: null, + idModelMaterialChannel: 0 + }); + expect(modelMaterialChannelNulls).toBeTruthy(); }); test('DB Creation: ModelProcessingAction', async () => { @@ -1083,36 +1179,6 @@ describe('DB Creation Test Suite', () => { expect(systemObjectXrefUnitProject2.idSystemObjectXref).toBeGreaterThan(0); }); - test('DB Creation: ModelUVMapFile', async () => { - if (modelGeometryFile && assetThumbnail) - modelUVMapFile = new DBAPI.ModelUVMapFile({ - idModelGeometryFile: modelGeometryFile.idModelGeometryFile, - idAsset: assetThumbnail.idAsset, - UVMapEdgeLength: 0, - idModelUVMapFile: 0 - }); - expect(modelUVMapFile).toBeTruthy(); - if (modelUVMapFile) { - expect(await modelUVMapFile.create()).toBeTruthy(); - expect(modelUVMapFile.idModelUVMapFile).toBeGreaterThan(0); - } - }); - - test('DB Creation: ModelUVMapChannel', async () => { - if (modelUVMapFile && vocabulary) - modelUVMapChannel = new DBAPI.ModelUVMapChannel({ - idModelUVMapFile: modelUVMapFile.idModelUVMapFile, - ChannelPosition: 0, ChannelWidth: 1, - idVUVMapType: vocabulary.idVocabulary, - idModelUVMapChannel: 0 - }); - expect(modelUVMapChannel).toBeTruthy(); - if (modelUVMapChannel) { - expect(await modelUVMapChannel.create()).toBeTruthy(); - expect(modelUVMapChannel.idModelUVMapChannel).toBeGreaterThan(0); - } - }); - test('DB Creation: License', async () => { license = new DBAPI.License({ Name: 'Test License', @@ -1127,10 +1193,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 +1255,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 +1270,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 +1285,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 +1319,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 +1510,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 +1667,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 +1710,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 +1721,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 +1732,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 +1743,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 +1754,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 +1960,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 +1994,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])); } @@ -1960,29 +2026,64 @@ describe('DB Fetch By ID Test Suite', () => { expect(modelFetch).toBeTruthy(); }); - test('DB Fetch By ID: ModelGeometryFile', async () => { - let modelGeometryFileFetch: DBAPI.ModelGeometryFile | null = null; - if (modelGeometryFile) { - modelGeometryFileFetch = await DBAPI.ModelGeometryFile.fetch(modelGeometryFile.idModelGeometryFile); - if (modelGeometryFileFetch) { - expect(modelGeometryFileFetch).toMatchObject(modelGeometryFile); - expect(modelGeometryFile).toMatchObject(modelGeometryFileFetch); + test('DB Fetch By ID: ModelMaterial', async () => { + let modelMaterialFetch: DBAPI.ModelMaterial | null = null; + if (modelMaterial) { + modelMaterialFetch = await DBAPI.ModelMaterial.fetch(modelMaterial.idModelMaterial); + if (modelMaterialFetch) { + expect(modelMaterialFetch).toMatchObject(modelMaterial); + expect(modelMaterial).toMatchObject(modelMaterialFetch); } } - expect(modelGeometryFileFetch).toBeTruthy(); + expect(modelMaterialFetch).toBeTruthy(); }); - test('DB Fetch ModelGeometryFile: ModelGeometryFile.fetchFromModel', async () => { - let modelGeometryFileFetch: DBAPI.ModelGeometryFile[] | null = null; - if (model) { - modelGeometryFileFetch = await DBAPI.ModelGeometryFile.fetchFromModel(model.idModel); - if (modelGeometryFileFetch) { - if (modelGeometryFile) { - expect(modelGeometryFileFetch).toEqual(expect.arrayContaining([modelGeometryFile])); - } + test('DB Fetch By ID: ModelMaterialChannel', async () => { + let modelMaterialChannelFetch: DBAPI.ModelMaterialChannel | null = null; + if (modelMaterialChannel) { + modelMaterialChannelFetch = await DBAPI.ModelMaterialChannel.fetch(modelMaterialChannel.idModelMaterialChannel); + if (modelMaterialChannelFetch) { + expect(modelMaterialChannelFetch).toMatchObject(modelMaterialChannel); + expect(modelMaterialChannel).toMatchObject(modelMaterialChannelFetch); + } + } + expect(modelMaterialChannelFetch).toBeTruthy(); + }); + + test('DB Fetch By ID: ModelMaterialUVMap', async () => { + let modelMaterialUVMapFetch: DBAPI.ModelMaterialUVMap | null = null; + if (modelMaterialUVMap) { + modelMaterialUVMapFetch = await DBAPI.ModelMaterialUVMap.fetch(modelMaterialUVMap.idModelMaterialUVMap); + if (modelMaterialUVMapFetch) { + expect(modelMaterialUVMapFetch).toMatchObject(modelMaterialUVMap); + expect(modelMaterialUVMap).toMatchObject(modelMaterialUVMapFetch); + } + } + expect(modelMaterialUVMapFetch).toBeTruthy(); + }); + + test('DB Fetch By ID: ModelMetrics', async () => { + let modelMetricsFetch: DBAPI.ModelMetrics | null = null; + if (modelMetrics) { + modelMetricsFetch = await DBAPI.ModelMetrics.fetch(modelMetrics.idModelMetrics); + if (modelMetricsFetch) { + expect(modelMetricsFetch).toMatchObject(modelMetrics); + expect(modelMetrics).toMatchObject(modelMetricsFetch); } } - expect(modelGeometryFileFetch).toBeTruthy(); + expect(modelMetricsFetch).toBeTruthy(); + }); + + test('DB Fetch By ID: ModelObject', async () => { + let modelObjectFetch: DBAPI.ModelObject | null = null; + if (modelObject) { + modelObjectFetch = await DBAPI.ModelObject.fetch(modelObject.idModelObject); + if (modelObjectFetch) { + expect(modelObjectFetch).toMatchObject(modelObject); + expect(modelObject).toMatchObject(modelObjectFetch); + } + } + expect(modelObjectFetch).toBeTruthy(); }); test('DB Fetch By ID: ModelProcessingAction', async () => { @@ -2067,52 +2168,6 @@ describe('DB Fetch By ID Test Suite', () => { expect(modelSceneXrefFetch).toBeTruthy(); }); - test('DB Fetch By ID: ModelUVMapChannel', async () => { - let modelUVMapChannelFetch: DBAPI.ModelUVMapChannel | null = null; - if (modelUVMapChannel) { - modelUVMapChannelFetch = await DBAPI.ModelUVMapChannel.fetch(modelUVMapChannel.idModelUVMapChannel); - if (modelUVMapChannelFetch) { - expect(modelUVMapChannelFetch).toMatchObject(modelUVMapChannel); - expect(modelUVMapChannel).toMatchObject(modelUVMapChannelFetch); - } - } - expect(modelUVMapChannelFetch).toBeTruthy(); - }); - - test('DB Fetch ModelUVMapChannel: ModelUVMapChannel.fetchFromModelUVMapFile', async () => { - let modelUVMapChannelFetch: DBAPI.ModelUVMapChannel[] | null = null; - if (modelUVMapFile) { - modelUVMapChannelFetch = await DBAPI.ModelUVMapChannel.fetchFromModelUVMapFile(modelUVMapFile.idModelUVMapFile); - 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) { - modelUVMapFileFetch = await DBAPI.ModelUVMapFile.fetch(modelUVMapFile.idModelUVMapFile); - if (modelUVMapFileFetch) { - expect(modelUVMapFileFetch).toMatchObject(modelUVMapFile); - expect(modelUVMapFile).toMatchObject(modelUVMapFileFetch); - } - } - expect(modelUVMapFileFetch).toBeTruthy(); - }); - - test('DB Fetch ModelUVMapFile: ModelUVMapFile.fetchFromModelGeometryFile', async () => { - let modelUVMapFileFetch: DBAPI.ModelUVMapFile[] | null = null; - if (modelGeometryFile) { - modelUVMapFileFetch = await DBAPI.ModelUVMapFile.fetchFromModelGeometryFile(modelGeometryFile.idModelGeometryFile); - 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) { @@ -2282,11 +2337,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 +2349,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 +2371,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 +2405,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 +2495,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 +2531,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])); } @@ -3291,6 +3346,42 @@ 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'); + }); + + 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', () => { @@ -3478,7 +3569,17 @@ 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: 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) eVocabID = await assetThumbnail.assetType(); @@ -3486,7 +3587,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 +3596,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(); @@ -3511,15 +3612,24 @@ 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 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(); @@ -3527,6 +3637,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) @@ -3551,6 +3671,36 @@ 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: 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(); @@ -3568,6 +3718,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) { @@ -3582,6 +3742,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) { @@ -3651,6 +3821,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) { @@ -3665,6 +3845,124 @@ describe('DB Fetch Special Test Suite', () => { expect(modelFetch).toBeTruthy(); }); + test('DB Fetch Special: ModelConstellation', async () => { + let modelConstellation1: DBAPI.ModelConstellation | null = null; + let modelConstellation2: DBAPI.ModelConstellation | null = null; + + if (model) { + modelConstellation1 = await DBAPI.ModelConstellation.fetch(model.idModel); + if (modelConstellation1) { + expect(modelConstellation1.model).toEqual(model); + expect(modelConstellation1.modelObjects).toEqual(expect.arrayContaining([modelObject])); + expect(modelConstellation1.modelMaterials).toEqual(expect.arrayContaining([modelMaterial])); + expect(modelConstellation1.modelMaterialChannels).toEqual(expect.arrayContaining([modelMaterialChannel])); + expect(modelConstellation1.modelMaterialUVMaps).toEqual(expect.arrayContaining([modelMaterialUVMap])); + if (modelMetrics) + expect(modelConstellation1.modelMetric).toEqual(modelMetrics); + expect(modelConstellation1.modelObjectMetrics).toEqual(expect.arrayContaining([modelMetrics])); + } + } + expect(modelConstellation1).toBeTruthy(); + + if (modelNulls) { + modelConstellation2 = await DBAPI.ModelConstellation.fetch(modelNulls.idModel); + if (modelConstellation2) { + expect(modelConstellation2.model).toEqual(modelNulls); + expect(modelConstellation2.modelObjects).toEqual([]); + expect(modelConstellation2.modelMaterials).toBeFalsy(); + expect(modelConstellation2.modelMaterialChannels).toBeFalsy(); + expect(modelConstellation2.modelMaterialUVMaps).toEqual([]); + expect(modelConstellation2.modelMetric).toBeFalsy(); + expect(modelConstellation2.modelObjectMetrics).toBeFalsy(); + } + } + expect(modelConstellation2).toBeTruthy(); + }); + + test('DB Fetch Special: ModelMaterial.fetchFromModelObjects', async () => { + let modelMaterials: DBAPI.ModelMaterial[] | null = null; + if (modelObject) { + modelMaterials = await DBAPI.ModelMaterial.fetchFromModelObjects([modelObject]); + if (modelMaterials) { + expect(modelMaterials.length).toEqual(1); + expect(modelMaterials).toEqual(expect.arrayContaining([modelMaterial])); + } + } + expect(modelMaterials).toBeTruthy(); + }); + + test('DB Fetch Special: ModelMaterialChannel.fetchFromModelMaterial', async () => { + let modelMaterialChannels: DBAPI.ModelMaterialChannel[] | null = null; + if (modelMaterial) { + modelMaterialChannels = await DBAPI.ModelMaterialChannel.fetchFromModelMaterial(modelMaterial.idModelMaterial); + if (modelMaterialChannels) { + expect(modelMaterialChannels.length).toEqual(2); + expect(modelMaterialChannels).toEqual(expect.arrayContaining([modelMaterialChannel, modelMaterialChannelNulls])); + } + } + expect(modelMaterialChannels).toBeTruthy(); + }); + + test('DB Fetch Special: ModelMaterialChannel.fetchFromModelMaterials', async () => { + let modelMaterialChannels: DBAPI.ModelMaterialChannel[] | null = null; + if (modelMaterial) { + modelMaterialChannels = await DBAPI.ModelMaterialChannel.fetchFromModelMaterials([modelMaterial]); + if (modelMaterialChannels) { + expect(modelMaterialChannels.length).toEqual(2); + expect(modelMaterialChannels).toEqual(expect.arrayContaining([modelMaterialChannel, modelMaterialChannelNulls])); + } + } + expect(modelMaterialChannels).toBeTruthy(); + }); + + test('DB Fetch Special: ModelMaterialUVMap.fetchFromModel', async () => { + let modelMaterialUVMaps: DBAPI.ModelMaterialUVMap[] | null = null; + if (model) { + modelMaterialUVMaps = await DBAPI.ModelMaterialUVMap.fetchFromModel(model.idModel); + if (modelMaterialUVMaps) { + expect(modelMaterialUVMaps.length).toEqual(1); + expect(modelMaterialUVMaps).toEqual(expect.arrayContaining([modelMaterialUVMap])); + } + } + expect(modelMaterialUVMaps).toBeTruthy(); + }); + + test('DB Fetch Special: ModelMaterialUVMap.fetchFromModels', async () => { + let modelMaterialUVMaps: DBAPI.ModelMaterialUVMap[] | null = null; + if (model) { + modelMaterialUVMaps = await DBAPI.ModelMaterialUVMap.fetchFromModels([model]); + if (modelMaterialUVMaps) { + expect(modelMaterialUVMaps.length).toEqual(1); + expect(modelMaterialUVMaps).toEqual(expect.arrayContaining([modelMaterialUVMap])); + } + } + expect(modelMaterialUVMaps).toBeTruthy(); + }); + + test('DB Fetch Special: ModelMetrics.fetchFromModelObjects', async () => { + let modelMetricsList: DBAPI.ModelMetrics[] | null = null; + if (modelObject) { + modelMetricsList = await DBAPI.ModelMetrics.fetchFromModelObjects([modelObject]); + if (modelMetricsList) { + expect(modelMetricsList.length).toEqual(1); + expect(modelMetricsList).toEqual(expect.arrayContaining([modelMetrics])); + } + } + expect(modelMetricsList).toBeTruthy(); + }); + + test('DB Fetch Special: ModelObject.fetchFromModel', async () => { + let modelObjects: DBAPI.ModelObject[] | null = null; + if (model) { + modelObjects = await DBAPI.ModelObject.fetchFromModel(model.idModel); + if (modelObjects) { + expect(modelObjects.length).toEqual(3); + expect(modelObjects).toEqual(expect.arrayContaining([modelObject, modelObject2, modelObject3])); + } + } + expect(modelObjects).toBeTruthy(); + }); + test('DB Fetch Special: Project.fetchMasterFromSubjects', async () => { let projectFetch: DBAPI.Project[] | null = null; if (subject && subjectNulls) { @@ -3715,6 +4013,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) { @@ -3725,6 +4033,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) { @@ -3739,6 +4057,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) { @@ -3749,6 +4077,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(); @@ -3828,6 +4166,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 +4448,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 +4851,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 +4894,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(); @@ -4603,17 +4966,19 @@ describe('DB Update Test Suite', () => { test('DB Update: Model.update', async () => { let bUpdated: boolean = false; - if (modelNulls && assetThumbnail) { + if (modelNulls && assetThumbnail && modelMetrics) { const SOOld: DBAPI.SystemObject | null = await modelNulls.fetchSystemObject(); expect(SOOld).toBeTruthy(); modelNulls.idAssetThumbnail = assetThumbnail.idAsset; + modelNulls.idModelMetrics = modelMetrics.idModelMetrics; bUpdated = await modelNulls.update(); const modelFetch: DBAPI.Model | null = await DBAPI.Model.fetch(modelNulls.idModel); expect(modelFetch).toBeTruthy(); if (modelFetch) { expect(modelFetch.idAssetThumbnail).toBe(assetThumbnail.idAsset); + expect(modelFetch.idModelMetrics).toBe(modelMetrics.idModelMetrics); const SONew: DBAPI.SystemObject | null = await modelFetch.fetchSystemObject(); expect(SONew).toBeTruthy(); @@ -4628,12 +4993,15 @@ describe('DB Update Test Suite', () => { let bUpdated: boolean = false; if (modelNulls) { modelNulls.idAssetThumbnail = null; + modelNulls.idModelMetrics = null; bUpdated = await modelNulls.update(); const modelFetch: DBAPI.Model | null = await DBAPI.Model.fetch(modelNulls.idModel); expect(modelFetch).toBeTruthy(); - if (modelFetch) + if (modelFetch) { expect(modelFetch.idAssetThumbnail).toBeNull(); + expect(modelFetch.idModelMetrics).toBeNull(); + } } expect(bUpdated).toBeTruthy(); }); @@ -4652,17 +5020,138 @@ describe('DB Update Test Suite', () => { expect(bUpdated).toBeTruthy(); }); - test('DB Update: ModelGeometryFile.update', async () => { + test('DB Update: ModelMaterial.update', async () => { + let bUpdated: boolean = false; + if (modelMaterial) { + const updatedName: string = 'Updated ModelMaterial Name'; + modelMaterial.Name = updatedName; + bUpdated = await modelMaterial.update(); + + const modelMaterialFetch: DBAPI.ModelMaterial | null = await DBAPI.ModelMaterial.fetch(modelMaterial.idModelMaterial); + expect(modelMaterialFetch).toBeTruthy(); + if (modelMaterialFetch) + expect(modelMaterialFetch.Name).toBe(updatedName); + } + expect(bUpdated).toBeTruthy(); + }); + + test('DB Update: ModelMaterialChannel.update', async () => { + let bUpdated: boolean = false; + if (modelMaterialChannel) { + const updatedName: string = 'Updated ModelMaterialChannel Material Type'; + modelMaterialChannel.MaterialTypeOther = updatedName; + bUpdated = await modelMaterialChannel.update(); + + const modelMaterialChannelFetch: DBAPI.ModelMaterialChannel | null = await DBAPI.ModelMaterialChannel.fetch(modelMaterialChannel.idModelMaterialChannel); + expect(modelMaterialChannelFetch).toBeTruthy(); + if (modelMaterialChannelFetch) + expect(modelMaterialChannelFetch.MaterialTypeOther).toBe(updatedName); + } + expect(bUpdated).toBeTruthy(); + }); + + test('DB Update: ModelMaterialChannel.update disconnect', async () => { + let bUpdated: boolean = false; + if (modelMaterialChannel) { + modelMaterialChannel.idVMaterialType = null; + modelMaterialChannel.idModelMaterialUVMap = null; + bUpdated = await modelMaterialChannel.update(); + + const modelMaterialChannelFetch: DBAPI.ModelMaterialChannel | null = await DBAPI.ModelMaterialChannel.fetch(modelMaterialChannel.idModelMaterialChannel); + expect(modelMaterialChannelFetch).toBeTruthy(); + if (modelMaterialChannelFetch) { + expect(modelMaterialChannelFetch.idVMaterialType).toBeNull(); + expect(modelMaterialChannelFetch.idModelMaterialUVMap).toBeNull(); + } + } + expect(bUpdated).toBeTruthy(); + }); + + test('DB Update: ModelMaterialChannel.update disconnect null', async () => { + let bUpdated: boolean = false; + if (modelMaterialChannel) { + expect(modelMaterialChannel.idVMaterialType).toBeNull(); + expect(modelMaterialChannel.idModelMaterialUVMap).toBeNull(); + bUpdated = await modelMaterialChannel.update(); + + const modelMaterialChannelFetch: DBAPI.ModelMaterialChannel | null = await DBAPI.ModelMaterialChannel.fetch(modelMaterialChannel.idModelMaterialChannel); + expect(modelMaterialChannelFetch).toBeTruthy(); + if (modelMaterialChannelFetch) { + expect(modelMaterialChannelFetch.idVMaterialType).toBeNull(); + expect(modelMaterialChannelFetch.idModelMaterialUVMap).toBeNull(); + } + } + expect(bUpdated).toBeTruthy(); + }); + + test('DB Update: ModelMaterialUVMap.update', async () => { + let bUpdated: boolean = false; + if (modelMaterialUVMap) { + const updated: number = 369; + modelMaterialUVMap.UVMapEdgeLength = updated; + bUpdated = await modelMaterialUVMap.update(); + + const modelMaterialUVMapFetch: DBAPI.ModelMaterialUVMap | null = await DBAPI.ModelMaterialUVMap.fetch(modelMaterialUVMap.idModelMaterialUVMap); + expect(modelMaterialUVMapFetch).toBeTruthy(); + if (modelMaterialUVMapFetch) + expect(modelMaterialUVMapFetch.UVMapEdgeLength).toBe(updated); + } + expect(bUpdated).toBeTruthy(); + }); + + test('DB Update: ModelMetrics.update', async () => { + let bUpdated: boolean = false; + if (modelMetrics) { + const updated: number = 369; + modelMetrics.CountFace = updated; + bUpdated = await modelMetrics.update(); + + const modelMetricsFetch: DBAPI.ModelMetrics | null = await DBAPI.ModelMetrics.fetch(modelMetrics.idModelMetrics); + expect(modelMetricsFetch).toBeTruthy(); + if (modelMetricsFetch) + expect(modelMetricsFetch.CountFace).toBe(updated); + } + expect(bUpdated).toBeTruthy(); + }); + + test('DB Update: ModelObject.update', async () => { + let bUpdated: boolean = false; + if (modelObject && modelMetrics2) { + modelObject.idModelMetrics = modelMetrics2.idModelMetrics; + bUpdated = await modelObject.update(); + + const modelObjectFetch: DBAPI.ModelObject | null = await DBAPI.ModelObject.fetch(modelObject.idModelObject); + expect(modelObjectFetch).toBeTruthy(); + if (modelObjectFetch) + expect(modelObjectFetch.idModelMetrics).toBe(modelMetrics2.idModelMetrics); + } + expect(bUpdated).toBeTruthy(); + }); + + test('DB Update: ModelObject.update disconnect', async () => { let bUpdated: boolean = false; - if (modelGeometryFileNulls) { - const roughness: number = 2.0; - modelGeometryFileNulls.Roughness = roughness; - bUpdated = await modelGeometryFileNulls.update(); + if (modelObject) { + modelObject.idModelMetrics = null; + bUpdated = await modelObject.update(); - const modelGeometryFileFetch: DBAPI.ModelGeometryFile | null = await DBAPI.ModelGeometryFile.fetch(modelGeometryFileNulls.idModelGeometryFile); - expect(modelGeometryFileFetch).toBeTruthy(); - if (modelGeometryFileFetch) - expect(modelGeometryFileFetch.Roughness).toBe(roughness); + const modelObjectFetch: DBAPI.ModelObject | null = await DBAPI.ModelObject.fetch(modelObject.idModelObject); + expect(modelObjectFetch).toBeTruthy(); + if (modelObjectFetch) + expect(modelObjectFetch.idModelMetrics).toBeNull(); + } + expect(bUpdated).toBeTruthy(); + }); + + test('DB Update: ModelObject.update disconnect null', async () => { + let bUpdated: boolean = false; + if (modelObject) { + expect(modelObject.idModelMetrics).toBeNull(); + bUpdated = await modelObject.update(); + + const modelObjectFetch: DBAPI.ModelObject | null = await DBAPI.ModelObject.fetch(modelObject.idModelObject); + expect(modelObjectFetch).toBeTruthy(); + if (modelObjectFetch) + expect(modelObjectFetch.idModelMetrics).toBeNull(); } expect(bUpdated).toBeTruthy(); }); @@ -4712,36 +5201,6 @@ describe('DB Update Test Suite', () => { expect(bUpdated).toBeTruthy(); }); - test('DB Update: ModelUVMapChannel.update', async () => { - let bUpdated: boolean = false; - if (modelUVMapChannel) { - const updatedWidth: number = 2; - modelUVMapChannel.ChannelWidth = updatedWidth; - bUpdated = await modelUVMapChannel.update(); - - const modelUVMapChannelFetch: DBAPI.ModelUVMapChannel | null = await DBAPI.ModelUVMapChannel.fetch(modelUVMapChannel.idModelUVMapChannel); - expect(modelUVMapChannelFetch).toBeTruthy(); - if (modelUVMapChannelFetch) - expect(modelUVMapChannelFetch.ChannelWidth).toBe(updatedWidth); - } - expect(bUpdated).toBeTruthy(); - }); - - test('DB Update: ModelUVMapFile.update', async () => { - let bUpdated: boolean = false; - if (modelUVMapFile) { - const updatedEdgeLen: number = 3; - modelUVMapFile.UVMapEdgeLength = updatedEdgeLen; - bUpdated = await modelUVMapFile.update(); - - const modelUVMapFileFetch: DBAPI.ModelUVMapFile | null = await DBAPI.ModelUVMapFile.fetch(modelUVMapFile.idModelUVMapFile); - expect(modelUVMapFileFetch).toBeTruthy(); - if (modelUVMapFileFetch) - expect(modelUVMapFileFetch.UVMapEdgeLength).toBe(updatedEdgeLen); - } - expect(bUpdated).toBeTruthy(); - }); - test('DB Update: Project.update', async () => { let bUpdated: boolean = false; if (project) { @@ -5090,12 +5549,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 +5562,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) { @@ -5344,6 +5840,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(); @@ -5375,8 +5872,19 @@ 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.ModelGeometryFile.fetch(0)).toBeNull(); - expect(await DBAPI.ModelGeometryFile.fetchFromModel(0)).toBeNull(); + expect(await DBAPI.ModelMaterial.fetch(0)).toBeNull(); + expect(await DBAPI.ModelMaterial.fetchFromModelObjects([])).toBeNull(); + expect(await DBAPI.ModelMaterialChannel.fetch(0)).toBeNull(); + expect(await DBAPI.ModelMaterialChannel.fetchFromModelMaterial(0)).toBeNull(); + expect(await DBAPI.ModelMaterialChannel.fetchFromModelMaterials([])).toBeNull(); + expect(await DBAPI.ModelMaterialUVMap.fetch(0)).toBeNull(); + expect(await DBAPI.ModelMaterialUVMap.fetchFromModel(0)).toBeNull(); + expect(await DBAPI.ModelMaterialUVMap.fetchFromModels([])).toBeNull(); + expect(await DBAPI.ModelMetrics.fetch(0)).toBeNull(); + expect(await DBAPI.ModelMetrics.fetchFromModelObjects([])).toBeNull(); + expect(await DBAPI.ModelObject.fetch(0)).toBeNull(); + expect(await DBAPI.ModelObject.fetchFromModel(0)).toBeNull(); + expect(await DBAPI.ModelConstellation.fetch(0)).toBeNull(); expect(await DBAPI.ModelProcessingAction.fetch(0)).toBeNull(); expect(await DBAPI.ModelProcessingAction.fetchFromModel(0)).toBeNull(); expect(await DBAPI.ModelProcessingActionStep.fetch(0)).toBeNull(); @@ -5384,10 +5892,6 @@ describe('DB Null/Zero ID Test', () => { expect(await DBAPI.ModelSceneXref.fetch(0)).toBeNull(); expect(await DBAPI.ModelSceneXref.fetchFromScene(0)).toBeNull(); expect(await DBAPI.ModelSceneXref.fetchFromModel(0)).toBeNull(); - expect(await DBAPI.ModelUVMapChannel.fetch(0)).toBeNull(); - expect(await DBAPI.ModelUVMapChannel.fetchFromModelUVMapFile(0)).toBeNull(); - expect(await DBAPI.ModelUVMapFile.fetch(0)).toBeNull(); - expect(await DBAPI.ModelUVMapFile.fetchFromModelGeometryFile(0)).toBeNull(); expect(await DBAPI.Project.fetch(0)).toBeNull(); expect(await DBAPI.Project.fetchDerivedFromUnits([])).toBeNull(); expect(await DBAPI.Project.fetchMasterFromSubjects([])).toBeNull(); 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/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/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; 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..d46ddf2a8 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 }; }; @@ -105,17 +106,20 @@ class TestSuiteUtils { createModelInput = (idVocabulary: number): CreateModelInput => { return { + Name: 'Test Name', Authoritative: true, idVCreationMethod: idVocabulary, idVModality: idVocabulary, idVPurpose: idVocabulary, idVUnits: idVocabulary, + idVFileType: idVocabulary, Master: true }; }; createCaptureDataInput = (idVocabulary: number): CreateCaptureDataInput => { return { + Name: 'Test Name', idVCaptureMethod: idVocabulary, DateCaptured: new Date(), Description: 'Test Description' diff --git a/server/tests/jest.config.js b/server/tests/jest.config.js index 9ec1a2711..d8c962b78 100644 --- a/server/tests/jest.config.js +++ b/server/tests/jest.config.js @@ -21,9 +21,12 @@ module.exports = { // '**/tests/utils/parser/bulkIngestReader.test.ts', // Individual tests, left here to aid in quick, focused testing: + // '**/tests/auth/local/login.test.ts', + // '**/tests/auth/local/logout.test.ts', // '**/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', diff --git a/server/tests/navigation/impl/NavigationDB.test.ts b/server/tests/navigation/impl/NavigationDB.test.ts index d20159144..0cdd0b27f 100644 --- a/server/tests/navigation/impl/NavigationDB.test.ts +++ b/server/tests/navigation/impl/NavigationDB.test.ts @@ -5,10 +5,12 @@ import * as LOG from '../../../utils/logger'; import * as CACHE from '../../../cache'; import { eSystemObjectType, SystemObjectBased, SystemObject } from '../../../db'; import { ObjectGraphTestSetup } from '../../db/composite/ObjectGraph.setup'; +import { NAVIGATION_TYPE } from '../../../config'; + let nav: INavigation | null = null; const OHTS: ObjectGraphTestSetup = new ObjectGraphTestSetup(); -const metadataColumns: eMetadata[] = [ eMetadata.eUnitAbbreviation, eMetadata.eSubjectIdentifier, eMetadata.eItemName ]; +const metadataColumns: eMetadata[] = [eMetadata.eHierarchyUnit, eMetadata.eHierarchySubject, eMetadata.eHierarchyItem]; LOG; afterAll(async done => { @@ -18,11 +20,11 @@ afterAll(async done => { }); describe('Navigation Init', () => { - test('Navigation Test Setup', async() => { + test('Navigation Test Setup', async () => { await OHTS.initialize(); await OHTS.wire(); - nav = await NavigationFactory.getInstance(); + nav = await NavigationFactory.getInstance(NAVIGATION_TYPE.DB); expect(nav).toBeTruthy(); nav = await NavigationFactory.getInstance(); @@ -30,48 +32,67 @@ describe('Navigation Init', () => { }); }); +const mockFilter: NavigationFilter = { + idRoot: 0, + objectTypes: [], + metadataColumns: [], + objectsToDisplay: [], + search: '', + units: [], + projects: [], + has: [], + missing: [], + captureMethod: [], + variantType: [], + modelPurpose: [], + modelFileType: [], + rows: 100, + cursorMark: '' +}; + describe('Navigation Traversal', () => { test('Navigation Root', async () => { - await testNavigation({ idRoot: 0, objectTypes: [ eSystemObjectType.eUnit ], metadataColumns }); - await testNavigation({ idRoot: 0, objectTypes: [ eSystemObjectType.eProject ], metadataColumns }); + + await testNavigation({ ...mockFilter, idRoot: 0, objectTypes: [eSystemObjectType.eUnit], metadataColumns }); + await testNavigation({ ...mockFilter, idRoot: 0, objectTypes: [eSystemObjectType.eProject], metadataColumns }); // Not yet implemented: - await testNavigation({ idRoot: 0, objectTypes: [ eSystemObjectType.eSubject ], metadataColumns }, false); - await testNavigation({ idRoot: 0, objectTypes: [ eSystemObjectType.eItem ], metadataColumns }, false); - await testNavigation({ idRoot: 0, objectTypes: [ eSystemObjectType.eCaptureData ], metadataColumns }, false); - await testNavigation({ idRoot: 0, objectTypes: [ eSystemObjectType.eModel ], metadataColumns }, false); - await testNavigation({ idRoot: 0, objectTypes: [ eSystemObjectType.eScene ], metadataColumns }, false); - await testNavigation({ idRoot: 0, objectTypes: [ eSystemObjectType.eIntermediaryFile ], metadataColumns }, false); - await testNavigation({ idRoot: 0, objectTypes: [ eSystemObjectType.eProjectDocumentation ], metadataColumns }, false); - await testNavigation({ idRoot: 0, objectTypes: [ eSystemObjectType.eAsset ], metadataColumns }, false); - await testNavigation({ idRoot: 0, objectTypes: [ eSystemObjectType.eAssetVersion ], metadataColumns }, false); - await testNavigation({ idRoot: 0, objectTypes: [ eSystemObjectType.eActor ], metadataColumns }, false); - await testNavigation({ idRoot: 0, objectTypes: [ eSystemObjectType.eStakeholder ], metadataColumns }, false); - await testNavigation({ idRoot: 0, objectTypes: [ eSystemObjectType.eUnknown ], metadataColumns }, false); + await testNavigation({ ...mockFilter, idRoot: 0, objectTypes: [eSystemObjectType.eSubject], metadataColumns }, false); + await testNavigation({ ...mockFilter, idRoot: 0, objectTypes: [eSystemObjectType.eItem], metadataColumns }, false); + await testNavigation({ ...mockFilter, idRoot: 0, objectTypes: [eSystemObjectType.eCaptureData], metadataColumns }, false); + await testNavigation({ ...mockFilter, idRoot: 0, objectTypes: [eSystemObjectType.eModel], metadataColumns }, false); + await testNavigation({ ...mockFilter, idRoot: 0, objectTypes: [eSystemObjectType.eScene], metadataColumns }, false); + await testNavigation({ ...mockFilter, idRoot: 0, objectTypes: [eSystemObjectType.eIntermediaryFile], metadataColumns }, false); + await testNavigation({ ...mockFilter, idRoot: 0, objectTypes: [eSystemObjectType.eProjectDocumentation], metadataColumns }, false); + await testNavigation({ ...mockFilter, idRoot: 0, objectTypes: [eSystemObjectType.eAsset], metadataColumns }, false); + await testNavigation({ ...mockFilter, idRoot: 0, objectTypes: [eSystemObjectType.eAssetVersion], metadataColumns }, false); + await testNavigation({ ...mockFilter, idRoot: 0, objectTypes: [eSystemObjectType.eActor], metadataColumns }, false); + await testNavigation({ ...mockFilter, idRoot: 0, objectTypes: [eSystemObjectType.eStakeholder], metadataColumns }, false); + await testNavigation({ ...mockFilter, idRoot: 0, objectTypes: [eSystemObjectType.eUnknown], metadataColumns }, false); }); test('Navigation Children', async () => { - await testNavigation({ idRoot: await getSOID(OHTS.unit1), objectTypes: [ eSystemObjectType.eSubject ], metadataColumns }); - await testNavigation({ idRoot: await getSOID(OHTS.project1), objectTypes: [ eSystemObjectType.eSubject ], metadataColumns }); - await testNavigation({ idRoot: await getSOID(OHTS.subject1), objectTypes: [ eSystemObjectType.eItem ], metadataColumns }); - await testNavigation({ idRoot: await getSOID(OHTS.item1), objectTypes: [ eSystemObjectType.eCaptureData, eSystemObjectType.eModel, eSystemObjectType.eScene ], metadataColumns }); + await testNavigation({ ...mockFilter, idRoot: await getSOID(OHTS.unit1), objectTypes: [eSystemObjectType.eSubject], metadataColumns }); + await testNavigation({ ...mockFilter, idRoot: await getSOID(OHTS.project1), objectTypes: [eSystemObjectType.eSubject], metadataColumns }); + await testNavigation({ ...mockFilter, idRoot: await getSOID(OHTS.subject1), objectTypes: [eSystemObjectType.eItem], metadataColumns }); + await testNavigation({ ...mockFilter, idRoot: await getSOID(OHTS.item1), objectTypes: [eSystemObjectType.eCaptureData, eSystemObjectType.eModel, eSystemObjectType.eScene], metadataColumns }); // Not yet implemented: - await testNavigation({ idRoot: await getSOID(OHTS.captureData1), objectTypes: [ eSystemObjectType.eAsset ], metadataColumns }, false); - await testNavigation({ idRoot: await getSOID(OHTS.model1), objectTypes: [ eSystemObjectType.eAsset ], metadataColumns }, false); - await testNavigation({ idRoot: await getSOID(OHTS.scene1), objectTypes: [ eSystemObjectType.eAsset ], metadataColumns }, false); - await testNavigation({ idRoot: await getSOID(OHTS.projectDocumentation1), objectTypes: [ eSystemObjectType.eAsset ], metadataColumns }, false); - await testNavigation({ idRoot: await getSOID(OHTS.intermediaryFile1), objectTypes: [ eSystemObjectType.eAsset ], metadataColumns }, false); - await testNavigation({ idRoot: await getSOID(OHTS.actor1), objectTypes: [ eSystemObjectType.eAsset ], metadataColumns }, false); - await testNavigation({ idRoot: await getSOID(OHTS.stakeholder1), objectTypes: [ eSystemObjectType.eAsset ], metadataColumns }, false); - await testNavigation({ idRoot: await getSOID(OHTS.asset1), objectTypes: [ eSystemObjectType.eAssetVersion ], metadataColumns }, false); - await testNavigation({ idRoot: await getSOID(OHTS.assetVersion1a), objectTypes: [ eSystemObjectType.eAssetVersion ], metadataColumns }, false); + await testNavigation({ ...mockFilter, idRoot: await getSOID(OHTS.captureData1), objectTypes: [eSystemObjectType.eAsset], metadataColumns }, false); + await testNavigation({ ...mockFilter, idRoot: await getSOID(OHTS.model1), objectTypes: [eSystemObjectType.eAsset], metadataColumns }, false); + await testNavigation({ ...mockFilter, idRoot: await getSOID(OHTS.scene1), objectTypes: [eSystemObjectType.eAsset], metadataColumns }, false); + await testNavigation({ ...mockFilter, idRoot: await getSOID(OHTS.projectDocumentation1), objectTypes: [eSystemObjectType.eAsset], metadataColumns }, false); + await testNavigation({ ...mockFilter, idRoot: await getSOID(OHTS.intermediaryFile1), objectTypes: [eSystemObjectType.eAsset], metadataColumns }, false); + await testNavigation({ ...mockFilter, idRoot: await getSOID(OHTS.actor1), objectTypes: [eSystemObjectType.eAsset], metadataColumns }, false); + await testNavigation({ ...mockFilter, idRoot: await getSOID(OHTS.stakeholder1), objectTypes: [eSystemObjectType.eAsset], metadataColumns }, false); + await testNavigation({ ...mockFilter, idRoot: await getSOID(OHTS.asset1), objectTypes: [eSystemObjectType.eAssetVersion], metadataColumns }, false); + await testNavigation({ ...mockFilter, idRoot: await getSOID(OHTS.assetVersion1a), objectTypes: [eSystemObjectType.eAssetVersion], metadataColumns }, false); }); test('Navigation Invalid', async () => { - await testNavigation({ idRoot: 100000000000, objectTypes: [ eSystemObjectType.eSubject ], metadataColumns }, false); + await testNavigation({ ...mockFilter, idRoot: 100000000000, objectTypes: [eSystemObjectType.eSubject], metadataColumns }, false); }); }); @@ -110,7 +131,7 @@ function validateResult(navResult: NavigationResult, expectSuccess: boolean = tr async function validateResultEntries(navResultEntries: NavigationResultEntry[], objectTypes: eSystemObjectType[]): Promise { for (const NRE of navResultEntries) { // LOG.logger.info(`${JSON.stringify(NRE)}`); - expect(objectTypes).toEqual(expect.arrayContaining([ NRE.objectType ])); + expect(objectTypes).toEqual(expect.arrayContaining([NRE.objectType])); expect(NRE.metadata.length).toEqual(metadataColumns.length); const oID: CACHE.ObjectIDAndType | undefined = await CACHE.SystemObjectCache.getObjectFromSystem(NRE.idSystemObject); diff --git a/server/types/graphql.ts b/server/types/graphql.ts index 4ac0cbffc..e5c333dce 100644 --- a/server/types/graphql.ts +++ b/server/types/graphql.ts @@ -17,11 +17,14 @@ export type Query = { areCameraSettingsUniform: AreCameraSettingsUniformResult; getAccessPolicy: GetAccessPolicyResult; getAsset: GetAssetResult; + getAssetDetailsForSystemObject: GetAssetDetailsForSystemObjectResult; getAssetVersionsDetails: GetAssetVersionsDetailsResult; getCaptureData: GetCaptureDataResult; getCaptureDataPhoto: GetCaptureDataPhotoResult; getContentsForAssetVersions: GetContentsForAssetVersionsResult; getCurrentUser: GetCurrentUserResult; + getDetailsTabDataForObject: GetDetailsTabDataForObjectResult; + getFilterViewData: GetFilterViewDataResult; getIngestionItemsForSubjects: GetIngestionItemsForSubjectsResult; getIngestionProjectsForSubjects: GetIngestionProjectsForSubjectsResult; getIntermediaryFile: GetIntermediaryFileResult; @@ -34,11 +37,14 @@ export type Query = { getProject: GetProjectResult; getProjectDocumentation: GetProjectDocumentationResult; getScene: GetSceneResult; + getSourceObjectIdentifer: GetSourceObjectIdentiferResult; getSubject: GetSubjectResult; getSubjectsForUnit: GetSubjectsForUnitResult; + getSystemObjectDetails: GetSystemObjectDetailsResult; getUnit: GetUnitResult; getUploadedAssetVersion: GetUploadedAssetVersionResult; getUser: GetUserResult; + getVersionsForSystemObject: GetVersionsForSystemObjectResult; getVocabulary: GetVocabularyResult; getVocabularyEntries: GetVocabularyEntriesResult; getWorkflow: GetWorkflowResult; @@ -57,6 +63,10 @@ export type QueryGetAssetArgs = { input: GetAssetInput; }; +export type QueryGetAssetDetailsForSystemObjectArgs = { + input: GetAssetDetailsForSystemObjectInput; +}; + export type QueryGetAssetVersionsDetailsArgs = { input: GetAssetVersionsDetailsInput; }; @@ -73,6 +83,10 @@ export type QueryGetContentsForAssetVersionsArgs = { input: GetContentsForAssetVersionsInput; }; +export type QueryGetDetailsTabDataForObjectArgs = { + input: GetDetailsTabDataForObjectInput; +}; + export type QueryGetIngestionItemsForSubjectsArgs = { input: GetIngestionItemsForSubjectsInput; }; @@ -121,6 +135,10 @@ export type QueryGetSceneArgs = { input: GetSceneInput; }; +export type QueryGetSourceObjectIdentiferArgs = { + input: GetSourceObjectIdentiferInput; +}; + export type QueryGetSubjectArgs = { input: GetSubjectInput; }; @@ -129,6 +147,10 @@ export type QueryGetSubjectsForUnitArgs = { input: GetSubjectsForUnitInput; }; +export type QueryGetSystemObjectDetailsArgs = { + input: GetSystemObjectDetailsInput; +}; + export type QueryGetUnitArgs = { input: GetUnitInput; }; @@ -137,6 +159,10 @@ export type QueryGetUserArgs = { input: GetUserInput; }; +export type QueryGetVersionsForSystemObjectArgs = { + input: GetVersionsForSystemObjectInput; +}; + export type QueryGetVocabularyArgs = { input: GetVocabularyInput; }; @@ -225,6 +251,7 @@ export type Mutation = { createVocabularySet: CreateVocabularySetResult; discardUploadedAssetVersions: DiscardUploadedAssetVersionsResult; ingestData: IngestDataResult; + updateObjectDetails: UpdateObjectDetailsResult; uploadAsset: UploadAssetResult; }; @@ -280,6 +307,10 @@ export type MutationIngestDataArgs = { input: IngestDataInput; }; +export type MutationUpdateObjectDetailsArgs = { + input: UpdateObjectDetailsInput; +}; + export type MutationUploadAssetArgs = { file: Scalars['Upload']; type: Scalars['Int']; @@ -350,17 +381,84 @@ export type IngestPhotogrammetry = { identifiers: Array; }; +export type IngestUvMap = { + __typename?: 'IngestUVMap'; + name: Scalars['String']; + edgeLength: Scalars['Int']; + mapType: Scalars['Int']; +}; + +export enum RelatedObjectType { + Source = 'Source', + Derived = 'Derived' +} + +export type RelatedObject = { + __typename?: 'RelatedObject'; + idSystemObject: Scalars['Int']; + name: Scalars['String']; + identifier?: Maybe; + objectType: 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; + sourceObjects: 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 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 = { @@ -371,6 +469,7 @@ export type GetAssetVersionDetailResult = { Item?: Maybe; CaptureDataPhoto?: Maybe; Model?: Maybe; + Scene?: Maybe; }; export type GetAssetVersionsDetailsResult = { @@ -415,6 +514,7 @@ export type Asset = { FileName: Scalars['String']; FilePath: Scalars['String']; idAssetGroup?: Maybe; + idVAssetType?: Maybe; idSystemObject?: Maybe; StorageKey?: Maybe; AssetGroup?: Maybe; @@ -448,6 +548,7 @@ export type AssetGroup = { }; export type CreateCaptureDataInput = { + Name: Scalars['String']; idVCaptureMethod: Scalars['Int']; DateCaptured: Scalars['DateTime']; Description: Scalars['String']; @@ -508,6 +609,7 @@ export type CaptureData = { VCaptureMethod?: Maybe; CaptureDataFile?: Maybe>>; CaptureDataGroup?: Maybe>>; + CaptureDataPhoto?: Maybe>>; SystemObject?: Maybe; }; @@ -602,16 +704,75 @@ export type IngestPhotogrammetryInput = { identifiers: Array; }; +export type IngestUvMapInput = { + name: Scalars['String']; + edgeLength: Scalars['Int']; + mapType: Scalars['Int']; +}; + +export type RelatedObjectInput = { + idSystemObject: Scalars['Int']; + name: Scalars['String']; + identifier?: Maybe; + objectType: 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; + sourceObjects: 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 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; }; export type IngestDataInput = { @@ -619,6 +780,9 @@ export type IngestDataInput = { project: IngestProjectInput; item: IngestItemInput; photogrammetry: Array; + model: Array; + scene: Array; + other: Array; }; export type IngestDataResult = { @@ -666,11 +830,13 @@ export type LicenseAssignment = { }; export type CreateModelInput = { + Name: Scalars['String']; Authoritative: Scalars['Boolean']; idVCreationMethod: Scalars['Int']; idVModality: Scalars['Int']; idVPurpose: Scalars['Int']; idVUnits: Scalars['Int']; + idVFileType: Scalars['Int']; Master: Scalars['Boolean']; idAssetThumbnail?: Maybe; }; @@ -692,49 +858,97 @@ export type GetModelResult = { export type Model = { __typename?: 'Model'; idModel: Scalars['Int']; - Authoritative: Scalars['Boolean']; + Name: Scalars['String']; DateCreated: Scalars['DateTime']; - idAssetThumbnail?: Maybe; + Master: Scalars['Boolean']; + Authoritative: Scalars['Boolean']; idVCreationMethod: Scalars['Int']; idVModality: Scalars['Int']; idVPurpose: Scalars['Int']; idVUnits: Scalars['Int']; - Master: Scalars['Boolean']; - AssetThumbnail?: Maybe; + idVFileType: Scalars['Int']; + idAssetThumbnail?: Maybe; + idModelMetrics?: Maybe; + ModelConstellation?: Maybe; VCreationMethod?: Maybe; VModality?: Maybe; VPurpose?: Maybe; VUnits?: Maybe; - ModelGeometryFile?: Maybe>>; + VFileType?: Maybe; + AssetThumbnail?: Maybe; + ModelMetrics?: Maybe; + ModelObject?: Maybe>>; ModelProcessingAction?: Maybe>>; ModelSceneXref?: Maybe>>; SystemObject?: Maybe; }; -export type ModelGeometryFile = { - __typename?: 'ModelGeometryFile'; - idModelGeometryFile: Scalars['Int']; - idAsset: Scalars['Int']; +export type ModelMaterial = { + __typename?: 'ModelMaterial'; + idModelMaterial: Scalars['Int']; + idModelObject: Scalars['Int']; + Name?: Maybe; + ModelObject: ModelObject; +}; + +export type ModelMaterialChannel = { + __typename?: 'ModelMaterialChannel'; + idModelMaterialChannel: Scalars['Int']; + idModelMaterial: Scalars['Int']; + idVMaterialType?: Maybe; + MaterialTypeOther?: Maybe; + idModelMaterialUVMap?: Maybe; + ChannelPosition?: Maybe; + ChannelWidth?: Maybe; + Scalar1?: Maybe; + Scalar2?: Maybe; + Scalar3?: Maybe; + Scalar4?: Maybe; + ModelMaterial: ModelMaterial; + VMaterialType?: Maybe; + ModelMaterialUVMap?: Maybe; +}; + +export type ModelMaterialUvMap = { + __typename?: 'ModelMaterialUVMap'; + idModelMaterialUVMap: Scalars['Int']; idModel: Scalars['Int']; - idVModelFileType: Scalars['Int']; + idAsset: Scalars['Int']; + UVMapEdgeLength: Scalars['Int']; + Model: Model; + Asset: Asset; +}; + +export type ModelMetrics = { + __typename?: 'ModelMetrics'; + idModelMetrics: Scalars['Int']; BoundingBoxP1X?: Maybe; BoundingBoxP1Y?: Maybe; BoundingBoxP1Z?: Maybe; BoundingBoxP2X?: Maybe; BoundingBoxP2Y?: Maybe; BoundingBoxP2Z?: Maybe; - FaceCount?: Maybe; - HasNormals?: Maybe; - HasUVSpace?: Maybe; + CountPoint?: Maybe; + CountFace?: Maybe; + CountColorChannel?: Maybe; + CountTextureCoorinateChannel?: Maybe; + HasBones?: Maybe; + HasFaceNormals?: Maybe; + HasTangents?: Maybe; + HasTextureCoordinates?: Maybe; + HasVertexNormals?: Maybe; HasVertexColor?: Maybe; + IsManifold?: Maybe; IsWatertight?: Maybe; - Metalness?: Maybe; - PointCount?: Maybe; - Roughness?: Maybe; - Asset?: Maybe; - Model?: Maybe; - VModelFileType?: Maybe; - ModelUVMapFile?: Maybe>>; +}; + +export type ModelObject = { + __typename?: 'ModelObject'; + idModelObject: Scalars['Int']; + idModel: Scalars['Int']; + idModelMetrics?: Maybe; + Model: Model; + ModelMetrics?: Maybe; }; export type ModelProcessingAction = { @@ -776,26 +990,15 @@ export type ModelSceneXref = { Scene?: Maybe; }; -export type ModelUvMapChannel = { - __typename?: 'ModelUVMapChannel'; - idModelUVMapChannel: Scalars['Int']; - ChannelPosition: Scalars['Int']; - ChannelWidth: Scalars['Int']; - idModelUVMapFile: Scalars['Int']; - idVUVMapType: Scalars['Int']; - ModelUVMapFile?: Maybe; - VUVMapType?: Maybe; -}; - -export type ModelUvMapFile = { - __typename?: 'ModelUVMapFile'; - idModelUVMapFile: Scalars['Int']; - idAsset: Scalars['Int']; - idModelGeometryFile: Scalars['Int']; - UVMapEdgeLength: Scalars['Int']; - Asset?: Maybe; - ModelGeometryFile?: Maybe; - ModelUVMapChannel?: Maybe>>; +export type ModelConstellation = { + __typename?: 'ModelConstellation'; + Model: Model; + ModelObjects?: Maybe>>; + ModelMaterials?: Maybe>>; + ModelMaterialChannels?: Maybe>>; + ModelMaterialUVMaps?: Maybe>>; + ModelMetric?: Maybe; + ModelObjectMetrics?: Maybe>>; }; export type PaginationInput = { @@ -808,7 +1011,17 @@ export type PaginationInput = { export type GetObjectChildrenInput = { idRoot: Scalars['Int']; objectTypes: Array; + objectsToDisplay: Array; metadataColumns: Array; + search: Scalars['String']; + units: Array; + projects: Array; + has: Array; + missing: Array; + captureMethod: Array; + variantType: Array; + modelPurpose: Array; + modelFileType: Array; }; export type NavigationResultEntry = { @@ -828,6 +1041,12 @@ export type GetObjectChildrenResult = { metadataColumns: Array; }; +export type GetFilterViewDataResult = { + __typename?: 'GetFilterViewDataResult'; + units: Array; + projects: Array; +}; + export type CreateSceneInput = { Name: Scalars['String']; HasBeenQCd: Scalars['Boolean']; @@ -889,6 +1108,402 @@ export type IntermediaryFile = { SystemObject?: Maybe; }; +export type UpdateObjectDetailsInput = { + idSystemObject: Scalars['Int']; + idObject: 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; + HasBeenQCd?: Maybe; + IsOriented?: 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 = { + Name?: Maybe; + Retired?: Maybe; + 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; + boundingBoxP1X?: Maybe; + boundingBoxP1Y?: Maybe; + boundingBoxP1Z?: Maybe; + boundingBoxP2X?: Maybe; + boundingBoxP2Y?: Maybe; + boundingBoxP2Z?: Maybe; + countPoint?: Maybe; + countFace?: Maybe; + countColorChannel?: Maybe; + countTextureCoorinateChannel?: Maybe; + hasBones?: Maybe; + hasFaceNormals?: Maybe; + hasTangents?: Maybe; + hasTextureCoordinates?: Maybe; + hasVertexNormals?: Maybe; + hasVertexColor?: Maybe; + isManifold?: Maybe; + isWatertight?: Maybe; +}; + +export type SceneDetailFields = { + __typename?: 'SceneDetailFields'; + Links: Array; + AssetType?: Maybe; + Tours?: Maybe; + Annotation?: Maybe; + HasBeenQCd?: Maybe; + IsOriented?: 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; +}; + +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'; + idObject: Scalars['Int']; + name: Scalars['String']; + retired: Scalars['Boolean']; + objectType: Scalars['Int']; + allowed: Scalars['Boolean']; + publishedState: Scalars['String']; + thumbnail?: Maybe; + identifiers: Array; + objectAncestors: Array>; + sourceObjects: Array; + derivedObjects: Array; + unit?: Maybe; + project?: Maybe; + subject?: Maybe; + item?: 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 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']; 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 { diff --git a/server/utils/parser/bulkIngestReader.ts b/server/utils/parser/bulkIngestReader.ts index c966d7339..e65b92cea 100644 --- a/server/utils/parser/bulkIngestReader.ts +++ b/server/utils/parser/bulkIngestReader.ts @@ -364,17 +364,22 @@ 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: [], + sourceObjects: [] }; } diff --git a/server/utils/parser/csvTypes.ts b/server/utils/parser/csvTypes.ts index 3c6c7713a..70f6f1e84 100644 --- a/server/utils/parser/csvTypes.ts +++ b/server/utils/parser/csvTypes.ts @@ -4,9 +4,7 @@ export enum CSVTypes { captureDataPhoto = 'capture_data_photo', } -type CSVHeadersType = { - [key: string]: string[]; -}; +type CSVHeadersType = Record; export const CSVHeaders: CSVHeadersType = { models: ['subject_guid', 'subject_name', 'unit_guid', 'unit_name', 'item_guid', 'item_name', 'entire_subject', 'date_created', 'creation_method', 'master', 'authoritative', 'modality', 'units', 'purpose', 'directory_path'], diff --git a/server/utils/profiler.ts b/server/utils/profiler.ts new file mode 100644 index 000000000..01cb7cc88 --- /dev/null +++ b/server/utils/profiler.ts @@ -0,0 +1,37 @@ +import * as LOG from '../utils/logger'; + +/* +// c.f. https://stackoverflow.com/questions/29822773/passing-class-method-as-parameter-in-typescript/39366724 +function bar(functionToProfile: (this: void) => any, thisArg?: undefined): any; +function bar(functionToProfile: (this: T) => any, thisArg: T): any; +function bar(functionToProfile: (this: T) => TResult, thisArg: T): TResult { + return functionToProfile.call(thisArg); +} +*/ + +// export async function CreateProfile(functionToProfile: (this: void) => TResult, thisArg?: undefined): Promise; +export async function CreateProfile(functionToProfile: (this: T) => Promise, thisArg: T): Promise { + LOG.logger.info('****************************************'); + LOG.logger.info('CreateProfile() 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 () => { + // const retValue: boolean = await functionToProfile(); + const retValue: TResult = await functionToProfile.call(thisArg); + resolve(retValue); + + // some time later... + session.post('Profiler.stop', (err, { profile }) => { + // Write profile to disk + if (!err) + fs.writeFileSync('./profile.cpuprofile', JSON.stringify(profile)); + }); + }); + }); + }); +} \ No newline at end of file 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; +} diff --git a/yarn.lock b/yarn.lock index 9a0c13141..95e1390e7 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" @@ -3540,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" @@ -3924,6 +3943,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" @@ -4076,7 +4103,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= @@ -4353,6 +4380,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" @@ -4869,6 +4901,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" @@ -4886,7 +4923,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== @@ -5580,7 +5617,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== @@ -5698,7 +5735,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== @@ -6123,6 +6160,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" @@ -6157,6 +6199,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" @@ -6447,6 +6498,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" @@ -6778,6 +6837,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" @@ -6961,7 +7025,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== @@ -7082,6 +7146,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" @@ -7111,6 +7183,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" @@ -8832,6 +8911,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" @@ -9031,6 +9115,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" @@ -9475,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" @@ -10388,6 +10486,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" @@ -10478,7 +10583,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= @@ -10915,7 +11020,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= @@ -11128,7 +11233,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== @@ -11664,6 +11769,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 +13838,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= @@ -13904,6 +14016,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" @@ -13925,6 +14052,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" @@ -14014,6 +14146,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" @@ -14569,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== @@ -14795,7 +14940,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== @@ -15146,6 +15291,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" @@ -15184,7 +15341,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== @@ -15337,6 +15494,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" @@ -16076,6 +16238,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" @@ -17319,6 +17493,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"