Skip to content

Commit 1bd28d2

Browse files
authored
Persist analyses (#191)
* See if ?preset works to load experiment remotely * Only hide toast on success + allow toast of failure to be closed manually * Load experiment from remote URL via `?e=<remote-url>` Fixes #158 * Embed json file into README * Do not try to parse non-200 responses * Plot pressure, temperature and relative humidity from observations Fixes #184 * If shareable link is too large give hosting state file as alternative * Always clear adress bar from `?s=...` * more e to s replacements + spelling * Use union type for Analysis + setAnalysis on loading state It is before setExperiments to have now plots being added by runExperiment * Encode/decode analyses array using a JSON schema validator + move Analysis types to src/lib/analysis_type.ts Moved types so type and its derived JSON schema are in same file. * Make oneOfs the same
1 parent 82f5bef commit 1bd28d2

File tree

5 files changed

+115
-43
lines changed

5 files changed

+115
-43
lines changed

apps/class-solid/src/components/Analysis.tsx

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,20 +27,18 @@ import {
2727
createUniqueId,
2828
} from "solid-js";
2929
import { createStore } from "solid-js/store";
30+
import type {
31+
Analysis,
32+
ProfilesAnalysis,
33+
SkewTAnalysis,
34+
TimeseriesAnalysis,
35+
} from "~/lib/analysis_type";
3036
import type { Observation } from "~/lib/experiment_config";
3137
import {
3238
observationsForProfile,
3339
observationsForSounding,
3440
} from "~/lib/profiles";
35-
import {
36-
type Analysis,
37-
type ProfilesAnalysis,
38-
type SkewTAnalysis,
39-
type TimeseriesAnalysis,
40-
deleteAnalysis,
41-
experiments,
42-
updateAnalysis,
43-
} from "~/lib/store";
41+
import { deleteAnalysis, experiments, updateAnalysis } from "~/lib/store";
4442
import { MdiCamera, MdiDelete, MdiImageFilterCenterFocus } from "./icons";
4543
import { AxisBottom, AxisLeft, getNiceAxisLimits } from "./plots/Axes";
4644
import { Chart, ChartContainer, type ChartData } from "./plots/ChartContainer";
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import { ajv } from "@classmodel/class/validate";
2+
import { type DefinedError, type JSONSchemaType, ValidationError } from "ajv";
3+
4+
export interface BaseAnalysis {
5+
id: string;
6+
description: string;
7+
type: string;
8+
name: string;
9+
}
10+
11+
export type TimeseriesAnalysis = BaseAnalysis & {
12+
xVariable: string;
13+
yVariable: string;
14+
};
15+
16+
export type ProfilesAnalysis = BaseAnalysis & {
17+
variable: string;
18+
time: number;
19+
};
20+
21+
export type SkewTAnalysis = BaseAnalysis & {
22+
time: number;
23+
};
24+
25+
export type Analysis = TimeseriesAnalysis | ProfilesAnalysis | SkewTAnalysis;
26+
export const analysisNames = [
27+
"Vertical profiles",
28+
"Timeseries",
29+
"Thermodynamic diagram",
30+
];
31+
32+
export function parseAnalysis(raw: unknown): Analysis {
33+
const schema = {
34+
oneOf: [
35+
{
36+
type: "object",
37+
required: [
38+
"id",
39+
"description",
40+
"type",
41+
"name",
42+
"xVariable",
43+
"yVariable",
44+
],
45+
properties: {
46+
id: { type: "string" },
47+
description: { type: "string" },
48+
type: { const: "timeseries" },
49+
name: { type: "string" },
50+
xVariable: { type: "string" },
51+
yVariable: { type: "string" },
52+
},
53+
additionalProperties: false,
54+
},
55+
{
56+
type: "object",
57+
required: ["id", "description", "type", "name", "variable", "time"],
58+
properties: {
59+
id: { type: "string" },
60+
description: { type: "string" },
61+
type: { const: "profiles" },
62+
name: { type: "string" },
63+
variable: { type: "string" },
64+
time: { type: "number" },
65+
},
66+
additionalProperties: false,
67+
},
68+
{
69+
type: "object",
70+
required: ["id", "description", "type", "name", "time"],
71+
properties: {
72+
id: { type: "string" },
73+
description: { type: "string" },
74+
type: { const: "skewT" },
75+
name: { type: "string" },
76+
time: { type: "number" },
77+
},
78+
additionalProperties: false,
79+
},
80+
],
81+
} as unknown as JSONSchemaType<Analysis>;
82+
const validate = ajv.compile(schema);
83+
if (!validate(raw)) {
84+
throw new ValidationError(validate.errors as DefinedError[]);
85+
}
86+
return raw;
87+
}

apps/class-solid/src/lib/encode.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import { pruneConfig } from "@classmodel/class/config_utils";
22
import { unwrap } from "solid-js/store";
3+
import { parseAnalysis } from "./analysis_type";
4+
import type { Analysis } from "./analysis_type";
35
import {
46
type ExperimentConfig,
57
type PartialExperimentConfig,
68
parseExperimentConfig,
79
} from "./experiment_config";
810
import { findPresetByName } from "./presets";
9-
import type { Analysis, Experiment } from "./store";
11+
import type { Experiment } from "./store";
1012

1113
export function decodeAppState(encoded: string): [Experiment[], Analysis[]] {
1214
const decoded = decodeURI(encoded);
@@ -28,8 +30,14 @@ export function decodeAppState(encoded: string): [Experiment[], Analysis[]] {
2830
} else {
2931
console.error("No experiments found in ", encoded);
3032
}
31-
3233
const analyses: Analysis[] = [];
34+
if (typeof parsed === "object" && Array.isArray(parsed.analyses)) {
35+
for (const analysisRaw of parsed.analyses) {
36+
const analysis = parseAnalysis(analysisRaw);
37+
analyses.push(analysis);
38+
}
39+
}
40+
3341
return [experiments, analyses];
3442
}
3543

@@ -38,8 +46,10 @@ export function encodeAppState(
3846
analyses: Analysis[],
3947
) {
4048
const rawExperiments = unwrap(experiments);
49+
const rawAnalyses = unwrap(analyses);
4150
const minimizedState = {
4251
experiments: rawExperiments.map((exp) => toPartial(exp.config)),
52+
analyses: rawAnalyses,
4353
};
4454
return encodeURI(JSON.stringify(minimizedState, undefined, 0));
4555
}

apps/class-solid/src/lib/store.ts

Lines changed: 7 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@ import {
88
mergeConfigurations,
99
pruneConfig,
1010
} from "@classmodel/class/config_utils";
11+
import type {
12+
Analysis,
13+
ProfilesAnalysis,
14+
SkewTAnalysis,
15+
TimeseriesAnalysis,
16+
} from "./analysis_type";
1117
import { decodeAppState } from "./encode";
1218
import { parseExperimentConfig } from "./experiment_config";
1319
import type { ExperimentConfig } from "./experiment_config";
@@ -233,41 +239,11 @@ export function swapPermutationAndReferenceConfiguration(
233239

234240
export async function loadStateFromString(rawState: string): Promise<void> {
235241
const [loadedExperiments, loadedAnalyses] = decodeAppState(rawState);
242+
setAnalyses(loadedAnalyses);
236243
setExperiments(loadedExperiments);
237244
await Promise.all(loadedExperiments.map((_, i) => runExperiment(i)));
238245
}
239246

240-
export interface Analysis {
241-
id: string;
242-
description: string;
243-
type: string;
244-
name: string;
245-
}
246-
247-
export type TimeseriesAnalysis = Analysis & {
248-
xVariable: string;
249-
yVariable: string;
250-
};
251-
252-
export type ProfilesAnalysis = Analysis & {
253-
variable: string;
254-
time: number;
255-
};
256-
257-
export type SkewTAnalysis = Analysis & {
258-
time: number;
259-
};
260-
261-
export type AnalysisType =
262-
| TimeseriesAnalysis
263-
| ProfilesAnalysis
264-
| SkewTAnalysis;
265-
export const analysisNames = [
266-
"Vertical profiles",
267-
"Timeseries",
268-
"Thermodynamic diagram",
269-
];
270-
271247
export function addAnalysis(name: string) {
272248
let newAnalysis: Analysis;
273249

apps/class-solid/src/routes/index.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ import { Flex } from "~/components/ui/flex";
1616
import { Toaster } from "~/components/ui/toast";
1717
import { onPageLoad } from "~/lib/state";
1818

19-
import { addAnalysis, analysisNames, experiments } from "~/lib/store";
19+
import { analysisNames } from "~/lib/analysis_type";
20+
import { addAnalysis, experiments } from "~/lib/store";
2021
import { analyses } from "~/lib/store";
2122

2223
export default function Home() {

0 commit comments

Comments
 (0)