Skip to content
Merged
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
1 change: 1 addition & 0 deletions e2e-tests/.ci/server.generate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ services:
MM_FEATUREFLAGS_PERMISSIONPOLICIES: "true"
MM_FEATUREFLAGS_TEAMMEMBERSHIPACCESSCONTROL: "true"
MM_FEATUREFLAGS_CLASSIFICATIONMARKINGS: "true"
MM_FEATUREFLAGS_PROPERTYFIELDRANK: "true"
MM_FEATUREFLAGS_ATTRIBUTEVALUEMASKING: "true"
MM_LOGSETTINGS_ENABLEDIAGNOSTICS: "false"
MM_LOGSETTINGS_CONSOLELEVEL: "DEBUG"
Expand Down
1 change: 1 addition & 0 deletions e2e-tests/playwright/lib/src/server/default_config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -788,6 +788,7 @@
EnableAIPluginBridge: false,
EnableAIRecaps: false,
ClassificationMarkings: true,
PropertyFieldRank: true,
IntegratedBoards: false,
CJKSearch: true,
ManagedChannelCategories: false,
Expand All @@ -809,7 +810,7 @@
MoveThreadFromPrivateChannelEnable: false,
MoveThreadFromDirectMessageChannelEnable: false,
MoveThreadFromGroupMessageChannelEnable: false,
},

Check warning on line 813 in e2e-tests/playwright/lib/src/server/default_config.ts

View workflow job for this annotation

GitHub Actions / check

File has too many lines (870). Maximum allowed is 800
ConnectedWorkspacesSettings: {
EnableSharedChannels: false,
EnableRemoteClusterService: false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -252,4 +252,123 @@ export default class SystemProperties {
validationMessage(text: string | RegExp): Locator {
return this.container.getByText(text);
}

// ── Ranked fields ───────────────────────────────────────────────────

/**
* The ranked-values cell for the most recently added row. Ranked rows
* render numbered chips + an add-value input instead of the react-select.
*/
lastRankValues(): Locator {
return this.container.locator('.user-property-rank-values').last();
}

/**
* Add-value input inside the ranked-values cell (auto-assigns next rank).
* Located by class, not placeholder: the placeholder ('Add values… (required)')
* only renders in the empty state and disappears once a value is added, so
* placeholder-based lookups break when adding more than one value.
*/
rankAddInput(): Locator {
return this.lastRankValues().locator('.user-property-rank-values__add-input');
}

async addRankValueToLast(value: string) {
const input = this.rankAddInput();
await input.fill(value);
await input.press('Enter');
}

async addRankValuesToLast(values: string[]) {
for (const value of values) {
await this.addRankValueToLast(value);
}
}

/** All ranked chip buttons, in DOM order (ascending rank, left→right). */
rankChips(): Locator {
return this.container.locator('.user-property-rank-values__chip');
}

/**
* A single ranked chip button by its option label. Unlike the test-id
* locator, this works for both newly-added options (no server id yet) and
* API-created options (real id). Exact text avoids matching "Secret"
* inside "TopSecret".
*/
rankChip(name: string): Locator {
return this.container
.locator('.user-property-rank-values__chip')
.filter({has: this.page.getByText(name, {exact: true})});
}

/** The numbered badge inside a chip; its text is the rank integer. */
rankBadge(name: string): Locator {
return this.rankChip(name).locator('.rank-badge');
}

/** Ordered chip labels as displayed (trimmed of the badge text). */
async rankChipLabels(): Promise<string[]> {
return this.rankChips().locator('.user-property-rank-values__chip-label').allInnerTexts();
}

// Per-chip popover (rendered at page level via portal).

async openRankChipPopover(name: string) {
await this.rankChip(name).click();
}

/**
* The label textbox in the per-chip popover. The Input widget surfaces
* "Option label" as a floating ARIA label (not a placeholder attribute),
* so locate it by role within the popover menu.
*/
rankPopoverLabelInput(): Locator {
return this.page.getByRole('menu', {name: 'Edit option'}).getByRole('textbox');
}

rankPopoverRankSubmenu(): Locator {
return this.page.getByRole('menuitem', {name: /^Rank/});
}

async moveRankOptionToPosition(rankValue: number) {
await this.rankPopoverRankSubmenu().hover();
await this.page.getByRole('menuitemradio', {name: String(rankValue), exact: true}).click();
}

async removeRankOptionFromPopover() {
await this.page.getByRole('menuitem', {name: 'Remove option'}).click();
}

async openEditRanking(fieldId: string) {
await this.openDotMenu(fieldId);
await this.page.getByRole('menuitem', {name: 'Edit ranking'}).click();
}

async openEditRankingForUnsaved() {
await this.openDotMenuForUnsaved();
await this.page.getByRole('menuitem', {name: 'Edit ranking'}).click();
}

// ── Ranked schema modal ─────────────────────────────────────────────

rankedModal(): Locator {
return this.page.locator('#rankedSchemaModal');
}

rankedModalRows(): Locator {
return this.rankedModal().locator('.ranked-schema-modal__row');
}

rankedModalSaveButton(): Locator {
return this.rankedModal().getByRole('button', {name: 'Save'});
}

async addRankedModalValue() {
await this.rankedModal().getByRole('button', {name: 'Add value'}).click();
}

async saveRankedModal() {
await this.rankedModalSaveButton().click();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,33 @@ class AdminUserCard {
getCpaMultiselectContainer(labelText: string): Locator {
return this.getFieldColumn(labelText);
}

// ── Ranked CPA picker ───────────────────────────────────────────────

/** The menu-button for a ranked CPA field, located by its exact label. */
getCpaRankPicker(labelText: string): Locator {
return this.getFieldColumn(labelText).locator('.cpa-rank-select__button');
}

/** The open ranked-value menu (rendered at page level via portal). */
cpaRankMenu(): Locator {
return this.body.page().getByRole('menu', {name: 'Select an option'});
}

/** Menu option rows, in DOM order (highest rank first). */
cpaRankMenuItems(): Locator {
return this.cpaRankMenu().getByRole('menuitemradio');
}

async openCpaRankPicker(labelText: string): Promise<void> {
await this.getCpaRankPicker(labelText).click();
}

/** Open the picker and choose an option by its exact label. */
async selectCpaRankValue(labelText: string, optionName: string): Promise<void> {
await this.openCpaRankPicker(labelText);
await this.cpaRankMenu().getByText(optionName, {exact: true}).click();
}
}

class TeamMembershipPanel {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const TEMPLATE_OBJECT_TYPE = 'template';
const CHANNEL_OBJECT_TYPE = 'channel';
const TARGET_TYPE = 'system';
const CLASSIFICATION_FIELD_NAME = 'classification';
const CHANNEL_LINKED_FIELD_NAME = 'channel_classification';
const CHANNEL_LINKED_FIELD_NAME = 'classification';

export const TEST_LEVELS = [
{name: 'UNCLASSIFIED', color: '#007A33', rank: 1},
Expand Down Expand Up @@ -51,7 +51,7 @@ export async function deleteClassificationFieldsIfExist(adminClient: Client4) {
try {
const linkedFields = await adminClient.getPropertyFields(PROPERTY_GROUP, objectType, TARGET_TYPE, '');
for (const f of linkedFields.filter(
(f) => f.name === 'system_classification' && f.delete_at === 0 && f.linked_field_id,
(f) => f.name === 'classification' && f.delete_at === 0 && f.linked_field_id,
)) {
await adminClient.deletePropertyField(PROPERTY_GROUP, objectType, f.id);
}
Expand Down
Loading
Loading