Skip to content

Commit 7daa86d

Browse files
RD-5765 Migrate Blueprint Sources widget (#2467)
* RD-5765 Migrate Blueprint Sources widget * Fix lint * Address review comment * Fix typo * update size-limit * update size-limit * fix check-types * Address review comment * Revert; address review comment; * deploymentId * size-limit * Fix check-types * size-limit * Change to camelCase * align children type * deploymentIdRefined --> rawDeploymentId * aligning loop argument types, or aligning timestamp type. Same applies to children * isEmptyWidgetData * fix rpm * deploymentIds * Change loop signature * fix check types * NonNullable * Allign types * remove export * fix types * fix types * fix types * fix types * discriminated union * fix types * remove children prop * use correct type for item * fix types * fix types * fix types * fix types * fix types * remove duplication * fix types * fix status * fix types * remove isDir * remove as * shorter assertion * remove if * null should be part of GetSourceBrowseBlueprintArchiveResponse; blueprintTree nullish * Fix types * tree?.isDir && tree.children[0].key * archiveTree &&
1 parent 0ed0e12 commit 7daa86d

7 files changed

Lines changed: 96 additions & 77 deletions

File tree

backend/handler/SourceHandler.ts

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { isYamlFile } from '../sharedUtils';
1414
import * as ArchiveHelper from './ArchiveHelper';
1515

1616
import { getLogger } from './LoggerHandler';
17-
import type { ScanningItem } from './SourceHandler.types';
17+
import type { ScanningItem, ScanningFile, ScanningDir } from './SourceHandler.types';
1818

1919
const logger = getLogger('SourceHandler');
2020

@@ -45,14 +45,15 @@ function scanRecursive(rootDir: string, scannedFileOrDirPath: string) {
4545
return null;
4646
}
4747

48-
const item: ScanningItem = {
49-
key: toRelativeUrl(pathlib.relative(rootDir, scannedFileOrDirPath)),
50-
title: name,
51-
isDir: false
52-
};
48+
const itemKey = toRelativeUrl(pathlib.relative(rootDir, scannedFileOrDirPath));
5349

5450
if (stats.isFile()) {
55-
return item;
51+
const scannedFile: ScanningFile = {
52+
key: itemKey,
53+
title: name,
54+
isDir: false
55+
};
56+
return scannedFile;
5657
}
5758
if (stats.isDirectory()) {
5859
const scannedDir = scannedFileOrDirPath;
@@ -62,10 +63,12 @@ function scanRecursive(rootDir: string, scannedFileOrDirPath: string) {
6263
.map(child => scanRecursive(rootDir, pathlib.join(scannedDir, child)))
6364
.filter(e => !!e) as ScanningItem[];
6465

65-
item.isDir = true;
66-
item.children = _.sortBy(children, i => !i.isDir);
67-
68-
return item;
66+
return {
67+
key: itemKey,
68+
title: name,
69+
isDir: true,
70+
children: _.sortBy(children, i => !i.isDir)
71+
};
6972
} catch (error: any) {
7073
if (error.code === 'EACCES') {
7174
logger.debug('cannot access directory, ignoring');
@@ -79,7 +82,8 @@ function scanRecursive(rootDir: string, scannedFileOrDirPath: string) {
7982

8083
function scanArchive(archivePath: string) {
8184
logger.debug('scaning archive', archivePath);
82-
return scanRecursive(archivePath, archivePath);
85+
const rootDir = scanRecursive(archivePath, archivePath) as ScanningDir | null;
86+
return rootDir;
8387
}
8488

8589
export function browseArchiveTree(req: Request, timestamp = Date.now().toString()) {
@@ -94,10 +98,14 @@ export function browseArchiveTree(req: Request, timestamp = Date.now().toString(
9498
const archivePath = pathlib.join(data.archiveFolder, data.archiveFile);
9599
const extractedDir = pathlib.join(data.archiveFolder, blueprintExtractDir);
96100

97-
return ArchiveHelper.decompressArchive(archivePath, extractedDir).then(() => ({
98-
...scanArchive(extractedDir),
99-
timestamp
100-
}));
101+
return ArchiveHelper.decompressArchive(archivePath, extractedDir).then(() => {
102+
const archive: ScanningDir | null = scanArchive(extractedDir);
103+
if (archive === null) return null;
104+
return {
105+
...archive,
106+
timestamp
107+
};
108+
});
101109
});
102110
}
103111

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
1-
export interface ScanningItem {
1+
interface ScanningItemCommon {
22
key: string;
33
title: string;
4-
isDir: boolean;
5-
children?: ScanningItem[];
64
}
5+
6+
export interface ScanningDir extends ScanningItemCommon {
7+
isDir: true;
8+
children: ScanningItem[];
9+
}
10+
11+
export interface ScanningFile extends ScanningItemCommon {
12+
isDir: false;
13+
}
14+
15+
export type ScanningItem = ScanningDir | ScanningFile;

backend/routes/BlueprintUserData.ts

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,18 +24,17 @@ router.get('/layout/:blueprintId', (req, res: Response<GetBlueprintUserDataLayou
2424
return res.send(blueprintData.layout);
2525
}
2626
return browseArchiveTree(req).then(data => {
27-
const layoutFilePath = _.chain(data)
28-
.get('children[0].children')
29-
// @ts-ignore FIXME: Property 'find' does not exist on type 'LoDashExplicitWrapper<any>'
30-
.find({ title: 'info.yaml' })
31-
.get('key')
32-
.value();
33-
if (layoutFilePath) {
27+
if (data !== null) {
28+
const layoutFilePath = _.chain(data)
29+
.get('children[0].children')
30+
// @ts-ignore FIXME: Property 'find' does not exist on type 'LoDashExplicitWrapper<any>'
31+
.find({ title: 'info.yaml' })
32+
.get('key')
33+
.value();
3434
return getArchiveFileContent(req, data.timestamp, layoutFilePath)
3535
.then(yaml.load)
3636
.then(layout => res.send(layout));
3737
}
38-
3938
return res.status(404).send({});
4039
});
4140
})

backend/routes/SourceBrowser.types.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1-
import type { ScanningItem } from '../handler/SourceHandler.types';
1+
import type { ScanningDir } from '../handler/SourceHandler.types';
22

33
export type GetSourceBrowseBlueprintFileResponse = string;
44

5-
export interface GetSourceBrowseBlueprintArchiveResponse extends Partial<ScanningItem> {
6-
timestamp?: string;
5+
interface ScanningDirWithTimestamp extends ScanningDir {
6+
timestamp: string;
77
}
88

9+
export type GetSourceBrowseBlueprintArchiveResponse = ScanningDirWithTimestamp | null;
10+
911
export interface PutSourceListYamlQueryParams {
1012
includeFilename?: string;
1113
url?: string;

backend/test/handlers/SourceHandler.test.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,9 @@ jest.mock('fs-extra');
2525
describe('SourceHandler', () => {
2626
it('generates archive tree', () => {
2727
// @ts-ignore Passing mocked request
28-
return browseArchiveTree({ params: {} }).then(archiveTree =>
29-
expect(archiveTree?.children?.[0]?.children?.[0].key).toEqual('subdir/fileNameSpecial%3F%23Characters')
30-
);
28+
return browseArchiveTree({ params: {} }).then(archiveTree => {
29+
const tree = archiveTree?.children?.[0];
30+
expect(tree?.isDir && tree.children[0].key).toEqual('subdir/fileNameSpecial%3F%23Characters');
31+
});
3132
});
3233
});

widgets/blueprintSources/src/BlueprintSources.tsx

Lines changed: 9 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,28 +3,16 @@ import type { ComponentProps } from 'react';
33
import { useEffect } from 'react';
44
import SplitterLayout from 'react-splitter-layout';
55
import styled from 'styled-components';
6+
import type { ScanningItem } from 'backend/handler/SourceHandler.types';
7+
import type { WidgetData } from 'app/utils/StageAPI';
68
import Actions from './actions';
9+
import type { BlueprintSourcesData } from './widget';
710

811
const { CancelButton, NodesTree, Message, Label, Modal, HighlightText, ErrorMessage, Icon } = Stage.Basic;
912
const { useResettableState, useBoolean } = Stage.Hooks;
1013

1114
type FileType = ComponentProps<typeof HighlightText>['language'];
1215

13-
interface NodeTreeItem {
14-
children?: NodeTreeItem[];
15-
key: string;
16-
title: string;
17-
isDir: boolean;
18-
}
19-
20-
interface BlueprintTree {
21-
children: NodeTreeItem[];
22-
key: string;
23-
title: string;
24-
isDir: boolean;
25-
timestamp: number;
26-
}
27-
2816
const StyledHighlightText = styled(HighlightText)`
2917
margin-top: 2rem;
3018
margin-bottom: 0rem;
@@ -107,13 +95,7 @@ const RightPane = ({
10795
};
10896

10997
interface BlueprintSourcesProps {
110-
data: {
111-
blueprintId: string;
112-
blueprintTree: BlueprintTree;
113-
importedBlueprintIds: string[];
114-
importedBlueprintTrees: BlueprintTree[];
115-
yamlFileName: string;
116-
};
98+
data: NonNullable<WidgetData<BlueprintSourcesData>>;
11799
toolbox: Stage.Types.Toolbox;
118100
widget: Stage.Types.Widget;
119101
}
@@ -186,10 +168,10 @@ export default function BlueprintSources({ data, toolbox, widget }: BlueprintSou
186168
.finally(() => toolbox.loading(false));
187169
};
188170

189-
const loop = (blueprintId: string, timestamp: number, items: NodeTreeItem[]) => {
171+
const loop = (blueprintId: string, timestamp: string, items: ScanningItem[]) => {
190172
return items.map(item => {
191173
const key = `${blueprintId}/file/${timestamp}/${item.key}`;
192-
if (item.children) {
174+
if (item.isDir) {
193175
return (
194176
<NodesTree.Node
195177
key={key}
@@ -204,7 +186,7 @@ export default function BlueprintSources({ data, toolbox, widget }: BlueprintSou
204186
</NodesTree.Node>
205187
);
206188
}
207-
const blueprintRootDirectory = data.blueprintTree.children[0].key;
189+
const blueprintRootDirectory = data.blueprintTree!.children[0].key;
208190
const mainYamlFilePath = `${blueprintRootDirectory}/${data.yamlFileName}`;
209191
const label =
210192
mainYamlFilePath === item.key ? (
@@ -250,7 +232,7 @@ export default function BlueprintSources({ data, toolbox, widget }: BlueprintSou
250232
</Label>
251233
}
252234
>
253-
{loop(data.blueprintId, data.blueprintTree.timestamp, data.blueprintTree.children)}
235+
{loop(data.blueprintId, data.blueprintTree!.timestamp, data.blueprintTree!.children)}
254236
</NodesTree.Node>
255237
{_.size(data.importedBlueprintIds) > 0 && (
256238
<NodesTree.Node
@@ -275,7 +257,7 @@ export default function BlueprintSources({ data, toolbox, widget }: BlueprintSou
275257
</Label>
276258
}
277259
>
278-
{loop(data.importedBlueprintIds[index], tree.timestamp, tree.children)}
260+
{loop(data.importedBlueprintIds[index], tree!.timestamp, tree!.children)}
279261
</NodesTree.Node>
280262
))}
281263
</NodesTree.Node>

widgets/blueprintSources/src/widget.tsx

Lines changed: 35 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,28 @@
1-
// @ts-nocheck File not migrated fully to TS
2-
1+
import type { GetSourceBrowseBlueprintArchiveResponse } from 'backend/routes/SourceBrowser.types';
32
import Actions from './actions';
43
import BlueprintSources from './BlueprintSources';
54
import './widget.css';
65

7-
Stage.defineWidget({
6+
type BlueprintSourcesParams = {
7+
blueprintId: string;
8+
deploymentId: string;
9+
};
10+
11+
type BlueprintTree = GetSourceBrowseBlueprintArchiveResponse;
12+
13+
export type BlueprintSourcesData = {
14+
blueprintId: string;
15+
blueprintTree: BlueprintTree;
16+
importedBlueprintIds: string[];
17+
importedBlueprintTrees?: BlueprintTree[];
18+
yamlFileName: string;
19+
};
20+
21+
type BlueprintSourcesConfiguration = {
22+
contentPaneWidth: number;
23+
};
24+
25+
Stage.defineWidget<BlueprintSourcesParams, BlueprintSourcesData, BlueprintSourcesConfiguration>({
826
id: 'blueprintSources',
927
name: 'Blueprint Sources',
1028
description: 'Shows blueprint files',
@@ -23,21 +41,21 @@ Stage.defineWidget({
2341
}
2442
],
2543

26-
fetchParams(widget, toolbox) {
27-
const blueprintId = toolbox.getContext().getValue('blueprintId');
28-
const deploymentId = toolbox.getContext().getValue('deploymentId');
44+
fetchParams(_widget, toolbox) {
45+
const blueprintId = toolbox.getContext().getValue('blueprintId') ?? '';
46+
47+
// TODO(RD-2130): Use common utility function to get only the first ID
48+
const deploymentIds = toolbox.getContext().getValue('deploymentId');
49+
const deploymentId: string = Array.isArray(deploymentIds) ? deploymentIds[0] : deploymentIds ?? '';
2950

30-
return {
31-
blueprint_id: blueprintId,
32-
deployment_id: deploymentId
33-
};
51+
return { blueprintId, deploymentId };
3452
},
3553

36-
fetchData(widget, toolbox, params) {
54+
fetchData(_widget, toolbox, params) {
3755
const actions = new Actions(toolbox);
3856

39-
const paramBlueprintId = params.blueprint_id;
40-
const paramDeploymentId = params.deployment_id;
57+
const paramBlueprintId = params.blueprintId;
58+
const paramDeploymentId = params.deploymentId;
4159

4260
let promise = Promise.resolve({ blueprint_id: paramBlueprintId });
4361
if (!paramBlueprintId && paramDeploymentId) {
@@ -70,19 +88,19 @@ Stage.defineWidget({
7088
);
7189
}
7290
return {
73-
blueprintTree: {},
74-
importedBlueprintsTrees: [],
91+
blueprintTree: null,
92+
importedBlueprintTrees: [],
7593
blueprintId: '',
7694
importedBlueprintIds: [],
7795
yamlFileName: ''
7896
};
7997
});
8098
},
8199

82-
render(widget, data, error, toolbox) {
100+
render(widget, data, _error, toolbox) {
83101
const { Loading } = Stage.Basic;
84102

85-
if (_.isEmpty(data)) {
103+
if (Stage.Utils.isEmptyWidgetData(data)) {
86104
return <Loading />;
87105
}
88106

0 commit comments

Comments
 (0)