Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions components/settings/panel/Apps.vue
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,35 @@
</label>
</div>

<hr>
<h4 class="h5">During conflicts</h4>
<div class="form-check form-check-inline">
<label class="form-check-label">
<input
v-model="workspaceOverrideConflicts"
class="form-check-input"
type="radio"
name="workspaceOverrideConflicts"
:value="false"
:disabled="appControlsDisabled"
>
Resolve
</label>
</div>
<div class="form-check form-check-inline">
<label class="form-check-label">
<input
v-model="workspaceOverrideConflicts"
class="form-check-input"
type="radio"
name="workspaceOverrideConflicts"
:value="true"
:disabled="appControlsDisabled"
>
Override
</label>
</div>

<hr>

<h4 class="h5">
Expand Down Expand Up @@ -192,6 +221,7 @@ const longFormQuestSchema = ref<object | undefined>();
const longFormQuestType = ref(longFormQuestSettings.type);
const longFormQuestDef = ref(longFormQuestSettings.definition);
const longFormQuestUrl = ref(longFormQuestSettings.url);
const workspaceOverrideConflicts = ref(Boolean(workspace.overrideConflicts));
const longFormQuestError = ref<string | null>(null);
const isDraggingQuest = ref(false);

Expand Down Expand Up @@ -262,6 +292,7 @@ async function saveExternalAppConfiguration() {
await Promise.all([
workspacesClient.updateWorkspace(workspace.id, {
externalAppAccess: workspace.externalAppAccess,
overrideConflicts: workspaceOverrideConflicts.value,
}),
workspacesClient.saveLongFormQuestSettings(workspace.id, {
type,
Expand Down
2 changes: 2 additions & 0 deletions pages/workspace/[id]/settings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
// confirmation and sends the proper API call to the server (Swagger here: https://new-api.workspaces-stage.sidewalks.washington.edu/openapi.json)
// @test e2e: Under "External Apps", turning on "Publish this workspace" enables the other buttons and when clicking "Save" shows a confirmation and sends
// the proper API call.
// @test e2e: Under "External Apps" > "During conflicts", choosing "Override" (vs. the default "Resolve") and clicking "Save" sends the proper
// API call with the chosen overrideConflicts value.
// @test e2e: While External Apps settings are being saved, the Save button is disabled and displays "Saving..." to prevent duplicate submissions.
// @test e2e: the "Custom Imagery" box is validated against the JSON schema here (https://raw.githubusercontent.com/TaskarCenterAtUW/asr-imagery-list/refs/heads/main/schema/schema.json),
// and a toast shown when it passes and the API call to set its value is successful on the backend.
Expand Down
65 changes: 59 additions & 6 deletions test/e2e/settings.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,9 @@ test.describe('workspace settings', () => {
if (req.method() === 'GET') {
// Apps.vue binds the checkbox with :true-value="1"; seed externalAppAccess
// as 1 (published) so it renders checked, matching the true-value.
return route.fulfill({ json: { ...aWorkspace, externalAppAccess: 1 } });
// overrideConflicts seeded true; this test doesn't touch the "During
// conflicts" radios, so the value should pass through unchanged.
return route.fulfill({ json: { ...aWorkspace, externalAppAccess: 1, overrideConflicts: true } });
}
if (req.method() === 'PATCH') {
patchBody = JSON.parse(req.postData() ?? '{}');
Expand Down Expand Up @@ -209,8 +211,9 @@ test.describe('workspace settings', () => {

// Outline: a confirmation is shown (the page renders a success toast).
await expect(successToast(page)).toBeVisible();
// Proper API call: PATCH workspaces/1 with externalAppAccess set to 0 (off).
expect(patchBody).toEqual({ externalAppAccess: 0 });
// Proper API call: PATCH workspaces/1 with externalAppAccess set to 0 (off),
// plus the unchanged overrideConflicts value seeded above.
expect(patchBody).toEqual({ externalAppAccess: 0, overrideConflicts: true });
});

// @test e2e: While External Apps settings are being saved, the Save button is
Expand Down Expand Up @@ -262,7 +265,9 @@ test.describe('workspace settings', () => {
await page.route('**/workspaces/1', (route) => {
const req = route.request();
if (req.method() === 'GET') {
return route.fulfill({ json: { ...aWorkspace, externalAppAccess: 0 } });
// overrideConflicts seeded false; this test doesn't touch the "During
// conflicts" radios, so the value should pass through unchanged.
return route.fulfill({ json: { ...aWorkspace, externalAppAccess: 0, overrideConflicts: false } });
}
if (req.method() === 'PATCH') {
patchBody = JSON.parse(req.postData() ?? '{}');
Expand Down Expand Up @@ -298,8 +303,56 @@ test.describe('workspace settings', () => {
await apps.getByRole('button', { name: 'Save' }).click();

await expect(successToast(page)).toBeVisible();
// Proper API call: externalAppAccess turned on (enabled === 1).
expect(patchBody).toEqual({ externalAppAccess: 1 });
// Proper API call: externalAppAccess turned on (enabled === 1), plus the
// unchanged overrideConflicts value seeded above.
expect(patchBody).toEqual({ externalAppAccess: 1, overrideConflicts: false });
});

// @test e2e: Under "External Apps" > "During conflicts", choosing "Override" (vs. the default
// "Resolve") and clicking "Save" sends the proper API call with the chosen value.
test('choosing "Override" under During Conflicts and saving sends overrideConflicts: true', async ({ page }) => {
await seedAuthenticatedSession(page);
let patchBody: unknown = null;
// Published, with overrideConflicts seeded false so "Resolve" starts selected.
await page.route('**/workspaces/1', (route) => {
const req = route.request();
if (req.method() === 'GET') {
return route.fulfill({ json: { ...aWorkspace, externalAppAccess: 1, overrideConflicts: false } });
}
if (req.method() === 'PATCH') {
patchBody = JSON.parse(req.postData() ?? '{}');
return route.fulfill({ status: 204, body: '' });
}
return route.fallback();
});
await page.route('**/workspaces/1/quests/long/settings', (route) => {
if (route.request().method() === 'GET') {
return route.fulfill({ json: emptyQuestSettings });
}
return route.fulfill({ status: 204, body: '' });
});
await page.route('**/workspaces/1/imagery/settings', route =>
route.fulfill({ json: emptyImagerySettings })
);
await page.route(/(?:imagery-schema|schema)\.json$/, route =>
route.fulfill({ json: imagerySchema })
);

await page.goto('/workspace/1/settings');

const apps = page.locator('form.card', { hasText: 'External Apps' });
const resolve = apps.getByLabel('Resolve');
const override = apps.getByLabel('Override');
// Seeded overrideConflicts: false, so "Resolve" starts selected.
await expect(resolve).toBeChecked();
await expect(override).not.toBeChecked();

await override.check();
await apps.getByRole('button', { name: 'Save' }).click();

await expect(successToast(page)).toBeVisible();
// Proper API call: PATCH workspaces/1 with the chosen overrideConflicts value.
expect(patchBody).toEqual({ externalAppAccess: 1, overrideConflicts: true });
});

// @test e2e: the "Custom Imagery" box is validated against the JSON schema, and a toast shown
Expand Down
2 changes: 2 additions & 0 deletions types/workspaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export interface Workspace {
externalAppAccess: WorkspaceAppAccess;
kartaViewToken?: string;
autoFlagReview?: boolean;
overrideConflicts?: boolean;
role?: WorkspaceRole;
center?: WorkspaceCenter;
projectsCount?: number | null;
Expand All @@ -72,6 +73,7 @@ export interface WorkspacePatch {
description?: string;
externalAppAccess?: WorkspaceAppAccess;
autoFlagReview?: boolean;
overrideConflicts?: boolean;
}

export type QuestSettingsType = 'NONE' | 'JSON' | 'URL';
Expand Down
Loading