Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Import SATF Library Sequence #1592

Open
wants to merge 7 commits into
base: develop
Choose a base branch
from
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
67 changes: 67 additions & 0 deletions src/components/modals/LibrarySequenceModal.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<svelte:options immutable={true} />

<script lang="ts">
import { createEventDispatcher } from 'svelte';
import { parcels } from '../../stores/sequencing';
import Modal from './Modal.svelte';
import ModalContent from './ModalContent.svelte';
import ModalFooter from './ModalFooter.svelte';
import ModalHeader from './ModalHeader.svelte';

export let height: number = 200;
export let width: number = 380;

const dispatch = createEventDispatcher<{
close: void;
save: { library: FileList; parcel: number };
}>();

let saveButtonDisabled: boolean = true;
let modalTitle: string;
let libraryName: FileList;
let parcelId: number;

$: saveButtonDisabled = parcelId === null || libraryName === undefined;
$: modalTitle = 'Import Library';

function save() {
if (!saveButtonDisabled) {
dispatch('save', { library: libraryName, parcel: parcelId });
}
}

function onKeydown(event: KeyboardEvent) {
const { key } = event;
if (key === 'Enter') {
event.preventDefault();
save();
}
}
</script>

<svelte:window on:keydown={onKeydown} />

<Modal {height} {width}>
<ModalHeader on:close>{modalTitle}</ModalHeader>

<ModalContent>
<div class="st-typography-body">Parcel (required)</div>
<select bind:value={parcelId} class="st-select w-100" name="parcel">
<option value={null} />
{#each $parcels as parcel}
<option value={parcel.id}>
{parcel.name}
</option>
{/each}
</select>
<fieldset>
<label for="name">Imported Library</label>
<input bind:files={libraryName} class="w-100" name="libraryFile" type="file" />
</fieldset>
</ModalContent>

<ModalFooter>
<button class="st-button secondary" on:click={() => dispatch('close')}> Cancel </button>
<button class="st-button" disabled={saveButtonDisabled} on:click={save}> Import </button>
</ModalFooter>
</Modal>
51 changes: 51 additions & 0 deletions src/components/sequencing/Sequences.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,13 @@
import { parcels, userSequences, userSequencesColumns, workspaces } from '../../stores/sequencing';
import type { User } from '../../types/app';
import type { Parcel, UserSequence, Workspace } from '../../types/sequencing';
import { satfToSequence } from '../../utilities/codemirror/satf/satf-sasf-utils';
import effects from '../../utilities/effects';
import { getSearchParameterNumber, setQueryParam } from '../../utilities/generic';
import { showLibrarySequenceModel } from '../../utilities/modal';
import { permissionHandler } from '../../utilities/permissionHandler';
import { featurePermissions } from '../../utilities/permissions';
import { showFailureToast } from '../../utilities/toast';
import Input from '../form/Input.svelte';
import CssGrid from '../ui/CssGrid.svelte';
import CssGridGutter from '../ui/CssGridGutter.svelte';
Expand Down Expand Up @@ -59,6 +63,41 @@
const workspaceId = getSearchParameterNumber(SearchParameters.WORKSPACE_ID);
goto(`${base}/sequencing/new${workspaceId ? `?${SearchParameters.WORKSPACE_ID}=${workspaceId}` : ''}`);
}
async function importLibarySequences(): Promise<void> {
const workspaceId = getSearchParameterNumber(SearchParameters.WORKSPACE_ID);
if (workspaceId === null) {
console.log("Workspace doesn't exist");
showFailureToast("Library Import: Workspace doesn't exist");
return;
}
const { confirm, value } = await showLibrarySequenceModel();
if (confirm && value) {
const fileName = await value.libraryFile.name;
const type = fileName.slice(fileName.lastIndexOf('.') + 1);
if (type !== 'satf') {
console.log(`Unsupported file type ${type}`);
showFailureToast('Library Import: Unsupported file type');
return;
}
const contents = await value.libraryFile.text();
const parcel = await value.parcel;
const sequences = (await satfToSequence(contents)).sequences;
sequences.forEach(async seqN => {
await effects.createUserSequence(
{
definition: seqN.sequence,
name: seqN.name,
parcel_id: parcel,
seq_json: '',
workspace_id: workspaceId,
},
user,
);
});
}
}
</script>

<CssGrid bind:columns={$userSequencesColumns}>
Expand Down Expand Up @@ -86,6 +125,18 @@
>
New Sequence
</button>

<button
class="st-button secondary ellipsis"
use:permissionHandler={{
hasPermission: featurePermissions.sequences.canCreate(user),
permissionError: 'You do not have permission to upload library sequences',
}}
disabled={workspace === undefined}
on:click|stopPropagation={importLibarySequences}
>
Import Library
</button>
</div>
</svelte:fragment>

