lhcb-ntuple-wizard 2.0.0 → 2.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -4
- package/dist/App.js +1 -3
- package/dist/components/BookkeepingPathDropdown.d.ts +1 -0
- package/dist/components/BookkeepingPathDropdown.js +35 -0
- package/dist/components/DatasetInfo.d.ts +1 -1
- package/dist/components/DatasetInfo.js +9 -3
- package/dist/components/DttNameInput.js +13 -16
- package/dist/components/NtupleWizard.d.ts +2 -7
- package/dist/components/NtupleWizard.js +6 -11
- package/dist/components/ProductionNameInput.d.ts +1 -0
- package/dist/components/ProductionNameInput.js +7 -0
- package/dist/components/RequestButtonGroup.d.ts +7 -0
- package/dist/components/RequestButtonGroup.js +29 -0
- package/dist/components/RequestEmailInput.d.ts +1 -0
- package/dist/components/RequestEmailInput.js +7 -0
- package/dist/components/RequestRow.d.ts +7 -0
- package/dist/components/RequestRow.js +20 -0
- package/dist/components/StrippingLineBadge.js +2 -3
- package/dist/components/StrippingLineDropdown.d.ts +1 -0
- package/dist/components/StrippingLineDropdown.js +48 -0
- package/dist/components/modals/ReasonForRequestModal.d.ts +2 -3
- package/dist/components/modals/ReasonForRequestModal.js +3 -10
- package/dist/components/modals/UploadDttConfigModal.d.ts +2 -2
- package/dist/components/modals/UploadDttConfigModal.js +55 -25
- package/dist/constants.d.ts +2 -0
- package/dist/constants.js +2 -0
- package/dist/models/bkPath.d.ts +9 -9
- package/dist/models/bkPath.js +37 -30
- package/dist/models/dtt.d.ts +33 -3
- package/dist/models/dtt.js +62 -0
- package/dist/models/yamlFile.d.ts +0 -1
- package/dist/models/yamlFile.js +1 -24
- package/dist/pages/DecaySearchPage.js +1 -1
- package/dist/pages/DecayTreeConfigPage.js +2 -4
- package/dist/pages/RequestPage.d.ts +10 -0
- package/dist/pages/RequestPage.js +105 -0
- package/dist/providers/RequestProvider.d.ts +14 -1
- package/dist/providers/RequestProvider.js +48 -2
- package/dist/providers/RowProvider.d.ts +15 -0
- package/dist/providers/RowProvider.js +41 -0
- package/dist/providers/RowsProvider.d.ts +4 -1
- package/dist/providers/RowsProvider.js +9 -2
- package/dist/utils/utils.d.ts +15 -0
- package/dist/utils/utils.js +535 -0
- package/package.json +8 -4
- package/dist/components/LineTableRow.d.ts +0 -9
- package/dist/components/LineTableRow.js +0 -123
- package/dist/components/SubmitRequestButton.d.ts +0 -11
- package/dist/components/SubmitRequestButton.js +0 -39
- package/dist/components/modals/UploadProdConfigModal.d.ts +0 -5
- package/dist/components/modals/UploadProdConfigModal.js +0 -27
- package/dist/models/strippingLineOption.d.ts +0 -6
- package/dist/models/strippingLineOption.js +0 -1
- package/dist/pages/LinesTablePage.d.ts +0 -14
- package/dist/pages/LinesTablePage.js +0 -120
- package/dist/providers/ProductionConfigProvider.d.ts +0 -14
- package/dist/providers/ProductionConfigProvider.js +0 -76
package/dist/models/bkPath.js
CHANGED
|
@@ -1,40 +1,47 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
const LHCB_PATH_LENGTH = 9;
|
|
2
|
+
const MC_PATH_LENGTH = 10;
|
|
3
|
+
export class BkPath {
|
|
4
|
+
year;
|
|
5
|
+
strippingVersion;
|
|
6
|
+
polarity;
|
|
7
|
+
eventType;
|
|
8
|
+
fileName;
|
|
4
9
|
constructor(path) {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
return matchingPolarities.length === 1 ? matchingPolarities[0] : null;
|
|
10
|
+
if (!BkPath.isValid(path)) {
|
|
11
|
+
throw new Error(`Invalid bookkeeping path: ${path}`);
|
|
12
|
+
}
|
|
13
|
+
const parts = path.split("/");
|
|
14
|
+
const [, type, year] = parts;
|
|
15
|
+
const { strippingVersion, eventType, fileName } = type === "MC" ? BkPath.extractMcParts(parts) : BkPath.extractLhcbParts(parts);
|
|
16
|
+
this.year = year.replace("Collision", "20"); // e.g. Collision16 -> 2016
|
|
17
|
+
this.strippingVersion = strippingVersion.replace("Stripping", "").replace("NoPrescalingFlagged", "");
|
|
18
|
+
this.polarity = path.includes("MagUp") ? "MagUp" : path.includes("MagDown") ? "MagDown" : null;
|
|
19
|
+
this.eventType = eventType;
|
|
20
|
+
this.fileName = fileName;
|
|
17
21
|
}
|
|
18
|
-
|
|
19
|
-
return
|
|
22
|
+
static extractMcParts(parts) {
|
|
23
|
+
return {
|
|
24
|
+
strippingVersion: parts[7],
|
|
25
|
+
eventType: parts[8],
|
|
26
|
+
fileName: parts[9],
|
|
27
|
+
};
|
|
20
28
|
}
|
|
21
|
-
|
|
22
|
-
return
|
|
29
|
+
static extractLhcbParts(parts) {
|
|
30
|
+
return {
|
|
31
|
+
strippingVersion: parts[6],
|
|
32
|
+
eventType: parts[7],
|
|
33
|
+
fileName: parts[8],
|
|
34
|
+
};
|
|
23
35
|
}
|
|
24
|
-
isValid() {
|
|
25
|
-
|
|
26
|
-
if (bkArray[0] !== "") {
|
|
36
|
+
static isValid(path) {
|
|
37
|
+
if (!path.startsWith("/")) {
|
|
27
38
|
return false;
|
|
28
39
|
}
|
|
29
|
-
if (!
|
|
40
|
+
if (!path.toLowerCase().endsWith("dst")) {
|
|
30
41
|
return false;
|
|
31
42
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
else if (bkArray.length === 10 && bkArray[1] === "MC") {
|
|
36
|
-
return true;
|
|
37
|
-
}
|
|
38
|
-
return false;
|
|
43
|
+
const parts = path.split("/");
|
|
44
|
+
const [, type] = parts;
|
|
45
|
+
return ((parts.length === LHCB_PATH_LENGTH && type === "LHCb") || (parts.length === MC_PATH_LENGTH && type === "MC"));
|
|
39
46
|
}
|
|
40
47
|
}
|
package/dist/models/dtt.d.ts
CHANGED
|
@@ -51,31 +51,56 @@ export type AnyToolParam = {
|
|
|
51
51
|
export interface ToolConfig {
|
|
52
52
|
[toolParamName: string]: AnyToolParam;
|
|
53
53
|
}
|
|
54
|
+
interface SavedToolConfig {
|
|
55
|
+
[toolName: string]: {
|
|
56
|
+
[paramName: string]: ToolParamTypeMap[keyof ToolParamTypeMap];
|
|
57
|
+
};
|
|
58
|
+
}
|
|
54
59
|
export interface BranchConfig {
|
|
55
60
|
particle: string;
|
|
56
61
|
tools: {
|
|
57
62
|
[toolString: string]: ToolConfig;
|
|
58
63
|
};
|
|
59
64
|
}
|
|
65
|
+
interface SavedBranchConfig {
|
|
66
|
+
particle: string;
|
|
67
|
+
tools: SavedToolConfig[];
|
|
68
|
+
}
|
|
60
69
|
export interface GroupConfig {
|
|
61
70
|
particles: string[];
|
|
62
71
|
tools: {
|
|
63
72
|
[toolString: string]: ToolConfig;
|
|
64
73
|
};
|
|
65
74
|
}
|
|
75
|
+
interface SavedGroupConfig {
|
|
76
|
+
particles: string[];
|
|
77
|
+
tools: SavedToolConfig[];
|
|
78
|
+
}
|
|
66
79
|
export interface DttConfig {
|
|
67
|
-
|
|
80
|
+
branches: {
|
|
81
|
+
[particleId: string]: BranchConfig;
|
|
82
|
+
};
|
|
68
83
|
descriptorTemplate?: string;
|
|
84
|
+
inputs?: string[];
|
|
85
|
+
groups: {
|
|
86
|
+
[particleGroup: string]: GroupConfig;
|
|
87
|
+
};
|
|
88
|
+
name?: string;
|
|
69
89
|
tools: {
|
|
70
90
|
[toolName: string]: ToolConfig;
|
|
71
91
|
};
|
|
92
|
+
}
|
|
93
|
+
export interface SavedDttConfig {
|
|
72
94
|
branches: {
|
|
73
|
-
[particleId: string]:
|
|
95
|
+
[particleId: string]: SavedBranchConfig;
|
|
74
96
|
};
|
|
97
|
+
descriptorTemplate?: string;
|
|
75
98
|
groups: {
|
|
76
|
-
[particleGroup: string]:
|
|
99
|
+
[particleGroup: string]: SavedGroupConfig;
|
|
77
100
|
};
|
|
101
|
+
inputs?: string[];
|
|
78
102
|
name?: string;
|
|
103
|
+
tools: SavedToolConfig[];
|
|
79
104
|
}
|
|
80
105
|
export interface Branch {
|
|
81
106
|
branch: string;
|
|
@@ -86,6 +111,10 @@ export default class Dtt {
|
|
|
86
111
|
config: DttConfig;
|
|
87
112
|
metadata: ToolMetadata;
|
|
88
113
|
constructor(config: DttConfig, toolMetadata: ToolMetadata);
|
|
114
|
+
private static formatToolsForSaving;
|
|
115
|
+
private static formatToolsForLoading;
|
|
116
|
+
static fromSavedConfig(savedConfig: SavedDttConfig, toolMetadata: ToolMetadata): Dtt;
|
|
117
|
+
toSavedConfig(): SavedDttConfig;
|
|
89
118
|
private static getBranchConfigs;
|
|
90
119
|
static create(descriptorTemplate: string, branchItemsList: BranchMapItem[], inputs: string[], name: string, toolMetadata: ToolMetadata): Dtt;
|
|
91
120
|
withInputsFromLines(lines: StrippingLine[]): Dtt;
|
|
@@ -106,3 +135,4 @@ export default class Dtt {
|
|
|
106
135
|
listTools(branch?: string[]): TupleTool[];
|
|
107
136
|
toolExists(tool: TupleTool, branch?: string[]): boolean;
|
|
108
137
|
}
|
|
138
|
+
export {};
|
package/dist/models/dtt.js
CHANGED
|
@@ -9,6 +9,68 @@ export default class Dtt {
|
|
|
9
9
|
this.config = config;
|
|
10
10
|
this.metadata = toolMetadata;
|
|
11
11
|
}
|
|
12
|
+
static formatToolsForSaving(tools) {
|
|
13
|
+
return Object.entries(tools).map(([toolName, toolConfig]) => ({
|
|
14
|
+
[toolName]: Object.fromEntries(Object.entries(toolConfig).map(([paramName, param]) => [paramName, param.value])),
|
|
15
|
+
}));
|
|
16
|
+
}
|
|
17
|
+
static formatToolsForLoading(tools, toolMetadata) {
|
|
18
|
+
return Object.fromEntries(tools.map((toolConfig) => {
|
|
19
|
+
const toolName = Object.keys(toolConfig)[0];
|
|
20
|
+
const params = toolConfig[toolName];
|
|
21
|
+
return [
|
|
22
|
+
toolName,
|
|
23
|
+
Object.fromEntries(Object.entries(params).map(([paramName, value]) => {
|
|
24
|
+
const paramMetadata = toolMetadata[toolName].interface.find((param) => param.name === paramName);
|
|
25
|
+
if (!paramMetadata) {
|
|
26
|
+
throw new Error(`No metadata for param ${paramName} of tool ${toolName}`);
|
|
27
|
+
}
|
|
28
|
+
return [paramName, { value, ...paramMetadata }];
|
|
29
|
+
})),
|
|
30
|
+
];
|
|
31
|
+
}));
|
|
32
|
+
}
|
|
33
|
+
static fromSavedConfig(savedConfig, toolMetadata) {
|
|
34
|
+
const config = {
|
|
35
|
+
...savedConfig,
|
|
36
|
+
tools: Dtt.formatToolsForLoading(savedConfig.tools, toolMetadata),
|
|
37
|
+
branches: Object.fromEntries(Object.entries(savedConfig.branches).map(([branchId, branchConfig]) => [
|
|
38
|
+
branchId,
|
|
39
|
+
{
|
|
40
|
+
...branchConfig,
|
|
41
|
+
tools: Dtt.formatToolsForLoading(branchConfig.tools, toolMetadata),
|
|
42
|
+
},
|
|
43
|
+
])),
|
|
44
|
+
groups: Object.fromEntries(Object.entries(savedConfig.groups).map(([groupId, groupConfig]) => [
|
|
45
|
+
groupId,
|
|
46
|
+
{
|
|
47
|
+
...groupConfig,
|
|
48
|
+
tools: Dtt.formatToolsForLoading(groupConfig.tools, toolMetadata),
|
|
49
|
+
},
|
|
50
|
+
])),
|
|
51
|
+
};
|
|
52
|
+
return new Dtt(config, {});
|
|
53
|
+
}
|
|
54
|
+
toSavedConfig() {
|
|
55
|
+
return {
|
|
56
|
+
...this.config,
|
|
57
|
+
tools: Dtt.formatToolsForSaving(this.config.tools),
|
|
58
|
+
branches: Object.fromEntries(Object.entries(this.config.branches).map(([branchId, branchConfig]) => [
|
|
59
|
+
branchId,
|
|
60
|
+
{
|
|
61
|
+
...branchConfig,
|
|
62
|
+
tools: Dtt.formatToolsForSaving(branchConfig.tools),
|
|
63
|
+
},
|
|
64
|
+
])),
|
|
65
|
+
groups: Object.fromEntries(Object.entries(this.config.groups).map(([groupId, groupConfig]) => [
|
|
66
|
+
groupId,
|
|
67
|
+
{
|
|
68
|
+
...groupConfig,
|
|
69
|
+
tools: Dtt.formatToolsForSaving(groupConfig.tools),
|
|
70
|
+
},
|
|
71
|
+
])),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
12
74
|
static getBranchConfigs(branchItemsList) {
|
|
13
75
|
const branches = {};
|
|
14
76
|
const walk = (item, parentId) => {
|
package/dist/models/yamlFile.js
CHANGED
|
@@ -8,30 +8,7 @@ export class YamlFile {
|
|
|
8
8
|
this.content = content;
|
|
9
9
|
}
|
|
10
10
|
static fromDtt(dtt) {
|
|
11
|
-
|
|
12
|
-
...dtt.config,
|
|
13
|
-
tools: YamlFile.reformatTools(dtt.config.tools),
|
|
14
|
-
branches: Object.fromEntries(Object.entries(dtt.config.branches).map(([branchId, branchConfig]) => [
|
|
15
|
-
branchId,
|
|
16
|
-
{
|
|
17
|
-
...branchConfig,
|
|
18
|
-
tools: YamlFile.reformatTools(branchConfig.tools),
|
|
19
|
-
},
|
|
20
|
-
])),
|
|
21
|
-
groups: Object.fromEntries(Object.entries(dtt.config.groups).map(([groupId, groupConfig]) => [
|
|
22
|
-
groupId,
|
|
23
|
-
{
|
|
24
|
-
...groupConfig,
|
|
25
|
-
tools: YamlFile.reformatTools(groupConfig.tools),
|
|
26
|
-
},
|
|
27
|
-
])),
|
|
28
|
-
};
|
|
29
|
-
return new YamlFile(`${dtt.getName()}.yaml`, yaml.dump(yamlContent));
|
|
30
|
-
}
|
|
31
|
-
static reformatTools(tools) {
|
|
32
|
-
return Object.entries(tools).map(([toolName, toolConfig]) => ({
|
|
33
|
-
[toolName]: Object.fromEntries(Object.entries(toolConfig).map(([paramName, param]) => [paramName, param.value])),
|
|
34
|
-
}));
|
|
11
|
+
return new YamlFile(`${dtt.getName()}.yaml`, yaml.dump(dtt.toSavedConfig()));
|
|
35
12
|
}
|
|
36
13
|
static createInfoYaml(rows, metadata) {
|
|
37
14
|
const uniquePathSet = new Set(rows.flatMap((row) => row.paths));
|
|
@@ -191,7 +191,7 @@ export function DecaySearchPage({ basePath }) {
|
|
|
191
191
|
? allStrippingLines().map((line) => ({ value: line, label: line }))
|
|
192
192
|
: [], onChange: setSelectedLine, isLoading: !metadata }) })] }) }));
|
|
193
193
|
};
|
|
194
|
-
return (_jsxs(_Fragment, { children: [_jsx("h3", { children: "
|
|
194
|
+
return (_jsxs(_Fragment, { children: [_jsx("h3", { children: "Decay search" }), _jsxs(InputGroup, { size: "sm", children: [_jsxs(DropdownButton, { title: "Head (" +
|
|
195
195
|
(headMatchType === HeadMatchType.exactly ? headMatchType : headTagMatchType + " of") +
|
|
196
196
|
"):", variant: "outline-secondary", children: [_jsx(DropdownMatchTypes, { options: Object.values(HeadMatchType), stateItem: headMatchType, stateSetter: setHeadMatchType }), _jsx(Dropdown.Divider, {}), headMatchType === HeadMatchType.exactly ? (_jsx(DropdownChargeConjugate, { stateItem: chargeConjugateHeads, stateSetter: setChargeConjugateHeads })) : (_jsx(DropdownMatchTypes, { options: Object.values(MatchType), stateItem: headTagMatchType, stateSetter: setHeadTagMatchType }))] }), headMatchType === HeadMatchType.exactly ? (_jsx(ParticleDropdown, { isClearable: true, onlyHeads: true, placeholder: "DecayLatex head", onChange: (value) => setSelectedHead(value) })) : (_jsx(TagDropdown, { type: "particleTags", placeholder: "DecayLatex head", filter: (tag) => !tag.hide, onChange: (values) => setSelectedHeadTags([...values]) })), _jsxs(DropdownButton, { title: "Contains (" + containsMatchType + " of):", variant: "outline-secondary", children: [_jsx(DropdownMatchTypes, { options: Object.values(MatchType), stateItem: containsMatchType, stateSetter: setContainsMatchType }), _jsx(Dropdown.Divider, {}), _jsx(DropdownChargeConjugate, { stateItem: chargeConjugateContains, stateSetter: setChargeConjugateContains })] }), _jsx(ParticleDropdown, { isMulti: true, onlyKnown: true, placeholder: "Particles in decay...", onChange: (values) => setSelectedContains(values), closeMenuOnSelect: false }), _jsx(InputGroup.Text, { children: "Show only selected:" }), _jsx(InputGroup.Checkbox, { onChange: () => setOnlySelected(!onlySelected) })] }), getExtraInputs(), getDecayList(), _jsx(Link, { to: basePath, children: _jsx(OverlayTrigger, { overlay: _jsx(Tooltip, { children: "Return to production configuration" }), children: _jsxs(Button, { variant: "success", type: "submit", onClick: sendData, children: ["Select ", _jsx(Badge, { pill: true, bg: "secondary", children: selectedDecays.length })] }) }) })] }));
|
|
197
197
|
}
|
|
@@ -21,7 +21,7 @@ import { useEffect, useState } from "react";
|
|
|
21
21
|
export function DecayTreeConfigPage({ basePath }) {
|
|
22
22
|
const navigate = useNavigate();
|
|
23
23
|
const metadata = useMetadata();
|
|
24
|
-
const { rows,
|
|
24
|
+
const { rows, updateRow } = useRows();
|
|
25
25
|
const [dirtyDtts, setDirtyDtts] = useState(new Set());
|
|
26
26
|
// Listen for when the user is about to leave the page and alert them if there are unsaved changes
|
|
27
27
|
useEffect(() => {
|
|
@@ -40,9 +40,7 @@ export function DecayTreeConfigPage({ basePath }) {
|
|
|
40
40
|
}
|
|
41
41
|
const rowsToEdit = rows.filter((row) => !!row.editTree);
|
|
42
42
|
return (_jsxs(_Fragment, { children: [_jsx("h3", { children: "DecayTreeTuple configuration" }), _jsx(Stack, { gap: 5, children: rowsToEdit.map((row) => (_jsx(Row, { className: "justify-content-lg-center", children: _jsx(Col, { lg: "auto", children: _jsx(DttProvider, { metadata: metadata, decay: row.decay, initialConfig: row.dtt.config, children: _jsx(DecayTreeCard, { onConfigSaved: (newConfig) => {
|
|
43
|
-
|
|
44
|
-
return prev.map((r) => r.id === row.id ? { ...r, dtt: new Dtt(newConfig, {}) } : r);
|
|
45
|
-
});
|
|
43
|
+
updateRow(row.id, (r) => ({ ...r, dtt: new Dtt(newConfig, {}) }));
|
|
46
44
|
}, onDirtyUpdated: (dttName, dirty) => setDirtyDtts((prev) => {
|
|
47
45
|
if (dirty) {
|
|
48
46
|
return new Set(prev.add(dttName));
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { ReactNode } from "react";
|
|
2
|
+
export type NtupleWizardVariant = "standalone" | "embedded";
|
|
3
|
+
interface Props {
|
|
4
|
+
basePath: string;
|
|
5
|
+
submitLocation?: string;
|
|
6
|
+
requestReasonMessage?: string;
|
|
7
|
+
requestSubmittedMessage?: ReactNode;
|
|
8
|
+
}
|
|
9
|
+
export declare function RequestPage({ basePath, submitLocation, requestReasonMessage, requestSubmittedMessage }: Props): import("react/jsx-runtime").JSX.Element;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
/*****************************************************************************\
|
|
3
|
+
* (c) Copyright 2021-2024 CERN for the benefit of the LHCb Collaboration *
|
|
4
|
+
* *
|
|
5
|
+
* This software is distributed under the terms of the GNU General Public *
|
|
6
|
+
* Licence version 3 (GPL Version 3), copied verbatim in the file "COPYING". *
|
|
7
|
+
* *
|
|
8
|
+
* In applying this licence, CERN does not waive the privileges and immunities *
|
|
9
|
+
* granted to it by virtue of its status as an Intergovernmental Organization *
|
|
10
|
+
* or submit itself to any jurisdiction. *
|
|
11
|
+
\*****************************************************************************/
|
|
12
|
+
import yaml from "js-yaml";
|
|
13
|
+
import { useEffect, useState } from "react";
|
|
14
|
+
import { Alert, Button, Col, Row, Stack } from "react-bootstrap";
|
|
15
|
+
import { Download, Send } from "react-bootstrap-icons";
|
|
16
|
+
import { useLocation } from "react-router-dom";
|
|
17
|
+
import { downloadZip, parseProductionFiles } from "../utils/utils";
|
|
18
|
+
import { DeleteButton } from "../components/DeleteButton";
|
|
19
|
+
import { useMetadata } from "../providers/MetadataProvider";
|
|
20
|
+
import { ConfigFilesUploadingAlert } from "../components/ConfigFilesUploadingAlert";
|
|
21
|
+
import { useRows } from "../providers/RowsProvider";
|
|
22
|
+
import { useRequest } from "../providers/RequestProvider";
|
|
23
|
+
import { LoadingIndicator } from "../components/LoadingIndicator";
|
|
24
|
+
import { RequestRow } from "../components/RequestRow";
|
|
25
|
+
import { UploadDttConfigModal } from "../components/modals/UploadDttConfigModal";
|
|
26
|
+
import { VARIABLES_PATH } from "../constants";
|
|
27
|
+
import { ReasonForRequestModal } from "../components/modals/ReasonForRequestModal";
|
|
28
|
+
import { RowProvider } from "../providers/RowProvider";
|
|
29
|
+
import { ProductionNameInput } from "../components/ProductionNameInput";
|
|
30
|
+
import { RequestEmailInput } from "../components/RequestEmailInput";
|
|
31
|
+
import { RequestButtonGroup } from "../components/RequestButtonGroup";
|
|
32
|
+
export function RequestPage({ basePath, submitLocation, requestReasonMessage, requestSubmittedMessage }) {
|
|
33
|
+
const location = useLocation();
|
|
34
|
+
const metadata = useMetadata();
|
|
35
|
+
const { rows, setRows, generateAllFiles } = useRows();
|
|
36
|
+
const { emailIsKnown, productionName, validation, showErrors, clearAll, trySubmit } = useRequest();
|
|
37
|
+
const [showProdUploadModal, setShowProdUploadModal] = useState(false);
|
|
38
|
+
const [prodUploadLoading, setProdUploadLoading] = useState(false);
|
|
39
|
+
const [requestSubmitted, setRequestSubmitted] = useState(false);
|
|
40
|
+
const [showReasonForRequestModal, setShowReasonForRequestModal] = useState(false);
|
|
41
|
+
const variant = submitLocation ? "embedded" : "standalone";
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
if (!metadata) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const urlParams = new URLSearchParams(location.search);
|
|
47
|
+
if (urlParams.get("clone") === "1") {
|
|
48
|
+
handleClone().catch(console.error);
|
|
49
|
+
}
|
|
50
|
+
}, [metadata, location.search]);
|
|
51
|
+
async function handleClone() {
|
|
52
|
+
if (!metadata) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const raw = localStorage.getItem("yamlFilesToClone");
|
|
56
|
+
if (!raw) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
setProdUploadLoading(true);
|
|
60
|
+
setRows([]);
|
|
61
|
+
try {
|
|
62
|
+
const yamlFiles = JSON.parse(raw);
|
|
63
|
+
for (const file of yamlFiles) {
|
|
64
|
+
if (file.defaults)
|
|
65
|
+
file.name = "info.yaml";
|
|
66
|
+
file.blob = new Blob([yaml.dump(file)]);
|
|
67
|
+
}
|
|
68
|
+
const { rows } = await parseProductionFiles(yamlFiles, metadata);
|
|
69
|
+
setRows(rows);
|
|
70
|
+
}
|
|
71
|
+
finally {
|
|
72
|
+
setProdUploadLoading(false);
|
|
73
|
+
localStorage.removeItem("yamlFilesToClone");
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
const handlePrimaryAction = async () => {
|
|
77
|
+
if (trySubmit()) {
|
|
78
|
+
if (variant === "standalone") {
|
|
79
|
+
const files = generateAllFiles();
|
|
80
|
+
await downloadZip(files, `${productionName}.zip`);
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
setShowReasonForRequestModal(true);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
if (!metadata) {
|
|
88
|
+
return _jsx(LoadingIndicator, { height: "70vh" });
|
|
89
|
+
}
|
|
90
|
+
if (requestSubmitted) {
|
|
91
|
+
return (_jsx(Row, { className: "mt-3", children: _jsx(Col, { children: _jsxs(Alert, { variant: "success", dismissible: true, onClose: () => setRequestSubmitted(false), children: [_jsx(Alert.Heading, { children: "Request submitted!" }), requestSubmittedMessage] }) }) }));
|
|
92
|
+
}
|
|
93
|
+
return (_jsxs(_Fragment, { children: [showReasonForRequestModal && (_jsx(ReasonForRequestModal, { submitLocation: submitLocation, requestReasonMessage: requestReasonMessage, onClose: (submitted) => {
|
|
94
|
+
setShowReasonForRequestModal(false);
|
|
95
|
+
if (submitted) {
|
|
96
|
+
setRequestSubmitted(true);
|
|
97
|
+
}
|
|
98
|
+
} })), showProdUploadModal && _jsx(UploadDttConfigModal, { onClose: () => setShowProdUploadModal(false) }), _jsxs("div", { className: "d-flex flex-column gap-3", children: [rows.map((row) => (_jsx(RowProvider, { row: row, children: _jsx(RequestRow, { hideDownloadButtons: variant === "standalone", hideUploadButtons: variant === "standalone", variablesPath: VARIABLES_PATH }, row.id) }, row.id))), prodUploadLoading && _jsx(ConfigFilesUploadingAlert, {}), requestSubmitted && (_jsxs(Alert, { variant: "success", dismissible: true, onClose: () => setRequestSubmitted(false), children: [_jsx(Alert.Heading, { children: "Request submitted!" }), requestSubmittedMessage] })), showErrors &&
|
|
99
|
+
(rows.length === 0 ||
|
|
100
|
+
!validation.allRowsHaveDtt ||
|
|
101
|
+
!validation.allRowsHaveStrippingLine ||
|
|
102
|
+
!validation.allRowsHavePaths) && (_jsx(Alert, { variant: "danger", className: "mb-0", children: rows.length === 0
|
|
103
|
+
? "Please select at least one decay"
|
|
104
|
+
: "Please name all DecayTreeTuples and select at least one stripping line and bookkeeping path for each decay" })), _jsx(Row, { children: _jsx(Col, { xs: "auto", children: _jsx(RequestButtonGroup, { variant: variant, basePath: basePath }) }) }), _jsx(Row, { children: _jsxs(Col, { xs: 4, children: [_jsx(ProductionNameInput, {}), !emailIsKnown && _jsx(RequestEmailInput, {}), _jsxs(Stack, { direction: "horizontal", gap: 1, className: "mt-3 mb-3", children: [_jsxs(Button, { className: "align-items-center d-flex gap-1", onClick: () => void handlePrimaryAction(), children: [variant === "standalone" ? _jsx(Download, {}) : _jsx(Send, {}), variant === "standalone" ? "Download" : "Submit"] }), _jsx(DeleteButton, { action: clearAll, disabled: validation.isEmptySession, outline: undefined, children: "Clear all" })] })] }) })] })] }));
|
|
105
|
+
}
|
|
@@ -1,15 +1,28 @@
|
|
|
1
1
|
import { ReactNode } from "react";
|
|
2
2
|
interface RequestContextType {
|
|
3
|
+
emailIsKnown: boolean;
|
|
3
4
|
productionName: string;
|
|
4
5
|
setProductionName: (name: string) => void;
|
|
5
6
|
contactEmails: string[];
|
|
6
7
|
setContactEmails: (emails: string[]) => void;
|
|
7
8
|
reasonForRequest: string;
|
|
8
9
|
setReasonForRequest: (reason: string) => void;
|
|
10
|
+
validation: {
|
|
11
|
+
isEmptySession: boolean;
|
|
12
|
+
isEmailValid: boolean;
|
|
13
|
+
allRowsHaveDtt: boolean;
|
|
14
|
+
allRowsHaveStrippingLine: boolean;
|
|
15
|
+
allRowsHavePaths: boolean;
|
|
16
|
+
validDttNames: boolean;
|
|
17
|
+
};
|
|
18
|
+
showErrors: boolean;
|
|
19
|
+
clearAll: () => void;
|
|
20
|
+
trySubmit: () => boolean;
|
|
9
21
|
}
|
|
10
22
|
interface RequestProviderProps {
|
|
23
|
+
emailIsKnown: boolean;
|
|
11
24
|
children: ReactNode;
|
|
12
25
|
}
|
|
13
|
-
export declare const RequestProvider: ({ children }: RequestProviderProps) => import("react/jsx-runtime").JSX.Element;
|
|
26
|
+
export declare const RequestProvider: ({ emailIsKnown, children }: RequestProviderProps) => import("react/jsx-runtime").JSX.Element;
|
|
14
27
|
export declare const useRequest: () => RequestContextType;
|
|
15
28
|
export {};
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { createContext, useContext, useState } from "react";
|
|
2
|
+
import { createContext, useContext, useMemo, useState } from "react";
|
|
3
|
+
import EmailValidator from "email-validator";
|
|
4
|
+
import { useRows } from "./RowsProvider";
|
|
3
5
|
const RequestContext = createContext(null);
|
|
4
|
-
export const RequestProvider = ({ children }) => {
|
|
6
|
+
export const RequestProvider = ({ emailIsKnown, children }) => {
|
|
7
|
+
const { rows, configuredRows, setRows } = useRows();
|
|
5
8
|
const [productionName, setProductionName] = useState(localStorage.getItem("name") || "");
|
|
6
9
|
const [contactEmails, setContactEmails] = useState((localStorage.getItem("email") || "").split(/,/).filter((s) => s));
|
|
7
10
|
const [reasonForRequest, setReasonForRequest] = useState(localStorage.getItem("reasonForRequest") || "");
|
|
11
|
+
const [hasTriedSubmit, setHasTriedSubmit] = useState(false);
|
|
8
12
|
const updateProductionName = (newProductionName) => {
|
|
9
13
|
setProductionName(newProductionName);
|
|
10
14
|
localStorage.setItem("name", newProductionName);
|
|
@@ -17,13 +21,55 @@ export const RequestProvider = ({ children }) => {
|
|
|
17
21
|
setReasonForRequest(newReasonForRequest);
|
|
18
22
|
localStorage.setItem("reasonForRequest", newReasonForRequest);
|
|
19
23
|
};
|
|
24
|
+
const clearAll = () => {
|
|
25
|
+
setProductionName("");
|
|
26
|
+
setContactEmails([]);
|
|
27
|
+
setReasonForRequest("");
|
|
28
|
+
setRows([]);
|
|
29
|
+
setHasTriedSubmit(false);
|
|
30
|
+
};
|
|
31
|
+
const trySubmit = () => {
|
|
32
|
+
setHasTriedSubmit(true);
|
|
33
|
+
return canSubmit;
|
|
34
|
+
};
|
|
35
|
+
const validation = useMemo(() => {
|
|
36
|
+
const isEmptySession = !productionName && contactEmails.length === 0 && rows.length === 0;
|
|
37
|
+
const isEmailValid = contactEmails.length > 0 && contactEmails.every(EmailValidator.validate);
|
|
38
|
+
const allRowsHaveDtt = configuredRows.length > 0 && configuredRows.length === rows.length;
|
|
39
|
+
const allRowsHaveStrippingLine = rows.every((r) => r.lines.length > 0);
|
|
40
|
+
const allRowsHavePaths = rows.every((r) => r.paths.length > 0);
|
|
41
|
+
const dttNames = configuredRows.map((r) => r.dtt.getName().trim());
|
|
42
|
+
const validDttNames = dttNames.every(Boolean) && new Set(dttNames).size === dttNames.length;
|
|
43
|
+
return {
|
|
44
|
+
isEmptySession,
|
|
45
|
+
isEmailValid,
|
|
46
|
+
allRowsHaveDtt,
|
|
47
|
+
allRowsHaveStrippingLine,
|
|
48
|
+
allRowsHavePaths,
|
|
49
|
+
validDttNames,
|
|
50
|
+
};
|
|
51
|
+
}, [rows, configuredRows, productionName, contactEmails]);
|
|
52
|
+
const canSubmit = [
|
|
53
|
+
validation.allRowsHaveDtt,
|
|
54
|
+
validation.allRowsHaveStrippingLine,
|
|
55
|
+
validation.allRowsHavePaths,
|
|
56
|
+
Boolean(productionName),
|
|
57
|
+
emailIsKnown || validation.isEmailValid,
|
|
58
|
+
validation.validDttNames,
|
|
59
|
+
].every(Boolean);
|
|
60
|
+
const showErrors = hasTriedSubmit && !canSubmit;
|
|
20
61
|
return (_jsx(RequestContext.Provider, { value: {
|
|
62
|
+
emailIsKnown,
|
|
21
63
|
productionName,
|
|
22
64
|
setProductionName: updateProductionName,
|
|
23
65
|
contactEmails,
|
|
24
66
|
setContactEmails: updateContactEmails,
|
|
25
67
|
reasonForRequest,
|
|
26
68
|
setReasonForRequest: updateReasonForRequest,
|
|
69
|
+
validation,
|
|
70
|
+
showErrors,
|
|
71
|
+
clearAll,
|
|
72
|
+
trySubmit,
|
|
27
73
|
}, children: children }));
|
|
28
74
|
};
|
|
29
75
|
export const useRequest = () => {
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { ReactNode } from "react";
|
|
2
|
+
import { RowData } from "../models/rowData";
|
|
3
|
+
import { StrippingLine } from "../models/strippingLine";
|
|
4
|
+
interface RowContextType {
|
|
5
|
+
row: RowData;
|
|
6
|
+
validateBkPath: (choice: string, lines?: StrippingLine[]) => boolean;
|
|
7
|
+
updateBkPaths: (paths: string[], lines?: StrippingLine[]) => void;
|
|
8
|
+
}
|
|
9
|
+
interface RowProviderProps {
|
|
10
|
+
row: RowData;
|
|
11
|
+
children: ReactNode;
|
|
12
|
+
}
|
|
13
|
+
export declare const RowProvider: ({ row, children }: RowProviderProps) => import("react/jsx-runtime").JSX.Element;
|
|
14
|
+
export declare const useRow: () => RowContextType;
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { createContext, useCallback, useContext } from "react";
|
|
3
|
+
import { BkPath } from "../models/bkPath";
|
|
4
|
+
import { useRows } from "./RowsProvider";
|
|
5
|
+
const RowContext = createContext(null);
|
|
6
|
+
export const RowProvider = ({ row, children }) => {
|
|
7
|
+
const { updateRow } = useRows();
|
|
8
|
+
const validateBkPathsForLines = useCallback((choice, lines = row.lines) => {
|
|
9
|
+
try {
|
|
10
|
+
const path = new BkPath(choice);
|
|
11
|
+
return lines.every((line) => {
|
|
12
|
+
const streamName = line.stream.toLowerCase();
|
|
13
|
+
const fileName = path.fileName.split(".")[0].toLowerCase();
|
|
14
|
+
const matchingVersion = line.versions.some((v) => path.strippingVersion === v);
|
|
15
|
+
return ["allstreams", streamName].includes(fileName) && matchingVersion;
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
}, [row.lines]);
|
|
22
|
+
const updateBkPathsForLines = useCallback((paths, lines = row.lines) => {
|
|
23
|
+
updateRow(row.id, (r) => ({
|
|
24
|
+
...r,
|
|
25
|
+
paths: paths.filter((p) => validateBkPathsForLines(p, lines)),
|
|
26
|
+
pathOptions: r.pathOptions.filter((p) => validateBkPathsForLines(p, lines)),
|
|
27
|
+
}));
|
|
28
|
+
}, [row.id, validateBkPathsForLines, updateRow]);
|
|
29
|
+
return (_jsx(RowContext.Provider, { value: {
|
|
30
|
+
row,
|
|
31
|
+
validateBkPath: validateBkPathsForLines,
|
|
32
|
+
updateBkPaths: updateBkPathsForLines,
|
|
33
|
+
}, children: children }));
|
|
34
|
+
};
|
|
35
|
+
export const useRow = () => {
|
|
36
|
+
const ctx = useContext(RowContext);
|
|
37
|
+
if (!ctx) {
|
|
38
|
+
throw new Error("useRow must be used within a RowProvider");
|
|
39
|
+
}
|
|
40
|
+
return ctx;
|
|
41
|
+
};
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { Dispatch, ReactNode, SetStateAction } from "react";
|
|
2
|
-
import { RowData } from "../models/rowData.js";
|
|
2
|
+
import { ConfiguredRow, RowData } from "../models/rowData.js";
|
|
3
3
|
import { YamlFile } from "../models/yamlFile";
|
|
4
4
|
interface RowsContextType {
|
|
5
5
|
rows: RowData[];
|
|
6
|
+
configuredRows: ConfiguredRow[];
|
|
6
7
|
setRows: Dispatch<SetStateAction<RowData[]>>;
|
|
8
|
+
updateRow: (id: number, update: (row: RowData) => RowData) => void;
|
|
9
|
+
removeRow: (id: number) => void;
|
|
7
10
|
generateAllFiles: () => YamlFile[];
|
|
8
11
|
}
|
|
9
12
|
interface Props {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { createContext, useContext, useEffect, useState } from "react";
|
|
2
|
+
import { createContext, useContext, useEffect, useMemo, useState } from "react";
|
|
3
3
|
import Dtt from "../models/dtt";
|
|
4
4
|
import { useMetadata } from "./MetadataProvider";
|
|
5
5
|
import { YamlFile } from "../models/yamlFile";
|
|
@@ -11,6 +11,12 @@ export const RowsProvider = ({ children }) => {
|
|
|
11
11
|
useEffect(() => {
|
|
12
12
|
localStorage.setItem("rows", JSON.stringify(rows));
|
|
13
13
|
}, [rows]);
|
|
14
|
+
const updateRow = (id, update) => {
|
|
15
|
+
setRows((prev) => prev.map((r) => (r.id === id ? update(r) : r)));
|
|
16
|
+
};
|
|
17
|
+
const removeRow = (id) => {
|
|
18
|
+
setRows((prev) => prev.filter((r) => r.id !== id));
|
|
19
|
+
};
|
|
14
20
|
const generateAllFiles = () => {
|
|
15
21
|
if (!metadata) {
|
|
16
22
|
return [];
|
|
@@ -20,7 +26,8 @@ export const RowsProvider = ({ children }) => {
|
|
|
20
26
|
YamlFile.createInfoYaml(rows, metadata),
|
|
21
27
|
];
|
|
22
28
|
};
|
|
23
|
-
|
|
29
|
+
const configuredRows = useMemo(() => rows.filter((r) => !!r.dtt), [rows]);
|
|
30
|
+
return (_jsx(RowsContext.Provider, { value: { rows, configuredRows, setRows, updateRow, removeRow, generateAllFiles }, children: children }));
|
|
24
31
|
};
|
|
25
32
|
export const useRows = () => {
|
|
26
33
|
const context = useContext(RowsContext);
|
package/dist/utils/utils.d.ts
CHANGED
|
@@ -1,4 +1,19 @@
|
|
|
1
1
|
import { YamlFile } from "../models/yamlFile";
|
|
2
|
+
import { BlobFile } from "../models/blobFile";
|
|
3
|
+
import { RowData } from "../models/rowData";
|
|
4
|
+
import { MetadataContext } from "../providers/MetadataProvider";
|
|
5
|
+
/**
|
|
6
|
+
* Parses a set of production configuration files to DttConfigs and returns the corresponding rows and emails.
|
|
7
|
+
* @param files The files to parse
|
|
8
|
+
* @param metadata The metadata to use for parsing
|
|
9
|
+
* @returns The parsed rows and emails
|
|
10
|
+
*/
|
|
11
|
+
export declare const parseProductionFiles: (files: (BlobFile | File)[], metadata: MetadataContext) => Promise<{
|
|
12
|
+
rows: RowData[];
|
|
13
|
+
emails: string[];
|
|
14
|
+
errors: Record<string, string>;
|
|
15
|
+
warnings: Record<string, string>;
|
|
16
|
+
}>;
|
|
2
17
|
/**
|
|
3
18
|
* Sanitizes a user-provided filename to prevent path traversal (Zip Slip)
|
|
4
19
|
* and unsafe filesystem characters.
|