Skip to content

Commit d6d17df

Browse files
Merge branch 'main' into chile
2 parents 460cd2a + 9193b3c commit d6d17df

File tree

177 files changed

+13195
-1051
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

177 files changed

+13195
-1051
lines changed

.editorconfig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ root = true
44
[*]
55
charset = utf-8
66
indent_style = space
7-
indent_size = 2
7+
indent_size = 4
88
insert_final_newline = true
99
trim_trailing_whitespace = true
1010

CHANGELOG.md

Lines changed: 71 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,75 @@
66
Reason: d3 cannot handle dom-nodes that dont exist yet.
77
For the new d3-graphs we need a dynamicPopupComponent.
88
And for a dynamicPopupComponent we need to return a ProductCustomLayer, not a VectorLayerProduct.
9-
9+
- Integrate new, comparison-based frontend
10+
- Integrate backend for content-redaction
11+
- Old frontend: modularize
12+
13+
# [2.0.5]
14+
- Ongoing
15+
- link click on eqSelection with form
16+
- dropdown doesnt know which of the options the new value is
17+
- add a `valueToKey` property to wizardComplex?
18+
- new rule: focus on first step from beginning
19+
- Upcoming
20+
- create a docker-compose.yml for backend, frontends and monitor
21+
- create a global config-file
22+
- style layer differently on click
23+
- translation service
24+
- focus on eq-selection immediately
25+
- steps: show errors
26+
- auto-pilot: configurable start-strategy
27+
28+
# [2.0.4](https://github.com/riesgos/dlr-riesgos-frontend/releases/tag/2.0.4) (May 16 2023) Created monitor and new frontend
1029
## Features
11-
- created new frontend: allows to compare two scenarios side-by-side.
12-
- new ui without clarity
13-
- layercontrol and config-wizard merged
14-
- backend now provides optional default values for steps' inputs.
30+
- monitor:
31+
- created service that regularly executes all riesgos-services
32+
- backend:
33+
- now provides optional default values for steps' inputs.
34+
- tickets now stay alive for a certain time after they've been fetched
35+
- added `execute?skipCache=true` option for execute-requests
36+
- added `runall.ts` which runs all steps once every hour.
37+
- fix: logging threw error on circular objects.
38+
- added `expires` headers
39+
- frontend:
40+
- now allows multiple (dynamic) legends for one layer at the same time. Applied to eq-catalogue.
41+
- now has circle-legend
42+
- fix: race condition in making post-eq-damage mapable.
43+
- fix: wrong style and legend for chile-damage.
44+
- fix: *should* have fixed NS_BINDING_ABORTED.
45+
- fix: error in one process does not stop processing of other processes.
46+
- Compare frontend
47+
- all code now in modules
48+
- map allows per-step data-converters which are semi-automatically injected
49+
- now displays available eq's, too.
50+
- using rules to decide what exactly to do in reducers
51+
- user can chose between rule-sets
52+
- map has click-behavior
53+
- add popup to map on click
54+
- can now close popups
55+
- popups get features at click location as input
56+
- composites can do arbitrary things on click
57+
- Rules now calculated from rule-set-name by dedicated service
58+
- LayerComposite methods are non-anonymous: allows accessing all properties with `this.`
59+
- Custom style for eq-selection layer.
60+
- Wizard now converts from dynamically selected converter, too, just like map does.
61+
- More than one step can be focused
62+
- Arbitrary screen-partitions possible
63+
- Wizards now have a default converter to fall back to
64+
- error-handling during process-execution
65+
- click on step toggles focus
66+
- hides layers of un-toggled steps
67+
- parameter-selection shows labels in dropdown
68+
- insideOne ruleset: data now mirrored
69+
- auto-pilot only started when rules allow it
70+
- legends now displayed - if present
71+
- fix: layer- and wizard-services no longer cause multiple ui-updates on every state-change (reason: base-observable now shared)
72+
- popup: increased close button clickable area
73+
- fix: click on map only fires click-handler *once*.
74+
- dropdown now selects value when clicked on map
75+
- wizard expanded from beginning: did need to move `share` up in filter-obs
76+
- reducers: parseAPIScenariosIntoNewState: strict typing
77+
- openlayers now runs outside of angular-zone
1578

