Skip to content

Commit fc6efcd

Browse files
OmniLab Teamcopybara-github
authored andcommitted
Internal change
PiperOrigin-RevId: 945870741
1 parent a7bbc6a commit fc6efcd

8 files changed

Lines changed: 214 additions & 6 deletions

File tree

src/devtools/mobileharness/fe/v6/angular/app/features/device_detail/components/device_config/device_wizard/device_wizard.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
justify-content: center;
4444

4545
overflow-y: auto;
46+
@include common.shared-scrollbar-style();
4647
}
4748

4849
.stepper-section ::ng-deep .mat-horizontal-content-container {

src/devtools/mobileharness/fe/v6/angular/app/features/device_detail/components/device_config/device_wizard/device_wizard.spec.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,7 @@ describe('Device Wizard Component', () => {
269269
deviceId: 'test-id',
270270
source: 'copy',
271271
config: {
272+
permissions: {owners: ['owner1']},
272273
wifi: {type: 'none', ssid: 'dummy'},
273274
},
274275
},
@@ -377,6 +378,7 @@ describe('Device Wizard Component', () => {
377378
);
378379

379380
component.currentStep.set('review-and-submit');
381+
component.config.permissions = {owners: ['user1'], executors: []};
380382
component.submit();
381383

382384
expect(dialogSpy).toHaveBeenCalled();
@@ -421,6 +423,7 @@ describe('Device Wizard Component', () => {
421423
confirmDialogRefSpy.afterClosed.and.returnValue(of('primary')); // Proceed anyway
422424
spyOn(dialog, 'open').and.returnValue(confirmDialogRefSpy);
423425

426+
component.config.permissions = {owners: ['user1'], executors: []};
424427
component.submit();
425428

426429
expect(updateSpy).toHaveBeenCalledTimes(2);
@@ -429,4 +432,70 @@ describe('Device Wizard Component', () => {
429432
expect(firstCallArgs.options?.overrideSelfLockout).toBeFalse();
430433
expect(secondCallArgs.options?.overrideSelfLockout).toBeTrue();
431434
});
435+
436+
it('should return to permissions step when canceling empty owner warning', async () => {
437+
const fixture = TestBed.createComponent(
438+
MatTestDialogOpener.withComponent(DeviceWizard, {
439+
data: {
440+
deviceId: 'test-id',
441+
source: 'new',
442+
},
443+
}),
444+
);
445+
fixture.detectChanges();
446+
await fixture.whenStable();
447+
448+
const component = fixture.componentInstance.dialogRef.componentInstance;
449+
const confirmDialogRefSpy = jasmine.createSpyObj('MatDialogRef', [
450+
'afterClosed',
451+
]);
452+
confirmDialogRefSpy.afterClosed.and.returnValue(of('secondary')); // clicks "Go Back"
453+
const dialogSpy = spyOn(MatDialog.prototype, 'open').and.returnValue(
454+
confirmDialogRefSpy,
455+
);
456+
457+
component.currentStep.set('review-and-submit');
458+
component.config.permissions = {owners: [], executors: []};
459+
component.submit();
460+
461+
expect(dialogSpy).toHaveBeenCalled();
462+
expect(component.currentStep()).toBe('permissions');
463+
});
464+
465+
it('should retry submit with override self lockout set when empty owner warning is confirmed', async () => {
466+
const fixture = TestBed.createComponent(
467+
MatTestDialogOpener.withComponent(DeviceWizard, {
468+
data: {
469+
deviceId: 'test-id',
470+
source: 'new',
471+
},
472+
}),
473+
);
474+
fixture.detectChanges();
475+
await fixture.whenStable();
476+
477+
const component = fixture.componentInstance.dialogRef.componentInstance;
478+
const configService = TestBed.inject(CONFIG_SERVICE);
479+
const updateSpy = spyOn(
480+
configService,
481+
'updateDeviceConfig',
482+
).and.returnValue(of({success: true, deviceConfig: {}, uiStatus: {}}));
483+
484+
const dialog = TestBed.inject(MatDialog);
485+
const confirmDialogRefSpy = jasmine.createSpyObj('MatDialogRef', [
486+
'afterClosed',
487+
]);
488+
confirmDialogRefSpy.afterClosed.and.returnValue(of('primary')); // Proceed anyway
489+
const dialogSpy = spyOn(dialog, 'open').and.returnValue(
490+
confirmDialogRefSpy,
491+
);
492+
493+
component.config.permissions = {owners: [], executors: []};
494+
component.submit();
495+
496+
expect(dialogSpy).toHaveBeenCalledTimes(2);
497+
expect(updateSpy).toHaveBeenCalledTimes(1);
498+
const callArgs = updateSpy.calls.first().args[0];
499+
expect(callArgs.options?.overrideSelfLockout).toBeTrue();
500+
});
432501
});

src/devtools/mobileharness/fe/v6/angular/app/features/device_detail/components/device_config/device_wizard/device_wizard.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ export class DeviceWizard implements OnInit {
8787
},
8888
onSubmitOverride: () => {
8989
// If user proceeds anyway, force-save with selfLockout override enabled.
90-
this.submit(true);
90+
this.submit(true, true);
9191
},
9292
});
9393
readonly isGoogleInternal = this.environment.isGoogleInternal();
@@ -241,8 +241,18 @@ export class DeviceWizard implements OnInit {
241241
}
242242
}
243243

