Skip to content

Commit

Permalink
Added names to sequence adaptations (#1329)
Browse files Browse the repository at this point in the history
* Added names to sequence adaptations

* Removed unnecessary cancel button

* Removed the skip on sequence adaptation creation test

* Fixed some issues with the seq name tests

* PR fixes

Changed file type check to be for .js, disabled create button when a name isn't provided, clear the input form on creation

* Added a check for files length for create button disabling and seq adaptation checking
  • Loading branch information
cohansen authored Jun 25, 2024
1 parent cf295d3 commit 92e2736
Show file tree
Hide file tree
Showing 8 changed files with 142 additions and 67 deletions.
121 changes: 73 additions & 48 deletions e2e-tests/fixtures/Dictionaries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ export class Dictionaries {
parameterDictionaryTableRow: Locator;
parameterDictionaryTableRowDeleteButton: Locator;
sequenceAdaptationBuffer: Buffer;
sequenceAdaptationName: string;
sequenceAdaptationNameInputField: Locator;
sequenceAdaptationPath: string = 'e2e-tests/data/sequence-adaptation.js';
sequenceAdaptationTable: Locator;
sequenceAdaptationTableRow: Locator;
Expand All @@ -48,7 +50,8 @@ export class Dictionaries {
this.commandDictionaryBuffer = this.readDictionary(this.commandDictionaryName, COMMAND_DICTIONARY_PATH);
this.parameterDictionaryName = uniqueNamesGenerator({ dictionaries: [adjectives, colors, animals] });
this.parameterDictionaryBuffer = this.readDictionary(this.parameterDictionaryName, this.parameterDictionaryPath);
this.sequenceAdaptationBuffer = this.readDictionary(DictionaryType.SequenceAdaptation, this.sequenceAdaptationPath);
this.sequenceAdaptationName = uniqueNamesGenerator({ dictionaries: [adjectives, colors, animals] });
this.sequenceAdaptationBuffer = this.readDictionary(this.sequenceAdaptationName, this.sequenceAdaptationPath);

this.page = page;
}
Expand Down Expand Up @@ -84,14 +87,15 @@ export class Dictionaries {
tableRow: Locator,
type: DictionaryType,
): Promise<void> {
// TODO: Remove this conditional when we add Sequence Adaptation names and we can tie to a specific row.
if (type !== DictionaryType.SequenceAdaptation) {
await expect(tableRow).not.toBeVisible();
await this.fillInputFile(dictionaryBuffer, dictionaryName, type);

if (type === DictionaryType.SequenceAdaptation) {
await expect(this.sequenceAdaptationNameInputField).toBeVisible();
await this.sequenceAdaptationNameInputField.fill(this.sequenceAdaptationName);
}

await this.fillInputFile(dictionaryBuffer, dictionaryName);
await this.createButton.click();
await this.filterTable(table, dictionaryName);
await this.filterTable(table, dictionaryName, type);
await tableRow.waitFor({ state: 'attached' });
await tableRow.waitFor({ state: 'visible' });
await expect(tableRow).toBeVisible();
Expand All @@ -110,11 +114,11 @@ export class Dictionaries {
}

async createSequenceAdaptation(): Promise<void> {
await this.updatePage(this.page, DictionaryType.SequenceAdaptation);
await this.updatePage(this.page, DictionaryType.SequenceAdaptation, this.sequenceAdaptationName);

await this.createDictionary(
this.sequenceAdaptationBuffer,
'Sequence Adaptation',
this.sequenceAdaptationName,
this.sequenceAdaptationTable,
this.sequenceAdaptationTableRow,
DictionaryType.SequenceAdaptation,
Expand All @@ -124,13 +128,13 @@ export class Dictionaries {
async deleteChannelDictionary(): Promise<void> {
await this.updatePage(this.page, DictionaryType.ChannelDictionary, this.channelDictionaryName);

await this.filterTable(this.channelDictionaryTable, this.channelDictionaryName);
await this.filterTable(this.channelDictionaryTable, this.channelDictionaryName, DictionaryType.ChannelDictionary);
await this.deleteDictionary(this.channelDictionaryTableRow, this.channelDictionaryTableRowDeleteButton);
}

async deleteCommandDictionary(): Promise<void> {
await this.updatePage(this.page, DictionaryType.CommandDictionary, this.commandDictionaryName);
await this.filterTable(this.commandDictionaryTable, this.commandDictionaryName);
await this.filterTable(this.commandDictionaryTable, this.commandDictionaryName, DictionaryType.CommandDictionary);

await this.deleteDictionary(this.commandDictionaryTableRow, this.commandDictionaryTableRowDeleteButton);
}
Expand Down Expand Up @@ -164,31 +168,57 @@ export class Dictionaries {
async deleteParameterDictionary(): Promise<void> {
await this.updatePage(this.page, DictionaryType.ParameterDictionary, this.parameterDictionaryName);

await this.filterTable(this.parameterDictionaryTable, this.parameterDictionaryName);
await this.filterTable(
this.parameterDictionaryTable,
this.parameterDictionaryName,
DictionaryType.ParameterDictionary,
);
await this.deleteDictionary(this.parameterDictionaryTableRow, this.parameterDictionaryTableRowDeleteButton);
}

async deleteSequenceAdaptation(): Promise<void> {
await this.updatePage(this.page, DictionaryType.SequenceAdaptation);
await this.updatePage(this.page, DictionaryType.SequenceAdaptation, this.sequenceAdaptationName);

await this.filterTable(
this.sequenceAdaptationTable,
this.sequenceAdaptationName,
DictionaryType.SequenceAdaptation,
);
await this.deleteDictionary(this.sequenceAdaptationTableRow, this.sequenceAdaptationTableRowDeleteButton);
}

private async fillInputFile(dictionaryBuffer: Buffer, dictionaryName: string) {
private async fillInputFile(dictionaryBuffer: Buffer, dictionaryName: string, type: DictionaryType) {
let mimeType: string;
let name: string;

if (type === DictionaryType.SequenceAdaptation) {
mimeType = 'application/x-javascript';
name = dictionaryName + '.js';
} else {
mimeType = 'application/xml';
name = dictionaryName + '.xml';
}

await this.inputFile.focus();
await this.inputFile.setInputFiles({
buffer: dictionaryBuffer,
mimeType: 'application/xml',
name: dictionaryName,
mimeType,
name,
});
await this.inputFile.evaluate(e => e.blur());
}

private async filterTable(table: Locator, dictionaryName: string) {
private async filterTable(table: Locator, dictionaryName: string, type: DictionaryType) {
await table.waitFor({ state: 'attached' });
await table.waitFor({ state: 'visible' });
let nameColumnHeader: Locator | undefined = undefined;

if (type === DictionaryType.SequenceAdaptation) {
nameColumnHeader = table.getByRole('columnheader', { name: 'Name' });
} else {
nameColumnHeader = table.getByRole('columnheader', { name: 'Mission' });
}

const nameColumnHeader = await table.getByRole('columnheader', { name: 'Mission' });
await nameColumnHeader.hover();

const filterIcon = await nameColumnHeader.locator('.ag-icon-menu');
Expand Down Expand Up @@ -221,36 +251,31 @@ export class Dictionaries {
this.createButton = this.page.locator(`button:has-text("Create")`);
this.inputFile = this.page.locator('input[name="file"]');

// TODO: Sequence Adaptations don't have a name, so skip this for these tests. Can be cleaned up when we add names.
if (dictionaryName !== undefined) {
this.channelDictionaryTable = this.page.locator('.panel:has-text("Channel Dictionaries")').getByRole('treegrid');
this.channelDictionaryTableRow = this.channelDictionaryTable.getByRole('row', { name: dictionaryName });
this.channelDictionaryTableRowDeleteButton = this.channelDictionaryTableRow
.getByRole('gridcell')
.getByRole('button', { name: `Delete ${DictionaryType.ChannelDictionary}` });

this.commandDictionaryTable = this.page.locator('.panel:has-text("Command Dictionaries")').getByRole('treegrid');
this.commandDictionaryTableRow = this.commandDictionaryTable.getByRole('row', { name: dictionaryName });
this.commandDictionaryTableRowDeleteButton = this.commandDictionaryTable
.getByRole('gridcell')
.getByRole('button', { name: `Delete ${DictionaryType.CommandDictionary}` });

this.parameterDictionaryTable = this.page
.locator('.panel:has-text("Parameter Dictionaries")')
.getByRole('treegrid');
this.parameterDictionaryTableRow = this.parameterDictionaryTable.getByRole('row', { name: dictionaryName });
this.parameterDictionaryTableRowDeleteButton = this.parameterDictionaryTable
.getByRole('gridcell')
.getByRole('button', { name: `Delete ${DictionaryType.ParameterDictionary}` });
} else {
this.sequenceAdaptationTable = this.page.locator('.panel:has-text("Sequence Adaptations")').getByRole('treegrid');
this.sequenceAdaptationTableRows = this.page
.locator('.panel', { hasText: 'Sequence Adaptations' })
.locator('.body .ag-row');
this.sequenceAdaptationTableRow = this.sequenceAdaptationTableRows.first();
this.sequenceAdaptationTableRowDeleteButton = this.sequenceAdaptationTableRow.locator(
`button[aria-label="Delete ${DictionaryType.SequenceAdaptation}"]`,
);
}
this.channelDictionaryTable = this.page.locator('.panel:has-text("Channel Dictionaries")').getByRole('treegrid');
this.channelDictionaryTableRow = this.channelDictionaryTable.getByRole('row', { name: dictionaryName });
this.channelDictionaryTableRowDeleteButton = this.channelDictionaryTableRow
.getByRole('gridcell')
.getByRole('button', { name: `Delete ${DictionaryType.ChannelDictionary}` });

this.commandDictionaryTable = this.page.locator('.panel:has-text("Command Dictionaries")').getByRole('treegrid');
this.commandDictionaryTableRow = this.commandDictionaryTable.getByRole('row', { name: dictionaryName });
this.commandDictionaryTableRowDeleteButton = this.commandDictionaryTable
.getByRole('gridcell')
.getByRole('button', { name: `Delete ${DictionaryType.CommandDictionary}` });

this.parameterDictionaryTable = this.page
.locator('.panel:has-text("Parameter Dictionaries")')
.getByRole('treegrid');
this.parameterDictionaryTableRow = this.parameterDictionaryTable.getByRole('row', { name: dictionaryName });
this.parameterDictionaryTableRowDeleteButton = this.parameterDictionaryTable
.getByRole('gridcell')
.getByRole('button', { name: `Delete ${DictionaryType.ParameterDictionary}` });

this.sequenceAdaptationTable = this.page.locator('.panel:has-text("Sequence Adaptations")').getByRole('treegrid');
this.sequenceAdaptationTableRow = this.sequenceAdaptationTable.getByRole('row', { name: dictionaryName });
this.sequenceAdaptationTableRowDeleteButton = this.sequenceAdaptationTable
.getByRole('gridcell')
.getByRole('button', { name: `Delete ${DictionaryType.SequenceAdaptation}` });
this.sequenceAdaptationNameInputField = this.page.locator(`input[name="sequenceAdaptationName"]`);
}
}
2 changes: 0 additions & 2 deletions e2e-tests/fixtures/Parcels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { Locator, Page, expect } from '@playwright/test';
import { adjectives, animals, colors, uniqueNamesGenerator } from 'unique-names-generator';

export class Parcels {
cancelButton: Locator;
closeButton: Locator;
confirmModal: Locator;
confirmModalDeleteButton: Locator;
Expand Down Expand Up @@ -107,7 +106,6 @@ export class Parcels {
updatePage(page: Page): void {
this.page = page;

this.cancelButton = page.locator(`button:has-text("Cancel")`);
this.closeButton = page.locator(`button:has-text("Close")`);
this.confirmModal = page.locator(`.modal:has-text("Delete Parcel")`);
this.confirmModalDeleteButton = this.confirmModal.getByRole('button', { name: 'Delete' });
Expand Down
2 changes: 1 addition & 1 deletion e2e-tests/tests/dictionaries.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ test.describe('Dictionaries', () => {
});
});

test.describe.skip('Sequence Adaptation', () => {
test.describe('Sequence Adaptation', () => {
test('Create sequence adaptation', async () => {
await dictionaries.createSequenceAdaptation();
});
Expand Down
1 change: 1 addition & 0 deletions src/components/parcels/DictionaryTable.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@
$: sequenceAdaptationColumDefs = [
...(isEditingParcel ? editingColumnDefs : []),
{ field: 'name', filter: 'text', headerName: 'Name', sortable: true, width: 100 },
{
field: 'id',
filter: 'number',
Expand Down
45 changes: 40 additions & 5 deletions src/routes/dictionaries/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,32 @@
let createDictionaryError: string | null = null;
let creatingDictionary: boolean = false;
let files: FileList;
let file: File;
let fileInput: HTMLInputElement;
let isSequenceAdaptation: boolean = false;
let sequenceAdaptationName: string;
$: hasCreatePermission = featurePermissions.commandDictionary.canCreate(data.user);
$: createButtonDisabled = !files;
$: createButtonDisabled = !files || files?.length === 0 || (isSequenceAdaptation && sequenceAdaptationName === '');
$: {
if (files && files.length > 0) {
file = files[0];
async function uploadDictionaryOrAdaptation(files: FileList) {
isSequenceAdaptation = file.name.substring(file.name.lastIndexOf('.')) === '.js';
sequenceAdaptationName = '';
}
}
async function uploadDictionaryOrAdaptation() {
createDictionaryError = null;
creatingDictionary = true;
try {
const uploadedDictionaryOrAdaptation = await effects.uploadDictionaryOrAdaptation(files, data.user);
const uploadedDictionaryOrAdaptation = await effects.uploadDictionaryOrAdaptation(
file,
data.user,
sequenceAdaptationName,
);
if (uploadedDictionaryOrAdaptation === null) {
throw Error('Failed to upload file');
Expand All @@ -58,11 +74,15 @@
showSuccessToast('Parameter Dictionary Created Successfully');
break;
}
case 'ADAPTATION': {
case DictionaryTypes.ADAPTATION: {
showSuccessToast('Sequence Adaptation Created Successfully');
break;
}
}
fileInput.value = '';
isSequenceAdaptation = false;
sequenceAdaptationName = '';
} catch (e) {
createDictionaryError = (e as Error).message;
showFailureToast('Command Dictionary Create Failed');
Expand Down Expand Up @@ -102,24 +122,39 @@
</svelte:fragment>

<svelte:fragment slot="body">
<form on:submit|preventDefault={() => uploadDictionaryOrAdaptation(files)}>
<form on:submit|preventDefault={uploadDictionaryOrAdaptation}>
<AlertError class="m-2" error={createDictionaryError} />

<fieldset>
<label for="file">AMPCS XML File or Sequence Adaptation</label>
<input
accept=".xml,.js"
class="w-100 st-typography-body"
name="file"
required
type="file"
bind:files
bind:this={fileInput}
use:permissionHandler={{
hasPermission: hasCreatePermission,
permissionError: createPermissionError,
}}
/>
</fieldset>

{#if isSequenceAdaptation}
<fieldset>
<input
bind:value={sequenceAdaptationName}
autocomplete="off"
class="st-input w-100"
name="sequenceAdaptationName"
placeholder="Enter Sequence Adaptation Name"
required={isSequenceAdaptation}
/>
</fieldset>
{/if}

<fieldset>
<button
class="st-button w-100"
Expand Down
1 change: 1 addition & 0 deletions src/types/sequencing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export type ParameterDictionary = {

export type SequenceAdaptation = {
adaptation: string;
name: string;
type: DictionaryTypes.ADAPTATION;
} & DictionaryType;

Expand Down
Loading

0 comments on commit 92e2736

Please sign in to comment.