1679
# [2.0.3](https://github.com/riesgos/dlr-riesgos-frontend/releases/tag/2.0.3) (Mar. 9 2023) Bug fixes
1780

@@ -20,6 +83,9 @@
2083
- literal parameters of eq-catalog were not accepted
2184
- because no `userDataProvided` action triggered when user edits literal values
2285

86+
87+
88+
2389
# [2.0.2](https://github.com/riesgos/dlr-riesgos-frontend/releases/tag/2.0.2) (Feb. 2 2023) Redactional
2490

2591

backend/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "scenarios",
3-
"version": "2.0.3",
3+
"version": "2.0.4",
44
"description": "",
55
"scripts": {
66
"build:prod": "tsc --build",

backend/src/config.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export const config = {
22
sourceEmail: 'test@example.com',
33
adminEmail: 'michael.langbein@dlr.de',
4-
port: 8008
4+
port: 8008,
5+
maxStoreLifeTimeMinutes: 24 * 60
56
};

backend/src/logging/logger.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { appendFileSync } from "fs"
22
import { createFileSync, getFileAgeSync, renameFileSync } from "../utils/files";
33
import { MailClient } from "../web/mailClient";
44
import { config } from '../config';
5+
import { inspect } from 'util';
56

67

78
export class Logger {
@@ -64,7 +65,7 @@ export class Logger {
6465
else if (message instanceof Error || (message.stack && message.message) ) { // if an Error object ...
6566
messageString = JSON.stringify(message, Object.getOwnPropertyNames(message));
6667
} else { // if any other object ...
67-
messageString = JSON.stringify(message);
68+
messageString = inspect(message);
6869
}
6970
return messageString;
7071
}

backend/src/main.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import express from 'express';
44
import { addScenarioApi } from './scenarios/scenario.interface';
55
// import { parseCode } from './parser/scenarioParser';
66
import { peruFactory } from './usr/peru/peru';
7+
import { peruShortFactory } from './usr/peru_short/peru';
78
import { chileFactory } from './usr/chile/chile';
89
import { ecuadorFactory } from './usr/ecuador/ecuador';
910

@@ -18,11 +19,12 @@ const scriptDir = './data/scenarios'; // user-defined logic
1819
async function main() {
1920
const app = express();
2021
app.use(cors());
21-
// const scenarioFactories = await parseCode(scriptDir);
22-
const scenarioFactories = [chileFactory, ecuadorFactory, peruFactory];
2322

24-
addScenarioApi(app, scenarioFactories, storeDir, logDir);
23+
// const scenarioFactories = await parseCode(scriptDir);
24+
const scenarioFactories = [chileFactory, ecuadorFactory, peruFactory, peruShortFactory];
25+
26+
addScenarioApi(app, scenarioFactories, storeDir, logDir, 'verbose', true);
2527
const server = app.listen(port, () => console.log(`app now listening on port ${port}`));
2628
}
2729

28-
main();
30+
main();

backend/src/scenarios/autorun.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { parseCode } from '../parser/scenarioParser';
22
import { deleteFile } from '../utils/files';
33
import { FileStorage } from '../storage/fileStorage';
4-
import { DatumLinage, isDatumReference, isUserSelection, ScenarioState } from './scenarios';
4+
import { DatumLinage, isDatumReference, ScenarioState } from './scenarios';
55

66

77

@@ -19,7 +19,7 @@ afterAll(async () => {
1919

2020
test('autorun', async () => {
2121
const factories = await parseCode(codeDir);
22-
const store = new FileStorage<DatumLinage>(storeDir);
22+
const store = new FileStorage<DatumLinage>(storeDir, 60);
2323

2424
for (const factory of factories) {
2525

@@ -32,7 +32,7 @@ test('autorun', async () => {
3232

3333
// 1. simulating a user selecting some input
3434
for (const input of step.inputs) {
35-
if (isUserSelection(input)) {
35+
if (input.options) {
3636
state.data.push({
3737
id: input.id,
3838
value: input.options[Math.floor(Math.random() * input.options.length)]

backend/src/scenarios/pool.ts

Lines changed: 98 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,14 @@ import objectHash from "object-hash";
22

33

44
export type Task = () => Promise<any>;
5+
interface PoolEntry {
6+
key: string,
7+
data?: any,
8+
startedTime: Date,
9+
completedTime?: Date,
10+
failedTime?: Date,
11+
error?: any
12+
}
513

614

715
/**
@@ -11,44 +19,118 @@ export type Task = () => Promise<any>;
1119
*/
1220
export class ProcessPool {
1321

14-
private ongoing: string[] = [];
15-
private completed: {[hash: string]: any} = {};
16-
private failed: {[hash: string]: any} = {};
22+
private entries: PoolEntry[] = [];
1723

1824
public scheduleTask(key: string, task: Task): { ticket: string } {
19-
if (this.ongoing.includes(key)) return { ticket: key };
20-
this.ongoing.push(key);
25+
if (this.getOngoing().find(e => e.key === key)) return { ticket: key };
26+
this.addNewEntry(key);
2127
task().then(results => {
22-
this.ongoing = this.ongoing.filter(h => h !== key);
23-
this.completed[key] = results;
28+
this.setCompleted(key, results);
2429
}).catch(error => {
2530
console.error(`An error occured while trying to execute task ${key}: `);
2631
console.error(error);
27-
this.ongoing = this.ongoing.filter(h => h !== key);
28-
this.failed[key] = error;
32+
this.setFailed(key, error);
2933
});
3034
return { ticket: key };
3135
}
3236

3337
poll(ticket: string): { error: string } | { ticket: string } | { results: any } {
34-
const error = this.failed[ticket];
38+
this.cleanOlderThan(24*60*60, 3*24*60*60);
39+
40+
const entry = this.getEntry(ticket);
41+
42+
const error = entry.error;
3543
if (error) {
36-
delete this.failed[ticket];
3744
return {
3845
// Error objects are not properly stringified by default.
3946
error: JSON.stringify(error, Object.getOwnPropertyNames(error))
4047
};
4148
}
4249

43-
const data = this.completed[ticket];
50+
const data = entry.data;
4451
if (data) {
45-
delete this.completed[ticket];
4652
return { results: data };
4753
}
48-
if (!this.ongoing.includes(ticket)) {
49-
return { error: `No such ticket: ${ticket}` };
50-
}
54+
5155
return { ticket: ticket };
5256
}
57+
58+
public cleanOlderThan(maxAgeSeconds: number, abandonedAgeSeconds?: number) {
59+
const currentTime = new Date().getTime();
60+
for (const entry of this.entries) {
61+
62+
if (entry.completedTime) {
63+
const deltaSecs = (currentTime - entry.completedTime.getTime()) / 1000;
64+
if (deltaSecs > maxAgeSeconds) {
65+
console.log(`Cleaning entry: ${entry.key} because completed ${deltaSecs} seconds ago.`);
66+
this.removeEntry(entry.key);
67+
}
68+
}
69+
70+
if (entry.failedTime) {
71+
const deltaSecs = (currentTime - entry.failedTime.getTime()) / 1000;
72+
if (deltaSecs > maxAgeSeconds) {
73+
console.log(`Cleaning entry: ${entry.key} because failed ${deltaSecs} seconds ago.`);
74+
this.removeEntry(entry.key);
75+
}
76+
}
77+
78+
// Additionally, removing entries that have been started but never finished in, say, a few days?
79+
if (abandonedAgeSeconds) {
80+
if (!entry.completedTime && !entry.failedTime) {
81+
const deltaSecs = (currentTime - entry.startedTime.getTime()) / 1000;
82+
if (deltaSecs > abandonedAgeSeconds) {
83+
console.log(`Cleaning entry: ${entry.key} because unfinished since ${deltaSecs} seconds.`);
84+
this.removeEntry(entry.key);
85+
}
86+
}
87+
}
88+
}
89+
}
90+
91+
private setCompleted(key: string, data: any): PoolEntry {
92+
const entry = this.getEntry(key);
93+
entry.completedTime = new Date();
94+
entry.data = data;
95+
return entry;
96+
}
97+
98+
private setFailed(key: string, error: any): PoolEntry {
99+
const entry = this.getEntry(key);
100+
entry.error = error;
101+
entry.failedTime = new Date();
102+
return entry;
103+
}
104+
105+
private addNewEntry(key: string): PoolEntry {
106+
const entry: PoolEntry = {
107+
key: key,
108+
startedTime: new Date(),
109+
};
110+
this.entries.push(entry);
111+
return entry;
112+
}
113+
114+
private removeEntry(key: string) {
115+
this.entries = this.entries.filter(e => e.key !== key);
116+
}
117+
118+
private getEntry(key: string): PoolEntry {
119+
const entry = this.entries.find(e => e.key === key);
120+
if (!entry) throw new Error(`No such entry in pool: ${key}`);
121+
return entry;
122+
}
123+
124+
private getOngoing(): PoolEntry[] {
125+
return this.entries.filter(e => !e.completedTime && !e.error);
126+
}
127+
128+
private getFailed(): PoolEntry[] {
129+
return this.entries.filter(e => e.error);
130+
}
131+
132+
private getCompleted(): PoolEntry[] {
133+
return this.entries.filter(e => e.completedTime);
134+
}
53135
}
54136

backend/src/scenarios/scenario.interface.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,20 @@ import { ProcessPool } from './pool';
44
import { DatumLinage, Scenario, ScenarioFactory, ScenarioState } from './scenarios';
55
import { Logger } from '../logging/logger';
66
import { FileStorage } from '../storage/fileStorage';
7+
import { config } from '../config';
78

89

910
export function addScenarioApi(app: Express, scenarioFactories: ScenarioFactory[], storeDir: string, loggingDir: string, verbosity: 'verbose' | 'silent' = 'verbose', sendMailOnError = true) {
1011
app.use(express.json({
1112
limit: '50mb' // required because exposure objects can become pretty big
1213
}));
14+
app.use((req, res, next) => {
15+
res.setHeader('Expires', new Date(Date.now() + 1 * 60 * 1000).toUTCString());
16+
next();
17+
});
1318

1419
const pool = new ProcessPool();
15-
const fs = new FileStorage<DatumLinage>(storeDir);
20+
const fs = new FileStorage<DatumLinage>(storeDir, config.maxStoreLifeTimeMinutes * 60);
1621
const scenarios = scenarioFactories.map(sf => sf.createScenario(fs));
1722
const logger = new Logger(loggingDir, verbosity, undefined, sendMailOnError);
1823
logger.monkeyPatch();
@@ -37,15 +42,18 @@ export function addScenarioApi(app: Express, scenarioFactories: ScenarioFactory[
3742
if (!scenario) return [];
3843
const stepId = req.params.stepId;
3944
const state: ScenarioState = req.body;
45+
const skipCache = req.query.skipCache === 'true' || req.query.skipCache === 'True' || req.query.skipCache === 'TRUE' ? true : false;
4046
const key = objectHash({scenarioId, stepId, state});
41-
pool.scheduleTask(key, async () => await scenario.execute(stepId, state));
47+
pool.scheduleTask(key, async () => await scenario.execute(stepId, state, skipCache));
4248
// send user a ticket for polling
49+
res.setHeader('Expires', new Date(Date.now() + 1 * 1000).toUTCString());
4350
res.send({ ticket: key });
4451
});
4552

4653
app.get('/scenarios/:scenarioId/steps/:stepId/execute/poll/:ticket', async (req, res) => {
4754
const key = req.params.ticket;
4855
const response = pool.poll(key);
56+
res.setHeader('Expires', new Date(Date.now() + 1 * 1000).toUTCString());
4957
res.send(response);
5058
});
5159

backend/src/scenarios/scenarios.errors.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,9 @@ fakeScenarioFactory.registerStep({
6161
})
6262

6363

64-
const sendMailOnError = true;
64+
const sendMailOnError = false;
6565
const http = axios.create();
66-
const port = 5001;
66+
const port = 5003;
6767
const logDir = './test-data/scenario-errors/logs';
6868
const storeDir = './test-data/scenario-errors/store';
6969
let app: Express;

0 commit comments

Comments
 (0)