244-
// used for apply changes button
245-
submit(overrideSelfLockout = false) {
244+
submit(overrideSelfLockout = false, bypassOwnerCheck = false) {
245+
const owners = this.config.permissions?.owners || [];
246+
if (!bypassOwnerCheck && owners.length === 0) {
247+
this.dialogActions.emptyOwnerWarning('device', () => {
248+
this.submit(
249+
/* overrideSelfLockout= */ true,
250+
/* bypassOwnerCheck= */ true,
251+
);
252+
});
253+
return;
254+
}
255+
246256
if (this.data.source === 'copy') {
247257
this.verifying.set(true);
248258
} else {

src/devtools/mobileharness/fe/v6/angular/app/features/host_detail/components/host_config/host_wizard/host_wizard.spec.ts

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -552,6 +552,12 @@ describe('HostWizard Component', () => {
552552
);
553553

554554
comp.currentStep.set('review-and-submit');
555+
comp.hostConfig.set({
556+
...comp.hostConfig(),
557+
permissions: {
558+
hostAdmins: ['admin'],
559+
},
560+
});
555561
comp.submit();
556562

557563
expect(dialogSpy).toHaveBeenCalled();
@@ -571,4 +577,75 @@ describe('HostWizard Component', () => {
571577
comp.onCurrentStepChange('device-config-mode');
572578
expect(comp.currentStep()).toBe('device-config-mode');
573579
});
580+
581+
it('should return to host-permissions step when canceling empty owner warning', async () => {
582+
const dialogOpener = TestBed.createComponent(
583+
MatTestDialogOpener.withComponent(HostWizard, {
584+
data: {hostName: 'test-host', source: 'new'},
585+
}),
586+
) as ComponentFixture<MatTestDialogOpener<HostWizard>>;
587+
dialogOpener.detectChanges();
588+
await dialogOpener.whenStable();
589+
590+
const comp = dialogOpener.componentInstance.dialogRef.componentInstance;
591+
const dialog = TestBed.inject(MatDialog);
592+
const confirmDialogRefSpy = jasmine.createSpyObj('MatDialogRef', [
593+
'afterClosed',
594+
]);
595+
confirmDialogRefSpy.afterClosed.and.returnValue(of('secondary')); // clicks "Go Back"
596+
const dialogSpy = spyOn(dialog, 'open').and.returnValue(
597+
confirmDialogRefSpy,
598+
);
599+
600+
comp.currentStep.set('review-and-submit');
601+
comp.hostConfig.set({
602+
...comp.hostConfig(),
603+
permissions: {
604+
hostAdmins: [],
605+
},
606+
});
607+
comp.submit();
608+
609+
expect(dialogSpy).toHaveBeenCalled();
610+
expect(comp.currentStep()).toBe('host-permissions');
611+
});
612+
613+
it('should retry submit with override self lockout set when empty owner warning is confirmed', async () => {
614+
const dialogOpener = TestBed.createComponent(
615+
MatTestDialogOpener.withComponent(HostWizard, {
616+
data: {hostName: 'test-host', source: 'new'},
617+
}),
618+
) as ComponentFixture<MatTestDialogOpener<HostWizard>>;
619+
dialogOpener.detectChanges();
620+
await dialogOpener.whenStable();
621+
622+
const comp = dialogOpener.componentInstance.dialogRef.componentInstance;
623+
const configService = TestBed.inject(CONFIG_SERVICE);
624+
const updateSpy = spyOn(configService, 'updateHostConfig').and.returnValue(
625+
of({success: true, uiStatus: {}}),
626+
);
627+
628+
const dialog = TestBed.inject(MatDialog);
629+
const confirmDialogRefSpy = jasmine.createSpyObj('MatDialogRef', [
630+
'afterClosed',
631+
]);
632+
confirmDialogRefSpy.afterClosed.and.returnValue(of('primary')); // Proceed anyway
633+
const dialogSpy = spyOn(dialog, 'open').and.returnValue(
634+
confirmDialogRefSpy,
635+
);
636+
637+
comp.currentStep.set('review-and-submit');
638+
comp.hostConfig.set({
639+
...comp.hostConfig(),
640+
permissions: {
641+
hostAdmins: [],
642+
},
643+
});
644+
comp.submit();
645+
646+
expect(dialogSpy).toHaveBeenCalledTimes(2);
647+
expect(updateSpy).toHaveBeenCalledTimes(1);
648+
const callArgs = updateSpy.calls.first().args[0];
649+
expect(callArgs.options?.overrideSelfLockout).toBeTrue();
650+
});
574651
});

src/devtools/mobileharness/fe/v6/angular/app/features/host_detail/components/host_config/host_wizard/host_wizard.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ export class HostWizard implements OnInit {
7575
this.currentStep.set('host-permissions');
7676
},
7777
onSubmitOverride: () => {
78-
this.submit(true);
78+
this.submit(true, true);
7979
},
8080
});
8181

