/
index.tsx
157 lines (134 loc) · 4.96 KB
/
index.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import NestedResourceTable from './NestedResourceTable';
import { ResourceRow, ResourceRowGroup } from './types';
import { css } from '@emotion/css';
import { GrafanaTheme2 } from '@grafana/data';
import { Button, useStyles2 } from '@grafana/ui';
import ResourcePickerData from '../../resourcePicker/resourcePickerData';
import { Space } from '../Space';
import { addResources, findRow, parseResourceURI } from './utils';
interface ResourcePickerProps {
resourcePickerData: ResourcePickerData;
resourceURI: string | undefined;
templateVariables: string[];
onApply: (resourceURI: string | undefined) => void;
onCancel: () => void;
}
const ResourcePicker = ({
resourcePickerData,
resourceURI,
templateVariables,
onApply,
onCancel,
}: ResourcePickerProps) => {
const styles = useStyles2(getStyles);
const [azureRows, setAzureRows] = useState<ResourceRowGroup>([]);
const [internalSelected, setInternalSelected] = useState<string | undefined>(resourceURI);
// Sync the resourceURI prop to internal state
useEffect(() => {
setInternalSelected(resourceURI);
}, [resourceURI]);
const rows = useMemo(() => {
const templateVariableRow = resourcePickerData.transformVariablesToRow(templateVariables);
return templateVariables.length ? [...azureRows, templateVariableRow] : azureRows;
}, [resourcePickerData, azureRows, templateVariables]);
// Map the selected item into an array of rows
const selectedResourceRows = useMemo(() => {
const found = internalSelected && findRow(rows, internalSelected);
return found
? [
{
...found,
children: undefined,
},
]
: [];
}, [internalSelected, rows]);
// Request resources for a expanded resource group
const requestNestedRows = useCallback(
async (resourceGroup: ResourceRow) => {
// If we already have children, we don't need to re-fetch them. Also abort if we're expanding the special
// template variable group, though that shouldn't happen in practice
if (resourceGroup.children?.length || resourceGroup.id === ResourcePickerData.templateVariableGroupID) {
return;
}
// fetch and set nested resources for the resourcegroup into the bigger state object
const resources = await resourcePickerData.getResourcesForResourceGroup(resourceGroup);
const newRows = addResources(azureRows, resourceGroup.id, resources);
setAzureRows(newRows);
},
[resourcePickerData, azureRows]
);
// Select
const handleSelectionChanged = useCallback((row: ResourceRow, isSelected: boolean) => {
isSelected ? setInternalSelected(row.id) : setInternalSelected(undefined);
}, []);
// Request initial data on first mount
useEffect(() => {
resourcePickerData.getResourcePickerData().then((initalRows) => {
setAzureRows(initalRows);
});
}, [resourcePickerData]);
// Request sibling resources for a selected resource - in practice should only be on first mount
useEffect(() => {
if (!internalSelected || !rows.length) {
return;
}
// If we can find this resource in the rows, then we don't need to load anything
const foundResourceRow = findRow(rows, internalSelected);
if (foundResourceRow) {
return;
}
const parsedURI = parseResourceURI(internalSelected);
const resourceGroupURI = `/subscriptions/${parsedURI?.subscriptionID}/resourceGroups/${parsedURI?.resourceGroup}`;
const resourceGroupRow = findRow(rows, resourceGroupURI);
if (!resourceGroupRow) {
// We haven't loaded the data from Azure yet
return;
}
requestNestedRows(resourceGroupRow);
}, [requestNestedRows, internalSelected, rows]);
const handleApply = useCallback(() => {
onApply(internalSelected);
}, [internalSelected, onApply]);
return (
<div>
<NestedResourceTable
rows={rows}
requestNestedRows={requestNestedRows}
onRowSelectedChange={handleSelectionChanged}
selectedRows={selectedResourceRows}
/>
<div className={styles.selectionFooter}>
{selectedResourceRows.length > 0 && (
<>
<Space v={2} />
<h5>Selection</h5>
<NestedResourceTable
rows={selectedResourceRows}
requestNestedRows={requestNestedRows}
onRowSelectedChange={handleSelectionChanged}
selectedRows={selectedResourceRows}
noHeader={true}
/>
</>
)}
<Space v={2} />
<Button onClick={handleApply}>Apply</Button>
<Space layout="inline" h={1} />
<Button onClick={onCancel} variant="secondary">
Cancel
</Button>
</div>
</div>
);
};
export default ResourcePicker;
const getStyles = (theme: GrafanaTheme2) => ({
selectionFooter: css({
position: 'sticky',
bottom: 0,
background: theme.colors.background.primary,
paddingTop: theme.spacing(2),
}),
});