Expand Down
5 changes: 3 additions & 2 deletions src/utilities/codemirror/codemirror-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,10 +120,11 @@ export function getDefaultVariableArgs(parameters: VariableDeclaration[]): strin
return parameter.allowable_values && parameter.allowable_values.length > 0
? `"${parameter.allowable_values[0]}"`
: parameter.enum_name
? `${parameter.enum_name}`
? `"${parameter.enum_name}"`
: 'UNKNOWN';
default:
throw Error(`unknown argument type ${parameter.type}`);
console.log(`Unknown argument type ${parameter.type}`);
return `ERROR:"${parameter.name}"`;
}
}) as string[];
}
Expand Down
12 changes: 6 additions & 6 deletions src/utilities/codemirror/satf/satf-sasf-utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,9 @@ describe('satfToSequence', () => {
expect(result.sequences[0].name).toStrictEqual('test');
expect(result.sequences[0].sequence).toStrictEqual(`## test
R00:01:00 01VV param6 10 false "abc" # This command turns, to correct position.
@MODEL(x,1,"00:00:00")
@MODEL(z,1.1,"00:00:00")
@MODEL(y,"abc","00:00:00")`);
@MODEL "x" 1 "00:00:00"
@MODEL "z" 1.1 "00:00:00"
@MODEL "y" "abc" "00:00:00"`);
});

it('should return multiple sequence with models', async () => {
Expand Down Expand Up @@ -127,9 +127,9 @@ R00:01:00 01VV param6 10 false "abc" # This command turns, to correct position.
expect(result.sequences[0].name).toStrictEqual('test');
expect(result.sequences[0].sequence).toStrictEqual(`## test
R00:01:00 01VV param6 10 false "abc" # This command turns, to correct position.
@MODEL(x,1,"00:00:00")
@MODEL(z,1.1,"00:00:00")
@MODEL(y,"abc","00:00:00")`);
@MODEL "x" 1 "00:00:00"
@MODEL "z" 1.1 "00:00:00"
@MODEL "y" "abc" "00:00:00"`);
});
});

Expand Down
2 changes: 1 addition & 1 deletion src/utilities/codemirror/satf/satf-sasf-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -881,7 +881,7 @@ function parseModel(modelNode: SyntaxNode | null, text: string): string {
if (!keyNode || !valueNode) {
return null;
}
return `@MODEL(${text.slice(keyNode.from, keyNode.to)},${text.slice(valueNode.from, valueNode.to)},"00:00:00")`;
return `@MODEL "${text.slice(keyNode.from, keyNode.to)}" ${text.slice(valueNode.from, valueNode.to)} "00:00:00"`;
})
.filter(model => model !== null)
.join('\n');
Expand Down
2 changes: 1 addition & 1 deletion src/utilities/codemirror/satf/satf-sasf.grammar
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
HeaderPairs { HeaderPair* }
HeaderPair {Key"="Value ";" newLine}
Key { (identifier | ":")* }
Value { headerValue+ }
Value { (headerValue | "/")+ }
SfduHeader { headerMarker newLine HeaderPairs headerMarker}

Start { "$$"identifier identifier* newLine}
Expand Down
34 changes: 34 additions & 0 deletions src/utilities/modal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import DeleteExternalEventSourceTypeModal from '../components/modals/DeleteExter
import DeleteExternalSourceModal from '../components/modals/DeleteExternalSourceModal.svelte';
import EditViewModal from '../components/modals/EditViewModal.svelte';
import ExpansionSequenceModal from '../components/modals/ExpansionSequenceModal.svelte';
import LibrarySequenceModal from '../components/modals/LibrarySequenceModal.svelte';
import ManageGroupsAndTypesModal from '../components/modals/ManageGroupsAndTypesModal.svelte';
import ManagePlanConstraintsModal from '../components/modals/ManagePlanConstraintsModal.svelte';
import ManagePlanDerivationGroupsModal from '../components/modals/ManagePlanDerivationGroupsModal.svelte';
Expand Down Expand Up @@ -556,6 +557,39 @@ export async function showWorkspaceModal(
});
}

export async function showLibrarySequenceModel(): Promise<ModalElementValue<{ libraryFile: File; parcel: number }>> {
return new Promise(resolve => {
if (browser) {
const target: ModalElement | null = document.querySelector('#svelte-modal');

if (target) {
const workspaceModal = new LibrarySequenceModal({
target,
});
target.resolve = resolve;

workspaceModal.$on('close', () => {
target.replaceChildren();
target.resolve = null;
resolve({ confirm: false });
workspaceModal.$destroy();
});

workspaceModal.$on('save', (e: CustomEvent<{ library: FileList; parcel: number }>) => {
const library = e.detail.library[0];
const parcel = e.detail.parcel;
target.replaceChildren();
target.resolve = null;
resolve({ confirm: true, value: { libraryFile: library, parcel } });
workspaceModal.$destroy();
});
}
} else {
resolve({ confirm: false });
}
});
}

/**
* Shows a CreatePlanBranchModal with the supplied arguments.
*/
Expand Down
18 changes: 13 additions & 5 deletions src/utilities/sequence-editor/sequence-linter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -566,16 +566,24 @@ function validateActivateLoad(
} else {
value = parseInt(num);
}
parameter.allowable_ranges?.forEach(range => {
if (value < range.min || value > range.max) {

if (parameter.allowable_ranges) {
const invalidRanges = parameter.allowable_ranges.filter(range => {
return value < range.min || value > range.max;
});
if (invalidRanges.length === parameter.allowable_ranges.length) {
diagnostics.push({
from: arg.from,
message: `Value must be between ${range.min} and ${range.max}`,
message: `Value must be between ${parameter.allowable_ranges
.map(range => {
return `[${range.min} and ${range.max}]`;
})
.join(' or ')}`,
severity: 'error',
to: arg.to,
});
}
});
}

if (parameter.type === 'UINT') {
if (value < 0) {
Expand Down Expand Up @@ -1237,7 +1245,7 @@ function validateCommandStructure(
addDefault: (view: any) => any,
): Diagnostic | undefined {
if (arguments.length > 0) {
if (!argsNode || argsNode.length === 0) {
if (!argsNode || (argsNode.length === 0 && exactArgSize > 0)) {
return {
actions: [],
from: stemNode.from,
Expand Down
Loading