frontend/app/ProjectEntryList/helpers.ts (540 lines of code) (raw):
import Axios from "axios";
import axios from "axios";
import {
SystemNotifcationKind,
SystemNotification,
} from "@guardian/pluto-headers";
import { saveAs } from "file-saver";
const API = "/api";
const API_PROJECTS = `${API}/project`;
const API_PROJECTS_FILTER = `${API_PROJECTS}/list`;
const API_FILES = `${API}/file`;
declare var deploymentRootPath: string;
interface ProjectsOnPage {
page?: number;
pageSize?: number;
filterTerms?: FilterTerms;
order?: string;
orderBy?: string | number | symbol;
}
interface PlutoFilesAPIResponse<T> {
files: T;
}
interface PlutoBucketsAPIResponse<T> {
buckets: T;
}
interface PlutoDeleteJobAPIResponse<T> {
job_status: T;
}
interface PlutoItemDeleteDataAPIResponse<T> {
results: T;
}
export const getProjectsOnPage = async ({
page = 0,
pageSize = 25,
filterTerms,
order,
orderBy,
}: ProjectsOnPage): Promise<[Project[], number]> => {
try {
const {
status,
data: { result },
data: { count },
} = filterTerms
? await Axios.put<PlutoApiResponseWithCount<Project[]>>(
`${API_PROJECTS_FILTER}?startAt=${
page * pageSize
}&length=${pageSize}&sort=${String(orderBy)}&sortDirection=${order}`,
filterTerms
)
: await Axios.get<PlutoApiResponseWithCount<Project[]>>(
`${API_PROJECTS}?startAt=${page * pageSize}&length=${pageSize}`
);
if (status === 200) {
return [result, count];
}
throw new Error(`Could not retrieve projects. ${status}`);
} catch (error) {
console.error(error);
throw error;
}
};
export const getProject = async (id: number): Promise<Project> => {
try {
const {
status,
data: { result },
} = await Axios.get<PlutoApiResponse<Project>>(`${API_PROJECTS}/${id}`);
if (status === 200) {
return result;
}
throw new Error(`Could not get project ${id}. ${status}`);
} catch (error) {
console.error(error);
throw error;
}
};
export const getFileStorageMetadata = async (
fileId: number
): Promise<Map<string, string>> => {
const response = await Axios.get<FileMetadataResponse>(
`${API_FILES}/${fileId}/storageMetadata`,
{ validateStatus: (s) => s == 200 || s == 404 }
);
switch (response.status) {
case 200:
return new Map(Object.entries(response.data.metadata));
case 404:
throw `There is no file with id ${fileId}`;
default:
//this should not happen
throw `axios returned an unexpected response code ${response.status}`;
}
};
export const getProjectFiles = async (id: number): Promise<FileEntry[]> => {
const response = await Axios.get<ProjectFilesResponse>(
`${API_PROJECTS}/${id}/files?allVersions=true`,
{ validateStatus: (s) => s == 200 || s == 404 }
);
switch (response.status) {
case 200:
return response.data.files;
case 404:
throw `The project with id ${id} does not exist`;
default:
//this should not happen
throw `axios returned an unexpected response code ${response.status}`;
}
};
export const getProjectType = async (id: number): Promise<ProjectType> => {
const response = await Axios.get<PlutoApiResponse<ProjectType>>(
`/api/projecttype/${id}`
);
return response.data.result;
};
export const getProjectByVsid = async (vsid: string): Promise<Project> => {
const response = await Axios.get<PlutoApiResponse<Project>>(
`${API_PROJECTS}/vsid/${vsid}`
);
//if status!=200 we raise
return response.data.result;
};
export const updateProject = async (project: Project): Promise<void> => {
try {
const { status } = await Axios.put<PlutoApiResponse<void>>(
`${API_PROJECTS}/${project.id}`,
project
);
if (status !== 200) {
throw new Error(
`Could not update project ${project.id}: server said ${status}`
);
}
} catch (error) {
console.error(error);
throw error;
}
};
export const updateProjectOpenedStatus = async (id: number): Promise<void> => {
try {
const { status } = await Axios.put<PlutoApiResponse<void>>(
`${API_PROJECTS}/${id}/wasopened`
);
if (status !== 200) {
throw new Error(
`Could not update project opened status ${id}. ${status}`
);
}
} catch (error) {
console.error(error);
throw error;
}
};
export const setProjectStatusToKilled = async (id: number): Promise<void> => {
try {
const { status } = await Axios.put<PlutoApiResponse<void>>(
`${API_PROJECTS}/${id}/status`,
{ status: "Killed" }
);
if (status !== 200) {
throw new Error(`Could not update project status ${id}. ${status}`);
}
} catch (error) {
console.error(error);
throw error;
}
};
//Calls the backend to retrieve files associated with the given project id. that are not backups.
export const getFileData = async (id: number): Promise<FileEntry[]> => {
try {
const {
status,
data: { files },
} = await Axios.get<PlutoFilesAPIResponse<FileEntry[]>>(
`${API_PROJECTS}/${id}/files`
);
if (status === 200) {
return files;
}
throw new Error(`Could not get project data for project ${id}. ${status}`);
} catch (error) {
console.error(error);
throw error;
}
};
export const getStorageData = async (id: number): Promise<StorageEntry> => {
try {
const {
status,
data: { result },
} = await Axios.get<PlutoApiResponse<StorageEntry>>(`${API}/storage/${id}`);
if (status === 200) {
return result;
}
throw new Error(`Could not get storage data for storage ${id}. ${status}`);
} catch (error) {
console.error(error);
throw error;
}
};
export const translatePremiereVersion = async (
internalVersion: number,
silenceNotifications: boolean
): Promise<string | undefined> => {
try {
const response = await axios.get(
`/api/premiereVersion/internal/${internalVersion}`,
{ validateStatus: (status) => status === 200 || status === 404 }
);
switch (response.status) {
case 200:
const content = response.data as {
version: PremiereVersionTranslation;
};
console.log(
`Premiere version ${internalVersion} corresponds to ${content.version.name} ${content.version.displayedVersion}`
);
return content.version.displayedVersion;
case 404:
console.warn(
`Premiere version ${internalVersion} is not known to us, going to attempt to open blind`
);
if (!silenceNotifications) {
//set when testing, as this borks if SystemNotificationComponent is not intialised/rendered
SystemNotification.open(
SystemNotifcationKind.Warning,
"Did not recognise Premiere version, will attempt to open anyway"
);
}
return undefined;
}
} catch (err) {
console.error(
`Could not look up premiere version ${internalVersion}: `,
err
);
if (!silenceNotifications) {
SystemNotification.open(
SystemNotifcationKind.Error,
"Could not look up premiere version, will attempt to open anyway"
);
}
return undefined;
}
};
export const getOpenUrl = async (entry: FileEntry, id: number) => {
const storageResult = await getStorageData(entry.storage);
const isAudition = await isAuditionProject(id);
const auditionPath = await getAssetFolderPath(id);
const normalisedPath = auditionPath.value.replace(/^\/srv/, "/Volumes");
const pathToUse = isAudition
? normalisedPath
: storageResult.clientpath
? storageResult.clientpath
: storageResult.rootpath;
const premiereDisplayVersion = entry.premiereVersion
? await translatePremiereVersion(entry.premiereVersion, false)
: undefined;
const versionPart = premiereDisplayVersion
? `?premiereVersion=${premiereDisplayVersion}`
: "";
return `pluto:openproject:${pathToUse}/${entry.filepath}${versionPart}`;
};
export const getSesxProjectTypeIds = async () => {
try {
const { data } = await Axios.get(`${API}/projecttype`);
if (data.status === "ok") {
return data.result
.filter(
(projectType: { fileExtension: string }) =>
projectType.fileExtension === ".sesx"
)
.map((projectType: { id: any }) => projectType.id);
} else {
throw new Error("Failed to fetch project types");
}
} catch (error) {
console.error("Error fetching project types:", error);
throw error;
}
};
const isAuditionProject = async (id: number) => {
try {
const sesxProjectTypeIds = await getSesxProjectTypeIds();
console.log("sesxProjectTypeIds", sesxProjectTypeIds);
const {
status,
data: { result },
} = await Axios.get<PlutoApiResponse<Project>>(`${API_PROJECTS}/${id}`);
if (status === 200) {
if (sesxProjectTypeIds.includes(result.projectTypeId)) {
return true;
}
} else {
return false;
}
} catch (error) {
console.error(error);
throw error;
}
};
export const getAssetFolderPath = async (
id: number
): Promise<ProjectMetadataResponse> => {
try {
const {
status,
data: { result },
} = await Axios.get<PlutoApiResponse<ProjectMetadataResponse>>(
`${API_PROJECTS}/${id}/assetfolder`
);
if (status === 200) {
return result;
}
// Handle the non-200 status without throwing an error
console.error(
`Could not get asset folder path for project ${id}. ${status}`
);
return Promise.reject(
new Error(`Could not get asset folder path for project ${id}. ${status}`)
);
} catch (error) {
console.error(error);
return Promise.reject(error);
}
};
export const getOpenUrlForId = async (id: number) => {
const fileResult = await getFileData(id);
if (fileResult.length == 0) {
SystemNotification.open(
SystemNotifcationKind.Error,
"This project has no suitable files"
);
return;
}
return getOpenUrl(fileResult[0], id);
};
/**
* Sends a custom URL to PlutoHelperAgent which runs on the user's local machine. The URL contains the path of the project to open.
* @param id - numeric file ID representing the project to be opened. This will derive the most recent suitable FileEntry and use that
* to open the project.
* @returns - nothing; runs window.open which interrupts the running javascript and opens a new tab
*/
export const openProject = async (id: number) => {
const url = await getOpenUrlForId(id);
window.open(url, "_blank");
};
export const getSimpleProjectTypeData = async () => {
const response = await Axios.get(`${API}/projecttype`, {
validateStatus: (s) => s == 200,
});
switch (response.status) {
case 200:
const searchStrings = [
"Premiere",
"Cubase",
"After Effects",
"Audition",
"Prelude",
"Migrated",
];
let typeData: any = {};
for (const type of response.data.result) {
for (const currentString of searchStrings) {
if (type.name.includes(currentString)) {
typeData[type.id] = currentString;
}
}
}
return typeData;
default:
throw `Axios returned an unexpected response code ${response.status}`;
}
};
export const startDelete = async (
id: number,
pluto: boolean,
file: boolean,
backups: boolean,
pTR: boolean,
deliverables: boolean,
sAN: boolean,
matrix: boolean,
s3: boolean,
buckets: string[],
bucketBooleans: boolean[]
): Promise<void> => {
try {
let bucketsArray = `[`;
for (const bucket of buckets) {
bucketsArray = bucketsArray + `"${bucket}",`;
}
bucketsArray = bucketsArray.slice(0, -1);
bucketsArray = bucketsArray + `]`;
let booleansArray = `[`;
for (const boolean of bucketBooleans) {
booleansArray = booleansArray + `${boolean},`;
}
booleansArray = booleansArray.slice(0, -1);
booleansArray = booleansArray + `]`;
const { status } = await Axios.put<PlutoApiResponse<void>>(
`${API_PROJECTS}/${id}/deleteData`,
`{"pluto":${pluto},"file":${file},"backups":${backups},"PTR":${pTR},"deliverables":${deliverables},"SAN":${sAN},"matrix":${matrix},"S3":${s3},"buckets":${bucketsArray},"bucketBooleans":${booleansArray}}`,
{
headers: {
"Content-Type": "application/json",
},
}
);
if (status !== 200) {
throw new Error(
`Could not start deletion of data for project ${id}: server said ${status}`
);
}
} catch (error) {
console.error(error);
throw error;
}
};
export const getBuckets = async (): Promise<string[]> => {
try {
const {
status,
data: { buckets },
} = await Axios.get<PlutoBucketsAPIResponse<string[]>>(`${API}/buckets`);
if (status === 200) {
return buckets;
}
throw new Error(`Could not get buckets. ${status}`);
} catch (error) {
console.error(error);
throw error;
}
};
export const getDeleteJob = async (id: number): Promise<string> => {
try {
const {
status,
data: { job_status },
} = await Axios.get<PlutoDeleteJobAPIResponse<string>>(
`${API_PROJECTS}/${id}/deleteJob`
);
if (status === 200) {
return job_status;
}
throw new Error(`Could not get job status for project ${id}. ${status}`);
} catch (error) {
console.error(error);
throw error;
}
};
export const getItemsNotDeleted = async (
id: number
): Promise<ItemsNotDeleted[]> => {
try {
const {
status,
data: { results },
} = await Axios.get<PlutoItemDeleteDataAPIResponse<ItemsNotDeleted[]>>(
`${API_PROJECTS}/${id}/deleteItems`
);
if (status === 200) {
return results;
}
throw new Error(
`Could not get items not deleted for project ${id}. ${status}`
);
} catch (error) {
console.error(error);
throw error;
}
};
export const getAssetFolderProjectFiles = async (
id: number
): Promise<AssetFolderFileEntry[]> => {
const response = await Axios.get<AssetFolderProjectFilesResponse>(
`${API_PROJECTS}/${id}/assetFolderFiles?allVersions=false`,
{ validateStatus: (s) => s == 200 || s == 404 }
);
switch (response.status) {
case 200:
return response.data.files;
case 404:
throw `The project with id. ${id} does not exist`;
default:
throw `Axios returned an unexpected response code ${response.status}`;
}
};
export const getAssetFolderFileStorageMetadata = async (
fileId: number
): Promise<Map<string, string>> => {
const response = await Axios.get<FileMetadataResponse>(
`${API_FILES}/${fileId}/assetFolderStorageMetadata`,
{ validateStatus: (s) => s == 200 || s == 404 }
);
switch (response.status) {
case 200:
return new Map(Object.entries(response.data.metadata));
case 404:
throw `There is no file with id. ${fileId}`;
default:
throw `Axios returned an unexpected response code ${response.status}`;
}
};
export const getMissingFiles = async (id: number): Promise<MissingFiles[]> => {
try {
const {
status,
data: { results },
} = await Axios.get<PlutoItemDeleteDataAPIResponse<MissingFiles[]>>(
`${API_PROJECTS}/${id}/missingFiles`
);
if (status === 200) {
return results;
}
throw new Error(`Could not get missing files for project ${id}. ${status}`);
} catch (error) {
console.error(error);
throw error;
}
};
export const downloadProjectFile = async (id: number) => {
const url = `${deploymentRootPath}${API_PROJECTS}/${id}/fileDownload`;
const token = localStorage.getItem("pluto:access-token");
if (!token) {
console.log("No local access token, performing request without it");
}
const toAddTo = {};
const newInit = Object.assign({}, toAddTo, {
headers: Object.assign({}, null, {
Authorization: `Bearer ${token}`,
}),
});
let filename = "";
fetch(url, newInit)
.then((response) => {
// @ts-ignore
filename = response.headers
.get("Content-Disposition")
.split('filename="')[1]
.split('";')[0];
if (filename.substr(filename.length - 1)) {
filename = filename.slice(0, -1);
}
return response.blob();
})
.then((blob) => {
saveAs(blob, filename);
})
.catch((err) => {
console.log(err);
});
};