Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

VUU54: Add validation for layout/metadata IDs #60

Merged
merged 18 commits into from
Oct 10, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export interface LayoutPersistenceManager {
*
* @returns Unique identifier assigned to the saved layout
*/
createLayout: (metadata: Omit<LayoutMetadata, "id">, layout: LayoutJSON) => string;
createLayout: (metadata: Omit<LayoutMetadata, "id">, layout: LayoutJSON) => Promise<string>;

/**
* Overwrites an existing layout and its corresponding metadata with the provided infromation
cfisher-scottlogic marked this conversation as resolved.
Show resolved Hide resolved
Expand All @@ -19,14 +19,14 @@ export interface LayoutPersistenceManager {
* @param metadata - Metadata describing the new layout to overwrite with
* @param layout - Full JSON representation of the new layout to overwrite with
*/
updateLayout: (id: string, metadata: Omit<LayoutMetadata, "id">, layout: LayoutJSON) => void;
updateLayout: (id: string, metadata: Omit<LayoutMetadata, "id">, layout: LayoutJSON) => Promise<void>;

/**
* Deletes an existing layout and its corresponding metadata
*
* @param id - Unique identifier of the existing layout to be deleted
*/
deleteLayout: (id: string) => void;
deleteLayout: (id: string) => Promise<void>;

/**
* Retrieves an existing layout
Expand All @@ -35,12 +35,12 @@ export interface LayoutPersistenceManager {
*
* @returns Full JSON representation of the layout corresponding to the provided ID
*/
loadLayout: (id: string) => LayoutJSON;
loadLayout: (id: string) => Promise<LayoutJSON>;

/**
* Retrieves metadata for all existing layouts
*
* @returns an array of all persisted layout metadata
*/
loadMetadata: () => LayoutMetadata[];
loadMetadata: () => Promise<LayoutMetadata[]>;
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,72 +8,124 @@ const metadataSaveLocation = "layouts/metadata";
const layoutsSaveLocation = "layouts/layouts";

export class LocalLayoutPersistenceManager implements LayoutPersistenceManager {
createLayout(metadata: Omit<LayoutMetadata, "id">, layout: LayoutJSON): string {
console.log(`Saving layout as ${metadata.name} to group ${metadata.group}...`);
createLayout(metadata: Omit<LayoutMetadata, "id">, layout: LayoutJSON): Promise<string> {
return new Promise(async (resolve) => {
vferraro-scottlogic marked this conversation as resolved.
Show resolved Hide resolved
console.log(`Saving layout as ${metadata.name} to group ${metadata.group}...`);

const existingLayouts = this.loadLayouts();
const existingMetadata = this.loadMetadata();
const existingLayouts = await this.loadLayouts();
const existingMetadata = await this.loadMetadata();
pling-scottlogic marked this conversation as resolved.
Show resolved Hide resolved

const id = getUniqueId();
const id = getUniqueId();

this.appendAndPersist(id, metadata, layout, existingLayouts, existingMetadata);
this.appendAndPersist(id, metadata, layout, existingLayouts, existingMetadata);

return id;
resolve(id);
})
}

updateLayout(id: string, metadata: Omit<LayoutMetadata, "id">, newLayoutJson: LayoutJSON): void {
const existingLayouts = this.loadLayouts().filter(layout => layout.id !== id);
const existingMetadata = this.loadMetadata().filter(metadata => metadata.id !== id);

this.appendAndPersist(id, metadata, newLayoutJson, existingLayouts, existingMetadata);
updateLayout(id: string, metadata: Omit<LayoutMetadata, "id">, newLayoutJson: LayoutJSON): Promise<void> {
return new Promise(async (resolve, reject) => {
vferraro-scottlogic marked this conversation as resolved.
Show resolved Hide resolved
this.validateIds(id)
.then(async () => {
const existingLayouts = (await this.loadLayouts()).filter(layout => layout.id !== id);
vferraro-scottlogic marked this conversation as resolved.
Show resolved Hide resolved
const existingMetadata = (await this.loadMetadata()).filter(metadata => metadata.id !== id);
this.appendAndPersist(id, metadata, newLayoutJson, existingLayouts, existingMetadata);
resolve();
})
.catch(e => reject(e));
pling-scottlogic marked this conversation as resolved.
Show resolved Hide resolved
});
}

deleteLayout(id: string): void {
const layouts = this.loadLayouts().filter(layout => layout.id !== id);
const metadata = this.loadMetadata().filter(metadata => metadata.id !== id);

this.saveLayoutsWithMetadata(layouts, metadata);
deleteLayout(id: string): Promise<void> {
return new Promise(async (resolve, reject) => {
vferraro-scottlogic marked this conversation as resolved.
Show resolved Hide resolved
this.validateIds(id)
.then(async () => {
const layouts = (await this.loadLayouts()).filter((layout) => layout.id !== id);
vferraro-scottlogic marked this conversation as resolved.
Show resolved Hide resolved
const metadata = (await this.loadMetadata()).filter(metadata => metadata.id !== id);
this.saveLayoutsWithMetadata(layouts, metadata);
resolve();
})
.catch(e => reject(e));
});
}

loadLayout(id: string): LayoutJSON {
const layout = this.loadLayouts().filter(layout => layout.id === id);

switch (layout.length) {
case 1: {
return layout[0].json;
}
case 0: {
console.log(`WARNING: no layout exists for ID "${id}"; returning empty layout`);
return {} as LayoutJSON;
}
default: {
console.log(`WARNING: multiple layouts exist for ID "${id}"; returning first instance`)
return layout[0].json;
}
}
loadLayout(id: string): Promise<LayoutJSON> {
return new Promise((resolve, reject) => {
this.validateId(id, false)
.then(async () => {
const layouts = (await this.loadLayouts()).filter(layout => layout.id === id);
resolve(layouts[0].json);
})
.catch(e => reject(e));
});
}

loadMetadata(): LayoutMetadata[] {
return getLocalEntity<LayoutMetadata[]>(metadataSaveLocation) || [];
loadMetadata(): Promise<LayoutMetadata[]> {
return new Promise((resolve) => {
const metadata = getLocalEntity<LayoutMetadata[]>(metadataSaveLocation);
resolve(metadata || []);
pling-scottlogic marked this conversation as resolved.
Show resolved Hide resolved
})
}

private loadLayouts(): Layout[] {
return getLocalEntity<Layout[]>(layoutsSaveLocation) || [];
private loadLayouts(): Promise<Layout[]> {
return new Promise((resolve) => {
const layouts = getLocalEntity<Layout[]>(layoutsSaveLocation);
resolve(layouts || []);
});
}

private appendAndPersist(newId: string,
newMetadata: Omit<LayoutMetadata, "id">,
newLayout: LayoutJSON,
existingLayouts: Layout[],
existingMetadata: LayoutMetadata[]) {
existingLayouts.push({id: newId, json: newLayout});
existingMetadata.push({id: newId, ...newMetadata});
private appendAndPersist(
newId: string,
newMetadata: Omit<LayoutMetadata, "id">,
newLayout: LayoutJSON,
existingLayouts: Layout[],
existingMetadata: LayoutMetadata[]
) {
existingLayouts.push({ id: newId, json: newLayout });
existingMetadata.push({ id: newId, ...newMetadata });

this.saveLayoutsWithMetadata(existingLayouts, existingMetadata);
}

private saveLayoutsWithMetadata(layouts: Layout[], metadata: LayoutMetadata[]): void {
private saveLayoutsWithMetadata(
layouts: Layout[],
metadata: LayoutMetadata[]
): void {
saveLocalEntity<Layout[]>(layoutsSaveLocation, layouts);
saveLocalEntity<LayoutMetadata[]>(metadataSaveLocation, metadata);
}

private async validateIds(id: string): Promise<void> {
vferraro-scottlogic marked this conversation as resolved.
Show resolved Hide resolved
return Promise
.all([
vferraro-scottlogic marked this conversation as resolved.
Show resolved Hide resolved
this.validateId(id, true).catch(error => error.message),
this.validateId(id, false).catch(error => error.message)
])
.then((errorMessages: string[]) => {
const combinedMessage = errorMessages.filter(Boolean).join("; ");
vferraro-scottlogic marked this conversation as resolved.
Show resolved Hide resolved
if (combinedMessage) {
throw new Error(combinedMessage);
cfisher-scottlogic marked this conversation as resolved.
Show resolved Hide resolved
}
});
}

private validateId(id: string, metadata: boolean): Promise<void> {
vferraro-scottlogic marked this conversation as resolved.
Show resolved Hide resolved
return new Promise((resolve, reject) => {
const loadFunc = metadata ? this.loadMetadata : this.loadLayouts;
loadFunc().then(result => {
const count = result.filter(x => x.id === id).length;
cfisher-scottlogic marked this conversation as resolved.
Show resolved Hide resolved
cfisher-scottlogic marked this conversation as resolved.
Show resolved Hide resolved
switch (count) {
case 1: {
resolve();
break;
};
vferraro-scottlogic marked this conversation as resolved.
Show resolved Hide resolved
case 0: {
reject(new Error(`No ${metadata ? "metadata" : "layout"} with ID ${id}`));
break;
}
default: reject(new Error(`Non-unique ${metadata ? "metadata" : "layout"} with ID ${id}`));
}
});
})
}
}
14 changes: 14 additions & 0 deletions vuu-ui/packages/vuu-layout/src/layout-persistence/data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export const warningLayout = {
type: "View",
props: {
style: { height: "calc(100% - 6px)" },
},
children: [
{
props: {
className: "vuuShell-warningPlaceholder",
},
type: "Placeholder",
},
],
};
Loading
Loading