Skip to content

Commit 77301cb

Browse files
authored
Update container item descendants so that they indicate any unsaved changes (#767)
1 parent 3436e1c commit 77301cb

File tree

7 files changed

+261
-79
lines changed

7 files changed

+261
-79
lines changed

src/commands/registerCommands.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ export function registerCommands(): void {
5959
registerCommandWithTreeNodeUnwrapping('containerApps.editContainerApp', editContainerApp);
6060
registerCommandWithTreeNodeUnwrapping('containerApps.openConsoleInPortal', openConsoleInPortal);
6161
registerCommandWithTreeNodeUnwrapping('containerApps.updateImage', updateImage);
62-
registerCommandWithTreeNodeUnwrapping('containerapps.toggleEnvironmentVariableVisibility',
62+
registerCommandWithTreeNodeUnwrapping('containerApps.toggleEnvironmentVariableVisibility',
6363
async (context: IActionContext, item: EnvironmentVariableItem) => {
6464
await item.toggleValueVisibility(context);
6565
});

src/tree/containers/ContainerItem.ts

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,40 +3,51 @@
33
* Licensed under the MIT License. See License.md in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6-
import { type Container, type Revision } from "@azure/arm-appcontainers";
7-
import { nonNullProp, nonNullValue, type TreeElementBase } from "@microsoft/vscode-azext-utils";
6+
import { KnownActiveRevisionsMode, type Container, type Revision } from "@azure/arm-appcontainers";
7+
import { nonNullProp, type TreeElementBase } from "@microsoft/vscode-azext-utils";
88
import { type AzureSubscription, type ViewPropertiesModel } from "@microsoft/vscode-azureresources-api";
9+
import * as deepEqual from "deep-eql";
910
import { TreeItemCollapsibleState, type TreeItem } from "vscode";
1011
import { getParentResource } from "../../utils/revisionDraftUtils";
1112
import { type ContainerAppModel } from "../ContainerAppItem";
12-
import { type RevisionsItemModel } from "../revisionManagement/RevisionItem";
13+
import { RevisionDraftDescendantBase } from "../revisionManagement/RevisionDraftDescendantBase";
14+
import { RevisionDraftItem } from "../revisionManagement/RevisionDraftItem";
1315
import { EnvironmentVariablesItem } from "./EnvironmentVariablesItem";
1416
import { ImageItem } from "./ImageItem";
1517

16-
export class ContainerItem implements RevisionsItemModel {
18+
export class ContainerItem extends RevisionDraftDescendantBase {
1719
id: string;
1820
label: string;
21+
1922
static readonly contextValue: string = 'containerItem';
2023
static readonly contextValueRegExp: RegExp = new RegExp(ContainerItem.contextValue);
2124

22-
constructor(readonly subscription: AzureSubscription, readonly containerApp: ContainerAppModel, readonly revision: Revision, readonly container: Container) {
25+
constructor(
26+
subscription: AzureSubscription,
27+
containerApp: ContainerAppModel,
28+
revision: Revision,
29+
readonly containersIdx: number,
30+
31+
// Used as the basis for the view; can reflect either the original or the draft changes
32+
readonly container: Container,
33+
) {
34+
super(subscription, containerApp, revision);
2335
this.id = `${this.parentResource.id}/${container.name}`;
24-
this.label = nonNullValue(this.container.name);
2536
}
2637

2738
getTreeItem(): TreeItem {
2839
return {
2940
id: this.id,
30-
label: `${this.container.name}`,
41+
label: this.label,
3142
contextValue: ContainerItem.contextValue,
3243
collapsibleState: TreeItemCollapsibleState.Collapsed,
3344
}
3445
}
3546

3647
getChildren(): TreeElementBase[] {
3748
return [
38-
new ImageItem(this.subscription, this.containerApp, this.revision, this.id, this.container),
39-
new EnvironmentVariablesItem(this.subscription, this.containerApp, this.revision, this.id, this.container)
49+
RevisionDraftDescendantBase.createTreeItem(ImageItem, this.subscription, this.containerApp, this.revision, this.containersIdx, this.container),
50+
RevisionDraftDescendantBase.createTreeItem(EnvironmentVariablesItem, this.subscription, this.containerApp, this.revision, this.containersIdx, this.container),
4051
];
4152
}
4253

@@ -48,4 +59,25 @@ export class ContainerItem implements RevisionsItemModel {
4859
data: this.container,
4960
label: nonNullProp(this.container, 'name'),
5061
}
62+
63+
protected setProperties(): void {
64+
this.label = this.container.name ?? '';
65+
}
66+
67+
protected setDraftProperties(): void {
68+
this.label = `${this.container.name}*`;
69+
}
70+
71+
hasUnsavedChanges(): boolean {
72+
// We only care about showing changes to descendants of the revision draft item when in multiple revisions mode
73+
if (this.containerApp.revisionsMode === KnownActiveRevisionsMode.Multiple && !RevisionDraftItem.hasDescendant(this)) {
74+
return false;
75+
}
76+
77+
const currentContainers: Container[] = this.parentResource.template?.containers ?? [];
78+
const currentContainer: Container | undefined = currentContainers[this.containersIdx];
79+
80+
return !currentContainer || !deepEqual(this.container, currentContainer);
81+
}
5182
}
83+

src/tree/containers/ContainersItem.ts

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import { KnownActiveRevisionsMode, type Container, type Revision } from "@azure/arm-appcontainers";
7-
import { nonNullValue, nonNullValueAndProp, type TreeElementBase } from "@microsoft/vscode-azext-utils";
7+
import { nonNullValueAndProp, type TreeElementBase } from "@microsoft/vscode-azext-utils";
88
import { type AzureSubscription, type ViewPropertiesModel } from "@microsoft/vscode-azureresources-api";
99
import * as deepEqual from 'deep-eql';
1010
import { TreeItemCollapsibleState, type TreeItem } from "vscode";
@@ -19,49 +19,64 @@ import { ContainerItem } from "./ContainerItem";
1919
import { EnvironmentVariablesItem } from "./EnvironmentVariablesItem";
2020
import { ImageItem } from "./ImageItem";
2121

22+
export const container: string = localize('container', 'Container');
23+
export const containers: string = localize('containers', 'Containers');
24+
2225
export class ContainersItem extends RevisionDraftDescendantBase {
2326
id: string;
2427
label: string;
25-
private containers: Container[] = [];
2628

27-
constructor(public readonly subscription: AzureSubscription,
28-
public readonly containerApp: ContainerAppModel,
29-
public readonly revision: Revision,) {
29+
static readonly contextValue: string = 'containersItem';
30+
static readonly contextValueRegExp: RegExp = new RegExp(ContainersItem.contextValue);
31+
32+
constructor(
33+
subscription: AzureSubscription,
34+
containerApp: ContainerAppModel,
35+
revision: Revision,
36+
37+
// Used as the basis for the view; can reflect either the original or the draft changes
38+
private containers: Container[],
39+
) {
3040
super(subscription, containerApp, revision);
3141
this.id = `${this.parentResource.id}/containers`;
32-
this.containers = nonNullValue(revision.template?.containers);
33-
this.label = this.containers.length === 1 ? localize('container', 'Container') : localize('containers', 'Containers');
3442
}
3543

3644
getChildren(): TreeElementBase[] {
3745
if (this.containers.length === 1) {
38-
return [new ImageItem(this.subscription, this.containerApp, this.revision, this.id, this.containers[0]),
39-
new EnvironmentVariablesItem(this.subscription, this.containerApp, this.revision, this.id, this.containers[0])];
46+
return [
47+
RevisionDraftDescendantBase.createTreeItem(ImageItem, this.subscription, this.containerApp, this.revision, 0, this.containers[0]),
48+
RevisionDraftDescendantBase.createTreeItem(EnvironmentVariablesItem, this.subscription, this.containerApp, this.revision, 0, this.containers[0]),
49+
];
4050
}
41-
return nonNullValue(this.containers?.map(container => new ContainerItem(this.subscription, this.containerApp, this.revision, container)));
51+
return this.containers?.map((container, idx) => RevisionDraftDescendantBase.createTreeItem(ContainerItem, this.subscription, this.containerApp, this.revision, idx, container)) ?? [];
4252
}
4353

4454
getTreeItem(): TreeItem {
4555
return {
4656
id: this.id,
4757
label: this.label,
4858
iconPath: treeUtils.getIconPath('containers'),
59+
contextValue: this.contextValue,
4960
collapsibleState: TreeItemCollapsibleState.Collapsed
5061
}
5162
}
5263

64+
private get contextValue(): string {
65+
return ContainersItem.contextValue;
66+
}
67+
5368
private get parentResource(): ContainerAppModel | Revision {
5469
return getParentResource(this.containerApp, this.revision);
5570
}
5671

5772
protected setProperties(): void {
58-
this.label = this.containers.length === 1 ? localize('container', 'Container') : localize('containers', 'Containers');
5973
this.containers = nonNullValueAndProp(this.parentResource.template, 'containers');
74+
this.label = this.containers.length === 1 ? container : containers;
6075
}
6176

6277
protected setDraftProperties(): void {
63-
this.label = this.containers.length === 1 ? localize('container*', 'Container*') : localize('containers*', 'Containers*');
6478
this.containers = nonNullValueAndProp(ext.revisionDraftFileSystem.parseRevisionDraft(this), 'containers');
79+
this.label = this.containers.length === 1 ? `${container}*` : `${containers}*`;
6580
}
6681

6782
viewProperties: ViewPropertiesModel = {
@@ -71,6 +86,12 @@ export class ContainersItem extends RevisionDraftDescendantBase {
7186
}
7287
}
7388

89+
static isContainersItem(item: unknown): item is ContainersItem {
90+
return typeof item === 'object' &&
91+
typeof (item as ContainersItem).id === 'string' &&
92+
(item as ContainersItem).id.endsWith('/containers');
93+
}
94+
7495
hasUnsavedChanges(): boolean {
7596
// We only care about showing changes to descendants of the revision draft item when in multiple revisions mode
7697
if (this.containerApp.revisionsMode === KnownActiveRevisionsMode.Multiple && !RevisionDraftItem.hasDescendant(this)) {

src/tree/containers/EnvironmentVariableItem.ts

Lines changed: 62 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,52 +3,94 @@
33
* Licensed under the MIT License. See License.md in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6-
import { type Container, type EnvironmentVar, type Revision } from "@azure/arm-appcontainers";
6+
import { KnownActiveRevisionsMode, type Container, type EnvironmentVar, type Revision } from "@azure/arm-appcontainers";
77
import { type IActionContext } from "@microsoft/vscode-azext-utils";
88
import { type AzureSubscription } from "@microsoft/vscode-azureresources-api";
9+
import * as deepEqual from "deep-eql";
910
import { ThemeIcon, type TreeItem } from "vscode";
1011
import { ext } from "../../extensionVariables";
1112
import { localize } from "../../utils/localize";
1213
import { getParentResource } from "../../utils/revisionDraftUtils";
1314
import { type ContainerAppModel } from "../ContainerAppItem";
14-
import { type RevisionsItemModel } from "../revisionManagement/RevisionItem";
15-
16-
export class EnvironmentVariableItem implements RevisionsItemModel {
17-
_hideValue: boolean;
18-
constructor(public readonly subscription: AzureSubscription,
19-
public readonly containerApp: ContainerAppModel,
20-
public readonly revision: Revision,
21-
readonly containerId: string,
15+
import { RevisionDraftDescendantBase } from "../revisionManagement/RevisionDraftDescendantBase";
16+
import { RevisionDraftItem } from "../revisionManagement/RevisionDraftItem";
17+
18+
const clickToView: string = localize('clickToView', 'Hidden value. Click to view.');
19+
20+
export class EnvironmentVariableItem extends RevisionDraftDescendantBase {
21+
static readonly contextValue: string = 'environmentVariableItem';
22+
static readonly contextValueRegExp: RegExp = new RegExp(EnvironmentVariableItem.contextValue);
23+
24+
id: string = `${this.parentResource.id}/${this.container.image}/${this.envVariable.name}`;
25+
26+
private hideValue: boolean = true;
27+
private hiddenMessage: string; // Shown when 'hideValue' is true
28+
private hiddenValue: string; // Shown when 'hideValue' is false
29+
30+
constructor(
31+
subscription: AzureSubscription,
32+
containerApp: ContainerAppModel,
33+
revision: Revision,
34+
readonly containersIdx: number,
35+
36+
// Used as the basis for the view; can reflect either the original or the draft changes
2237
readonly container: Container,
23-
readonly envVariable: EnvironmentVar) {
24-
this._hideValue = true;
38+
readonly envVariable: EnvironmentVar,
39+
) {
40+
super(subscription, containerApp, revision);
2541
}
26-
id: string = `${this.parentResource.id}/${this.container.image}/${this.envVariable.name}`
2742

2843
getTreeItem(): TreeItem {
2944
return {
30-
label: this._hideValue ? localize('viewHidden', '{0}=Hidden value. Click to view.', this.envVariable.name) : `${this.envVariable.name}=${this.envOutput}`,
31-
description: this.envVariable.secretRef && !this._hideValue ? localize('secretRef', 'Secret reference') : undefined,
32-
contextValue: 'environmentVariableItem',
45+
label: this.label,
46+
contextValue: EnvironmentVariableItem.contextValue,
47+
description: this.envVariable.secretRef && !this.hideValue ? localize('secretRef', 'Secret reference') : undefined,
3348
iconPath: new ThemeIcon('symbol-constant'),
3449
command: {
35-
command: 'containerapps.toggleEnvironmentVariableVisibility',
50+
command: 'containerApps.toggleEnvironmentVariableVisibility',
3651
title: localize('commandtitle', 'Toggle Environment Variable Visibility'),
37-
arguments: [this, this._hideValue,]
52+
arguments: [this, this.hideValue,]
3853
}
3954
}
4055
}
4156

4257
public async toggleValueVisibility(_: IActionContext): Promise<void> {
43-
this._hideValue = !this._hideValue;
58+
this.hideValue = !this.hideValue;
4459
ext.branchDataProvider.refresh(this);
4560
}
4661

62+
public get label(): string {
63+
return this.hideValue ? this.hiddenMessage : this.hiddenValue;
64+
}
65+
66+
private get envOutput(): string {
67+
return this.envVariable.value || this.envVariable.secretRef || '';
68+
}
69+
4770
private get parentResource(): ContainerAppModel | Revision {
4871
return getParentResource(this.containerApp, this.revision);
4972
}
5073

51-
private get envOutput(): string {
52-
return this.envVariable.value ?? this.envVariable.secretRef ?? '';
74+
protected setProperties(): void {
75+
this.hiddenMessage = `${this.envVariable.name}=${clickToView}`;
76+
this.hiddenValue = `${this.envVariable.name}=${this.envOutput}`;
77+
}
78+
79+
protected setDraftProperties(): void {
80+
this.hiddenMessage = `${this.envVariable.name}=${clickToView} *`;
81+
this.hiddenValue = `${this.envVariable.name}=${this.envOutput} *`;
82+
}
83+
84+
hasUnsavedChanges(): boolean {
85+
// We only care about showing changes to descendants of the revision draft item when in multiple revisions mode
86+
if (this.containerApp.revisionsMode === KnownActiveRevisionsMode.Multiple && !RevisionDraftItem.hasDescendant(this)) {
87+
return false;
88+
}
89+
90+
const currentContainers: Container[] = this.parentResource.template?.containers ?? [];
91+
const currentContainer: Container | undefined = currentContainers[this.containersIdx];
92+
const currentEnv: EnvironmentVar | undefined = currentContainer.env?.find(env => env.name === this.envVariable.name);
93+
94+
return !currentEnv || !deepEqual(this.envVariable, currentEnv);
5395
}
5496
}

src/tree/containers/EnvironmentVariablesItem.ts

Lines changed: 42 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,46 +3,74 @@
33
* Licensed under the MIT License. See License.md in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6-
import { type Container, type Revision } from "@azure/arm-appcontainers";
6+
import { KnownActiveRevisionsMode, type Container, type Revision } from "@azure/arm-appcontainers";
77
import { type TreeElementBase } from "@microsoft/vscode-azext-utils";
88
import { type AzureSubscription } from "@microsoft/vscode-azureresources-api";
9+
import * as deepEqual from "deep-eql";
910
import { ThemeIcon, TreeItemCollapsibleState, type TreeItem } from "vscode";
1011
import { localize } from "../../utils/localize";
1112
import { getParentResource } from "../../utils/revisionDraftUtils";
1213
import { type ContainerAppModel } from "../ContainerAppItem";
13-
import { type RevisionsItemModel } from "../revisionManagement/RevisionItem";
14+
import { RevisionDraftDescendantBase } from "../revisionManagement/RevisionDraftDescendantBase";
15+
import { RevisionDraftItem } from "../revisionManagement/RevisionDraftItem";
1416
import { EnvironmentVariableItem } from "./EnvironmentVariableItem";
1517

16-
export class EnvironmentVariablesItem implements RevisionsItemModel {
18+
const environmentVariables: string = localize('environmentVariables', 'Environment Variables');
19+
20+
export class EnvironmentVariablesItem extends RevisionDraftDescendantBase {
1721
static readonly contextValue: string = 'environmentVariablesItem';
1822
static readonly contextValueRegExp: RegExp = new RegExp(EnvironmentVariablesItem.contextValue);
1923

20-
constructor(public readonly subscription: AzureSubscription,
21-
public readonly containerApp: ContainerAppModel,
22-
public readonly revision: Revision,
23-
readonly containerId: string,
24-
readonly container: Container) {
24+
constructor(
25+
subscription: AzureSubscription,
26+
containerApp: ContainerAppModel,
27+
revision: Revision,
28+
readonly containersIdx: number,
29+
30+
// Used as the basis for the view; can reflect either the original or the draft changes
31+
readonly container: Container,
32+
) {
33+
super(subscription, containerApp, revision);
2534
}
35+
2636
id: string = `${this.parentResource.id}/environmentVariables/${this.container.image}`;
37+
label: string;
2738

2839
getTreeItem(): TreeItem {
2940
return {
3041
id: this.id,
31-
label: localize('environmentVariables', 'Environment Variables'),
42+
label: this.label,
3243
iconPath: new ThemeIcon('settings'),
3344
contextValue: EnvironmentVariablesItem.contextValue,
3445
collapsibleState: TreeItemCollapsibleState.Collapsed
3546
}
3647
}
3748

38-
getChildren(): TreeElementBase[] | undefined {
39-
if (!this.container.env) {
40-
return;
41-
}
42-
return this.container.env?.map(env => new EnvironmentVariableItem(this.subscription, this.containerApp, this.revision, this.id, this.container, env));
49+
getChildren(): TreeElementBase[] {
50+
return this.container.env?.map(env => RevisionDraftDescendantBase.createTreeItem(EnvironmentVariableItem, this.subscription, this.containerApp, this.revision, this.containersIdx, this.container, env)) ?? [];
4351
}
4452

4553
private get parentResource(): ContainerAppModel | Revision {
4654
return getParentResource(this.containerApp, this.revision);
4755
}
56+
57+
protected setProperties(): void {
58+
this.label = environmentVariables;
59+
}
60+
61+
protected setDraftProperties(): void {
62+
this.label = `${environmentVariables}*`;
63+
}
64+
65+
hasUnsavedChanges(): boolean {
66+
// We only care about showing changes to descendants of the revision draft item when in multiple revisions mode
67+
if (this.containerApp.revisionsMode === KnownActiveRevisionsMode.Multiple && !RevisionDraftItem.hasDescendant(this)) {
68+
return false;
69+
}
70+
71+
const currentContainers: Container[] = this.parentResource.template?.containers ?? [];
72+
const currentContainer: Container | undefined = currentContainers[this.containersIdx];
73+
74+
return !deepEqual(this.container.env ?? [], currentContainer?.env ?? []);
75+
}
4876
}

0 commit comments

Comments
 (0)