Skip to content

Commit d3a5a5e

Browse files
Merge pull request #2611 from github/robertbrignull/data-unsaved-checkbox
Show whether changes to a method are saved or not
2 parents fc77a52 + 5617331 commit d3a5a5e

6 files changed

Lines changed: 86 additions & 47 deletions

File tree

extensions/ql-vscode/src/view/data-extensions-editor/DataExtensionsEditor.tsx

Lines changed: 19 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ import { DataExtensionEditorViewState } from "../../data-extensions-editor/share
1717
import { ModeledMethodsList } from "./ModeledMethodsList";
1818
import { percentFormatter } from "./formatters";
1919
import { Mode } from "../../data-extensions-editor/shared/mode";
20-
import { groupMethods } from "../../data-extensions-editor/shared/sorting";
2120

2221
const LoadingContainer = styled.div`
2322
text-align: center;
@@ -75,7 +74,9 @@ export function DataExtensionsEditor({
7574
const [externalApiUsages, setExternalApiUsages] = useState<
7675
ExternalApiUsage[]
7776
>(initialExternalApiUsages);
78-
const [unsavedModels, setUnsavedModels] = useState<Set<string>>(new Set());
77+
const [modifiedSignatures, setModifiedSignatures] = useState<Set<string>>(
78+
new Set(),
79+
);
7980

8081
const [modeledMethods, setModeledMethods] = useState<
8182
Record<string, ModeledMethod>
@@ -119,15 +120,11 @@ export function DataExtensionsEditor({
119120
),
120121
};
121122
});
122-
setUnsavedModels(
123-
(oldUnsavedModels) =>
123+
setModifiedSignatures(
124+
(oldModifiedSignatures) =>
124125
new Set([
125-
...oldUnsavedModels,
126-
...modelsAffectedByNewModeledMethods(
127-
msg.modeledMethods,
128-
externalApiUsages,
129-
viewState?.mode ?? Mode.Application,
130-
),
126+
...oldModifiedSignatures,
127+
...Object.keys(msg.modeledMethods),
131128
]),
132129
);
133130
break;
@@ -145,7 +142,7 @@ export function DataExtensionsEditor({
145142
return () => {
146143
window.removeEventListener("message", listener);
147144
};
148-
}, [externalApiUsages, viewState?.mode]);
145+
}, []);
149146

150147
const modeledPercentage = useMemo(
151148
() => calculateModeledPercentage(externalApiUsages),
@@ -160,8 +157,9 @@ export function DataExtensionsEditor({
160157
...oldModeledMethods,
161158
[method.signature]: model,
162159
}));
163-
setUnsavedModels(
164-
(oldUnsavedModels) => new Set([...oldUnsavedModels, modelName]),
160+
setModifiedSignatures(
161+
(oldModifiedSignatures) =>
162+
new Set([...oldModifiedSignatures, method.signature]),
165163
);
166164
},
167165
[],
@@ -179,12 +177,11 @@ export function DataExtensionsEditor({
179177
externalApiUsages,
180178
modeledMethods,
181179
});
182-
setUnsavedModels(new Set());
180+
setModifiedSignatures(new Set());
183181
}, [externalApiUsages, modeledMethods]);
184182

185183
const onSaveModelClick = useCallback(
186184
(
187-
modelName: string,
188185
externalApiUsages: ExternalApiUsage[],
189186
modeledMethods: Record<string, ModeledMethod>,
190187
) => {
@@ -193,10 +190,12 @@ export function DataExtensionsEditor({
193190
externalApiUsages,
194191
modeledMethods,
195192
});
196-
setUnsavedModels((oldUnsavedModels) => {
197-
const newUnsavedModels = new Set(oldUnsavedModels);
198-
newUnsavedModels.delete(modelName);
199-
return newUnsavedModels;
193+
setModifiedSignatures((oldModifiedSignatures) => {
194+
const newModifiedSignatures = new Set([...oldModifiedSignatures]);
195+
for (const externalApiUsage of externalApiUsages) {
196+
newModifiedSignatures.delete(externalApiUsage.signature);
197+
}
198+
return newModifiedSignatures;
200199
});
201200
},
202201
[],
@@ -317,8 +316,8 @@ export function DataExtensionsEditor({
317316
</ButtonsContainer>
318317
<ModeledMethodsList
319318
externalApiUsages={externalApiUsages}
320-
unsavedModels={unsavedModels}
321319
modeledMethods={modeledMethods}
320+
modifiedSignatures={modifiedSignatures}
322321
viewState={viewState}
323322
onChange={onChange}
324323
onSaveModelClick={onSaveModelClick}
@@ -331,15 +330,3 @@ export function DataExtensionsEditor({
331330
</DataExtensionsEditorContainer>
332331
);
333332
}
334-
335-
function modelsAffectedByNewModeledMethods(
336-
modeledMethods: Record<string, ModeledMethod>,
337-
externalApiUsages: ExternalApiUsage[],
338-
mode: Mode,
339-
): string[] {
340-
const signatures = new Set(Object.keys(modeledMethods));
341-
const affectedExternalApiUsages = externalApiUsages.filter(
342-
(externalApiUsage) => signatures.has(externalApiUsage.signature),
343-
);
344-
return Object.keys(groupMethods(affectedExternalApiUsages, mode));
345-
}

extensions/ql-vscode/src/view/data-extensions-editor/LibraryRow.tsx

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -71,15 +71,14 @@ type Props = {
7171
libraryVersion?: string;
7272
externalApiUsages: ExternalApiUsage[];
7373
modeledMethods: Record<string, ModeledMethod>;
74+
modifiedSignatures: Set<string>;
7475
viewState: DataExtensionEditorViewState;
75-
hasUnsavedChanges: boolean;
7676
onChange: (
7777
modelName: string,
7878
externalApiUsage: ExternalApiUsage,
7979
modeledMethod: ModeledMethod,
8080
) => void;
8181
onSaveModelClick: (
82-
modelName: string,
8382
externalApiUsages: ExternalApiUsage[],
8483
modeledMethods: Record<string, ModeledMethod>,
8584
) => void;
@@ -95,8 +94,8 @@ export const LibraryRow = ({
9594
libraryVersion,
9695
externalApiUsages,
9796
modeledMethods,
97+
modifiedSignatures,
9898
viewState,
99-
hasUnsavedChanges,
10099
onChange,
101100
onSaveModelClick,
102101
onGenerateFromLlmClick,
@@ -137,11 +136,11 @@ export const LibraryRow = ({
137136

138137
const handleSave = useCallback(
139138
async (e: React.MouseEvent) => {
140-
onSaveModelClick(title, externalApiUsages, modeledMethods);
139+
onSaveModelClick(externalApiUsages, modeledMethods);
141140
e.stopPropagation();
142141
e.preventDefault();
143142
},
144-
[title, externalApiUsages, modeledMethods, onSaveModelClick],
143+
[externalApiUsages, modeledMethods, onSaveModelClick],
145144
);
146145

147146
const onChangeWithModelName = useCallback(
@@ -151,6 +150,12 @@ export const LibraryRow = ({
151150
[onChange, title],
152151
);
153152

153+
const hasUnsavedChanges = useMemo(() => {
154+
return externalApiUsages.some((externalApiUsage) =>
155+
modifiedSignatures.has(externalApiUsage.signature),
156+
);
157+
}, [externalApiUsages, modifiedSignatures]);
158+
154159
return (
155160
<LibraryContainer>
156161
<TitleContainer onClick={toggleExpanded} aria-expanded={isExpanded}>
@@ -195,6 +200,7 @@ export const LibraryRow = ({
195200
<ModeledMethodDataGrid
196201
externalApiUsages={externalApiUsages}
197202
modeledMethods={modeledMethods}
203+
modifiedSignatures={modifiedSignatures}
198204
mode={viewState.mode}
199205
onChange={onChangeWithModelName}
200206
/>

extensions/ql-vscode/src/view/data-extensions-editor/MethodRow.tsx

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import {
2-
VSCodeCheckbox,
32
VSCodeDataGridCell,
43
VSCodeDataGridRow,
54
VSCodeLink,
@@ -20,6 +19,10 @@ import { extensiblePredicateDefinitions } from "../../data-extensions-editor/pre
2019
import { Mode } from "../../data-extensions-editor/shared/mode";
2120
import { Dropdown } from "../common/Dropdown";
2221
import { MethodClassifications } from "./MethodClassifications";
22+
import {
23+
ModelingStatus,
24+
ModelingStatusIndicator,
25+
} from "./ModelingStatusIndicator";
2326

2427
const ApiOrMethodCell = styled(VSCodeDataGridCell)`
2528
display: flex;
@@ -51,6 +54,7 @@ const modelTypeOptions: Array<{ value: ModeledMethodType; label: string }> = [
5154
type Props = {
5255
externalApiUsage: ExternalApiUsage;
5356
modeledMethod: ModeledMethod | undefined;
57+
methodIsUnsaved: boolean;
5458
mode: Mode;
5559
onChange: (
5660
externalApiUsage: ExternalApiUsage,
@@ -59,11 +63,12 @@ type Props = {
5963
};
6064

6165
export const MethodRow = (props: Props) => {
62-
const { externalApiUsage, modeledMethod } = props;
66+
const { externalApiUsage, modeledMethod, methodIsUnsaved } = props;
6367

6468
const methodCanBeModeled =
6569
!externalApiUsage.supported ||
66-
(modeledMethod && modeledMethod?.type !== "none");
70+
(modeledMethod && modeledMethod?.type !== "none") ||
71+
methodIsUnsaved;
6772

6873
if (methodCanBeModeled) {
6974
return <ModelableMethodRow {...props} />;
@@ -73,7 +78,8 @@ export const MethodRow = (props: Props) => {
7378
};
7479

7580
function ModelableMethodRow(props: Props) {
76-
const { externalApiUsage, modeledMethod, mode, onChange } = props;
81+
const { externalApiUsage, modeledMethod, methodIsUnsaved, mode, onChange } =
82+
props;
7783

7884
const argumentsList = useMemo(() => {
7985
if (externalApiUsage.methodParameters === "()") {
@@ -192,10 +198,12 @@ function ModelableMethodRow(props: Props) {
192198
: undefined;
193199
const showKindCell = predicate?.supportedKinds;
194200

201+
const modelingStatus = getModelingStatus(modeledMethod, methodIsUnsaved);
202+
195203
return (
196204
<VSCodeDataGridRow>
197205
<ApiOrMethodCell gridColumn={1}>
198-
<VSCodeCheckbox />
206+
<ModelingStatusIndicator status={modelingStatus} />
199207
<ExternalApiUsageName {...props} />
200208
{mode === Mode.Application && (
201209
<UsagesButton onClick={jumpToUsage}>
@@ -251,7 +259,7 @@ function UnmodelableMethodRow(props: Props) {
251259
return (
252260
<VSCodeDataGridRow>
253261
<ApiOrMethodCell gridColumn={1}>
254-
<VSCodeCheckbox />
262+
<ModelingStatusIndicator status="saved" />
255263
<ExternalApiUsageName {...props} />
256264
{mode === Mode.Application && (
257265
<UsagesButton onClick={jumpToUsage}>
@@ -287,3 +295,17 @@ function sendJumpToUsageMessage(externalApiUsage: ExternalApiUsage) {
287295
location: externalApiUsage.usages[0].url,
288296
});
289297
}
298+
299+
function getModelingStatus(
300+
modeledMethod: ModeledMethod | undefined,
301+
methodIsUnsaved: boolean,
302+
): ModelingStatus {
303+
if (modeledMethod) {
304+
if (methodIsUnsaved) {
305+
return "unsaved";
306+
} else if (modeledMethod.type !== "none") {
307+
return "saved";
308+
}
309+
}
310+
return "unmodeled";
311+
}

extensions/ql-vscode/src/view/data-extensions-editor/ModeledMethodDataGrid.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { sortMethods } from "../../data-extensions-editor/shared/sorting";
1414
type Props = {
1515
externalApiUsages: ExternalApiUsage[];
1616
modeledMethods: Record<string, ModeledMethod>;
17+
modifiedSignatures: Set<string>;
1718
mode: Mode;
1819
onChange: (
1920
externalApiUsage: ExternalApiUsage,
@@ -24,6 +25,7 @@ type Props = {
2425
export const ModeledMethodDataGrid = ({
2526
externalApiUsages,
2627
modeledMethods,
28+
modifiedSignatures,
2729
mode,
2830
onChange,
2931
}: Props) => {
@@ -56,6 +58,7 @@ export const ModeledMethodDataGrid = ({
5658
key={externalApiUsage.signature}
5759
externalApiUsage={externalApiUsage}
5860
modeledMethod={modeledMethods[externalApiUsage.signature]}
61+
methodIsUnsaved={modifiedSignatures.has(externalApiUsage.signature)}
5962
mode={mode}
6063
onChange={onChange}
6164
/>

extensions/ql-vscode/src/view/data-extensions-editor/ModeledMethodsList.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,15 @@ import { DataExtensionEditorViewState } from "../../data-extensions-editor/share
1212

1313
type Props = {
1414
externalApiUsages: ExternalApiUsage[];
15-
unsavedModels: Set<string>;
1615
modeledMethods: Record<string, ModeledMethod>;
16+
modifiedSignatures: Set<string>;
1717
viewState: DataExtensionEditorViewState;
1818
onChange: (
1919
modelName: string,
2020
externalApiUsage: ExternalApiUsage,
2121
modeledMethod: ModeledMethod,
2222
) => void;
2323
onSaveModelClick: (
24-
modelName: string,
2524
externalApiUsages: ExternalApiUsage[],
2625
modeledMethods: Record<string, ModeledMethod>,
2726
) => void;
@@ -38,8 +37,8 @@ const libraryNameOverrides: Record<string, string> = {
3837

3938
export const ModeledMethodsList = ({
4039
externalApiUsages,
41-
unsavedModels,
4240
modeledMethods,
41+
modifiedSignatures,
4342
viewState,
4443
onChange,
4544
onSaveModelClick,
@@ -79,8 +78,8 @@ export const ModeledMethodsList = ({
7978
title={libraryNameOverrides[libraryName] ?? libraryName}
8079
libraryVersion={libraryVersions[libraryName]}
8180
externalApiUsages={grouped[libraryName]}
82-
hasUnsavedChanges={unsavedModels.has(libraryName)}
8381
modeledMethods={modeledMethods}
82+
modifiedSignatures={modifiedSignatures}
8483
viewState={viewState}
8584
onChange={onChange}
8685
onSaveModelClick={onSaveModelClick}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import * as React from "react";
2+
import { assertNever } from "../../common/helpers-pure";
3+
import { Codicon } from "../common/icon/Codicon";
4+
5+
export type ModelingStatus = "unmodeled" | "unsaved" | "saved";
6+
7+
interface Props {
8+
status: ModelingStatus;
9+
}
10+
11+
export function ModelingStatusIndicator({ status }: Props) {
12+
switch (status) {
13+
case "unmodeled":
14+
return <Codicon name="circle-large-outline" label="Method not modeled" />;
15+
case "unsaved":
16+
return <Codicon name="pass" label="Changes have not been saved" />;
17+
case "saved":
18+
return <Codicon name="pass-filled" label="Method modeled" />;
19+
default:
20+
assertNever(status);
21+
}
22+
}

0 commit comments

Comments
 (0)