Skip to content

Commit

Permalink
(0.9.22f1) HotFix: Invalid Handling of Scene Summary Data (#629)
Browse files Browse the repository at this point in the history
* DPO3DPKRT-854/Invalid handling of scene summary (#627)
* DPO3DPKRT/CSV Export for Scene Summary (#628)
  • Loading branch information
EMaslowskiQ authored Sep 13, 2024
1 parent 40b62a1 commit 4e2356f
Show file tree
Hide file tree
Showing 2 changed files with 171 additions and 39 deletions.
137 changes: 124 additions & 13 deletions client/src/pages/Admin/components/AdminToolsView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -220,19 +220,28 @@ const SelectScenesTable = <T extends DBReference>({ onUpdateSelection, data, col
return 'NA';
}

// if we're doing a link just return it
if(path.includes('_link'))
return obj[path] ?? '#';

// otherwise, we split it up and try to resolve
const keys = path.split('.');

/* eslint-disable @typescript-eslint/no-explicit-any */
let result: any = '';

// get the value stored (only two levels deep)
// get the value stored (only three levels deep)
switch(keys.length){
case 1: {
result = ((obj[keys[0]]) ?? 'NA');
result = ((obj?.[keys[0]]) ?? 'NA');
break;
}
case 2: {
result = ((obj[keys[0]][keys[1]]) ?? 'NA');
result = ((obj?.[keys[0]]?.[keys[1]]) ?? 'NA');
break;
}
case 3: {
result = ((obj?.[keys[0]]?.[keys[1]]?.[keys[2]]) ?? 'NA');
break;
}
default: {
Expand Down Expand Up @@ -464,11 +473,19 @@ const SelectScenesTable = <T extends DBReference>({ onUpdateSelection, data, col
style={{ whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden', maxWidth: '10rem', }}
>
{ (column.link && column.link===true) ? (
<>
<a href={resolveProperty(row, `${column.key}_link`)} target='_blank' rel='noopener noreferrer' onClick={handleElementClick}>
{resolveProperty(row,column.key)}
</a>
</>
(() => {
// if our link has a # we just skip adding the href so we don't
// reload page on click or go to different unrelated page
const link = resolveProperty(row, `${column.key}_link`);
const displayText = resolveProperty(row, column.key);
return link.includes('#') ? (
displayText
) : (
<a href={link} target='_blank' rel='noopener noreferrer' onClick={handleElementClick}>
{displayText}
</a>
);
})()
) : (
resolveProperty(row, column.key)
)}
Expand Down Expand Up @@ -503,7 +520,6 @@ const SelectScenesTable = <T extends DBReference>({ onUpdateSelection, data, col
};

// TODO: add enums and types to library and/or COMMON as needed
// TODO: refresh data table button
// NOTE: 'Summary' types/objects are intended for return via the API and for external use
// so non-standard types (e.g. enums) are converted to strings for clarity/accessibility.
type AssetSummary = DBReference & {
Expand All @@ -517,14 +533,23 @@ type AssetList = {
items: AssetSummary[];
};
type SceneSummary = DBReference & {
publishedState: string,
datePublished: Date,
isReviewed: boolean
project: DBReference,
subject: DBReference,
mediaGroup: DBReference,
dateCreated: Date,
downloads: AssetList,
publishedState: string,
datePublished: Date,
isReviewed: boolean
derivatives: {
models: AssetList, // holds all derivative models
downloads: AssetList, // specific models for download
ar: AssetList, // models specific to AR
},
sources: {
models: AssetList,
captureData: AssetList,
}
};
const AdminToolsBatchGeneration = (): React.ReactElement => {
const classes = useStyles();
Expand Down Expand Up @@ -603,7 +628,7 @@ const AdminToolsBatchGeneration = (): React.ReactElement => {
{ key: 'name', label: 'Scene', align: 'center', tooltip: 'Name of the scene', link: true },
{ key: 'mediaGroup.name', label: 'Media Group', align: 'center', tooltip: 'What MediaGroup the scene belongs to. Includes the the subtitle (if any).' },
{ key: 'subject.name', label: 'Subject', align: 'center', tooltip: 'The official subject name for the object' },
{ key: 'downloads.status', label: 'Downloads', align: 'center', tooltip: 'Are downloads in good standing (GOOD), available but contain errors (ERROR), or are not available (MISSING).' },
{ key: 'derivatives.downloads.status', label: 'Downloads', align: 'center', tooltip: 'Are downloads in good standing (GOOD), available but contain errors (ERROR), or are not available (MISSING).' },
{ key: 'publishedState', label: 'Published', align: 'center', tooltip: 'Is the scene published and with what accessibility' },
// { key: 'datePublished', label: 'Published (Date)', align: 'center' },
// { key: 'isReviewed', label: 'Reviewed', align: 'center' }
Expand Down Expand Up @@ -697,6 +722,85 @@ const AdminToolsBatchGeneration = (): React.ReactElement => {
const onRefreshList = async () => {
getProjectScenes(projectSelected);
};
const onExportTableDataToCSV = (): boolean => {
// Helper function to format date to a string or default 'N/A'
const formatDate = (date) => date ? new Date(date).toISOString().split('T')[0] : 'N/A';

// Helper function to handle null or undefined values and return 'N/A' as default
const handleNull = (value) => value != null ? value : 'N/A';

// Create CSV headers (clean names)
const headers = [
'ID',
'Scene Name',
'Published',
// 'Date Published',
'Reviewed',
// 'Project ID',
'Project',
// 'Subject ID',
'Subject',
// 'Media Group ID',
'Media Group',
'Date Created',
// 'Models Status',
// 'Models Items Count',
'Downloads',
// 'Downloads Items Count',
'AR',
// 'AR Items Count',
'Master Model',
// 'Source Models Items Count',
'Capture Data',
// 'Source Capture Data Items Count'
];

// Build CSV rows
const rows = projectScenes.map(scene => {
return [
handleNull(scene.id),
handleNull(scene.name),
handleNull(scene.publishedState),
// formatDate(scene.datePublished),
scene.isReviewed != null ? (scene.isReviewed ? 'Yes' : 'No') : 'N/A',
// handleNull(scene.project?.id),
handleNull(scene.project?.name),
// handleNull(scene.subject?.id),
handleNull(scene.subject?.name),
// handleNull(scene.mediaGroup?.id),
handleNull(scene.mediaGroup?.name),
formatDate(scene.dateCreated),
// handleNull(scene.derivatives.models?.status),
// handleNull(scene.derivatives.models?.items?.length),
handleNull(scene.derivatives.downloads?.status),
// handleNull(scene.derivatives.downloads?.items?.length),
handleNull(scene.derivatives.ar?.status),
// handleNull(scene.derivatives.ar?.items?.length),
handleNull(scene.sources.models?.status),
// handleNull(scene.sources.models?.items?.length),
handleNull(scene.sources.captureData?.status),
// handleNull(scene.sources.captureData?.items?.length)
].join(','); // Join the row values with commas
});

// Combine headers and rows into CSV format
const csvContent = [headers.join(','), ...rows].join('\n');

// Create a Blob and trigger download
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
const link = document.createElement('a');
const url = URL.createObjectURL(blob);
const fileName = !projectSelected ? 'scene_summaries' : projectSelected?.Name;

// add our temp link to the DOM, click it, and then return
link.href = url;
link.setAttribute('download', `${fileName}.csv`);
document.body.appendChild(link);
link.click();
document.body.removeChild(link);

return true;
};
const filteredProjectScenes = useMemo(() => {
const filterPattern: string = sceneNameFilter.toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g, '').trim();
return projectScenes.filter(row => {
Expand Down Expand Up @@ -859,6 +963,13 @@ const AdminToolsBatchGeneration = (): React.ReactElement => {
>
Refresh
</Button>
<Button
className={classes.btn}
onClick={onExportTableDataToCSV}
disableElevation
>
CSV
</Button>
</Box>
</Box>
</Collapse>
Expand Down
73 changes: 47 additions & 26 deletions server/http/routes/api/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,7 @@ const buildSummaryMasterModels = async (idScene: number): Promise<AssetList | nu

return result;
};
const buildAssetSummaryFromModel = async (idModel: number): Promise<AssetSummary | null> => {
const buildAssetSummaryFromModel = async (idModel: number, fromAsset: boolean = true): Promise<AssetSummary | null> => {

// get our actual model so we can get the date created
const model: DBAPI.Model | null = await DBAPI.Model.fetch(idModel);
Expand All @@ -348,36 +348,57 @@ const buildAssetSummaryFromModel = async (idModel: number): Promise<AssetSummary
return null;
}

// get our asset for the model
const modelAsset: DBAPI.Asset[] | null = await DBAPI.Asset.fetchFromModel(model.idModel);
if(!modelAsset || modelAsset.length===0) {
LOG.error(`API.Project.buildAssetSummaryFromModel cannot fetch asset from model (idModel: ${model.idModel} | msx: ${model.Name})`,LOG.LS.eDB);
return null;
} else if(modelAsset.length>1)
LOG.info(`API.Project.buildAssetSummaryFromModel more than one asset assigned to model. using first one. (idModel: ${model.idModel} | count: ${modelAsset.length})`,LOG.LS.eDB);

// get our system object id
const modelSO: DBAPI.SystemObject | null = await modelAsset[0].fetchSystemObject();
if(!modelSO) {
LOG.error(`API.Project.buildAssetSummaryFromModel cannot fetch SystemObject model asset (idModel: ${model.idModel} | idAsset: ${modelAsset[0].idAsset} | msx: ${modelAsset[0].FileName})`,LOG.LS.eDB);
return null;
}

// get our latest asset version
const modelAssetVer: DBAPI.AssetVersion | null = await DBAPI.AssetVersion.fetchLatestFromAsset(modelAsset[0].fetchID());
if(!modelAssetVer) {
LOG.error(`API.Project.buildAssetSummaryFromModel cannot fetch asset version from from model asset (idModel: ${model.idModel} | idAsset: ${modelAsset[0].idAsset})`,LOG.LS.eDB);
return null;
}

const result: AssetSummary = {
id: modelSO.idSystemObject,
name: modelAsset[0].FileName,
id: -1,
name: 'undefined',
quality: 'Highest',
usage: 'Source',
downloadable: false,
dateCreated: modelAssetVer.DateCreated, // if erro store epoch as date
dateCreated: new Date()
};

if(fromAsset===true) {
// get our asset for the model
const modelAsset: DBAPI.Asset[] | null = await DBAPI.Asset.fetchFromModel(model.idModel);
if(!modelAsset || modelAsset.length===0) {
LOG.error(`API.Project.buildAssetSummaryFromModel cannot fetch asset from model (idModel: ${model.idModel} | msx: ${model.Name})`,LOG.LS.eDB);
return null;
} else if(modelAsset.length>1)
LOG.info(`API.Project.buildAssetSummaryFromModel more than one asset assigned to model. using first one. (idModel: ${model.idModel} | count: ${modelAsset.length})`,LOG.LS.eDB);

// get our latest asset version
const modelAssetVer: DBAPI.AssetVersion | null = await DBAPI.AssetVersion.fetchLatestFromAsset(modelAsset[0].fetchID());
if(!modelAssetVer) {
LOG.error(`API.Project.buildAssetSummaryFromModel cannot fetch asset version from from model asset (idModel: ${model.idModel} | idAsset: ${modelAsset[0].idAsset})`,LOG.LS.eDB);
return null;
}

// find the one that is geometry or zip
// ...

// get our system object id for the model
const modelSO: DBAPI.SystemObject | null = await modelAsset[0].fetchSystemObject();
if(!modelSO) {
LOG.error(`API.Project.buildAssetSummaryFromModel cannot fetch SystemObject model (idModel: ${model.idModel} | msx: ${model.Name})`,LOG.LS.eDB);
return null;
}

result.id = modelSO.idSystemObject;
result.name = modelAsset[0].FileName;
result.dateCreated = modelAssetVer.DateCreated;
} else {
// get our system object id for the model
const modelSO: DBAPI.SystemObject | null = await model.fetchSystemObject();
if(!modelSO) {
LOG.error(`API.Project.buildAssetSummaryFromModel cannot fetch SystemObject model (idModel: ${model.idModel} | msx: ${model.Name})`,LOG.LS.eDB);
return null;
}

result.id = modelSO.idSystemObject;
result.name = model.Name;
result.dateCreated = model.DateCreated;
}

return result;
};
const buildAssetSummaryFromMSX = async (msx: DBAPI.ModelSceneXref): Promise<AssetSummary | null> => {
Expand Down

0 comments on commit 4e2356f

Please sign in to comment.