+
{renderSelected && (
{!selected && }
{selected && }
)}
-
-
-
+
+ event.stopPropagation()}
+ target='_blank'
+ rel='noopener noreferrer'
+ className={makeStyles?.link}
+ >
-
+
}
/>
diff --git a/client/src/pages/Repository/components/RepositoryTreeView/index.tsx b/client/src/pages/Repository/components/RepositoryTreeView/index.tsx
index b3b0a22dd..3e111510a 100644
--- a/client/src/pages/Repository/components/RepositoryTreeView/index.tsx
+++ b/client/src/pages/Repository/components/RepositoryTreeView/index.tsx
@@ -30,7 +30,7 @@ import TreeLabel, { TreeLabelEmpty, TreeLabelLoading } from './TreeLabel';
import InViewTreeItem from './InViewTreeItem';
import { repositoryRowCount } from '../../../../types/server';
-const useStyles = makeStyles(({ breakpoints }) => ({
+const useStyles = makeStyles(({ breakpoints, typography, palette }) => ({
container: {
display: 'flex',
flex: 5,
@@ -51,6 +51,82 @@ const useStyles = makeStyles(({ breakpoints }) => ({
},
fullWidth: {
maxWidth: '95.5vw'
+ },
+ iconContainer: {
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ height: 18,
+ width: 18,
+ borderRadius: 2.5,
+ [breakpoints.down('lg')]: {
+ height: 15,
+ width: 15,
+ },
+ },
+ initial: {
+ fontSize: 10,
+ fontWeight: typography.fontWeightMedium,
+ },
+ // TreeLabel
+ treeLabelContainer: {
+ display: 'flex',
+ },
+ label: {
+ display: 'flex',
+ flex: 0.9,
+ alignItems: 'center',
+ position: 'sticky',
+ left: 45,
+ [breakpoints.down('lg')]: {
+ left: 30
+ }
+ },
+ labelText: {
+ color: 'rgb(44,64,90)',
+ fontWeight: 400,
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'space-between',
+ width: '60%',
+ fontSize: '0.8em',
+ zIndex: 10,
+ [breakpoints.down('lg')]: {
+ fontSize: '0.9em',
+ }
+ },
+ column: {
+ display: 'flex',
+ alignItems: 'center',
+ padding: '0px 10px',
+ fontSize: undefined,
+ color: palette.grey[900],
+ fontWeight: typography.fontWeightLight,
+ overflow: 'hidden',
+ textOverflow: 'ellipsis'
+ },
+ text: {
+ color: 'rgb(44,64,90)',
+ fontWeight: 400,
+ fontSize: '0.8em',
+ [breakpoints.down('lg')]: {
+ fontSize: '0.9em',
+ }
+ },
+ options: {
+ display: 'flex',
+ alignItems: 'center',
+ position: 'sticky',
+ left: 0,
+ width: 120
+ },
+ option: {
+ display: 'flex',
+ alignItems: 'center'
+ },
+ link: {
+ color: palette.primary.dark,
+ textDecoration: 'none'
}
}));
@@ -98,6 +174,8 @@ function RepositoryTreeView(props: RepositoryTreeViewProps): React.ReactElement
// recursive
const renderTree = (children: NavigationResultEntry[] | undefined, isChild?: boolean, parentNodeId?: string) => {
+ // console.log(`renderTree: ${children?.length} total`, children);
+
if (!children) return null;
return children.map((child: NavigationResultEntry, index: number) => {
const { idSystemObject, objectType, idObject, name, metadata } = child;
@@ -116,10 +194,11 @@ function RepositoryTreeView(props: RepositoryTreeViewProps): React.ReactElement
}
const variant = getTreeColorVariant(index);
- const { icon, color } = getObjectInterfaceDetails(objectType, variant);
+ const { icon, color } = getObjectInterfaceDetails(objectType, variant, { container: classes.iconContainer, initial: classes.initial });
+
const treeColumns = getTreeViewColumns(metadataColumns, false, metadata);
- const isSelected = isRepositoryItemSelected(nodeId, selectedItems);
+ const isSelected = isRepositoryItemSelected(nodeId, selectedItems);
const select = (event: React.MouseEvent
) => {
if (onSelect) {
event.stopPropagation();
@@ -151,6 +230,7 @@ function RepositoryTreeView(props: RepositoryTreeViewProps): React.ReactElement
objectType={objectType}
color={color}
treeColumns={treeColumns}
+ makeStyles={{ container: classes.treeLabelContainer, label: classes.label, labelText: classes.labelText, column: classes.column, text: classes.text, options: classes.options, option: classes.option, link: classes.link }}
/>
);
@@ -208,7 +288,6 @@ function RepositoryTreeView(props: RepositoryTreeViewProps): React.ReactElement
const treeColumns = getTreeViewColumns(metadataColumns, false);
const width = getTreeWidth(treeColumns.length, sideBarExpanded, isModal);
const children = tree.get(treeRootKey);
-
content = (
} defaultExpandIcon={} onNodeToggle={onNodeToggle} style={{ width }}>
diff --git a/client/src/pages/Repository/hooks/useDetailsView.ts b/client/src/pages/Repository/hooks/useDetailsView.ts
index 2512c2f85..a1dd63280 100644
--- a/client/src/pages/Repository/hooks/useDetailsView.ts
+++ b/client/src/pages/Repository/hooks/useDetailsView.ts
@@ -21,10 +21,13 @@ import {
GetLicenseListDocument,
ClearLicenseAssignmentDocument,
AssignLicenseDocument,
+ PublishDocument,
ClearLicenseAssignmentMutation,
- AssignLicenseMutation
+ AssignLicenseMutation,
+ PublishMutation,
+ ExistingRelationship
} from '../../../types/graphql';
-import { eSystemObjectType } from '../../../types/server';
+import { eSystemObjectType, ePublishedState } from '../../../types/server';
export function useObjectDetails(idSystemObject: number): GetSystemObjectDetailsQueryResult {
return useQuery(GetSystemObjectDetailsDocument, {
@@ -32,7 +35,8 @@ export function useObjectDetails(idSystemObject: number): GetSystemObjectDetails
input: {
idSystemObject
}
- }
+ },
+ fetchPolicy: 'no-cache'
});
}
@@ -75,7 +79,8 @@ export async function getDetailsTabDataForObject(idSystemObject: number, objectT
idSystemObject,
objectType
}
- }
+ },
+ fetchPolicy: 'no-cache'
});
}
@@ -99,62 +104,50 @@ export function updateDetailsTabData(
});
}
-export function updateSourceObjects(idSystemObject: number, sources: number[], PreviouslySelected: number[]) {
+export function updateSourceObjects(idSystemObject: number, objectType: number, sources: ExistingRelationship[], PreviouslySelected: ExistingRelationship[]) {
return apolloClient.mutate({
mutation: UpdateSourceObjectsDocument,
variables: {
input: {
idSystemObject,
+ ChildObjectType: objectType,
Sources: sources,
PreviouslySelected
}
},
- refetchQueries: ['getSystemObjectDetails', 'getDetailsTabDataForObject']
+ refetchQueries: ['getSystemObjectDetails', 'getDetailsTabDataForObject'],
+ awaitRefetchQueries: true
});
}
-export function updateDerivedObjects(idSystemObject: number, derivatives: number[], PreviouslySelected: number[]) {
+export function updateDerivedObjects(idSystemObject: number, objectType: number, derivatives: ExistingRelationship[], PreviouslySelected: ExistingRelationship[]) {
return apolloClient.mutate({
mutation: UpdateDerivedObjectsDocument,
variables: {
input: {
idSystemObject,
+ ParentObjectType: objectType,
Derivatives: derivatives,
PreviouslySelected
}
},
- refetchQueries: ['getSystemObjectDetails', 'getDetailsTabDataForObject']
+ refetchQueries: ['getSystemObjectDetails', 'getDetailsTabDataForObject'],
+ awaitRefetchQueries: true
});
}
-export async function deleteObjectConnection(idSystemObjectMaster: number, idSystemObjectDerived: number, type: string, systemObjectType: number) {
+export async function deleteObjectConnection(idSystemObjectMaster: number, objectTypeMaster: eSystemObjectType, idSystemObjectDerived: number, objectTypeDerived: eSystemObjectType) {
return await apolloClient.mutate({
mutation: DeleteObjectConnectionDocument,
variables: {
input: {
idSystemObjectMaster,
- idSystemObjectDerived
+ objectTypeMaster,
+ idSystemObjectDerived,
+ objectTypeDerived
}
},
- refetchQueries: [
- {
- query: GetSystemObjectDetailsDocument,
- variables: {
- input: {
- idSystemObject: type === 'Source' ? idSystemObjectDerived : idSystemObjectMaster
- }
- }
- },
- {
- query: GetDetailsTabDataForObjectDocument,
- variables: {
- input: {
- idSystemObject: type === 'Source' ? idSystemObjectDerived : idSystemObjectMaster,
- objectType: systemObjectType
- }
- }
- }
- ],
+ refetchQueries: ['getSystemObjectDetails', 'getDetailsTabDataForObject'],
awaitRefetchQueries: true
});
}
@@ -166,7 +159,8 @@ export async function deleteIdentifier(idIdentifier: number) {
input: {
idIdentifier
}
- }
+ },
+ refetchQueries: ['getSystemObjectDetails', 'getDetailsTabDataForObject']
});
}
@@ -219,3 +213,17 @@ export async function assignLicense(idSystemObject: number, idLicense: number):
refetchQueries: ['getSystemObjectDetails', 'getDetailsTabDataForObject']
});
}
+
+export async function publish(idSystemObject: number, eState: ePublishedState): Promise> {
+ return await apolloClient.mutate({
+ mutation: PublishDocument,
+ variables: {
+ input: {
+ idSystemObject,
+ eState
+ }
+ },
+ refetchQueries: ['getSystemObjectDetails', 'getDetailsTabDataForObject']
+ });
+}
+
diff --git a/client/src/pages/Repository/hooks/useRepository.ts b/client/src/pages/Repository/hooks/useRepository.ts
index d77c2a2d3..4f8a96c8c 100644
--- a/client/src/pages/Repository/hooks/useRepository.ts
+++ b/client/src/pages/Repository/hooks/useRepository.ts
@@ -15,11 +15,11 @@ function getObjectChildrenForRoot(filter: RepositoryFilter, idSystemObject = 0):
fetchPolicy: 'network-only',
variables: {
input: {
- idRoot: idSystemObject,
+ idRoot: filter?.idRoot ?? idSystemObject,
objectTypes: filter.repositoryRootType,
metadataColumns: filter.metadataToDisplay,
objectsToDisplay: filter.objectsToDisplay,
- search: filter.search,
+ search: String(filter.search),
units: filter.units,
projects: filter.projects,
has: filter.has,
@@ -47,7 +47,7 @@ function getObjectChildren(idRoot: number, filter: RepositoryFilter): Promise ({
container: {
@@ -53,6 +55,7 @@ export type RepositoryFilter = {
dateCreatedFrom?: Date | string | null;
dateCreatedTo?: Date | string | null;
cursorMark?: string | null;
+ idRoot?: number | null;
};
function Repository(): React.ReactElement {
@@ -64,6 +67,7 @@ function Repository(): React.ReactElement {
+
@@ -89,6 +93,7 @@ function TreeViewPage(): React.ReactElement {
modelFileType,
dateCreatedFrom,
dateCreatedTo,
+ idRoot,
updateRepositoryFilter
} = useRepositoryStore();
const queries: RepositoryFilter = parseRepositoryUrl(location.search);
@@ -108,7 +113,8 @@ function TreeViewPage(): React.ReactElement {
modelPurpose: [],
modelFileType: [],
dateCreatedFrom: null,
- dateCreatedTo: null
+ dateCreatedTo: null,
+ idRoot: null
})};path=/`;
};
@@ -150,16 +156,19 @@ function TreeViewPage(): React.ReactElement {
modelPurpose,
modelFileType,
dateCreatedFrom,
- dateCreatedTo
+ dateCreatedTo,
+ idRoot
};
const route = generateRepositoryUrl(newRepositoryFilterState) || generateRepositoryUrl(cookieFilterSelections);
if (route !== location.search) {
- console.log(`*** src/pages/Repository/index.tsx TreeViewPage window.history.pushState(path: ${route}, '', ${route})`);
window.history.pushState({ path: route }, '', route);
}
return (
+
+ Repository
+
diff --git a/client/src/pages/Workflow/components/WorkflowView/WorkflowList.tsx b/client/src/pages/Workflow/components/WorkflowView/WorkflowList.tsx
index 82d8d0599..2912413b1 100644
--- a/client/src/pages/Workflow/components/WorkflowView/WorkflowList.tsx
+++ b/client/src/pages/Workflow/components/WorkflowView/WorkflowList.tsx
@@ -319,7 +319,12 @@ function WorkflowIcon(props: WorkflowIconProps): React.ReactElement {
if (reportType === eWorkflowLinkType.eSet) source = SetIcon;
return (
-
+
);
diff --git a/client/src/pages/Workflow/components/WorkflowView/index.tsx b/client/src/pages/Workflow/components/WorkflowView/index.tsx
index 72b14194e..016fb16ab 100644
--- a/client/src/pages/Workflow/components/WorkflowView/index.tsx
+++ b/client/src/pages/Workflow/components/WorkflowView/index.tsx
@@ -3,6 +3,7 @@ import React, { useEffect } from 'react';
import WorkflowFilter from './WorkflowFilter';
import WorkflowList from './WorkflowList';
import { useWorkflowStore } from '../../../../store/index';
+import { Helmet } from 'react-helmet';
function WorkflowView(): React.ReactElement {
const fetchWorkflowList = useWorkflowStore(state => state.fetchWorkflowList);
@@ -13,6 +14,9 @@ function WorkflowView(): React.ReactElement {
return (
+
+ Workflow
+
diff --git a/client/src/store/detailTab.tsx b/client/src/store/detailTab.tsx
index 23c588f64..ae86b8366 100644
--- a/client/src/store/detailTab.tsx
+++ b/client/src/store/detailTab.tsx
@@ -11,9 +11,10 @@ import {
AssetDetailFields,
AssetVersionDetailFields,
ActorDetailFields,
- UpdateObjectDetailsDataInput
+ UpdateObjectDetailsDataInput,
+ IngestFolder
} from '../types/graphql';
-import lodash from 'lodash';
+import * as yup from 'yup';
export interface ModelDetailsType {
DateCreated: string | null;
@@ -33,11 +34,16 @@ export type DetailsViewFieldErrors = {
name: boolean;
dateCaptured: boolean;
};
+ captureData: {
+ name: boolean;
+ datasetFieldId: boolean;
+ itemPositionFieldId: boolean;
+ itemArrangementFieldId: boolean;
+ clusterGeometryFieldId: boolean;
+ };
};
-interface SceneDetailsType {
- HasBeenQCd: boolean;
- IsOriented: boolean;
+export interface SceneDetailsType {
CountScene: number;
CountNode: number;
CountCamera: number;
@@ -47,6 +53,9 @@ interface SceneDetailsType {
CountSetup: number;
CountTour: number;
EdanUUID: string | null;
+ ApprovedForPublication: boolean;
+ PublicationApprover: string | null;
+ PosedAndQCd: boolean;
ModelSceneXref: any[];
}
@@ -77,7 +86,7 @@ type DetailTabStore = {
AssetDetails: AssetDetailFields;
ActorDetails: ActorDetailFields;
StakeholderDetails: StakeholderDetailFields;
- updateDetailField: (metadataType: eSystemObjectType, fieldName: string, value: number | string | boolean | Date | null) => void;
+ updateDetailField: (metadataType: eSystemObjectType, fieldName: string, value: number | string | boolean | Date | null | IngestFolder[]) => void;
getDetail: (type: eSystemObjectType) => DetailsTabType | void;
initializeDetailFields: (data: any, type: eSystemObjectType) => void;
getDetailsViewFieldErrors: (metadata: UpdateObjectDetailsDataInput, objectType: eSystemObjectType) => string[];
@@ -144,8 +153,6 @@ export const useDetailTabStore = create((set: SetState((set: SetState((set: SetState {
+ return {
+ name: folder.name,
+ variantType: folder.variantType
+ };
+ });
+ updateDetailField(eSystemObjectType.eCaptureData, 'folders', sanitizedFolders);
updateDetailField(eSystemObjectType.eCaptureData, 'isValidData', isValidData);
updateDetailField(eSystemObjectType.eCaptureData, 'itemArrangementFieldId', itemArrangementFieldId);
updateDetailField(eSystemObjectType.eCaptureData, 'itemPositionFieldId', itemPositionFieldId);
@@ -461,10 +477,11 @@ export const useDetailTabStore = create((set: SetState((set: SetState {
- // UPDATE these error fields as we include more validation for ingestion
- const errors: DetailsViewFieldErrors = {
- model: {
- name: false,
- dateCaptured: false
- }
+ // UPDATE these error fields as we include more validation for details tab
+ // errors should be responsible for rendering error version of the input
+ // const errors: DetailsViewFieldErrors = {
+ // model: {
+ // name: false,
+ // dateCaptured: false
+ // },
+ // captureData: {
+ // name: false,
+ // datasetFieldId: false,
+ // itemPositionFieldId: false,
+ // itemArrangementFieldId: false,
+ // clusterGeometryFieldId: false
+ // }
+ // };
+
+ const option = {
+ abortEarly: false
};
-
const errorMessages: string[] = [];
-
+ if (!metadata.Name?.trim().length) errorMessages.push('Please input a valid Name');
if (objectType === eSystemObjectType.eModel) {
- if (!lodash.isNil(metadata.Name)) {
- errors.model.name = !metadata.Name.trim().length;
+ const { Model } = metadata;
+
+ try {
+ schemaModel.validateSync(
+ {
+ dateCaptured: Model?.DateCaptured
+ },
+ option
+ );
+ } catch (error) {
+ if (error instanceof Error)
+ errorMessages.push(error.message);
}
- if (!lodash.isNil(metadata.Model?.DateCaptured)) {
- errors.model.dateCaptured = metadata?.Model?.DateCaptured.toString() === 'Invalid Date' || new Date(metadata?.Model?.DateCaptured).getTime() > new Date().getTime();
+ }
+
+ if (objectType === eSystemObjectType.eCaptureData) {
+ const { CaptureData } = metadata;
+
+ try {
+ schemaCD.validateSync(
+ {
+ datasetFieldId: CaptureData?.datasetFieldId,
+ itemArrangementFieldId: CaptureData?.itemArrangementFieldId,
+ itemPositionFieldId: CaptureData?.itemPositionFieldId,
+ clusterGeometryFieldId: CaptureData?.clusterGeometryFieldId
+ },
+ option
+ );
+ } catch (error) {
+ if (error instanceof Error)
+ errorMessages.push(error.message);
}
- for (const field in errors.model) {
- if (errors.model[field]) errorMessages.push(field);
+ }
+
+ if (objectType === eSystemObjectType.eItem || objectType === eSystemObjectType.eSubject) {
+ const { Item, Subject } = metadata;
+ const itemOrSubject = objectType === eSystemObjectType.eItem ? Item : Subject;
+ try {
+ schemaItemAndSubject.validateSync(
+ {
+ Latitude: Number(itemOrSubject?.Latitude),
+ Longitude: Number(itemOrSubject?.Longitude),
+ Altitude: Number(itemOrSubject?.Altitude),
+ TS0: Number(itemOrSubject?.TS0),
+ TS1: Number(itemOrSubject?.TS1),
+ TS2: Number(itemOrSubject?.TS2),
+ R0: Number(itemOrSubject?.R0),
+ R1: Number(itemOrSubject?.R1),
+ R2: Number(itemOrSubject?.R2),
+ R3: Number(itemOrSubject?.R3)
+ },
+ option
+ );
+ } catch (error) {
+ if (error instanceof Error)
+ errorMessages.push(error.message);
}
}
return errorMessages;
}
}));
+
+const schemaCD = yup.object().shape({
+ datasetFieldId: yup.number().positive('Dataset Field ID must be positive').max(2147483647, 'Dataset Field ID is too large').nullable(),
+ itemPositionFieldId: yup.number().positive('Item Position Field ID must be positive').max(2147483647, 'Item Position Field ID is too large').nullable(),
+ itemArrangementFieldId: yup.number().positive('Item Arrangement Field ID must be positive').max(2147483647, 'Item Arrangement Field ID is too large').nullable(),
+ clusterGeometryFieldId: yup.number().positive('Cluster Geometry Field ID must be positive').max(2147483647, 'Cluster Geometry Field ID is too large').nullable()
+});
+
+const schemaModel = yup.object().shape({
+ dateCaptured: yup.date().max(Date(), 'Date Created cannot be set in the future')
+});
+
+const schemaItemAndSubject = yup.object().shape({
+ Latitude: yup.number().typeError('Number must be in standard or scientific notation'),
+ Longitude: yup.number().typeError('Number must be in standard or scientific notation'),
+ Altitude: yup.number().typeError('Number must be in standard or scientific notation'),
+ TS0: yup.number().typeError('Number must be in standard or scientific notation'),
+ TS1: yup.number().typeError('Number must be in standard or scientific notation'),
+ TS2: yup.number().typeError('Number must be in standard or scientific notation'),
+ R0: yup.number().typeError('Number must be in standard or scientific notation'),
+ R1: yup.number().typeError('Number must be in standard or scientific notation'),
+ R2: yup.number().typeError('Number must be in standard or scientific notation'),
+ R3: yup.number().typeError('Number must be in standard or scientific notation')
+});
\ No newline at end of file
diff --git a/client/src/store/identifier.ts b/client/src/store/identifier.ts
index fdae07a7d..0b15242cb 100644
--- a/client/src/store/identifier.ts
+++ b/client/src/store/identifier.ts
@@ -15,22 +15,32 @@ type StateIdentifier = {
id: number;
identifier: string;
identifierType: number;
- selected: boolean;
idIdentifier: number;
+ preferred?: boolean;
};
type IdentifierStore = {
stateIdentifiers: StateIdentifier[];
+ originalIdentifiers: StateIdentifier[];
+ areIdentifiersUpdated: () => boolean;
getIdentifierState: () => StateIdentifier[];
addNewIdentifier: () => void;
initializeIdentifierState: (identifiers: Identifier[]) => void;
+ initializeIdentifierPreferred: () => void;
removeTargetIdentifier: (id: number, idInd?: boolean | number) => void;
updateIdentifier: (id: number, name: string, value: string | number | boolean) => void;
checkIdentifiersBeforeUpdate: () => string[];
+ initializePreferredIdentifier: (idIdentifier: number) => void;
+ updateIdentifierPreferred: (id: number) => void;
};
export const useIdentifierStore = create((set: SetState, get: GetState) => ({
stateIdentifiers: [],
+ originalIdentifiers: [],
+ areIdentifiersUpdated: () => {
+ const { stateIdentifiers, originalIdentifiers } = get();
+ return !lodash.isEqual(stateIdentifiers, originalIdentifiers);
+ },
getIdentifierState: () => {
const { stateIdentifiers } = get();
return stateIdentifiers;
@@ -41,24 +51,24 @@ export const useIdentifierStore = create((set: SetState {
+ initializeIdentifierState: identifiers => {
const initialIdentifiers: StateIdentifier[] = identifiers.map((identifier, ind) => {
return {
id: ind,
identifier: identifier.identifier,
identifierType: identifier.identifierType,
- selected: true,
idIdentifier: identifier.idIdentifier
};
});
- set({ stateIdentifiers: initialIdentifiers });
+ set({ stateIdentifiers: initialIdentifiers, originalIdentifiers: initialIdentifiers });
},
+ initializeIdentifierPreferred: () => {},
removeTargetIdentifier: (id: number, idInd = false) => {
const { stateIdentifiers } = get();
let updatedIdentifiers;
@@ -76,7 +86,7 @@ export const useIdentifierStore = create((set: SetState((set: SetState {
+ stateIdentifiers.forEach(identifier => {
if (!identifier.identifier) {
errors['Missing identifier field'] = 'Missing identifier field';
}
if (!identifier.identifierType) {
errors['Missing identifier type'] = 'Missing identifier type';
}
- if (!identifier.selected) {
- errors['Identifiers should not be unchecked'] = ('Identifiers should not be unchecked');
- }
});
for (const error in errors) {
result.push(error);
}
return result;
+ },
+ // this method will be responsible for setting the preferred identifier
+ initializePreferredIdentifier: (idIdentifier: number): void => {
+ const { stateIdentifiers } = get();
+ const stateIdentifiersCopy = stateIdentifiers.map(identifier => {
+ if (identifier.idIdentifier === idIdentifier) identifier.preferred = true;
+ return identifier;
+ });
+ set({ stateIdentifiers: stateIdentifiersCopy });
+ },
+ updateIdentifierPreferred: (id: number): void => {
+ const { stateIdentifiers } = get();
+ const stateIdentifiersCopy = stateIdentifiers.map(identifier => {
+ if (id === identifier.id) {
+ if (identifier.preferred) {
+ identifier.preferred = undefined;
+ } else {
+ identifier.preferred = true;
+ }
+ return identifier;
+ }
+ identifier.preferred = undefined;
+ return identifier;
+ });
+ set({ stateIdentifiers: stateIdentifiersCopy });
}
-}));
\ No newline at end of file
+}));
diff --git a/client/src/store/metadata/index.ts b/client/src/store/metadata/index.ts
index ffd080aba..b82dde9da 100644
--- a/client/src/store/metadata/index.ts
+++ b/client/src/store/metadata/index.ts
@@ -12,7 +12,6 @@ import { apolloClient } from '../../graphql';
import {
AreCameraSettingsUniformDocument,
AssetVersionContent,
-
GetAssetVersionsDetailsDocument,
GetAssetVersionsDetailsQuery,
GetContentsForAssetVersionsDocument,
@@ -60,7 +59,7 @@ type MetadataStore = {
export const useMetadataStore = create((set: SetState, get: GetState) => ({
metadatas: [],
- getSelectedIdentifiers: (identifiers: StateIdentifier[]): StateIdentifier[] | undefined => lodash.filter(identifiers, { selected: true }),
+ getSelectedIdentifiers: (identifiers: StateIdentifier[]): StateIdentifier[] | undefined => identifiers,
getFieldErrors: (metadata: StateMetadata): FieldErrors => {
const { getAssetType } = useVocabularyStore.getState();
// UPDATE these error fields as we include more validation for ingestion
@@ -352,12 +351,21 @@ export const useMetadataStore = create((set: SetState {
- const { getInitialEntry } = useVocabularyStore.getState();
- const stateFolders: StateFolder[] = folders.map((folder, index: number) => ({
- id: index,
- name: folder,
- variantType: getInitialEntry(eVocabularySetID.eCaptureDataFileVariantType)
- }));
+ const { getInitialEntry, getEntries } = useVocabularyStore.getState();
+ const stateFolders: StateFolder[] = folders.map((folder, index: number) => {
+ let variantType = getInitialEntry(eVocabularySetID.eCaptureDataFileVariantType);
+ const variantTypes = getEntries(eVocabularySetID.eCaptureDataFileVariantType);
+
+ if (folder.search('raw') !== -1) variantType = variantTypes[0].idVocabulary;
+ if (folder.search('processed') !== -1) variantType = variantTypes[1].idVocabulary;
+ if (folder.search('camera') !== -1) variantType = variantTypes[2].idVocabulary;
+
+ return {
+ id: index,
+ name: folder,
+ variantType
+ };
+ });
return stateFolders;
},
diff --git a/client/src/store/metadata/metadata.defaults.ts b/client/src/store/metadata/metadata.defaults.ts
index b70bf978b..3510b7601 100644
--- a/client/src/store/metadata/metadata.defaults.ts
+++ b/client/src/store/metadata/metadata.defaults.ts
@@ -3,9 +3,8 @@
*
* 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';
+import { ModelFields, OtherFields, PhotogrammetryFields, SceneFields } from './metadata.types';
const identifierWhenSelectedValidation = {
is: true,
@@ -16,8 +15,7 @@ const identifierWhenSelectedValidation = {
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()
+ identifierType: yup.number().nullable(true)
});
const folderSchema = yup.object().shape({
@@ -27,8 +25,8 @@ const folderSchema = yup.object().shape({
});
const identifierValidation = {
- test: array => !!lodash.filter(array as StateIdentifier[], { selected: true }).length,
- message: 'Should select/provide at least 1 identifier'
+ test: array => array.length && array.every(identifier => identifier.identifier.length),
+ message: 'Should provide at least 1 identifier with valid identifier ID'
};
const identifiersWhenValidation = {
@@ -65,19 +63,39 @@ export const photogrammetryFieldsSchema = yup.object().shape({
systemCreated: yup.boolean().required(),
identifiers: yup.array().of(identifierSchema).when('systemCreated', identifiersWhenValidation),
folders: yup.array().of(folderSchema),
- name: yup.string(),
- description: yup.string().required('Description cannot be empty'),
+ name: yup.string().required('Name cannot be empty'),
+ // 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),
+ datasetFieldId: yup
+ .number()
+ .nullable(true)
+ .typeError('Dataset Field ID must be a positive integer')
+ .positive('Dataset Field ID must be a positive integer')
+ .max(2147483647, 'Dataset Field ID is too large'),
itemPositionType: yup.number().nullable(true),
- itemPositionFieldId: yup.number().nullable(true),
- itemArrangementFieldId: yup.number().nullable(true),
+ itemPositionFieldId: yup
+ .number()
+ .nullable(true)
+ .typeError('Item Position Field ID must be a positive integer')
+ .positive('Item Position Field ID must be a positive integer')
+ .max(2147483647, 'Item Position Field ID is too large'),
+ itemArrangementFieldId: yup
+ .number()
+ .nullable(true)
+ .typeError('Item Arrangement Field ID must be a positive integer')
+ .positive('Item Arrangement Field ID must be a positive integer')
+ .max(2147483647, 'Item Arrangement Field ID is too large'),
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),
+ clusterGeometryFieldId: yup
+ .number()
+ .nullable(true)
+ .typeError('Cluster Geometry Field ID must be a positive integer')
+ .positive('Cluster Geometry Field ID must be a positive integer')
+ .max(2147483647, 'Cluster Geometry Field ID is too large'),
cameraSettingUniform: yup.boolean().required(),
directory: yup.string()
});
@@ -150,11 +168,11 @@ export const defaultSceneFields: SceneFields = {
sourceObjects: [],
derivedObjects: [],
referenceModels: [],
- hasBeenQCd: false,
- isOriented: false,
name: '',
directory: '',
EdanUUID: '',
+ approvedForPublication: false,
+ posedAndQCd: false,
};
export type SceneSchemaType = typeof sceneFieldsSchema;
diff --git a/client/src/store/metadata/metadata.types.ts b/client/src/store/metadata/metadata.types.ts
index 10ba4e855..3d7a4ac80 100644
--- a/client/src/store/metadata/metadata.types.ts
+++ b/client/src/store/metadata/metadata.types.ts
@@ -55,8 +55,8 @@ export type StateIdentifier = {
id: number;
identifier: string;
identifierType: number | null;
- selected: boolean;
idIdentifier: number;
+ preferred?: boolean;
};
export type StateFolder = {
@@ -111,11 +111,11 @@ export type SceneFields = {
sourceObjects: StateRelatedObject[];
derivedObjects: StateRelatedObject[];
referenceModels: StateReferenceModel[];
- hasBeenQCd: boolean;
- isOriented: boolean;
name: string;
directory: string;
EdanUUID: string;
+ approvedForPublication: boolean;
+ posedAndQCd: boolean;
idAsset?: number;
};
diff --git a/client/src/store/repository.ts b/client/src/store/repository.ts
index 6dc7c94e1..1bcf71d2c 100644
--- a/client/src/store/repository.ts
+++ b/client/src/store/repository.ts
@@ -8,7 +8,10 @@ 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';
+import { parseRepositoryTreeNodeId, validateArray, getTermForSystemObjectType } from '../utils/repository';
+import { apolloClient } from '../graphql';
+import { GetSystemObjectDetailsDocument } from '../types/graphql';
+import { toast } from 'react-toastify';
type RepositoryStore = {
isExpanded: boolean;
@@ -32,7 +35,9 @@ type RepositoryStore = {
modelFileType: number[];
dateCreatedFrom: Date | string | null;
dateCreatedTo: Date | string | null;
- repositoryBrowserRoot: number | null;
+ idRoot: number | null;
+ repositoryBrowserRootObjectType: string | null;
+ repositoryBrowserRootName: string | null;
getFilterState: () => RepositoryFilter;
removeUnitsOrProjects: (id: number, type: eSystemObjectType) => void;
updateFilterValue: (name: string, value: number | number[] | Date | null) => void;
@@ -44,9 +49,10 @@ type RepositoryStore = {
getMoreChildren: (nodeId: string, cursorMark: string) => Promise;
updateRepositoryFilter: (filter: RepositoryFilter) => void;
setCookieToState: () => void;
- setDefaultIngestionFilters: (systemObjectType: eSystemObjectType, idRoot: number | undefined) => void;
+ setDefaultIngestionFilters: (systemObjectType: eSystemObjectType, idRoot: number | undefined) => Promise;
getChildrenForIngestion: (idRoot: number) => void;
closeRepositoryBrowser: () => void;
+ resetRepositoryBrowserRoot: () => void;
};
export const treeRootKey: string = 'root';
@@ -72,7 +78,9 @@ export const useRepositoryStore = create((set: SetState {
const { initializeTree, setCookieToState, keyword } = get();
set({ [name]: value, loading: true, search: keyword });
@@ -88,10 +96,10 @@ export const useRepositoryStore = create((set: SetState => {
- const { getFilterState, getChildrenForIngestion, repositoryBrowserRoot } = get();
+ const { getFilterState, getChildrenForIngestion, idRoot } = get();
const filter = getFilterState();
- if (repositoryBrowserRoot) {
- getChildrenForIngestion(repositoryBrowserRoot);
+ if (idRoot) {
+ getChildrenForIngestion(idRoot);
} else {
const { data, error } = await getObjectChildrenForRoot(filter);
if (data && !error) {
@@ -165,6 +173,7 @@ export const useRepositoryStore = create((set: SetState = new Map(tree);
const previousEntries = updatedTree.get(nodeId) || [];
updatedTree.set(nodeId, [...previousEntries, ...entries]);
+ console.log(`getMoreChildren: ${updatedTree.size}`);
set({ tree: updatedTree });
if (cursorMark) {
const newCursors = cursors;
@@ -199,7 +208,7 @@ export const useRepositoryStore = create((set: SetState {
+ updateRepositoryFilter: async (filter: RepositoryFilter): Promise => {
const {
repositoryRootType,
objectsToDisplay,
@@ -217,7 +226,6 @@ export const useRepositoryStore = create((set: SetState((set: SetState(filter.captureMethod, captureMethod),
variantType: validateArray(filter.variantType, variantType),
modelPurpose: validateArray(filter.modelPurpose, modelPurpose),
- modelFileType: validateArray(filter.modelFileType, modelFileType)
+ modelFileType: validateArray(filter.modelFileType, modelFileType),
+ idRoot: filter.idRoot
// dateCreatedFrom: filter.dateCreatedFrom,
// dateCreatedTo: filter.dateCreatedTo,
};
-
set(stateValues);
+
+ if (filter.idRoot) {
+ const { data: { getSystemObjectDetails: { name, objectType } } } = await apolloClient.query({
+ query: GetSystemObjectDetailsDocument,
+ variables: {
+ input: {
+ idSystemObject: filter.idRoot
+ }
+ }
+ });
+ set({ idRoot: filter.idRoot, repositoryBrowserRootName: name, repositoryBrowserRootObjectType: getTermForSystemObjectType(objectType) });
+ }
}
setCookieToState();
initializeTree();
@@ -256,7 +276,10 @@ export const useRepositoryStore = create((set: SetState((set: SetState((set: SetState((set: SetState((set: SetState {
+ setDefaultIngestionFilters: async (systemObjectType: eSystemObjectType, idRoot: number | undefined): Promise => {
const { resetKeywordSearch, resetRepositoryFilter, getChildrenForIngestion } = get();
- set({ isExpanded: false, repositoryBrowserRoot: idRoot });
+ if (idRoot !== undefined) {
+ const { data: { getSystemObjectDetails: { name, objectType } } } = await apolloClient.query({
+ query: GetSystemObjectDetailsDocument,
+ variables: {
+ input: {
+ idSystemObject: idRoot
+ }
+ }
+ });
+ resetRepositoryFilter(false);
+ set({ isExpanded: false, idRoot, repositoryBrowserRootName: name, repositoryBrowserRootObjectType: getTermForSystemObjectType(objectType) });
+ } else {
+ toast.warn('Subject was not found in database.');
+ }
resetKeywordSearch();
- resetRepositoryFilter(false);
if (systemObjectType === eSystemObjectType.eModel) {
set({ repositoryRootType: [eSystemObjectType.eModel, eSystemObjectType.eScene], objectsToDisplay: [eSystemObjectType.eCaptureData, eSystemObjectType.eModel] });
@@ -375,6 +414,9 @@ export const useRepositoryStore = create((set: SetState {
- set({ isExpanded: true, repositoryBrowserRoot: null });
+ set({ isExpanded: true, idRoot: null });
+ },
+ resetRepositoryBrowserRoot: (): void => {
+ set({ idRoot: null, repositoryBrowserRootObjectType: null, repositoryBrowserRootName: null });
}
}));
\ No newline at end of file
diff --git a/client/src/store/utils.ts b/client/src/store/utils.ts
index 7c20f6c73..a0a7bc097 100644
--- a/client/src/store/utils.ts
+++ b/client/src/store/utils.ts
@@ -78,8 +78,8 @@ export function parseIdentifiersToState(identifiers: IngestIdentifier[], default
id: index,
identifier,
identifierType,
- selected: true,
- idIdentifier
+ idIdentifier,
+ preferred: false
})
);
diff --git a/client/src/store/workflow.tsx b/client/src/store/workflow.tsx
index 79aa3d449..8b97e116d 100644
--- a/client/src/store/workflow.tsx
+++ b/client/src/store/workflow.tsx
@@ -44,8 +44,8 @@ export const useWorkflowStore = create((set: SetState {
@@ -89,11 +89,9 @@ export const useWorkflowStore = create((set: SetState => {
const { fetchWorkflowList } = get();
- console.log('changeType', changeType, 'value', value, column, direction);
if (changeType === ePaginationChange.ePage && value !== null) set({ pageNumber: value });
if (changeType === ePaginationChange.eRowCount && value !== null) set({ rowCount: value });
diff --git a/client/src/types/graphql.tsx b/client/src/types/graphql.tsx
index fb05a5bf6..1ea77628f 100644
--- a/client/src/types/graphql.tsx
+++ b/client/src/types/graphql.tsx
@@ -357,6 +357,7 @@ export type Mutation = {
deleteObjectConnection: DeleteObjectConnectionResult;
discardUploadedAssetVersions: DiscardUploadedAssetVersionsResult;
ingestData: IngestDataResult;
+ publish: PublishResult;
rollbackSystemObjectVersion: RollbackSystemObjectVersionResult;
updateDerivedObjects: UpdateDerivedObjectsResult;
updateLicense: CreateLicenseResult;
@@ -462,6 +463,11 @@ export type MutationIngestDataArgs = {
};
+export type MutationPublishArgs = {
+ input: PublishInput;
+};
+
+
export type MutationRollbackSystemObjectVersionArgs = {
input: RollbackSystemObjectVersionInput;
};
@@ -625,8 +631,8 @@ export type IngestScene = {
idAssetVersion: Scalars['Int'];
systemCreated: Scalars['Boolean'];
name: Scalars['String'];
- hasBeenQCd: Scalars['Boolean'];
- isOriented: Scalars['Boolean'];
+ approvedForPublication: Scalars['Boolean'];
+ posedAndQCd: Scalars['Boolean'];
directory: Scalars['String'];
identifiers: Array;
referenceModels: Array;
@@ -934,8 +940,8 @@ export type IngestSceneInput = {
idAsset?: Maybe;
systemCreated: Scalars['Boolean'];
name: Scalars['String'];
- hasBeenQCd: Scalars['Boolean'];
- isOriented: Scalars['Boolean'];
+ approvedForPublication: Scalars['Boolean'];
+ posedAndQCd: Scalars['Boolean'];
directory: Scalars['String'];
identifiers: Array;
sourceObjects: Array;
@@ -1305,8 +1311,6 @@ export type GetFilterViewDataResult = {
export type CreateSceneInput = {
Name: Scalars['String'];
- HasBeenQCd: Scalars['Boolean'];
- IsOriented: Scalars['Boolean'];
idAssetThumbnail?: Maybe;
CountScene?: Maybe;
CountNode?: Maybe;
@@ -1317,6 +1321,8 @@ export type CreateSceneInput = {
CountSetup?: Maybe;
CountTour?: Maybe;
EdanUUID?: Maybe;
+ ApprovedForPublication: Scalars['Boolean'];
+ PosedAndQCd: Scalars['Boolean'];
};
export type CreateSceneResult = {
@@ -1345,9 +1351,7 @@ export type GetIntermediaryFileResult = {
export type Scene = {
__typename?: 'Scene';
idScene: Scalars['Int'];
- HasBeenQCd: Scalars['Boolean'];
idAssetThumbnail?: Maybe;
- IsOriented: Scalars['Boolean'];
Name: Scalars['String'];
CountScene?: Maybe;
CountNode?: Maybe;
@@ -1358,6 +1362,8 @@ export type Scene = {
CountSetup?: Maybe;
CountTour?: Maybe;
EdanUUID?: Maybe;
+ ApprovedForPublication: Scalars['Boolean'];
+ PosedAndQCd: Scalars['Boolean'];
AssetThumbnail?: Maybe;
ModelSceneXref?: Maybe>>;
SystemObject?: Maybe;
@@ -1415,6 +1421,7 @@ export type SubjectDetailFieldsInput = {
TS0?: Maybe;
TS1?: Maybe;
TS2?: Maybe;
+ idIdentifierPreferred?: Maybe;
};
export type ItemDetailFieldsInput = {
@@ -1448,6 +1455,7 @@ export type CaptureDataDetailFieldsInput = {
clusterType?: Maybe;
clusterGeometryFieldId?: Maybe;
folders: Array;
+ isValidData?: Maybe;
};
export type ModelDetailFieldsInput = {
@@ -1464,8 +1472,8 @@ export type SceneDetailFieldsInput = {
AssetType?: Maybe;
Tours?: Maybe;
Annotation?: Maybe;
- HasBeenQCd?: Maybe;
- IsOriented?: Maybe;
+ ApprovedForPublication?: Maybe;
+ PosedAndQCd?: Maybe;
};
export type ProjectDocumentationDetailFieldsInput = {
@@ -1522,35 +1530,46 @@ export type UpdateObjectDetailsResult = {
message: Scalars['String'];
};
+export type ExistingRelationship = {
+ idSystemObject: Scalars['Int'];
+ objectType: Scalars['Int'];
+};
+
export type UpdateDerivedObjectsInput = {
idSystemObject: Scalars['Int'];
- Derivatives: Array;
- PreviouslySelected: Array;
+ ParentObjectType: Scalars['Int'];
+ Derivatives: Array;
+ PreviouslySelected: Array;
};
export type UpdateDerivedObjectsResult = {
__typename?: 'UpdateDerivedObjectsResult';
success: Scalars['Boolean'];
+ message: Scalars['String'];
+ status: Scalars['String'];
};
export type UpdateSourceObjectsInput = {
idSystemObject: Scalars['Int'];
- Sources: Array;
- PreviouslySelected: Array;
+ ChildObjectType: Scalars['Int'];
+ Sources: Array;
+ PreviouslySelected: Array;
};
export type UpdateSourceObjectsResult = {
__typename?: 'UpdateSourceObjectsResult';
success: Scalars['Boolean'];
+ message: Scalars['String'];
+ status: Scalars['String'];
};
export type UpdateIdentifier = {
id: Scalars['Int'];
identifier: Scalars['String'];
identifierType: Scalars['Int'];
- selected: Scalars['Boolean'];
idSystemObject: Scalars['Int'];
idIdentifier: Scalars['Int'];
+ preferred?: Maybe;
};
export type DeleteObjectConnectionResult = {
@@ -1561,7 +1580,9 @@ export type DeleteObjectConnectionResult = {
export type DeleteObjectConnectionInput = {
idSystemObjectMaster: Scalars['Int'];
+ objectTypeMaster: Scalars['Int'];
idSystemObjectDerived: Scalars['Int'];
+ objectTypeDerived: Scalars['Int'];
};
export type DeleteIdentifierResult = {
@@ -1599,7 +1620,18 @@ export type CreateIdentifierInput = {
identifierValue: Scalars['String'];
identifierType: Scalars['Int'];
idSystemObject?: Maybe;
- selected: Scalars['Boolean'];
+ preferred?: Maybe;
+};
+
+export type PublishInput = {
+ idSystemObject: Scalars['Int'];
+ eState: Scalars['Int'];
+};
+
+export type PublishResult = {
+ __typename?: 'PublishResult';
+ success: Scalars['Boolean'];
+ message: Scalars['String'];
};
@@ -1631,6 +1663,7 @@ export type SubjectDetailFields = {
TS0?: Maybe;
TS1?: Maybe;
TS2?: Maybe;
+ idIdentifierPreferred?: Maybe;
};
export type ItemDetailFields = {
@@ -1675,8 +1708,6 @@ export type SceneDetailFields = {
AssetType?: Maybe;
Tours?: Maybe;
Annotation?: Maybe;
- HasBeenQCd?: Maybe;
- IsOriented?: Maybe;
CountScene?: Maybe;
CountNode?: Maybe;
CountCamera?: Maybe;
@@ -1686,6 +1717,9 @@ export type SceneDetailFields = {
CountSetup?: Maybe;
CountTour?: Maybe;
EdanUUID?: Maybe;
+ ApprovedForPublication?: Maybe;
+ PublicationApprover?: Maybe;
+ PosedAndQCd?: Maybe;
idScene?: Maybe;
};
@@ -1770,6 +1804,8 @@ export type GetSystemObjectDetailsResult = {
objectType: Scalars['Int'];
allowed: Scalars['Boolean'];
publishedState: Scalars['String'];
+ publishedEnum: Scalars['Int'];
+ publishable: Scalars['Boolean'];
thumbnail?: Maybe;
identifiers: Array;
objectAncestors: Array>;
@@ -2701,6 +2737,19 @@ export type DeleteObjectConnectionMutation = (
) }
);
+export type PublishMutationVariables = Exact<{
+ input: PublishInput;
+}>;
+
+
+export type PublishMutation = (
+ { __typename?: 'Mutation' }
+ & { publish: (
+ { __typename?: 'PublishResult' }
+ & Pick
+ ) }
+);
+
export type RollbackSystemObjectVersionMutationVariables = Exact<{
input: RollbackSystemObjectVersionInput;
}>;
@@ -2723,7 +2772,7 @@ export type UpdateDerivedObjectsMutation = (
{ __typename?: 'Mutation' }
& { updateDerivedObjects: (
{ __typename?: 'UpdateDerivedObjectsResult' }
- & Pick
+ & Pick
) }
);
@@ -2749,7 +2798,7 @@ export type UpdateSourceObjectsMutation = (
{ __typename?: 'Mutation' }
& { updateSourceObjects: (
{ __typename?: 'UpdateSourceObjectsResult' }
- & Pick
+ & Pick
) }
);
@@ -2978,7 +3027,7 @@ export type GetAssetVersionsDetailsQuery = (
)> }
)>, Scene?: Maybe<(
{ __typename?: 'IngestScene' }
- & Pick
+ & Pick
& { identifiers: Array<(
{ __typename?: 'IngestIdentifier' }
& Pick
@@ -3289,7 +3338,7 @@ export type GetSceneQuery = (
{ __typename?: 'GetSceneResult' }
& { Scene?: Maybe<(
{ __typename?: 'Scene' }
- & Pick
+ & Pick
& { ModelSceneXref?: Maybe
@@ -3312,7 +3361,7 @@ export type GetSceneForAssetVersionQuery = (
{ __typename?: 'SceneConstellation' }
& { Scene?: Maybe<(
{ __typename?: 'Scene' }
- & Pick
+ & Pick
)>, ModelSceneXref?: Maybe
@@ -3362,7 +3411,7 @@ export type GetDetailsTabDataForObjectQuery = (
& Pick
)>, Subject?: Maybe<(
{ __typename?: 'SubjectDetailFields' }
- & Pick
+ & Pick
)>, Item?: Maybe<(
{ __typename?: 'ItemDetailFields' }
& Pick
@@ -3396,7 +3445,7 @@ export type GetDetailsTabDataForObjectQuery = (
)>> }
)>, Scene?: Maybe<(
{ __typename?: 'SceneDetailFields' }
- & Pick
+ & Pick
)>, IntermediaryFile?: Maybe<(
{ __typename?: 'IntermediaryFileDetailFields' }
& Pick
@@ -3480,7 +3529,7 @@ export type GetSystemObjectDetailsQuery = (
{ __typename?: 'Query' }
& { getSystemObjectDetails: (
{ __typename?: 'GetSystemObjectDetailsResult' }
- & Pick
+ & Pick
& { identifiers: Array<(
{ __typename?: 'IngestIdentifier' }
& Pick
@@ -3612,7 +3661,7 @@ export type GetObjectsForItemQuery = (
& Pick
)>, Scene: Array<(
{ __typename?: 'Scene' }
- & Pick
+ & Pick
)>, IntermediaryFile: Array<(
{ __typename?: 'IntermediaryFile' }
& Pick
@@ -4336,6 +4385,40 @@ export function useDeleteObjectConnectionMutation(baseOptions?: Apollo.MutationH
export type DeleteObjectConnectionMutationHookResult = ReturnType;
export type DeleteObjectConnectionMutationResult = Apollo.MutationResult;
export type DeleteObjectConnectionMutationOptions = Apollo.BaseMutationOptions;
+export const PublishDocument = gql`
+ mutation publish($input: PublishInput!) {
+ publish(input: $input) {
+ success
+ message
+ }
+}
+ `;
+export type PublishMutationFn = Apollo.MutationFunction;
+
+/**
+ * __usePublishMutation__
+ *
+ * To run a mutation, you first call `usePublishMutation` within a React component and pass it any options that fit your needs.
+ * When your component renders, `usePublishMutation` 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 [publishMutation, { data, loading, error }] = usePublishMutation({
+ * variables: {
+ * input: // value for 'input'
+ * },
+ * });
+ */
+export function usePublishMutation(baseOptions?: Apollo.MutationHookOptions) {
+ const options = {...defaultOptions, ...baseOptions}
+ return Apollo.useMutation(PublishDocument, options);
+ }
+export type PublishMutationHookResult = ReturnType;
+export type PublishMutationResult = Apollo.MutationResult;
+export type PublishMutationOptions = Apollo.BaseMutationOptions;
export const RollbackSystemObjectVersionDocument = gql`
mutation rollbackSystemObjectVersion($input: RollbackSystemObjectVersionInput!) {
rollbackSystemObjectVersion(input: $input) {
@@ -4374,6 +4457,8 @@ export const UpdateDerivedObjectsDocument = gql`
mutation updateDerivedObjects($input: UpdateDerivedObjectsInput!) {
updateDerivedObjects(input: $input) {
success
+ message
+ status
}
}
`;
@@ -4441,6 +4526,8 @@ export const UpdateSourceObjectsDocument = gql`
mutation updateSourceObjects($input: UpdateSourceObjectsInput!) {
updateSourceObjects(input: $input) {
success
+ status
+ message
}
}
`;
@@ -4949,9 +5036,9 @@ export const GetAssetVersionsDetailsDocument = gql`
idAssetVersion
systemCreated
name
- hasBeenQCd
- isOriented
directory
+ approvedForPublication
+ posedAndQCd
identifiers {
identifier
identifierType
@@ -5674,8 +5761,6 @@ export const GetSceneDocument = gql`
getScene(input: $input) {
Scene {
idScene
- HasBeenQCd
- IsOriented
Name
CountCamera
CountScene
@@ -5686,6 +5771,8 @@ export const GetSceneDocument = gql`
CountSetup
CountTour
EdanUUID
+ ApprovedForPublication
+ PosedAndQCd
ModelSceneXref {
idModelSceneXref
idModel
@@ -5741,9 +5828,7 @@ export const GetSceneForAssetVersionDocument = gql`
SceneConstellation {
Scene {
idScene
- HasBeenQCd
idAssetThumbnail
- IsOriented
Name
CountScene
CountNode
@@ -5753,6 +5838,8 @@ export const GetSceneForAssetVersionDocument = gql`
CountMeta
CountSetup
CountTour
+ ApprovedForPublication
+ PosedAndQCd
}
ModelSceneXref {
idModelSceneXref
@@ -5871,6 +5958,7 @@ export const GetDetailsTabDataForObjectDocument = gql`
TS0
TS1
TS2
+ idIdentifierPreferred
}
Item {
EntireSubject
@@ -5979,9 +6067,10 @@ export const GetDetailsTabDataForObjectDocument = gql`
AssetType
Tours
Annotation
- HasBeenQCd
- IsOriented
EdanUUID
+ ApprovedForPublication
+ PublicationApprover
+ PosedAndQCd
idScene
}
IntermediaryFile {
@@ -6175,6 +6264,8 @@ export const GetSystemObjectDetailsDocument = gql`
objectType
allowed
publishedState
+ publishedEnum
+ publishable
thumbnail
identifiers {
identifier
@@ -6472,9 +6563,9 @@ export const GetObjectsForItemDocument = gql`
}
Scene {
idScene
- HasBeenQCd
- IsOriented
Name
+ ApprovedForPublication
+ PosedAndQCd
}
IntermediaryFile {
idIntermediaryFile
diff --git a/client/src/types/server.ts b/client/src/types/server.ts
index bebe6f66e..8dddeb354 100644
--- a/client/src/types/server.ts
+++ b/client/src/types/server.ts
@@ -153,8 +153,6 @@ export enum eMetadata {
eModelChannelPosition,
eModelChannelWidth,
eModelUVMapType,
- eSceneIsOriented,
- eSceneHasBeenQCd,
eSceneCountScene,
eSceneCountNode,
eSceneCountCamera,
@@ -163,6 +161,9 @@ export enum eMetadata {
eSceneCountMeta,
eSceneCountSetup,
eSceneCountTour,
+ eSceneEdanUUID,
+ eScenePosedAndQCd,
+ eSceneApprovedForPublication,
eAssetFileName,
eAssetFilePath,
eAssetType,
@@ -194,12 +195,17 @@ export enum eSystemObjectType {
eStakeholder = 13,
}
+export enum eLicense {
+ eViewDownloadCC0 = 1, // 'View and Download CC0'
+ eViewDownloadRestriction = 2, // 'View and Download with usage restrictions',
+ eViewOnly = 3, // 'View Only',
+ eRestricted = 4, // 'Restricted', default
+}
+
export enum ePublishedState {
eNotPublished = 0, // 'Not Published', default
- eRestricted = 1, // 'Restricted',
- eViewOnly = 2, // 'View Only',
- eViewDownloadRestriction = 3, // 'View and Download with usage restrictions',
- eViewDownloadCC0 = 4, // 'View and Download CC0'
+ eAPIOnly = 1, // 'API Only',
+ ePublished = 2, // 'Published'
}
export enum eIdentifierIdentifierType {
@@ -208,16 +214,24 @@ export enum eIdentifierIdentifierType {
eUnitCMSID = 81
}
-export const PublishedStateEnumToString = (eState: ePublishedState): string => {
+export function LicenseEnumToString(eState: eLicense): string {
switch (eState) {
- case ePublishedState.eRestricted: return 'Restricted';
- case ePublishedState.eViewOnly: return 'View Only';
- case ePublishedState.eViewDownloadRestriction: return 'View and Download with usage restrictions';
- case ePublishedState.eViewDownloadCC0: return 'View and Download CC0';
+ case eLicense.eViewDownloadCC0: return 'View and Download CC0';
+ case eLicense.eViewDownloadRestriction: return 'View and Download with usage restrictions';
+ case eLicense.eViewOnly: return 'View Only';
default:
- case ePublishedState.eNotPublished: return 'Not Published';
+ case eLicense.eRestricted: return 'Restricted';
}
-};
+}
+
+export function PublishedStateEnumToString(eState: ePublishedState): string {
+ switch (eState) {
+ case ePublishedState.eAPIOnly: return 'API Only';
+ case ePublishedState.ePublished: return 'Published';
+ default:
+ case ePublishedState.eNotPublished: return 'Not Published';
+ }
+}
export enum eSubjectUnitIdentifierSortColumns {
eUnitAbbreviation = 1,
diff --git a/client/src/utils/repository.tsx b/client/src/utils/repository.tsx
index 70c28780c..51ad31f60 100644
--- a/client/src/utils/repository.tsx
+++ b/client/src/utils/repository.tsx
@@ -22,6 +22,7 @@ import Colors, { RepositoryColorVariant } from '../theme/colors';
import { NavigationResultEntry } from '../types/graphql';
import { eMetadata, eSystemObjectType } from '../types/server';
import { safeDate, convertLocalDateToUTC } from './shared';
+import { ExistingRelationship } from '../types/graphql';
export function getSystemObjectTypesForFilter(filter: RepositoryFilter): eSystemObjectType[] {
const objectTypes: eSystemObjectType[] = [];
@@ -210,7 +211,7 @@ type ObjectInterfaceDetails = {
};
// prettier-ignore
-export function getObjectInterfaceDetails(objectType: eSystemObjectType, variant: RepositoryColorVariant): ObjectInterfaceDetails {
+export function getObjectInterfaceDetails(objectType: eSystemObjectType, variant: RepositoryColorVariant, makeStyles: any): 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];
@@ -228,7 +229,8 @@ export function getObjectInterfaceDetails(objectType: eSystemObjectType, variant
case eSystemObjectType.eAssetVersion:
return { icon: , color };
}
- return { icon: , color };
+
+ return { icon: , color };
}
export function sortEntriesAlphabetically(entries: NavigationResultEntry[]): NavigationResultEntry[] {
@@ -262,10 +264,59 @@ export function getDownloadObjectVersionUrlForObject(serverEndPoint: string | un
return `${serverEndPoint}/download?idSystemObjectVersion=${idSystemObjectVersion}`;
}
-export function getRootSceneDownloadUrlForVoyager(serverEndPoint: string | undefined, idSystemObject: number, path: string): string {
- return `${serverEndPoint}/download/idSystemObject-${idSystemObject}/${path ? path + '/' : ''}`;
+export enum eVoyagerStoryMode {
+ eViewer,
+ eEdit,
+ eQC,
+ eAuthor,
+ eExpert,
+}
+
+export function getModeForVoyager(eMode?: eVoyagerStoryMode): string {
+ switch (eMode) {
+ default:
+ case eVoyagerStoryMode.eViewer: return '';
+ case eVoyagerStoryMode.eEdit: return 'edit';
+ case eVoyagerStoryMode.eQC: return 'qc';
+ case eVoyagerStoryMode.eAuthor: return 'author';
+ case eVoyagerStoryMode.eExpert: return 'expert';
+ }
+}
+
+export function getVoyagerModeFromParam(sMode: string): eVoyagerStoryMode {
+ switch (sMode) {
+ default:
+ case '': return eVoyagerStoryMode.eViewer;
+ case 'edit': return eVoyagerStoryMode.eEdit;
+ case 'qc': return eVoyagerStoryMode.eQC;
+ case 'author': return eVoyagerStoryMode.eAuthor;
+ case 'expert': return eVoyagerStoryMode.eExpert;
+ }
+}
+
+export function getRootSceneDownloadUrlForVoyager(serverEndPoint: string | undefined, idSystemObject: number,
+ path: string, eMode?: eVoyagerStoryMode | undefined): string {
+ let dlPath: string = 'download';
+ switch (eMode) {
+ default:
+ case eVoyagerStoryMode.eViewer: dlPath='download'; break;
+ case eVoyagerStoryMode.eEdit: dlPath='download-wd'; break;
+ case eVoyagerStoryMode.eQC: dlPath='download-wd'; break;
+ case eVoyagerStoryMode.eAuthor: dlPath='download-wd'; break;
+ case eVoyagerStoryMode.eExpert: dlPath='download-wd'; break;
+ }
+ return `${serverEndPoint}/${dlPath}/idSystemObject-${idSystemObject}/${path ? path + '/' : ''}`;
+}
+
+export function getVoyagerStoryUrl(serverEndPoint: string | undefined, idSystemObject: number,
+ document: string, path: string, eMode?: eVoyagerStoryMode | undefined): string {
+
+ const mode: string = getModeForVoyager(eMode);
+ const root: string = getRootSceneDownloadUrlForVoyager(serverEndPoint, idSystemObject, path, eMode);
+ return `/repository/voyager/${idSystemObject}?mode=${mode}&root=${root}&document=${document}`;
}
+
// prettier-ignore
export function getTreeViewStyleHeight(isExpanded: boolean, isModal: boolean, breakpoint: Breakpoint): string {
const isSmallScreen: boolean = breakpoint === 'lg';
@@ -310,3 +361,84 @@ export function getUpdatedCheckboxProps(updated: boolean): CheckboxProps {
color: updated ? 'secondary' : 'primary'
};
}
+
+export function isValidParentChildRelationship(
+ parent: number,
+ child: number,
+ selected: ExistingRelationship[],
+ existingParentRelationships: ExistingRelationship[],
+ isAddingSource: boolean
+): boolean {
+ let result = false;
+ /*
+ *NOTE: when updating this relationship validation function,
+ make sure to also apply changes to the server-side version located at
+ ingestData.ts to maintain consistency
+ **NOTE: this client-side validation function will be validating a selected item BEFORE adding it,
+ which means the maximum connection count will be different from those seen in ingestData.ts
+
+ xproject child to 1 - many unit parent
+ -skip on stakeholders for now
+ -skip on stakeholders for now
+ xitem child to only 1 parent project parent
+ xitem child to multiple subject parent
+ xCD child to 1 - many item parent
+ xmodel child to 1 - many parent Item
+ xscene child to 1 or more item parent
+ xmodel child to 0 - many CD parent
+ xCD child to 0 - many CD parent
+ -skip on actor for now
+ xmodel child to 0 to many model parent
+ xscene child to 1 to many model parent
+ -skip on actor for now
+ xmodel child to only 1 scene parent
+ -skip on IF for now
+ -skip on PD for now
+ */
+
+ const existingAndNewRelationships = [...existingParentRelationships, ...selected];
+ switch (child) {
+ case eSystemObjectType.eProject:
+ result = parent === eSystemObjectType.eUnit;
+ break;
+ case eSystemObjectType.eItem: {
+ if (parent === eSystemObjectType.eSubject) result = true;
+
+ if (parent === eSystemObjectType.eProject) {
+ if (isAddingSource) {
+ result = maximumConnections(existingAndNewRelationships, eSystemObjectType.eProject, 1);
+ } else {
+ result = maximumConnections(existingAndNewRelationships, eSystemObjectType.eProject, 1);
+ }
+ }
+ break;
+ }
+ case eSystemObjectType.eCaptureData: {
+
+ if (parent === eSystemObjectType.eCaptureData || parent === eSystemObjectType.eItem) result = true;
+ break;
+ }
+ case eSystemObjectType.eModel: {
+
+ if (parent === eSystemObjectType.eScene) {
+ if (isAddingSource) {
+ result = maximumConnections(existingAndNewRelationships, eSystemObjectType.eScene, 1);
+ } else {
+ result = maximumConnections(existingAndNewRelationships, eSystemObjectType.eScene, 1);
+ }
+ }
+
+ if (parent === eSystemObjectType.eCaptureData || parent === eSystemObjectType.eModel || parent === eSystemObjectType.eItem) result = true;
+ break;
+ }
+ case eSystemObjectType.eScene: {
+ if (parent === eSystemObjectType.eItem || parent === eSystemObjectType.eModel) result = true;
+ break;
+ }
+ }
+
+ return result;
+}
+
+const maximumConnections = (relationships: ExistingRelationship[], objectType: number, limit: number) =>
+ relationships.filter(relationship => relationship.objectType === objectType).length < limit;
diff --git a/client/src/utils/shared.ts b/client/src/utils/shared.ts
index a2e546c5a..0ae82721a 100644
--- a/client/src/utils/shared.ts
+++ b/client/src/utils/shared.ts
@@ -63,11 +63,6 @@ export const sharedLabelProps: CSSProperties = {
color: palette.primary.dark
};
-export function getHeaderTitle(title?: string): string {
- if (title) return `${title} - Packrat`;
- return 'Packrat';
-}
-
export function safeDate(value: any): Date | null {
if (value == null)
return null;
diff --git a/conf/docker/daemon.json b/conf/docker/daemon.json
new file mode 100644
index 000000000..ab97a9002
--- /dev/null
+++ b/conf/docker/daemon.json
@@ -0,0 +1,8 @@
+{
+ "default-address-pools": [
+ {
+ "base": "172.19.19.0/24",
+ "size": 24
+ }
+ ]
+}
\ No newline at end of file
diff --git a/conf/docker/docker-compose.deploy.yml b/conf/docker/docker-compose.deploy.yml
index 443d8c122..79c7cebb4 100644
--- a/conf/docker/docker-compose.deploy.yml
+++ b/conf/docker/docker-compose.deploy.yml
@@ -41,19 +41,19 @@ services:
- $PACKRAT_SERVER_PORT:4000
environment:
- NODE_ENV=$NODE_ENV
- - PACKRAT_CLIENT_ENDPOINT=$PACKRAT_CLIENT_ENDPOINT
+ - REACT_APP_PACKRAT_SERVER_ENDPOINT=$REACT_APP_PACKRAT_SERVER_ENDPOINT
- PACKRAT_DATABASE_URL=$PACKRAT_DATABASE_URL
+ - PACKRAT_CLIENT_ENDPOINT=$PACKRAT_CLIENT_ENDPOINT
- PACKRAT_SESSION_SECRET=$PACKRAT_SESSION_SECRET
- PACKRAT_EDAN_AUTH_KEY=$PACKRAT_EDAN_AUTH_KEY
- PACKRAT_EDAN_SERVER=$PACKRAT_EDAN_SERVER
- PACKRAT_EDAN_3D_API=$PACKRAT_EDAN_3D_API
- PACKRAT_EDAN_APPID=$PACKRAT_EDAN_APPID
- - PACKRAT_EDAN_UPSERT_RESOURCE_ROOT:$PACKRAT_EDAN_UPSERT_RESOURCE_ROOT
+ - PACKRAT_EDAN_UPSERT_RESOURCE_ROOT=$PACKRAT_EDAN_UPSERT_RESOURCE_ROOT
- PACKRAT_EDAN_STAGING_ROOT=$PACKRAT_EDAN_STAGING_ROOT
+ - PACKRAT_EDAN_RESOURCES_HOTFOLDER=$PACKRAT_EDAN_RESOURCES_HOTFOLDER
- PACKRAT_OCFL_STORAGE_ROOT=$PACKRAT_OCFL_STORAGE_ROOT
- PACKRAT_OCFL_STAGING_ROOT=$PACKRAT_OCFL_STAGING_ROOT
- - PACKRAT_EDAN_RESOURCES_HOTFOLDER=$PACKRAT_EDAN_RESOURCES_HOTFOLDER
- - PACKRAT_SOLR_HOST=$PACKRAT_SOLR_HOST
- PACKRAT_COOK_SERVER_URL=$PACKRAT_COOK_SERVER_URL
- PACKRAT_AUTH_TYPE=$PACKRAT_AUTH_TYPE
- PACKRAT_LDAP_SERVER=$PACKRAT_LDAP_SERVER
@@ -61,8 +61,13 @@ services:
- PACKRAT_LDAP_CN=$PACKRAT_LDAP_CN
- PACKRAT_LDAP_OU=$PACKRAT_LDAP_OU
- PACKRAT_LDAP_DC=$PACKRAT_LDAP_DC
+ - PACKRAT_SOLR_HOST=$PACKRAT_SOLR_HOST
+ - PACKRAT_SOLR_PORT=$PACKRAT_SOLR_PORT
+
volumes:
- $PACKRAT_OCFL_STORAGE_ROOT:$PACKRAT_OCFL_STORAGE_ROOT
+ - $PACKRAT_EDAN_STAGING_ROOT:$PACKRAT_EDAN_STAGING_ROOT
+ - $PACKRAT_EDAN_RESOURCES_HOTFOLDER:$PACKRAT_EDAN_RESOURCES_HOTFOLDER
packrat-server-prod:
container_name: packrat-server-prod
@@ -76,19 +81,19 @@ services:
- $PACKRAT_SERVER_PORT:4000
environment:
- NODE_ENV=$NODE_ENV
- - PACKRAT_CLIENT_ENDPOINT=$PACKRAT_CLIENT_ENDPOINT
+ - REACT_APP_PACKRAT_SERVER_ENDPOINT=$REACT_APP_PACKRAT_SERVER_ENDPOINT
- PACKRAT_DATABASE_URL=$PACKRAT_DATABASE_URL
+ - PACKRAT_CLIENT_ENDPOINT=$PACKRAT_CLIENT_ENDPOINT
- PACKRAT_SESSION_SECRET=$PACKRAT_SESSION_SECRET
- PACKRAT_EDAN_AUTH_KEY=$PACKRAT_EDAN_AUTH_KEY
- PACKRAT_EDAN_SERVER=$PACKRAT_EDAN_SERVER
- PACKRAT_EDAN_3D_API=$PACKRAT_EDAN_3D_API
- PACKRAT_EDAN_APPID=$PACKRAT_EDAN_APPID
- - PACKRAT_EDAN_UPSERT_RESOURCE_ROOT:$PACKRAT_EDAN_UPSERT_RESOURCE_ROOT
+ - PACKRAT_EDAN_UPSERT_RESOURCE_ROOT=$PACKRAT_EDAN_UPSERT_RESOURCE_ROOT
- PACKRAT_EDAN_STAGING_ROOT=$PACKRAT_EDAN_STAGING_ROOT
- PACKRAT_EDAN_RESOURCES_HOTFOLDER=$PACKRAT_EDAN_RESOURCES_HOTFOLDER
- PACKRAT_OCFL_STORAGE_ROOT=$PACKRAT_OCFL_STORAGE_ROOT
- PACKRAT_OCFL_STAGING_ROOT=$PACKRAT_OCFL_STAGING_ROOT
- - PACKRAT_SOLR_HOST=$PACKRAT_SOLR_HOST
- PACKRAT_COOK_SERVER_URL=$PACKRAT_COOK_SERVER_URL
- PACKRAT_AUTH_TYPE=$PACKRAT_AUTH_TYPE
- PACKRAT_LDAP_SERVER=$PACKRAT_LDAP_SERVER
@@ -96,6 +101,13 @@ services:
- PACKRAT_LDAP_CN=$PACKRAT_LDAP_CN
- PACKRAT_LDAP_OU=$PACKRAT_LDAP_OU
- PACKRAT_LDAP_DC=$PACKRAT_LDAP_DC
+ - PACKRAT_SOLR_HOST=$PACKRAT_SOLR_HOST
+ - PACKRAT_SOLR_PORT=$PACKRAT_SOLR_PORT
+
+ volumes:
+ - $PACKRAT_OCFL_STORAGE_ROOT:$PACKRAT_OCFL_STORAGE_ROOT
+ - $PACKRAT_EDAN_STAGING_ROOT:$PACKRAT_EDAN_STAGING_ROOT
+ - $PACKRAT_EDAN_RESOURCES_HOTFOLDER:$PACKRAT_EDAN_RESOURCES_HOTFOLDER
packrat-db-dev:
container_name: packrat-db-dev
diff --git a/conf/docker/docker-compose.dev.yml b/conf/docker/docker-compose.dev.yml
index 89c487e3e..1f40a8159 100644
--- a/conf/docker/docker-compose.dev.yml
+++ b/conf/docker/docker-compose.dev.yml
@@ -9,9 +9,6 @@ services:
context: ../..
dockerfile: ./conf/docker/proxy-dev.Dockerfile
target: proxy
- depends_on:
- - packrat-client
- - packrat-server
volumes:
- ../../conf/nginx/nginx-dev.conf:/etc/nginx/nginx.conf
ports:
@@ -47,19 +44,19 @@ services:
- $PACKRAT_SERVER_PORT:4000
environment:
- NODE_ENV=$NODE_ENV
- - PACKRAT_CLIENT_ENDPOINT=$PACKRAT_CLIENT_ENDPOINT
+ - REACT_APP_PACKRAT_SERVER_ENDPOINT=$REACT_APP_PACKRAT_SERVER_ENDPOINT
- PACKRAT_DATABASE_URL=$PACKRAT_DATABASE_URL
+ - PACKRAT_CLIENT_ENDPOINT=$PACKRAT_CLIENT_ENDPOINT
- PACKRAT_SESSION_SECRET=$PACKRAT_SESSION_SECRET
- PACKRAT_EDAN_AUTH_KEY=$PACKRAT_EDAN_AUTH_KEY
- PACKRAT_EDAN_SERVER=$PACKRAT_EDAN_SERVER
- PACKRAT_EDAN_3D_API=$PACKRAT_EDAN_3D_API
- PACKRAT_EDAN_APPID=$PACKRAT_EDAN_APPID
- - PACKRAT_EDAN_UPSERT_RESOURCE_ROOT:$PACKRAT_EDAN_UPSERT_RESOURCE_ROOT
+ - PACKRAT_EDAN_UPSERT_RESOURCE_ROOT=$PACKRAT_EDAN_UPSERT_RESOURCE_ROOT
- PACKRAT_EDAN_STAGING_ROOT=$PACKRAT_EDAN_STAGING_ROOT
- PACKRAT_EDAN_RESOURCES_HOTFOLDER=$PACKRAT_EDAN_RESOURCES_HOTFOLDER
- PACKRAT_OCFL_STORAGE_ROOT=$PACKRAT_OCFL_STORAGE_ROOT
- PACKRAT_OCFL_STAGING_ROOT=$PACKRAT_OCFL_STAGING_ROOT
- - PACKRAT_SOLR_HOST=$PACKRAT_SOLR_HOST
- PACKRAT_COOK_SERVER_URL=$PACKRAT_COOK_SERVER_URL
- PACKRAT_AUTH_TYPE=$PACKRAT_AUTH_TYPE
- PACKRAT_LDAP_SERVER=$PACKRAT_LDAP_SERVER
@@ -67,6 +64,8 @@ services:
- PACKRAT_LDAP_CN=$PACKRAT_LDAP_CN
- PACKRAT_LDAP_OU=$PACKRAT_LDAP_OU
- PACKRAT_LDAP_DC=$PACKRAT_LDAP_DC
+ - PACKRAT_SOLR_HOST=$PACKRAT_SOLR_HOST
+ - PACKRAT_SOLR_PORT=$PACKRAT_SOLR_PORT
volumes:
- ../../node_modules:/app/node_modules
diff --git a/conf/docker/docker-compose.prod.yml b/conf/docker/docker-compose.prod.yml
index ff1ba15e5..b73f59a1c 100644
--- a/conf/docker/docker-compose.prod.yml
+++ b/conf/docker/docker-compose.prod.yml
@@ -41,19 +41,19 @@ services:
- $PACKRAT_SERVER_PORT:4000
environment:
- NODE_ENV=$NODE_ENV
- - PACKRAT_CLIENT_ENDPOINT=$PACKRAT_CLIENT_ENDPOINT
+ - REACT_APP_PACKRAT_SERVER_ENDPOINT=$REACT_APP_PACKRAT_SERVER_ENDPOINT
- PACKRAT_DATABASE_URL=$PACKRAT_DATABASE_URL
+ - PACKRAT_CLIENT_ENDPOINT=$PACKRAT_CLIENT_ENDPOINT
- PACKRAT_SESSION_SECRET=$PACKRAT_SESSION_SECRET
- PACKRAT_EDAN_AUTH_KEY=$PACKRAT_EDAN_AUTH_KEY
- PACKRAT_EDAN_SERVER=$PACKRAT_EDAN_SERVER
- PACKRAT_EDAN_3D_API=$PACKRAT_EDAN_3D_API
- PACKRAT_EDAN_APPID=$PACKRAT_EDAN_APPID
- - PACKRAT_EDAN_UPSERT_RESOURCE_ROOT:$PACKRAT_EDAN_UPSERT_RESOURCE_ROOT
+ - PACKRAT_EDAN_UPSERT_RESOURCE_ROOT=$PACKRAT_EDAN_UPSERT_RESOURCE_ROOT
- PACKRAT_EDAN_STAGING_ROOT=$PACKRAT_EDAN_STAGING_ROOT
- PACKRAT_EDAN_RESOURCES_HOTFOLDER=$PACKRAT_EDAN_RESOURCES_HOTFOLDER
- PACKRAT_OCFL_STORAGE_ROOT=$PACKRAT_OCFL_STORAGE_ROOT
- PACKRAT_OCFL_STAGING_ROOT=$PACKRAT_OCFL_STAGING_ROOT
- - PACKRAT_SOLR_HOST=$PACKRAT_SOLR_HOST
- PACKRAT_COOK_SERVER_URL=$PACKRAT_COOK_SERVER_URL
- PACKRAT_AUTH_TYPE=$PACKRAT_AUTH_TYPE
- PACKRAT_LDAP_SERVER=$PACKRAT_LDAP_SERVER
@@ -61,6 +61,8 @@ services:
- PACKRAT_LDAP_CN=$PACKRAT_LDAP_CN
- PACKRAT_LDAP_OU=$PACKRAT_LDAP_OU
- PACKRAT_LDAP_DC=$PACKRAT_LDAP_DC
+ - PACKRAT_SOLR_HOST=$PACKRAT_SOLR_HOST
+ - PACKRAT_SOLR_PORT=$PACKRAT_SOLR_PORT
depends_on:
- db
@@ -86,6 +88,7 @@ services:
- $PACKRAT_SOLR_PORT:8983
volumes:
- ../../server/config/solr/data/packrat:/var/solr/data/packrat
+ - ../../server/config/solr/data/packratMeta:/var/solr/data/packratMeta
networks:
default:
diff --git a/conf/docker/server-dev.Dockerfile b/conf/docker/server-dev.Dockerfile
index 916effac4..4dbdb9fa3 100644
--- a/conf/docker/server-dev.Dockerfile
+++ b/conf/docker/server-dev.Dockerfile
@@ -10,8 +10,11 @@ COPY . .
FROM base AS server
# Remove client to prevent duplication
RUN rm -rf client
+RUN apk update
# Install perl, needed by exiftool
RUN apk add perl
+# Install git, needed to fetch npm-server-webdav
+RUN apk add git
# Expose port(s)
EXPOSE 4000
# Install dependencies
diff --git a/conf/docker/server-prod.Dockerfile b/conf/docker/server-prod.Dockerfile
index ff9c3cbf3..5f58d126d 100644
--- a/conf/docker/server-prod.Dockerfile
+++ b/conf/docker/server-prod.Dockerfile
@@ -9,6 +9,9 @@ COPY . .
FROM base AS server-builder
# Remove client from server build
RUN rm -rf client
+# Install git, needed to fetch npm-server-webdav
+RUN apk update
+RUN apk add git
# Install dependencies (production mode) and build
RUN yarn install --frozen-lockfile && yarn build:prod
diff --git a/conf/docker/solr.Dockerfile b/conf/docker/solr.Dockerfile
index 604434cf4..1785af0cb 100644
--- a/conf/docker/solr.Dockerfile
+++ b/conf/docker/solr.Dockerfile
@@ -1,2 +1,3 @@
FROM solr:8 as solr
COPY --chown=solr:solr ./server/config/solr/data/packrat/ /var/solr/data/packrat/
+COPY --chown=solr:solr ./server/config/solr/data/packratMeta/ /var/solr/data/packratMeta/
diff --git a/conf/nginx/conf.d/common-locations b/conf/nginx/conf.d/common-locations
index 6783bdad4..6c718ac87 100644
--- a/conf/nginx/conf.d/common-locations
+++ b/conf/nginx/conf.d/common-locations
@@ -1,6 +1,7 @@
location /server {
rewrite /server/(.*) /$1 break;
proxy_pass http://server-dev;
+ proxy_set_header Host $host//server;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
diff --git a/conf/nginx/nginx-dev.conf b/conf/nginx/nginx-dev.conf
index ad878bc8b..43c7b98a1 100644
--- a/conf/nginx/nginx-dev.conf
+++ b/conf/nginx/nginx-dev.conf
@@ -24,6 +24,7 @@ http {
location /server {
rewrite /server/(.*) /$1 break;
+ proxy_set_header Host localhost:6656//server/;
proxy_pass http://server;
}
diff --git a/conf/nginx/tls.howto.txt b/conf/nginx/tls.howto.txt
new file mode 100644
index 000000000..803e02b40
--- /dev/null
+++ b/conf/nginx/tls.howto.txt
@@ -0,0 +1,42 @@
+****************
+Initial Requests
+****************
+Generated CSRs following the instructions here: https://www.thesslstore.com/knowledgebase/ssl-generate/csr-generation-guide-for-nginx-openssl/
+* openssl req -new -newkey rsa:2048 -nodes -keyout packrat.si.edu.key -out packrat.si.edu.csr
+* openssl req -new -newkey rsa:2048 -nodes -keyout packrat-test.si.edu.key -out packrat-test.si.edu.csr
+
+Provide these CSR to OCIO staff (Mandy Hargis-Martin), by way of a ServiceNow ticket.
+
+Specified the following information:
+Country Name (2 letter code) [AU]:US
+State or Province Name (full name) [Some-State]:DC
+Locality Name (eg, city) []:Washington
+Organization Name (eg, company) [Internet Widgits Pty Ltd]:Smithsonian Institution
+Organizational Unit Name (eg, section) []:DPO 3D
+Common Name (e.g. server FQDN or YOUR name) []:packrat.si.edu
+Email Address []:
+
+Please enter the following 'extra' attributes
+to be sent with your certificate request
+A challenge password []:
+An optional company name []:
+
+********
+Renewals
+********
+Generate CSRs:
+* openssl req -new -nodes -key packrat.si.edu.key -out packrat.si.edu.csr
+* openssl req -new -nodes -key packrat-test.si.edu.key -out packrat-test.si.edu.csr
+
+Specified the same info as the initial request. Note that this generated the same actual CSR file ... so it seems like we can simply reuse the original CSR if we're not changing any of the details.
+
+Note that OCIO staff requested these CSRs without our prompting -- they appear to have their own reminders about upcoming TLS cert expirations.
+
+************
+Installation
+************
+1. Download the Full Chain PEM bundle from the certificate authority
+2. Edit this bundle in a text editor, reversing the order of the certificates
+3. Install the cert (as root) in /etc/pki/tls/certs, using the filename specified in nginx.conf (packrat.si.edu.cert and packrat-test.si.edu.cert)
+4. Restart nginx: sudo systemctl restart nginx
+5. Verify that the new cert is active (visit https://packrat-test.si.edu:8443/ and inspect the certificate)
diff --git a/package.json b/package.json
index a71bfdda3..d81a9ba1d 100644
--- a/package.json
+++ b/package.json
@@ -37,6 +37,7 @@
"devbox:down": "docker container rm packrat-devbox --force",
"dev": "docker-compose --env-file .env.dev -p dpo-packrat -f ./conf/docker/docker-compose.dev.yml up -d",
"dev:solr": "docker-compose --env-file .env.dev -p dpo-packrat -f ./conf/docker/docker-compose.dev.yml up -d packrat-solr",
+ "dev:proxy": "docker-compose --env-file .env.dev -p dpo-packrat -f ./conf/docker/docker-compose.dev.yml up -d packrat-proxy",
"prod": "docker-compose --env-file .env.prod -p dpo-packrat -f ./conf/docker/docker-compose.prod.yml up -d",
"deploy:dev": "sh ./scripts/deploy.sh dev",
"deploy:prod": "sh ./scripts/deploy.sh prod",
diff --git a/server/auth/index.ts b/server/auth/index.ts
index ae71f0dfc..21445a14c 100644
--- a/server/auth/index.ts
+++ b/server/auth/index.ts
@@ -27,12 +27,9 @@ if (!PACKRAT_SESSION_SECRET) {
const Store = MemoryStore(session);
const { maxAge, checkPeriod } = Config.auth.session;
-// const maxAge: number = Date.now() + age;
const sessionConfig = {
- cookie: {
- maxAge
- },
+ cookie: { maxAge },
secret: PACKRAT_SESSION_SECRET,
resave: true,
saveUninitialized: true,
diff --git a/server/cache/LicenseCache.ts b/server/cache/LicenseCache.ts
index 645f8bb7e..1a66fe87b 100644
--- a/server/cache/LicenseCache.ts
+++ b/server/cache/LicenseCache.ts
@@ -6,7 +6,7 @@ import { CacheControl } from './CacheControl';
export class LicenseCache {
private static singleton: LicenseCache | null = null;
private licenseMap: Map = new Map(); // map of idLicense -> License
- private publishedStateMap: Map = new Map(); // map of ePublishedState -> License
+ private licenseEnumMap: Map = new Map(); // map of eLicense -> License
private licenseResolverMap: Map = new Map(); // map of idSystemObject -> LicenseResolver, representing cache of resolved license information
// **************************
@@ -45,10 +45,10 @@ export class LicenseCache {
for (const license of LicenseFetch) {
this.licenseMap.set(license.idLicense, license);
switch (license.Name.toLowerCase()) {
- case 'view and download cc0': this.publishedStateMap.set(DBAPI.ePublishedState.eViewDownloadCC0, license); break;
- case 'view with download restrictions': this.publishedStateMap.set(DBAPI.ePublishedState.eViewDownloadRestriction, license); break;
- case 'view only': this.publishedStateMap.set(DBAPI.ePublishedState.eViewOnly, license); break;
- case 'restricted': this.publishedStateMap.set(DBAPI.ePublishedState.eRestricted, license); break;
+ case 'view and download cc0': this.licenseEnumMap.set(DBAPI.eLicense.eViewDownloadCC0, license); break;
+ case 'view with download restrictions': this.licenseEnumMap.set(DBAPI.eLicense.eViewDownloadRestriction, license); break;
+ case 'view only': this.licenseEnumMap.set(DBAPI.eLicense.eViewOnly, license); break;
+ case 'restricted': this.licenseEnumMap.set(DBAPI.eLicense.eRestricted, license); break;
}
}
// LOG.info(`LicenseCache publishedStateMap=\n${JSON.stringify(this.publishedStateMap, H.Helpers.saferStringify)}`, LOG.LS.eCACHE);
@@ -69,14 +69,14 @@ export class LicenseCache {
return license ?? undefined;
}
- private async getLicenseByPublishedStateInternal(eState: DBAPI.ePublishedState): Promise {
- return this.publishedStateMap.get(eState);
+ private async getLicenseByEnumInternal(eState: DBAPI.eLicense): Promise {
+ return this.licenseEnumMap.get(eState);
}
- private async getLicenseResolverInternal(idSystemObject: number): Promise {
+ private async getLicenseResolverInternal(idSystemObject: number, OGD?: DBAPI.ObjectGraphDatabase | undefined): Promise {
let licenseResolver: DBAPI.LicenseResolver | undefined | null = this.licenseResolverMap.get(idSystemObject);
if (!licenseResolver) { // cache miss, look it up
- licenseResolver = await DBAPI.LicenseResolver.fetch(idSystemObject);
+ licenseResolver = await DBAPI.LicenseResolver.fetch(idSystemObject, OGD);
if (licenseResolver)
this.licenseResolverMap.set(idSystemObject, licenseResolver);
// LOG.info(`LicenseCache.getLicenseResolverInternal(${idSystemObject}) computed ${JSON.stringify(licenseResolver)}`, LOG.LS.eCACHE);
@@ -127,12 +127,13 @@ export class LicenseCache {
return await (await this.getInstance()).getLicenseInternal(idLicense);
}
- static async getLicenseByPublishedState(eState: DBAPI.ePublishedState): Promise {
- return await (await this.getInstance()).getLicenseByPublishedStateInternal(eState);
+ static async getLicenseByEnum(eState: DBAPI.eLicense): Promise {
+ return await (await this.getInstance()).getLicenseByEnumInternal(eState);
}
- static async getLicenseResolver(idSystemObject: number): Promise {
- return await (await this.getInstance()).getLicenseResolverInternal(idSystemObject);
+ /** If passing in OGD, make sure to compute this navigating through the ancestors of idSystemObject */
+ static async getLicenseResolver(idSystemObject: number, OGD?: DBAPI.ObjectGraphDatabase | undefined): Promise {
+ return await (await this.getInstance()).getLicenseResolverInternal(idSystemObject, OGD);
}
static async clearAssignment(idSystemObject: number): Promise {
diff --git a/server/cache/SystemObjectCache.ts b/server/cache/SystemObjectCache.ts
index c4462f7d0..e5d3df937 100644
--- a/server/cache/SystemObjectCache.ts
+++ b/server/cache/SystemObjectCache.ts
@@ -8,6 +8,7 @@ export class SystemObjectCache {
private objectIDToSystemMap: Map = new Map(); // map of { idObject, eDBObjectType } -> { idSystemObject, Retired }
private systemIDToObjectMap: Map = new Map(); // map of idSystemObject -> { idObject, eDBObjectType }
+ private systemIDToNameMap: Map = new Map(); // map of idSystemObject -> name
// **************************
// Boilerplate Implementation
@@ -110,6 +111,16 @@ export class SystemObjectCache {
}
private async getObjectNameInternal(SO: DBAPI.SystemObject): Promise {
+ let name: string | undefined = this.systemIDToNameMap.get(SO.idSystemObject);
+ if (name)
+ return name;
+ name = await this.getObjectNameInternalWorker(SO);
+ if (name)
+ this.systemIDToNameMap.set(SO.idSystemObject, name);
+ return name;
+ }
+
+ private async getObjectNameInternalWorker(SO: DBAPI.SystemObject): Promise {
const oID: DBAPI.ObjectIDAndType | undefined = await this.getObjectFromSystemInternal(SO.idSystemObject);
if (!oID) /* istanbul ignore next */
return undefined;
@@ -175,6 +186,19 @@ export class SystemObjectCache {
}
}
+ private async getObjectNameByIDInternal(idSystemObject: number): Promise {
+ const name: string | undefined = this.systemIDToNameMap.get(idSystemObject);
+ if (name)
+ return name;
+
+ const SO: DBAPI.SystemObject | null = await DBAPI.SystemObject.fetch(idSystemObject);
+ if (!SO) {
+ LOG.error(`SystemObjectCache.getObjectNameByIDInternal unable to lookup SystemObject for id ${idSystemObject}`, LOG.LS.eCACHE);
+ return undefined;
+ }
+ return this.getObjectNameInternal(SO);
+ }
+
private async flushObjectWorker(idSystemObject: number): Promise {
const SO: SystemObject | null = await SystemObject.fetch(idSystemObject);
if (!SO) {
@@ -187,6 +211,8 @@ export class SystemObjectCache {
this.objectIDToSystemMap.set(oID, { idSystemObject, Retired: SO.Retired });
this.systemIDToObjectMap.set(idSystemObject, oID);
}
+
+ this.systemIDToNameMap.delete(idSystemObject);
return oID;
}
// #endregion
@@ -211,12 +237,7 @@ export class SystemObjectCache {
}
static async getObjectNameByID(idSystemObject: number): Promise {
- const SO: DBAPI.SystemObject | null = await DBAPI.SystemObject.fetch(idSystemObject);
- if (!SO) {
- LOG.error(`SystemObjectCache.getObjectNameByID unable to lookup SystemObject for id ${idSystemObject}`, LOG.LS.eCACHE);
- return undefined;
- }
- return SystemObjectCache.getObjectName(SO);
+ return await (await this.getInstance()).getObjectNameByIDInternal(idSystemObject);
}
/**
diff --git a/server/collections/impl/EdanCollection.ts b/server/collections/impl/EdanCollection.ts
index 6ee9d2906..30b961437 100644
--- a/server/collections/impl/EdanCollection.ts
+++ b/server/collections/impl/EdanCollection.ts
@@ -3,7 +3,9 @@
import fetch, { RequestInit } from 'node-fetch';
import { v4 as uuidv4 } from 'uuid';
import * as COL from '../interface';
+import { PublishScene } from './PublishScene';
import { Config } from '../../config';
+import * as DBAPI from '../../db';
import * as LOG from '../../utils/logger';
import * as H from '../../utils/helpers';
@@ -109,6 +111,20 @@ export class EdanCollection implements COL.ICollection {
return result;
}
+ async publish(idSystemObject: number, ePublishState: number): Promise {
+ switch (ePublishState) {
+ case DBAPI.ePublishedState.eNotPublished:
+ case DBAPI.ePublishedState.eAPIOnly:
+ case DBAPI.ePublishedState.ePublished:
+ break;
+ default:
+ LOG.error(`EdanCollection.publish called with invalid ePublishState ${ePublishState} for idSystemObject ${idSystemObject}`, LOG.LS.eCOLL);
+ return false;
+ }
+ const PS: PublishScene = new PublishScene(this, idSystemObject, ePublishState);
+ return PS.publish();
+ }
+
async createEdanMDM(edanmdm: COL.EdanMDMContent, status: number, publicSearch: boolean): Promise {
const body: any = {
url: `edanmdm:${edanmdm.descriptiveNonRepeating.record_ID}`,
@@ -139,7 +155,8 @@ export class EdanCollection implements COL.ICollection {
/** c.f. http://dev.3d.api.si.edu/apidocs/#api-admin-upsertContent */
private async upsertContent(body: any, caller: string): Promise {
- LOG.info(`EdanCollection.upsertContent: ${JSON.stringify(body)}`, LOG.LS.eCOLL);
+ // LOG.info(`EdanCollection.upsertContent: ${JSON.stringify(body)}`, LOG.LS.eCOLL);
+ LOG.info('EdanCollection.upsertContent', LOG.LS.eCOLL);
const reqResult: HttpRequestResult = await this.sendRequest(eAPIType.eEDAN3dApi, eHTTPMethod.ePost, 'api/v1.0/admin/upsertContent', '', JSON.stringify(body), 'application/json');
// LOG.info(`EdanCollection.upsertContent: ${JSON.stringify(body)}: ${reqResult.output}`, LOG.LS.eCOLL);
if (!reqResult.success) {
diff --git a/server/event/impl/InProcess/PublishScene.ts b/server/collections/impl/PublishScene.ts
similarity index 58%
rename from server/event/impl/InProcess/PublishScene.ts
rename to server/collections/impl/PublishScene.ts
index beb5dd1ab..2d9f1194c 100644
--- a/server/event/impl/InProcess/PublishScene.ts
+++ b/server/collections/impl/PublishScene.ts
@@ -1,18 +1,19 @@
-import { Config } from '../../../config';
-import * as DBAPI from '../../../db';
-import * as CACHE from '../../../cache';
-import * as COL from '../../../collections/interface/';
-import * as LOG from '../../../utils/logger';
-import * as H from '../../../utils/helpers';
-import * as ZIP from '../../../utils/zipStream';
-import * as STORE from '../../../storage/interface';
-import { SvxReader } from '../../../utils/parser';
-import { IDocument } from '../../../types/voyager';
+import { Config } from '../../config';
+import * as DBAPI from '../../db';
+import * as CACHE from '../../cache';
+import * as COL from '../../collections/interface/';
+import * as LOG from '../../utils/logger';
+import * as H from '../../utils/helpers';
+import * as ZIP from '../../utils/zipStream';
+import * as STORE from '../../storage/interface';
+import { SvxReader } from '../../utils/parser';
+import { IDocument } from '../../types/voyager';
import { v4 as uuidv4 } from 'uuid';
import path from 'path';
type SceneAssetCollector = {
+ idSystemObject: number;
asset: DBAPI.Asset;
assetVersion: DBAPI.AssetVersion;
model?: DBAPI.Model;
@@ -20,13 +21,15 @@ type SceneAssetCollector = {
};
export class PublishScene {
- private audit: DBAPI.Audit;
+ private ICol: COL.ICollection;
+ private idSystemObject: number;
+ private eState: DBAPI.ePublishedState;
- private idSystemObject?: number | null;
private scene?: DBAPI.Scene | null;
+ private systemObjectVersion?: DBAPI.SystemObjectVersion | null;
private subject?: DBAPI.Subject;
private DownloadMSXMap?: Map; // map of model's idSystemObject -> ModelSceneXref
- private SacMap?: Map; // map of idSystemObject for object owning asset -> SceneAssetCollector
+ private SacList: SceneAssetCollector[] = []; // array of SceneAssetCollector
private assetVersions?: DBAPI.AssetVersion[] | null = null;
@@ -38,21 +41,23 @@ export class PublishScene {
private sharedName?: string | undefined = undefined;
- constructor(audit: DBAPI.Audit) {
- this.audit = audit;
+ constructor(ICol: COL.ICollection, idSystemObject: number, eState: DBAPI.ePublishedState) {
+ this.ICol = ICol;
+ this.idSystemObject = idSystemObject;
+ this.eState = eState;
}
async publish(): Promise {
- if (!await this.handleAuditFetchScene() || !this.scene || !this.idSystemObject || !this.subject)
+ if (!await this.fetchScene() || !this.scene || !this.subject)
return false;
- LOG.info(`PublishScene.publish Publishing Scene with UUID ${this.scene.EdanUUID}`, LOG.LS.eEVENT);
+ LOG.info(`PublishScene.publish UUID ${this.scene.EdanUUID}`, LOG.LS.eCOLL);
// Process models, building a mapping from the model's idSystemObject -> ModelSceneXref, for those models that are for Downloads
if (!await this.computeMSXMap() || !this.DownloadMSXMap)
return false;
// collect and analyze assets
- if (!await this.collectAssets() || !this.SacMap || this.SacMap.size <= 0)
+ if (!await this.collectAssets() || this.SacList.length <= 0)
return false;
// stage scene
@@ -60,61 +65,75 @@ export class PublishScene {
return false;
// create EDAN 3D Package
- const ICol: COL.ICollection = COL.CollectionFactory.getInstance();
- let edanRecord: COL.EdanRecord | null = await ICol.createEdan3DPackage(this.sharedName, this.sceneFile);
+ let edanRecord: COL.EdanRecord | null = await this.ICol.createEdan3DPackage(this.sharedName, this.sceneFile);
if (!edanRecord) {
- LOG.error('PublishScene.publish publish to EDAN failed', LOG.LS.eEVENT);
+ LOG.error('PublishScene.publish EDAN failed', LOG.LS.eCOLL);
return false;
}
+ LOG.info(`PublishScene.publish ${edanRecord.url} succeeded with Edan status ${edanRecord.status}, publicSearch ${edanRecord.publicSearch}`, LOG.LS.eCOLL);
// stage downloads
if (!await this.stageDownloads() || !this.edan3DResourceList)
return false;
- // update EDAN 3D Package if we have downloads
- if (this.svxDocument && this.edan3DResourceList.length > 0) {
+ // update SystemObjectVersion.PublishedState
+ if (!await this.updatePublishedState())
+ return false;
+
+ const { status, publicSearch, downloads } = this.computeEdanSearchFlags(edanRecord, this.eState);
+ const haveDownloads: boolean = (this.edan3DResourceList.length > 0);
+ const updatePackage: boolean = haveDownloads // we have downloads, or
+ || (status !== edanRecord.status) // publication status changed
+ || (publicSearch !== edanRecord.publicSearch); // public search changed
+
+ // update EDAN 3D Package if we have downloads and/or if our published state has changed
+ if (this.svxDocument && updatePackage) {
const E3DPackage: COL.Edan3DPackageContent = {
document: this.svxDocument,
- resources: this.edan3DResourceList
+ resources: (downloads && haveDownloads) ? this.edan3DResourceList : undefined
};
- edanRecord = await ICol.updateEdan3DPackage(edanRecord.url, E3DPackage, edanRecord.status, edanRecord.publicSearch);
+
+ LOG.info(`PublishScene.publish updating ${edanRecord.url}`, LOG.LS.eCOLL);
+ edanRecord = await this.ICol.updateEdan3DPackage(edanRecord.url, E3DPackage, status, publicSearch);
if (!edanRecord) {
- LOG.error('PublishScene.publish publish of resources to EDAN failed', LOG.LS.eEVENT);
+ LOG.error('PublishScene.publish Edan3DPackage update failed', LOG.LS.eCOLL);
return false;
}
}
+
+ LOG.info(`PublishScene.publish UUID ${this.scene.EdanUUID}, status ${status}, publicSearch ${publicSearch}, downloads ${downloads}, has downloads ${haveDownloads}`, LOG.LS.eCOLL);
return true;
}
- private async handleAuditFetchScene(): Promise {
- this.idSystemObject = this.audit.idSystemObject;
- if (this.idSystemObject === null && this.audit.idDBObject && this.audit.DBObjectType) {
- const oID: DBAPI.ObjectIDAndType = { idObject: this.audit.idDBObject , eObjectType: this.audit.DBObjectType };
- const SOInfo: DBAPI.SystemObjectInfo | undefined = await CACHE.SystemObjectCache.getSystemFromObjectID(oID);
- if (SOInfo) {
- this.idSystemObject = SOInfo.idSystemObject;
- this.audit.idSystemObject = this.idSystemObject;
- }
+ private async fetchScene(): Promise {
+ const oID: DBAPI.ObjectIDAndType | undefined = await CACHE.SystemObjectCache.getObjectFromSystem(this.idSystemObject);
+ if (!oID) {
+ LOG.error(`PublishScene.fetchScene unable to retrieve object details from ${this.idSystemObject}`, LOG.LS.eCOLL);
+ return false;
}
- LOG.info(`PublishScene.handleAuditFetchScene Scene QCd ${this.audit.idDBObject}`, LOG.LS.eEVENT);
- if (this.audit.idAudit === 0)
- this.audit.create(); // don't use await so this happens asynchronously
-
- if (!this.idSystemObject) {
- LOG.error(`PublishScene.handleAuditFetchScene received eSceneQCd event for scene without idSystemObject ${JSON.stringify(this.audit, H.Helpers.saferStringify)}`, LOG.LS.eEVENT);
+ if (oID.eObjectType !== DBAPI.eSystemObjectType.eScene) {
+ LOG.error(`PublishScene.fetchScene received eSceneQCd event for non scene object ${JSON.stringify(oID, H.Helpers.saferStringify)}`, LOG.LS.eCOLL);
return false;
}
- if (this.audit.getDBObjectType() !== DBAPI.eSystemObjectType.eScene) {
- LOG.error(`PublishScene.handleAuditFetchScene received eSceneQCd event for non scene object ${JSON.stringify(this.audit, H.Helpers.saferStringify)}`, LOG.LS.eEVENT);
+ // fetch SystemObjectVersion
+ this.systemObjectVersion = await DBAPI.SystemObjectVersion.fetchLatestFromSystemObject(this.idSystemObject);
+ if (!this.systemObjectVersion) {
+ LOG.error(`PublishScene.fetchScene could not compute SystemObjectVersion for idSystemObject ${this.idSystemObject}`, LOG.LS.eCOLL);
return false;
}
// fetch scene
- this.scene = this.audit.idDBObject ? await DBAPI.Scene.fetch(this.audit.idDBObject) : null;
+ this.scene = oID.idObject ? await DBAPI.Scene.fetch(oID.idObject) : null;
if (!this.scene) {
- LOG.error(`PublishScene.handleAuditFetchScene received eSceneQCd event for non scene object ${JSON.stringify(this.audit, H.Helpers.saferStringify)}`, LOG.LS.eEVENT);
+ LOG.error(`PublishScene.fetchScene could not compute scene from ${JSON.stringify(oID, H.Helpers.saferStringify)}`, LOG.LS.eCOLL);
+ return false;
+ }
+
+ if (this.eState !== DBAPI.ePublishedState.eNotPublished &&
+ (!this.scene.ApprovedForPublication || !this.scene.PosedAndQCd)) {
+ LOG.error(`PublishScene.fetchScene attempting to publish non-Approved and/or non-QC'd scene ${JSON.stringify(this.scene, H.Helpers.saferStringify)}`, LOG.LS.eCOLL);
return false;
}
@@ -122,7 +141,7 @@ export class PublishScene {
if (!this.scene.EdanUUID) {
this.scene.EdanUUID = uuidv4();
if (!await this.scene.update()) {
- LOG.error(`PublishScene.handleAuditFetchScene unable to persist UUID for scene object ${JSON.stringify(this.audit, H.Helpers.saferStringify)}`, LOG.LS.eEVENT);
+ LOG.error(`PublishScene.fetchScene unable to persist UUID for scene object ${JSON.stringify(this.scene, H.Helpers.saferStringify)}`, LOG.LS.eCOLL);
return false;
}
}
@@ -130,7 +149,7 @@ export class PublishScene {
// compute subject(s) owning this scene
const OG: DBAPI.ObjectGraph = new DBAPI.ObjectGraph(this.idSystemObject, DBAPI.eObjectGraphMode.eAncestors);
if (!await OG.fetch()) {
- LOG.error(`PublishScene.handleAuditFetchScene unable to compute object graph for scene ${JSON.stringify(this.scene, H.Helpers.saferStringify)}`, LOG.LS.eEVENT);
+ LOG.error(`PublishScene.fetchScene unable to compute object graph for scene ${JSON.stringify(this.scene, H.Helpers.saferStringify)}`, LOG.LS.eCOLL);
return false;
}
if (OG.subject && OG.subject.length > 0)
@@ -143,7 +162,7 @@ export class PublishScene {
return false;
const MSXs: DBAPI.ModelSceneXref[] | null = await DBAPI.ModelSceneXref.fetchFromScene(this.scene.idScene);
if (!MSXs) {
- LOG.error(`PublishScene.computeMSXMap unable to fetch ModelSceneXrefs for scene ${this.scene.idScene}`, LOG.LS.eEVENT);
+ LOG.error(`PublishScene.computeMSXMap unable to fetch ModelSceneXrefs for scene ${this.scene.idScene}`, LOG.LS.eCOLL);
return false;
}
@@ -160,12 +179,11 @@ export class PublishScene {
}
private async collectAssets(): Promise {
- if (!this.idSystemObject || !this.DownloadMSXMap)
+ if (!this.DownloadMSXMap)
return false;
- this.SacMap = new Map();
this.assetVersions = await DBAPI.AssetVersion.fetchLatestFromSystemObject(this.idSystemObject);
if (!this.assetVersions || this.assetVersions.length === 0) {
- LOG.error(`PublishScene.collectAssets unable to load asset versions for scene ${JSON.stringify(this.scene, H.Helpers.saferStringify)}`, LOG.LS.eEVENT);
+ LOG.error(`PublishScene.collectAssets unable to load asset versions for scene ${JSON.stringify(this.scene, H.Helpers.saferStringify)}`, LOG.LS.eCOLL);
return false;
}
@@ -174,63 +192,63 @@ export class PublishScene {
let stageRes: H.IOResults = { success: true, error: '' };
for (const assetVersion of this.assetVersions) {
const asset: DBAPI.Asset | null = await DBAPI.Asset.fetch(assetVersion.idAsset);
+ LOG.info(`PublishScene.collectAssets considering assetVersion=${JSON.stringify(assetVersion, H.Helpers.saferStringify)} asset=${JSON.stringify(asset, H.Helpers.saferStringify)}`, LOG.LS.eCOLL);
if (!asset) {
- LOG.error(`PublishScene.collectAssets unable to load asset by id ${assetVersion.idAsset}`, LOG.LS.eEVENT);
+ LOG.error(`PublishScene.collectAssets unable to load asset by id ${assetVersion.idAsset}`, LOG.LS.eCOLL);
return false;
}
if (asset.idSystemObject) {
const modelSceneXref: DBAPI.ModelSceneXref | undefined = this.DownloadMSXMap.get(asset.idSystemObject ?? 0);
if (!modelSceneXref)
- this.SacMap.set(asset.idSystemObject, { asset, assetVersion });
+ this.SacList.push({ idSystemObject: asset.idSystemObject, asset, assetVersion });
else {
const model: DBAPI.Model | null = await DBAPI.Model.fetch(modelSceneXref.idModel);
if (!model) {
- LOG.error(`PublishScene.collectAssets unable to load model from xref ${JSON.stringify(modelSceneXref, H.Helpers.saferStringify)}`, LOG.LS.eEVENT);
+ LOG.error(`PublishScene.collectAssets unable to load model from xref ${JSON.stringify(modelSceneXref, H.Helpers.saferStringify)}`, LOG.LS.eCOLL);
return false;
}
- this.SacMap.set(asset.idSystemObject, { asset, assetVersion, model, modelSceneXref });
+ this.SacList.push({ idSystemObject: asset.idSystemObject, asset, assetVersion, model, modelSceneXref });
}
}
if (!this.sceneFile && assetVersion.FileName.toLowerCase().endsWith('.svx.json')) {
this.sceneFile = assetVersion.FileName;
this.extractedPath = asset.FilePath;
- // extract scene's SVX.JSON for use in creating downloads
- if (this.DownloadMSXMap.size > 0) {
- const RSR: STORE.ReadStreamResult = await STORE.AssetStorageAdapter.readAsset(asset, assetVersion);
- if (!RSR.success || !RSR.readStream) {
- LOG.error(`PublishScene.collectAssets failed to extract stream for scene's asset version ${assetVersion.idAssetVersion}`, LOG.LS.eEVENT);
- return false;
- }
- const svx: SvxReader = new SvxReader();
- stageRes = await svx.loadFromStream(RSR.readStream);
- if (!stageRes.success) {
- LOG.error(`PublishScene.collectAssets failed to extract scene's svx.json contents: ${stageRes.error}`, LOG.LS.eEVENT);
- return false;
- }
- this.svxDocument = svx.SvxDocument;
+ // extract scene's SVX.JSON for use in updating EDAN search status and creating downloads
+ const RSR: STORE.ReadStreamResult = await STORE.AssetStorageAdapter.readAsset(asset, assetVersion);
+ if (!RSR.success || !RSR.readStream) {
+ LOG.error(`PublishScene.collectAssets failed to extract stream for scene's asset version ${assetVersion.idAssetVersion}`, LOG.LS.eCOLL);
+ return false;
}
+
+ const svx: SvxReader = new SvxReader();
+ stageRes = await svx.loadFromStream(RSR.readStream);
+ if (!stageRes.success) {
+ LOG.error(`PublishScene.collectAssets failed to extract scene's svx.json contents: ${stageRes.error}`, LOG.LS.eCOLL);
+ return false;
+ }
+ this.svxDocument = svx.SvxDocument;
}
}
return true;
}
private async stageSceneFiles(): Promise {
- if (!this.SacMap || !this.scene)
+ if (this.SacList.length <= 0 || !this.scene)
return false;
let stageRes: H.IOResults = { success: true, error: '' };
// second pass: zip up appropriate assets; prepare to copy downloads
const zip: ZIP.ZipStream = new ZIP.ZipStream();
- for (const SAC of this.SacMap.values()) {
+ for (const SAC of this.SacList.values()) {
if (SAC.model) // skip downloads
continue;
const RSR: STORE.ReadStreamResult = await STORE.AssetStorageAdapter.readAsset(SAC.asset, SAC.assetVersion);
if (!RSR.success || !RSR.readStream) {
- LOG.error(`PublishScene.stageFiles failed to extract stream for asset version ${SAC.assetVersion.idAssetVersion}`, LOG.LS.eEVENT);
+ LOG.error(`PublishScene.stageSceneFiles failed to extract stream for asset version ${SAC.assetVersion.idAssetVersion}`, LOG.LS.eCOLL);
return false;
}
@@ -239,17 +257,17 @@ export class PublishScene {
rebasedPath = rebasedPath.substring(1);
const fileNameAndPath: string = path.posix.join(rebasedPath, SAC.assetVersion.FileName);
- LOG.info(`PublishScene.stageFiles adding ${fileNameAndPath} to zip`, LOG.LS.eEVENT);
+ LOG.info(`PublishScene.stageSceneFiles adding ${fileNameAndPath} to zip`, LOG.LS.eCOLL);
const res: H.IOResults = await zip.add(fileNameAndPath, RSR.readStream);
if (!res.success) {
- LOG.error(`PublishScene.stageFiles failed to add asset version ${SAC.assetVersion.idAssetVersion} to zip: ${res.error}`, LOG.LS.eEVENT);
+ LOG.error(`PublishScene.stageSceneFiles failed to add asset version ${SAC.assetVersion.idAssetVersion} to zip: ${res.error}`, LOG.LS.eCOLL);
return false;
}
}
const zipStream: NodeJS.ReadableStream | null = await zip.streamContent(null);
if (!zipStream) {
- LOG.error('PublishScene.stageFiles failed to extract stream from zip', LOG.LS.eEVENT);
+ LOG.error('PublishScene.stageSceneFiles failed to extract stream from zip', LOG.LS.eCOLL);
return false;
}
@@ -257,32 +275,33 @@ export class PublishScene {
if (!stageRes.success)
stageRes = await H.Helpers.createDirectory(Config.collection.edan.stagingRoot);
if (!stageRes.success) {
- LOG.error(`PublishScene.stageFiles unable to ensure existence of staging directory ${Config.collection.edan.stagingRoot}: ${stageRes.error}`, LOG.LS.eEVENT);
+ LOG.error(`PublishScene.stageSceneFiles unable to ensure existence of staging directory ${Config.collection.edan.stagingRoot}: ${stageRes.error}`, LOG.LS.eCOLL);
return false;
}
const noFinalSlash: boolean = !Config.collection.edan.upsertContentRoot.endsWith('/');
this.sharedName = Config.collection.edan.upsertContentRoot + (noFinalSlash ? '/' : '') + this.scene.EdanUUID! + '.zip'; // eslint-disable-line @typescript-eslint/no-non-null-assertion
const stagedName: string = path.join(Config.collection.edan.stagingRoot, this.scene.EdanUUID!) + '.zip'; // eslint-disable-line @typescript-eslint/no-non-null-assertion
- LOG.info(`*** PublishScene.stageFiles staging file ${stagedName}, referenced in publish as ${this.sharedName}`, LOG.LS.eEVENT);
+ LOG.info(`PublishScene.stageSceneFiles staging file ${stagedName}, referenced in publish as ${this.sharedName}`, LOG.LS.eCOLL);
stageRes = await H.Helpers.writeStreamToFile(zipStream, stagedName);
if (!stageRes.success) {
- LOG.error(`PublishScene.stageFiles unable to stage file ${stagedName}: ${stageRes.error}`, LOG.LS.eEVENT);
+ LOG.error(`PublishScene.stageSceneFiles unable to stage file ${stagedName}: ${stageRes.error}`, LOG.LS.eCOLL);
return false;
}
+ LOG.info(`PublishScene.stageSceneFiles staged file ${stagedName}`, LOG.LS.eCOLL);
return true;
}
private async stageDownloads(): Promise {
- if (!this.SacMap || !this.scene)
+ if (this.SacList.length <= 0 || !this.scene)
return false;
// third pass: stage downloads
let stageRes: H.IOResults = { success: true, error: '' };
this.edan3DResourceList = [];
this.resourcesHotFolder = path.join(Config.collection.edan.resourcesHotFolder, this.scene.EdanUUID!); // eslint-disable-line @typescript-eslint/no-non-null-assertion
- for (const SAC of this.SacMap.values()) {
+ for (const SAC of this.SacList.values()) {
if (!SAC.model) // SAC is not a download, skip it
continue;
@@ -291,18 +310,19 @@ export class PublishScene {
const RSR: STORE.ReadStreamResult = await STORE.AssetStorageAdapter.readAsset(SAC.asset, SAC.assetVersion);
if (!RSR.success || !RSR.readStream) {
- LOG.error(`PublishScene.stageFiles failed to extract stream for asset version ${SAC.assetVersion.idAssetVersion}`, LOG.LS.eEVENT);
+ LOG.error(`PublishScene.stageDownloads failed to extract stream for asset version ${SAC.assetVersion.idAssetVersion}`, LOG.LS.eCOLL);
return false;
}
// copy stream to resourcesHotFolder
const stagedName: string = path.join(this.resourcesHotFolder, SAC.assetVersion.FileName);
- LOG.info(`*** PublishScene.stageFiles staging file ${stagedName}`, LOG.LS.eEVENT);
+ LOG.info(`PublishScene.stageDownloads staging file ${stagedName}`, LOG.LS.eCOLL);
stageRes = await H.Helpers.writeStreamToFile(RSR.readStream, stagedName);
if (!stageRes.success) {
- LOG.error(`PublishScene.stageFiles unable to stage file ${stagedName}: ${stageRes.error}`, LOG.LS.eEVENT);
+ LOG.error(`PublishScene.stageDownloads unable to stage file ${stagedName}: ${stageRes.error}`, LOG.LS.eCOLL);
return false;
}
+ LOG.info(`PublishScene.stageDownloads staged file ${stagedName}`, LOG.LS.eCOLL);
// prepare download entry
const resource: COL.Edan3DResource | null = await this.extractResource(SAC, this.scene.EdanUUID!); // eslint-disable-line @typescript-eslint/no-non-null-assertion
@@ -322,7 +342,7 @@ export class PublishScene {
if (!stageRes.success)
stageRes = await H.Helpers.createDirectory(this.resourcesHotFolder);
if (!stageRes.success) {
- LOG.error(`PublishScene.ensureResourceHotFolderExists failed to create resources hot folder ${this.resourcesHotFolder}: ${stageRes.error}`, LOG.LS.eEVENT);
+ LOG.error(`PublishScene.ensureResourceHotFolderExists failed to create resources hot folder ${this.resourcesHotFolder}: ${stageRes.error}`, LOG.LS.eCOLL);
return false;
}
return true;
@@ -335,7 +355,7 @@ export class PublishScene {
let type: COL.Edan3DResourceType | undefined = undefined;
const typeV: DBAPI.Vocabulary | undefined = SAC.model.idVCreationMethod ? await CACHE.VocabularyCache.vocabulary(SAC.model.idVCreationMethod) : undefined;
switch (typeV?.Term) {
- default: LOG.error(`PublishScene.extractResource found no type mapping for ${typeV?.Term}`, LOG.LS.eEVENT); break;
+ default: LOG.error(`PublishScene.extractResource found no type mapping for ${typeV?.Term}`, LOG.LS.eCOLL); break;
case undefined: break;
case 'Scan To Mesh': type = '3D mesh'; break;
case 'CAD': type = 'CAD model'; break;
@@ -344,7 +364,7 @@ export class PublishScene {
let UNITS: COL.Edan3DResourceAttributeUnits | undefined = undefined;
const unitsV: DBAPI.Vocabulary | undefined = SAC.model.idVUnits ? await CACHE.VocabularyCache.vocabulary(SAC.model.idVUnits) : undefined;
switch (unitsV?.Term) {
- default: LOG.error(`PublishScene.extractResource found no units mapping for ${unitsV?.Term}`, LOG.LS.eEVENT); break;
+ default: LOG.error(`PublishScene.extractResource found no units mapping for ${unitsV?.Term}`, LOG.LS.eCOLL); break;
case undefined: break;
case 'Millimeter': UNITS = 'mm'; break;
case 'Centimeter': UNITS = 'cm'; break;
@@ -359,7 +379,7 @@ export class PublishScene {
let MODEL_FILE_TYPE: COL.Edan3DResourceAttributeModelFileType | undefined = undefined;
const modelTypeV: DBAPI.Vocabulary | undefined = SAC.model.idVFileType ? await CACHE.VocabularyCache.vocabulary(SAC.model.idVFileType) : undefined;
switch (modelTypeV?.Term) {
- default: LOG.error(`PublishScene.extractResource found no model file type mapping for ${modelTypeV?.Term}`, LOG.LS.eEVENT); break;
+ default: LOG.error(`PublishScene.extractResource found no model file type mapping for ${modelTypeV?.Term}`, LOG.LS.eCOLL); break;
case undefined: break;
case 'obj - Alias Wavefront Object': MODEL_FILE_TYPE = 'obj'; break;
case 'ply - Stanford Polygon File Format': MODEL_FILE_TYPE = 'ply'; break;
@@ -380,7 +400,7 @@ export class PublishScene {
let FILE_TYPE: COL.Edan3DResourceAttributeFileType | undefined = undefined;
switch (path.extname(SAC.assetVersion.FileName).toLowerCase()) {
- default: LOG.error(`PublishScene.extractResource found no file type mapping for ${SAC.assetVersion.FileName}`, LOG.LS.eEVENT); break;
+ default: LOG.error(`PublishScene.extractResource found no file type mapping for ${SAC.assetVersion.FileName}`, LOG.LS.eCOLL); break;
case '.zip': FILE_TYPE = 'zip'; break;
case '.glb': FILE_TYPE = 'glb'; break;
case '.usdz': FILE_TYPE = 'usdz'; break;
@@ -411,4 +431,42 @@ export class PublishScene {
const attributes: COL.Edan3DResourceAttribute[] = [{ UNITS, MODEL_FILE_TYPE, FILE_TYPE, GLTF_STANDARDIZED, DRACO_COMPRESSED }];
return { filename, url, type, title, name, attributes, category };
}
+
+ private async updatePublishedState(): Promise {
+ if (!this.systemObjectVersion)
+ return false;
+
+ // Determine if licensing prevents publishing
+ const LR: DBAPI.LicenseResolver | undefined = await CACHE.LicenseCache.getLicenseResolver(this.idSystemObject);
+ if (LR && LR.License &&
+ DBAPI.LicenseRestrictLevelToPublishedStateEnum(LR.License.RestrictLevel) === DBAPI.ePublishedState.eNotPublished)
+ this.eState = DBAPI.ePublishedState.eNotPublished;
+ LOG.info(`PublishScene.updatePublishedState computed license ${LR ? JSON.stringify(LR.License, H.Helpers.saferStringify) : 'none'}, resulting in published state of ${this.eState}`, LOG.LS.eCOLL);
+
+ if (this.systemObjectVersion.publishedStateEnum() !== this.eState) {
+ this.systemObjectVersion.setPublishedState(this.eState);
+ if (!await this.systemObjectVersion.update()) {
+ LOG.error(`PublishScene.updatePublishedState unable to update published state for ${JSON.stringify(this.systemObjectVersion, H.Helpers.saferStringify)}`, LOG.LS.eCOLL);
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private computeEdanSearchFlags(edanRecord: COL.EdanRecord, eState: DBAPI.ePublishedState): { status: number, publicSearch: boolean, downloads: boolean } {
+ let status: number = edanRecord.status;
+ let publicSearch: boolean = edanRecord.publicSearch;
+ let downloads: boolean = publicSearch;
+
+ switch (eState) {
+ default:
+ case DBAPI.ePublishedState.eNotPublished: status = 1; publicSearch = false; downloads = false; break;
+ case DBAPI.ePublishedState.eAPIOnly: status = 0; publicSearch = false; downloads = true; break;
+ case DBAPI.ePublishedState.ePublished: status = 0; publicSearch = true; downloads = true; break;
+ // case DBAPI.ePublishedState.eViewOnly: status = 0; publicSearch = true; downloads = false; break;
+ }
+ LOG.info(`PublishScene.computeEdanSearchFlags(${DBAPI.ePublishedState[eState]}) = { status ${status}, publicSearch ${publicSearch}, downloads ${downloads} }`, LOG.LS.eCOLL);
+ return { status, publicSearch, downloads };
+ }
}
\ No newline at end of file
diff --git a/server/collections/interface/ICollection.ts b/server/collections/interface/ICollection.ts
index 3945d469e..d243306c4 100644
--- a/server/collections/interface/ICollection.ts
+++ b/server/collections/interface/ICollection.ts
@@ -45,6 +45,7 @@ export type CollectionQueryOptions = {
*/
export interface ICollection {
queryCollection(query: string, rows: number, start: number, options: CollectionQueryOptions | null): Promise;
+ publish(idSystemObject: number, ePublishState: number): Promise;
createEdanMDM(edanmdm: EdanMDMContent, status: number, publicSearch: boolean): Promise;
createEdan3DPackage(path: string, sceneFile?: string | undefined): Promise;
updateEdan3DPackage(url: string, sceneContent: Edan3DPackageContent, status: number, publicSearch: boolean): Promise;
diff --git a/server/config/index.ts b/server/config/index.ts
index e6b336622..7dafdc240 100644
--- a/server/config/index.ts
+++ b/server/config/index.ts
@@ -126,7 +126,7 @@ export const Config: ConfigType = {
api3d: process.env.PACKRAT_EDAN_3D_API ? process.env.PACKRAT_EDAN_3D_API : /* istanbul ignore next */ 'https://3d-api.si.edu/',
appId: process.env.PACKRAT_EDAN_APPID ? process.env.PACKRAT_EDAN_APPID : /* istanbul ignore next */ 'OCIO3D',
authKey: process.env.PACKRAT_EDAN_AUTH_KEY ? process.env.PACKRAT_EDAN_AUTH_KEY : /* istanbul ignore next */ '',
- upsertContentRoot: process.env.PACKRAT_EDAN_UPSERT_RESOURCE_ROOT ? process.env.PACKRAT_EDAN_UPSERT_RESOURCE_ROOT : 'nfs:///ifs/smb/ocio/ocio-3ddigip01/upload/',
+ upsertContentRoot: process.env.PACKRAT_EDAN_UPSERT_RESOURCE_ROOT ? process.env.PACKRAT_EDAN_UPSERT_RESOURCE_ROOT : 'nfs:///si-3ddigi-staging/upload/',
stagingRoot: process.env.PACKRAT_EDAN_STAGING_ROOT ? process.env.PACKRAT_EDAN_STAGING_ROOT : '/3ddigip01/upload',
resourcesHotFolder: process.env.PACKRAT_EDAN_RESOURCES_HOTFOLDER ? process.env.PACKRAT_EDAN_RESOURCES_HOTFOLDER : '/3ddigip01/3d_api_hot_folder/dev/3d_api_hot_folder_downloads',
}
diff --git a/server/config/solr/data/packrat/conf/schema.xml b/server/config/solr/data/packrat/conf/schema.xml
index 99d0becf2..5d963502c 100644
--- a/server/config/solr/data/packrat/conf/schema.xml
+++ b/server/config/solr/data/packrat/conf/schema.xml
@@ -100,8 +100,6 @@
-
-
@@ -110,6 +108,9 @@
+
+
+
diff --git a/server/db/api/Audit.ts b/server/db/api/Audit.ts
index 5e63bf226..30fbb7ecf 100644
--- a/server/db/api/Audit.ts
+++ b/server/db/api/Audit.ts
@@ -1,8 +1,10 @@
/* eslint-disable camelcase */
-import { Audit as AuditBase } from '@prisma/client';
+import { Audit as AuditBase, User as UserBase } from '@prisma/client';
import * as DBC from '../connection';
import * as LOG from '../../utils/logger';
+// import * as H from '../../utils/helpers';
import { eDBObjectType, eAuditType /*, eSystemObjectType */ } from './ObjectType'; // importing eSystemObjectType causes as circular dependency
+import { User } from './User';
export class Audit extends DBC.DBObject implements AuditBase {
idAudit!: number;
@@ -93,4 +95,25 @@ export class Audit extends DBC.DBObject implements AuditBase {
return null;
}
}
+
+ static async fetchLastUser(idSystemObject: number, eAudit: eAuditType): Promise {
+ if (!idSystemObject || !eAudit)
+ return null;
+ try {
+ const userBaseList: UserBase[] | null =
+ await DBC.DBConnection.prisma.$queryRaw`
+ SELECT U.*
+ FROM Audit AS AU
+ JOIN User AS U ON (AU.idUser = U.idUser)
+ WHERE AU.AuditType = ${eAudit}
+ AND AU.idSystemObject = ${idSystemObject}
+ ORDER BY AU.AuditDate DESC
+ LIMIT 1`;
+ // LOG.info(`DBAPI.Audit.fetchLastUser(${idSystemObject}, ${eAudit}) raw ${JSON.stringify(userBaseList, H.Helpers.saferStringify)}`, LOG.LS.eDB);
+ return (userBaseList && userBaseList.length > 0) ? User.constructFromPrisma(userBaseList[0]) : /* istanbul ignore next */ null;
+ } catch (error) /* istanbul ignore next */ {
+ LOG.error('DBAPI.Audit.fetchLastUser', LOG.LS.eDB, error);
+ return null;
+ }
+ }
}
diff --git a/server/db/api/Model.ts b/server/db/api/Model.ts
index c18fb495c..a0f4ef954 100644
--- a/server/db/api/Model.ts
+++ b/server/db/api/Model.ts
@@ -33,6 +33,28 @@ export class Model extends DBC.DBObject implements ModelBase, SystemO
public fetchTableName(): string { return 'Model'; }
public fetchID(): number { return this.idModel; }
+ public cloneData(model: Model): void {
+ this.Name = model.Name;
+ this.DateCreated = model.DateCreated;
+ this.idVCreationMethod = model.idVCreationMethod;
+ this.idVModality = model.idVModality;
+ this.idVPurpose = model.idVPurpose;
+ this.idVUnits = model.idVUnits;
+ this.idVFileType = model.idVFileType;
+ this.idAssetThumbnail = model.idAssetThumbnail;
+ this.CountAnimations = model.CountAnimations;
+ this.CountCameras = model.CountCameras;
+ this.CountFaces = model.CountFaces;
+ this.CountLights = model.CountLights;
+ this.CountMaterials = model.CountMaterials;
+ this.CountMeshes = model.CountMeshes;
+ this.CountVertices = model.CountVertices;
+ this.CountEmbeddedTextures = model.CountEmbeddedTextures;
+ this.CountLinkedTextures = model.CountLinkedTextures;
+ this.FileEncoding = model.FileEncoding;
+ this.IsDracoCompressed = model.IsDracoCompressed;
+ this.AutomationTag = model.AutomationTag;
+ }
protected async createWorker(): Promise {
try {
diff --git a/server/db/api/ObjectType.ts b/server/db/api/ObjectType.ts
index ab0390ec0..cf2fb7518 100644
--- a/server/db/api/ObjectType.ts
+++ b/server/db/api/ObjectType.ts
@@ -44,6 +44,7 @@ export enum eNonSystemObjectType {
eModelProcessingAction = 45,
eModelProcessingActionStep = 46,
eModelSceneXref = 47,
+ eSentinel = 63,
eSystemObject = 48,
eSystemObjectVersion = 49,
eSystemObjectVersionAssetVersionXref = 60,
@@ -168,6 +169,7 @@ export function DBObjectTypeToName(dbType: eDBObjectType | null): string {
case eNonSystemObjectType.eModelProcessingAction: return 'ModelProcessingAction';
case eNonSystemObjectType.eModelProcessingActionStep: return 'ModelProessingActionStep';
case eNonSystemObjectType.eModelSceneXref: return 'ModelSceneXref';
+ case eNonSystemObjectType.eSentinel: return 'Sentinel';
case eNonSystemObjectType.eSystemObject: return 'SystemObject';
case eNonSystemObjectType.eSystemObjectVersion: return 'SystemObjectVersion';
case eNonSystemObjectType.eSystemObjectVersionAssetVersionXref: return 'SystemObjectVersionAssetVersionXref';
@@ -242,6 +244,7 @@ export function DBObjectNameToType(objectTypeName: string | null): eDBObjectType
case 'Model Proessing Action Step': return eNonSystemObjectType.eModelProcessingActionStep;
case 'ModelSceneXref': return eNonSystemObjectType.eModelSceneXref;
case 'Model Scene Xref': return eNonSystemObjectType.eModelSceneXref;
+ case 'Sentinel': return eNonSystemObjectType.eSentinel;
case 'SystemObject': return eNonSystemObjectType.eSystemObject;
case 'System Object': return eNonSystemObjectType.eSystemObject;
case 'SystemObjectVersion': return eNonSystemObjectType.eSystemObjectVersion;
@@ -284,25 +287,48 @@ export enum eAuditType {
eSceneQCd = 5
}
+export enum eLicense {
+ eViewDownloadCC0 = 1, // 'View and Download CC0'
+ eViewDownloadRestriction = 2, // 'View and Download with usage restrictions',
+ eViewOnly = 3, // 'View Only',
+ eRestricted = 4, // 'Restricted', default
+}
+
export enum ePublishedState {
eNotPublished = 0, // 'Not Published', default
- eRestricted = 1, // 'Restricted',
- eViewOnly = 2, // 'View Only',
- eViewDownloadRestriction = 3, // 'View and Download with usage restrictions',
- eViewDownloadCC0 = 4, // 'View and Download CC0'
+ eAPIOnly = 1, // 'API Only',
+ ePublished = 2, // 'Published'
+}
+
+export function LicenseEnumToString(eState: eLicense): string {
+ switch (eState) {
+ case eLicense.eViewDownloadCC0: return 'View and Download CC0';
+ case eLicense.eViewDownloadRestriction: return 'View and Download with usage restrictions';
+ case eLicense.eViewOnly: return 'View Only';
+ default:
+ case eLicense.eRestricted: return 'Restricted';
+ }
}
export function PublishedStateEnumToString(eState: ePublishedState): string {
switch (eState) {
- case ePublishedState.eRestricted: return 'Restricted';
- case ePublishedState.eViewOnly: return 'View Only';
- case ePublishedState.eViewDownloadRestriction: return 'View and Download with usage restrictions';
- case ePublishedState.eViewDownloadCC0: return 'View and Download CC0';
+ case ePublishedState.eAPIOnly: return 'API Only';
+ case ePublishedState.ePublished: return 'Published';
default:
case ePublishedState.eNotPublished: return 'Not Published';
}
}
+export function LicenseRestrictLevelToPublishedStateEnum(restrictLevel: number): ePublishedState {
+ if (restrictLevel <= 10)
+ return ePublishedState.ePublished;
+ if (restrictLevel <= 20)
+ return ePublishedState.ePublished;
+ if (restrictLevel <= 30)
+ return ePublishedState.ePublished;
+ return ePublishedState.eNotPublished;
+}
+
// Keep this in sync with SQL in WorkflowListResult.search()
export enum eWorkflowJobRunStatus {
eUnitialized = 0,
diff --git a/server/db/api/Scene.ts b/server/db/api/Scene.ts
index 02904350d..59b161f01 100644
--- a/server/db/api/Scene.ts
+++ b/server/db/api/Scene.ts
@@ -9,8 +9,6 @@ export class Scene extends DBC.DBObject implements SceneBase, SystemO
idScene!: number;
Name!: string;
idAssetThumbnail!: number | null;
- IsOriented!: boolean;
- HasBeenQCd!: boolean;
CountScene!: number | null;
CountNode!: number | null;
CountCamera!: number | null;
@@ -20,15 +18,17 @@ export class Scene extends DBC.DBObject