Skip to content

Commit

Permalink
First iteration
Browse files Browse the repository at this point in the history
  • Loading branch information
rubenthoms committed Jan 24, 2024
1 parent 11c71c3 commit b804210
Show file tree
Hide file tree
Showing 14 changed files with 327 additions and 32 deletions.
69 changes: 50 additions & 19 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"@tanstack/react-query-devtools": "^5.4.2",
"@webviz/subsurface-viewer": "^0.3.1",
"@webviz/well-completions-plot": "^0.0.1-alpha.1",
"ajv": "^8.12.0",
"animate.css": "^4.1.1",
"axios": "^1.6.5",
"culori": "^3.2.0",
Expand All @@ -37,6 +38,7 @@
"uuid": "^9.0.0"
},
"devDependencies": {
"@babel/plugin-syntax-import-attributes": "^7.23.3",
"@playwright/experimental-ct-react": "^1.39.0",
"@playwright/test": "^1.39.0",
"@trivago/prettier-plugin-sort-imports": "^4.0.0",
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/assets/field-configs/DROGON.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"fieldIdentifier": "DROGON",
"range": [0, 25]
}
18 changes: 18 additions & 0 deletions frontend/src/framework/FieldConfigs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
export interface FieldConfig {
fieldIdentifier: string;
range: [number, number];
}

