-
Notifications
You must be signed in to change notification settings - Fork 1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
New components for seeding the rule builder.
This uses the wizard introduced by David for workflow exports instead of adapting the upload paradigm. The old component was very compact but also very unintuitive and just hacked together as quickly as possible and never really revisited. The wizard provides more space to explain things and is probably the way we want to go but still needs some serious love from someone better at UI than me. I need a component like this... but different for sample sheet seeding so I wanted to do a refresh and get something we're all more comfortable with ahead of that.
- Loading branch information
Showing
17 changed files
with
561 additions
and
30 deletions.
There are no files selected for viewing
200 changes: 200 additions & 0 deletions
200
client/src/components/Collections/BuildFileSetWizard.vue
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,200 @@ | ||
<script setup lang="ts"> | ||
import { BCardGroup } from "bootstrap-vue"; | ||
import { computed, ref } from "vue"; | ||
import { type components } from "@/api/schema"; | ||
import { getGalaxyInstance } from "@/app"; | ||
import { useWizard } from "@/components/Common/Wizard/useWizard"; | ||
import { getRemoteEntriesAt } from "@/components/Upload/utils"; | ||
import { useToolRouting } from "@/composables/route"; | ||
import { type RulesCreatingWhat, type RulesSourceFrom } from "./wizard/types"; | ||
import CreatingWhat from "./wizard/CreatingWhat.vue"; | ||
import PasteData from "./wizard/PasteData.vue"; | ||
import SelectDataset from "./wizard/SelectDataset.vue"; | ||
import SelectFolder from "./wizard/SelectFolder.vue"; | ||
import SourceFromCollection from "./wizard/SourceFromCollection.vue"; | ||
import SourceFromDatasetAsTable from "./wizard/SourceFromDatasetAsTable.vue"; | ||
import SourceFromPastedData from "./wizard/SourceFromPastedData.vue"; | ||
import SourceFromRemoteFiles from "./wizard/SourceFromRemoteFiles.vue"; | ||
import GenericWizard from "@/components/Common/Wizard/GenericWizard.vue"; | ||
interface Props { | ||
fileSourcesConfigured: boolean; | ||
ftpUploadSite?: string; | ||
} | ||
const props = defineProps<Props>(); | ||
type ListUriResponse = components["schemas"]["ListUriResponse"]; | ||
type RemoteFile = components["schemas"]["RemoteFile"]; | ||
type RemoteDirectory = components["schemas"]["RemoteDirectory"]; | ||
const creatingWhat = ref<RulesCreatingWhat>("datasets"); | ||
const creatingWhatTitle = computed(() => { | ||
return creatingWhat.value == "datasets" ? "Datasets" : "Collections"; | ||
}); | ||
const sourceInstructions = computed(() => { | ||
return `${creatingWhatTitle.value} can be created from a set or files, URIs, or existing datasets.`; | ||
}); | ||
const sourceFrom = ref<RulesSourceFrom>("remote_files"); | ||
const pasteData = ref<string[][]>([] as string[][]); | ||
const tabularDatasetContents = ref<string[][]>([] as string[][]); | ||
const { routeToTool } = useToolRouting(); | ||
const wizard = useWizard({ | ||
"select-what": { | ||
label: "What is being created?", | ||
instructions: computed(() => { | ||
return `Are you creating datasets or collections?`; | ||
}), | ||
isValid: () => true, | ||
isSkippable: () => false, | ||
}, | ||
"select-source": { | ||
label: "Select source", | ||
instructions: sourceInstructions, | ||
isValid: () => true, | ||
isSkippable: () => false, | ||
}, | ||
"select-remote-files-folder": { | ||
label: "Select folder", | ||
instructions: "Select folder of files to import.", | ||
isValid: () => sourceFrom.value === "remote_files" && Boolean(uris.value.length > 0), | ||
isSkippable: () => sourceFrom.value !== "remote_files", | ||
}, | ||
"paste-data": { | ||
label: "Paste data", | ||
instructions: "Paste data containing URIs and optional extra metadata.", | ||
isValid: () => sourceFrom.value === "pasted_table" && pasteData.value.length > 0, | ||
isSkippable: () => sourceFrom.value !== "pasted_table", | ||
}, | ||
"select-dataset": { | ||
label: "Select dataset", | ||
instructions: "Select tabular dataset to load URIs and metadata from.", | ||
isValid: () => sourceFrom.value === "dataset_as_table" && tabularDatasetContents.value.length > 0, | ||
isSkippable: () => sourceFrom.value !== "dataset_as_table", | ||
}, | ||
}); | ||
const importButtonLabel = computed(() => { | ||
if (sourceFrom.value == "collection") { | ||
return "Transform"; | ||
} else { | ||
return "Import"; | ||
} | ||
}); | ||
const emit = defineEmits(["dismiss"]); | ||
type SelectionType = "raw" | "remote_files"; | ||
type ElementsType = RemoteFile[] | string[][]; | ||
// it would be nice to have a real type from the rule builder but | ||
// it is older code. This is really outlining what this component can | ||
// produce and not what the rule builder can consume which is a wide | ||
// superset of this. | ||
interface Entry { | ||
dataType: RulesCreatingWhat; | ||
ftpUploadSite?: string; | ||
elements?: ElementsType | undefined; | ||
content?: string; | ||
selectionType: SelectionType; | ||
} | ||
function launchRuleBuilder() { | ||
const Galaxy = getGalaxyInstance(); | ||
let elements: ElementsType | undefined = undefined; | ||
let selectionType: SelectionType = "raw"; | ||
if (sourceFrom.value == "remote_files") { | ||
elements = uris.value; | ||
selectionType = "remote_files"; | ||
} else if (sourceFrom.value == "pasted_table") { | ||
elements = pasteData.value; | ||
} else if (sourceFrom.value == "dataset_as_table") { | ||
elements = tabularDatasetContents.value; | ||
} | ||
const entry: Entry = { | ||
dataType: creatingWhat.value, | ||
ftpUploadSite: props.ftpUploadSite, | ||
selectionType: selectionType, | ||
elements: elements, | ||
}; | ||
Galaxy.currHistoryPanel.buildCollectionFromRules(entry, null, true); | ||
} | ||
function submit() { | ||
if (sourceFrom.value == "collection") { | ||
routeToTool("__APPLY_RULES__"); | ||
} else { | ||
launchRuleBuilder(); | ||
} | ||
emit("dismiss"); | ||
} | ||
const isBusy = ref<boolean>(false); | ||
function setCreatingWhat(what: RulesCreatingWhat) { | ||
creatingWhat.value = what; | ||
} | ||
const uris = ref<RemoteFile[]>([]); | ||
async function setRemoteFilesFolder(uri: string) { | ||
isBusy.value = true; | ||
const rawFiles = await getRemoteEntriesAt(uri); | ||
if (rawFiles) { | ||
const files: RemoteFile[] = (rawFiles as ListUriResponse).filter( | ||
(file: RemoteFile | RemoteDirectory) => file["class"] == "File" | ||
) as RemoteFile[]; | ||
uris.value = files; | ||
} | ||
isBusy.value = false; | ||
} | ||
async function onFtp() { | ||
setRemoteFilesFolder("gxftp://"); | ||
} | ||
function setDatasetContents(contents: string[][]) { | ||
tabularDatasetContents.value = contents; | ||
} | ||
function setSourceForm(newValue: RulesSourceFrom) { | ||
sourceFrom.value = newValue; | ||
} | ||
function setPasteTable(newValue: string[][]) { | ||
pasteData.value = newValue; | ||
} | ||
</script> | ||
|
||
<template> | ||
<GenericWizard :use="wizard" :submit-button-label="importButtonLabel" @submit="submit"> | ||
<div v-if="wizard.isCurrent('select-what')"> | ||
<CreatingWhat :creating-what="creatingWhat" @onChange="setCreatingWhat" /> | ||
</div> | ||
<div v-else-if="wizard.isCurrent('select-source')"> | ||
<BCardGroup deck> | ||
<SourceFromRemoteFiles :selected="sourceFrom === 'remote_files'" @select="setSourceForm" /> | ||
<SourceFromPastedData :selected="sourceFrom === 'pasted_table'" @select="setSourceForm" /> | ||
<SourceFromDatasetAsTable :selected="sourceFrom === 'dataset_as_table'" @select="setSourceForm" /> | ||
<SourceFromCollection | ||
v-if="creatingWhat == 'collections'" | ||
:selected="sourceFrom === 'collection'" | ||
@select="setSourceForm" /> | ||
</BCardGroup> | ||
</div> | ||
<div v-else-if="wizard.isCurrent('paste-data')"> | ||
<PasteData @onChange="setPasteTable" /> | ||
</div> | ||
<div v-else-if="wizard.isCurrent('select-remote-files-folder')"> | ||
<SelectFolder :ftp-upload-site="ftpUploadSite" @onChange="setRemoteFilesFolder" @onFtp="onFtp" /> | ||
</div> | ||
<div v-else-if="wizard.isCurrent('select-dataset')"> | ||
<SelectDataset @onChange="setDatasetContents" /> | ||
</div> | ||
</GenericWizard> | ||
</template> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
<script setup lang="ts"> | ||
import { BCard, BCardGroup, BCardTitle } from "bootstrap-vue"; | ||
import { borderVariant } from "@/components/Common/Wizard/utils"; | ||
import { type RulesCreatingWhat } from "./types"; | ||
interface Props { | ||
creatingWhat: RulesCreatingWhat; | ||
} | ||
const emit = defineEmits(["onChange"]); | ||
defineProps<Props>(); | ||
</script> | ||
|
||
<template> | ||
<BCardGroup deck> | ||
<BCard | ||
data-creating-what="datasets" | ||
class="wizard-selection-card" | ||
:border-variant="borderVariant(creatingWhat === 'datasets')" | ||
@click="emit('onChange', 'datasets')"> | ||
<BCardTitle> | ||
<b>Datasets</b> | ||
</BCardTitle> | ||
<div> | ||
This options lets you import any number of individual Galaxy datasets. These will be unique items in | ||
your Galaxy history. | ||
</div> | ||
</BCard> | ||
<BCard | ||
data-creating-what="collections" | ||
class="wizard-selection-card" | ||
:border-variant="borderVariant(creatingWhat === 'collections')" | ||
@click="emit('onChange', 'collections')"> | ||
<BCardTitle> | ||
<b>Collections</b> | ||
</BCardTitle> | ||
<div>This options lets you create or more Galaxy dataset collections.</div> | ||
</BCard> | ||
</BCardGroup> | ||
</template> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
<script setup lang="ts"> | ||
import { watch } from "vue"; | ||
import { useTableSummary } from "@/components/Collections/tables"; | ||
import JaggedDataAlert from "./JaggedDataAlert.vue"; | ||
const emit = defineEmits(["onChange"]); | ||
const { rawValue, jaggedDataWarning, table } = useTableSummary(); | ||
watch(table, (newValue: string[][]) => { | ||
emit("onChange", table.value); | ||
}); | ||
</script> | ||
|
||
<template> | ||
<div class="paste-data"> | ||
<JaggedDataAlert :jaggedDataWarning="jaggedDataWarning" /> | ||
<textarea v-model="rawValue" class="p-2 mt-2" placeholder="Paste URIs and optional metadata here." /> | ||
</div> | ||
</template> | ||
|
||
<style scoped> | ||
@import "theme/blue.scss"; | ||
.paste-data { | ||
min-width: 576px; | ||
display: flex; | ||
flex-flow: row wrap; | ||
margin-right: -15px; | ||
margin-left: -15px; | ||
} | ||
.paste-data textarea { | ||
resize: none; | ||
width: 100%; | ||
height: 300px; | ||
font-size: $font-size-base; | ||
-moz-border-radius: $border-radius-large; | ||
border-radius: $border-radius-large; | ||
border-color: $border-color; | ||
border-width: 2px; | ||
} | ||
</style> |
62 changes: 62 additions & 0 deletions
62
client/src/components/Collections/wizard/SelectDataset.vue
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
<script setup lang="ts"> | ||
import { BAlert, BCard, BCardTitle } from "bootstrap-vue"; | ||
import { ref, watch } from "vue"; | ||
import { getGalaxyInstance } from "@/app"; | ||
import { useTableSummary } from "@/components/Collections/tables"; | ||
import { errorMessageAsString } from "@/utils/simple-error"; | ||
import { urlData } from "@/utils/url"; | ||
import JaggedDataAlert from "./JaggedDataAlert.vue"; | ||
const emit = defineEmits(["onChange", "onError"]); | ||
const errorMessage = ref<string | undefined>(undefined); | ||
const { rawValue, jaggedDataWarning, table } = useTableSummary(); | ||
async function fetchAndHandleData(response: Record) { | ||
const selectedDatasetId = response.id; | ||
const historyId = response.history_id; | ||
try { | ||
const newSourceContent = await urlData({ | ||
url: `/api/histories/${historyId}/contents/${selectedDatasetId}/display`, | ||
}); | ||
rawValue.value = newSourceContent as string; | ||
} catch (error) { | ||
rawValue.value = ""; | ||
errorMessage.value = errorMessageAsString(error); | ||
} | ||
} | ||
interface Record { | ||
id: string; | ||
history_id: string; | ||
} | ||
function inputDialog() { | ||
const Galaxy = getGalaxyInstance(); | ||
Galaxy.data.dialog(fetchAndHandleData, { | ||
multiple: false, | ||
library: false, | ||
format: null, | ||
allowUpload: false, | ||
}); | ||
} | ||
watch(table, (newValue: string[][]) => { | ||
emit("onChange", table.value); | ||
}); | ||
</script> | ||
|
||
<template> | ||
<BCard class="wizard-selection-card" border-variant="primary" @click="inputDialog"> | ||
<BCardTitle> | ||
<b>Select dataset</b> | ||
</BCardTitle> | ||
<div> | ||
<BAlert v-if="errorMessage" show variant="danger">{{ errorMessage }}</BAlert> | ||
<JaggedDataAlert :jaggedDataWarning="jaggedDataWarning" /> | ||
Select a history datasets, the contents will be loaded as tabular data and made available to the rule based | ||
import utility. | ||
</div> | ||
</BCard> | ||
</template> |
Oops, something went wrong.