@@ -302,14 +302,25 @@ export class HostWizard implements OnInit {
302302
this.dataSource = dataSource;
303303
}
304304

305-
submit(overrideSelfLockout = false) {
305+
submit(overrideSelfLockout = false, bypassOwnerCheck = false) {
306+
const hostAdmins = this.hostConfig().permissions?.hostAdmins || [];
307+
if (!bypassOwnerCheck && hostAdmins.length === 0) {
308+
this.dialogActions.emptyOwnerWarning('host', () => {
309+
this.submit(
310+
/* overrideSelfLockout= */ true,
311+
/* bypassOwnerCheck= */ true,
312+
);
313+
});
314+
return;
315+
}
316+
306317
const wifi = this.hostConfig().deviceConfig?.wifi;
307318
const finalWifi = !wifi || wifi.type === 'none' ? undefined : wifi;
308319
const requestConfig: HostConfig = {
309320
...this.hostConfig(),
310321
deviceConfig: {
311322
permissions: {
312-
owners: this.hostConfig().permissions.hostAdmins,
323+
owners: hostAdmins,
313324
executors:
314325
this.hostConfig().deviceConfig?.permissions?.executors || [],
315326
},

src/devtools/mobileharness/fe/v6/angular/app/shared/components/config_common/wizard_stepper/wizard_stepper.scss

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
justify-content: center;
4848

4949
overflow-y: auto;
50+
@include common.shared-scrollbar-style();
5051
}
5152

5253
.stepper-section ::ng-deep .mat-horizontal-content-container {
@@ -60,6 +61,7 @@
6061
.step-content {
6162
padding: 1.5rem;
6263
overflow-y: auto;
64+
@include common.shared-scrollbar-style();
6365
flex-grow: 1;
6466
min-height: 0px;
6567
max-height: calc(90vh - 99px - 85px - 56px);

src/devtools/mobileharness/fe/v6/angular/app/shared/composables/config_dialog_actions.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,9 +97,43 @@ export function useConfigDialogActions<T>(
9797
});
9898
};
9999

100+
/** Handles empty owner warning by showing a confirmation dialog. */
101+
const emptyOwnerWarning = (
102+
configType: 'device' | 'host',
103+
onConfirm: () => void,
104+
onCancel?: () => void,
105+
) => {
106+
const dialogData = {
107+
title: 'Security Warning',
108+
content:
109+
configType === 'device'
110+
? 'No owners are set for this device configuration. This device will be public, allowing anyone to view and edit it. Are you sure you want to proceed?'
111+
: 'No host admins are set for this host configuration. This host will be public, allowing anyone to view and edit it. Are you sure you want to proceed?',
112+
type: 'warning',
113+
primaryButtonLabel: 'Proceed anyway',
114+
secondaryButtonLabel: 'Go back',
115+
};
116+
dialog
117+
.open(ConfirmDialog, {
118+
data: dialogData,
119+
disableClose: true,
120+
})
121+
.afterClosed()
122+
.subscribe((result) => {
123+
if (result === 'secondary') {
124+
(onCancel || options.onCancelSelfLockout)();
125+
return;
126+
}
127+
if (result === 'primary') {
128+
onConfirm();
129+
}
130+
});
131+
};
132+
100133
return {
101134
success,
102135
error,
103136
selfLockout,
137+
emptyOwnerWarning,
104138
};
105139
}

src/devtools/mobileharness/fe/v6/angular/app/shared/styles/configuration.scss

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
@use '@angular/material' as mat;
2+
@use 'common';
23

34
$labconsole-font: Roboto, sans-serif;
45

@@ -183,6 +184,7 @@ h1, h2, h3, h4, h5, h6, p {
183184
.config-content {
184185
padding: 1.5rem;
185186
overflow-y: auto;
187+
@include common.shared-scrollbar-style();
186188

187189
.empty-config-title {
188190
font-size: 0.875rem;
@@ -343,13 +345,15 @@ h1, h2, h3, h4, h5, h6, p {
343345
flex-grow: 1;
344346
min-height: 0;
345347
overflow-y: auto;
348+
@include common.shared-scrollbar-style();
346349
padding: 1.5rem;
347350
}
348351

349352
// Wizard Step Content
350353
.config-wizard-step-content {
351354
padding: 1.5rem;
352355
overflow-y: auto;
356+
@include common.shared-scrollbar-style();
353357
flex-grow: 1;
354358
min-height: 0px;
355359
max-height: calc(90vh - 99px - 85px - 56px);

0 commit comments

Comments
 (0)