export class FieldConfigSet {
private _configMap: Map<string, FieldConfig> = new Map();

constructor(configArray: FieldConfig[]) {
for (const config of configArray) {
this._configMap.set(config.fieldIdentifier, config);
}
}

getConfig(fieldIdentifier: string): FieldConfig | null {
return this._configMap.get(fieldIdentifier) || null;
}
}
23 changes: 21 additions & 2 deletions frontend/src/framework/Workbench.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { Template } from "./TemplateRegistry";
import { WorkbenchServices } from "./WorkbenchServices";
import { WorkbenchSession } from "./WorkbenchSession";
import { loadEnsembleSetMetadataFromBackend } from "./internal/EnsembleSetLoader";
import { loadFieldConfigs } from "./internal/FieldConfigSetLoader";
import { PrivateWorkbenchServices } from "./internal/PrivateWorkbenchServices";
import { PrivateWorkbenchSettings } from "./internal/PrivateWorkbenchSettings";
import { WorkbenchSessionPrivate } from "./internal/WorkbenchSessionPrivate";
Expand Down Expand Up @@ -204,7 +205,7 @@ export class Workbench {
queryClient: QueryClient,
specifiedEnsembleIdents: EnsembleIdent[]
): Promise<void> {
this.storeEnsembleSetInLocalStorage(specifiedEnsembleIdents);
this.storeSpecifiedEnsemblesInLocalStorage(specifiedEnsembleIdents);

const ensembleIdentsToLoad: EnsembleIdent[] = [];
for (const ensSpec of specifiedEnsembleIdents) {
Expand All @@ -217,14 +218,32 @@ export class Workbench {
console.debug("loadAndSetupEnsembleSetInSession - loading done");
console.debug("loadAndSetupEnsembleSetInSession - publishing");
this._workbenchSession.setEnsembleSetLoadingState(false);

return this._workbenchSession.setEnsembleSet(newEnsembleSet);
}

private storeEnsembleSetInLocalStorage(specifiedEnsembleIdents: EnsembleIdent[]): void {
async loadAndSetupFieldConfigSetInSession(fieldsToLoadConfigFor: string[]): Promise<void> {
this.storeFieldsToLoadConfigForInLocalStorage(fieldsToLoadConfigFor);

console.debug("loadAndSetupFieldConfigSetInSession - starting load");
this._workbenchSession.setFieldConfigSetLoadingState(true);
const newFieldConfigSet = await loadFieldConfigs(fieldsToLoadConfigFor);
console.debug("loadAndSetupFieldConfigSetInSession - loading done");
console.debug("loadAndSetupFieldConfigSetInSession - publishing");
this._workbenchSession.setFieldConfigSetLoadingState(false);

return this._workbenchSession.setFieldConfigSet(newFieldConfigSet);
}

private storeSpecifiedEnsemblesInLocalStorage(specifiedEnsembleIdents: EnsembleIdent[]): void {
const ensembleIdentsToStore = specifiedEnsembleIdents.map((el) => el.toString());
localStorage.setItem("ensembleIdents", JSON.stringify(ensembleIdentsToStore));
}

private storeFieldsToLoadConfigForInLocalStorage(fieldsToLoadConfigFor: string[]): void {
localStorage.setItem("fieldsToLoadConfigFor", JSON.stringify(fieldsToLoadConfigFor));
}

maybeLoadEnsembleSetFromLocalStorage(): EnsembleIdent[] | null {
const ensembleIdentsString = localStorage.getItem("ensembleIdents");
if (!ensembleIdentsString) return null;
Expand Down
57 changes: 57 additions & 0 deletions frontend/src/framework/WorkbenchSession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,37 @@ import React from "react";

import { Ensemble } from "./Ensemble";
import { EnsembleSet } from "./EnsembleSet";
import { FieldConfigSet } from "./FieldConfigs";

export enum WorkbenchSessionEvent {
EnsembleSetChanged = "EnsembleSetChanged",
EnsembleSetLoadingStateChanged = "EnsembleSetLoadingStateChanged",
FieldConfigSetChanged = "FieldConfigSetChanged",
FieldConfigSetLoadingStateChanged = "FieldConfigSetLoadingStateChanged",
}

export type WorkbenchSessionPayloads = {
[WorkbenchSessionEvent.EnsembleSetLoadingStateChanged]: {
isLoading: boolean;
};
[WorkbenchSessionEvent.FieldConfigSetLoadingStateChanged]: {
isLoading: boolean;
};
};

export class WorkbenchSession {
private _subscribersMap: Map<keyof WorkbenchSessionEvent, Set<(payload: any) => void>> = new Map();
protected _ensembleSet: EnsembleSet = new EnsembleSet([]);
protected _fieldConfigSet: FieldConfigSet = new FieldConfigSet([]);

getEnsembleSet(): EnsembleSet {
return this._ensembleSet;
}

getFieldConfigSet(): FieldConfigSet {
return this._fieldConfigSet;
}

subscribe<T extends Exclude<WorkbenchSessionEvent, keyof WorkbenchSessionPayloads>>(
event: T,
cb: () => void
Expand Down Expand Up @@ -108,3 +119,49 @@ export function useIsEnsembleSetLoading(workbenchSession: WorkbenchSession): boo

return isLoading;
}

export function useFieldConfigSet(workbenchSession: WorkbenchSession): FieldConfigSet {
const [storedFieldConfigSet, setStoredFieldConfigSet] = React.useState<FieldConfigSet>(
workbenchSession.getFieldConfigSet()
);

React.useEffect(
function subscribeToFieldConfigSetChanges() {
function handleFieldConfigSetChanged() {
setStoredFieldConfigSet(workbenchSession.getFieldConfigSet());
}

const unsubFunc = workbenchSession.subscribe(
WorkbenchSessionEvent.FieldConfigSetChanged,
handleFieldConfigSetChanged
);
return unsubFunc;
},
[workbenchSession]
);

return storedFieldConfigSet;
}

export function useIsFieldConfigSetLoading(workbenchSession: WorkbenchSession): boolean {
const [isLoading, setIsLoading] = React.useState<boolean>(false);

React.useEffect(
function subscribeToFieldConfigSetLoadingStateChanges() {
function handleFieldConfigSetLoadingStateChanged(
payload: WorkbenchSessionPayloads[WorkbenchSessionEvent.FieldConfigSetLoadingStateChanged]
) {
setIsLoading(payload.isLoading);
}

const unsubFunc = workbenchSession.subscribe(
WorkbenchSessionEvent.FieldConfigSetLoadingStateChanged,
handleFieldConfigSetLoadingStateChanged
);
return unsubFunc;
},
[workbenchSession]
);

return isLoading;
}
92 changes: 92 additions & 0 deletions frontend/src/framework/internal/FieldConfigSetLoader.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { FieldConfig, FieldConfigSet } from "@framework/FieldConfigs";

import Ajv, { ErrorObject, JSONSchemaType } from "ajv";

const CONFIG_JSON_SCHEMA: JSONSchemaType<FieldConfig> = {
type: "object",
properties: {
fieldIdentifier: { type: "string" },
range: {
type: "array",
items: [{ type: "number" }, { type: "number" }],
minItems: 2,
maxItems: 2,
},
},
required: ["fieldIdentifier"],
additionalProperties: false,
};

const validateConfig = new Ajv().compile(CONFIG_JSON_SCHEMA);

function stringifyConfigValidationErrors(errors: ErrorObject<string, Record<string, any>, unknown>[]): string {
return errors.map((error) => JSON.stringify(error)).join("\n");
}

type LoadConfigFromFileResult = {
config: FieldConfig | null;
error: Error | null;
};

async function loadConfigFromFile(fieldIdentifier: string): Promise<LoadConfigFromFileResult> {
const result: LoadConfigFromFileResult = {
config: null,
error: null,
};

try {
const content = await import(`@assets/field-configs/${fieldIdentifier}.json?init`);
if (!validateConfig(content.default)) {
result.error = new Error(
`Invalid format in config file '${fieldIdentifier}.json':\n${stringifyConfigValidationErrors(
validateConfig.errors ?? []
)}`
);
}

if (content.default.fieldIdentifier !== fieldIdentifier) {
result.error = new Error(`Field identifier mismatch in config file '${fieldIdentifier}.json'`);
}
} catch (error: any) {
result.error = new Error(`Failed to load config for field '${fieldIdentifier}': ${error.message}`);
}

return result;
}

export async function loadFieldConfigs(fieldsToLoadConfigFor: string[]): Promise<FieldConfigSet> {
const promiseArray: Promise<LoadConfigFromFileResult | null>[] = [];

for (const field of fieldsToLoadConfigFor) {
promiseArray.push(loadConfigFromFile(field));
}

const results = await Promise.allSettled(promiseArray);

const configArray: FieldConfig[] = [];
for (let i = 0; i < results.length; i++) {
const result = results[i];
const fieldIdentifier = fieldsToLoadConfigFor[i];

if (result.status === "rejected") {
console.error(`Error loading config for field '${fieldIdentifier}', dropping config`, result.reason);
continue;
}

const config = result.value?.config;

if (!config) {
console.error(`No config found for field '${fieldIdentifier}', dropping config`);
continue;
}

if (config.fieldIdentifier !== fieldIdentifier) {
console.error(`Field identifier mismatch in config for field '${fieldIdentifier}', dropping config`);
continue;
}

configArray.push(config);
}

return new FieldConfigSet(configArray);
}
Loading

0 comments on commit b804210

Please sign in to comment.