-
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.
Add basic task based history export UI
- Loading branch information
Showing
5 changed files
with
285 additions
and
1 deletion.
There are no files selected for viewing
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,69 @@ | ||
<script setup> | ||
import { computed } from "vue"; | ||
import { BCard, BCardTitle } from "bootstrap-vue"; | ||
import UtcDate from "components/UtcDate"; | ||
import LoadingSpan from "components/LoadingSpan"; | ||
import { formatDistanceToNow, parseISO } from "date-fns"; | ||
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome"; | ||
import { ExportRecordModel } from "./models/exportRecordModel"; | ||
const props = defineProps({ | ||
record: { | ||
type: ExportRecordModel, | ||
required: true, | ||
}, | ||
objectType: { | ||
type: String, | ||
required: true, | ||
}, | ||
}); | ||
const emit = defineEmits(["onReimport"]); | ||
const title = computed(() => (props.record.isReady ? `Exported` : `Export started`)); | ||
const elapsedTime = computed(() => formatDistanceToNow(parseISO(`${props.record.date}Z`), { addSuffix: true })); | ||
const statusMessage = computed(() => { | ||
if (props.record.hasFailed) { | ||
return `Something failed during this export. Please try again and if the problem persist contact your administrator.`; | ||
} | ||
if (props.record.isUpToDate) { | ||
return `This export contains the latest changes of the ${props.objectType}.`; | ||
} | ||
return `This export is outdated and contains the changes of this ${props.objectType} from ${elapsedTime.value}.`; | ||
}); | ||
const readyMessage = computed(() => `You can do the following actions with this ${props.objectType} export:`); | ||
const preparingMessage = computed( | ||
() => `Preparing export. This may take some time depending on the size of your ${props.objectType}` | ||
); | ||
function reimportObject() { | ||
emit("onReimport", props.record); | ||
} | ||
</script> | ||
|
||
<template> | ||
<b-card> | ||
<b-card-title> | ||
<b>{{ title }}</b> <UtcDate :date="props.record.date" mode="elapsed" /> | ||
</b-card-title> | ||
<span v-if="props.record.isPreparing"> | ||
<loading-span :message="preparingMessage" /> | ||
</span> | ||
<div v-else> | ||
<font-awesome-icon v-if="props.record.isUpToDate" icon="check-circle" class="text-success" /> | ||
<font-awesome-icon v-else-if="props.record.hasFailed" icon="exclamation-circle" class="text-danger" /> | ||
<font-awesome-icon v-else icon="exclamation-triangle" class="text-warning" /> | ||
<span> | ||
{{ statusMessage }} | ||
</span> | ||
<div v-if="props.record.isReady"> | ||
<p class="mt-3"> | ||
{{ readyMessage }} | ||
</p> | ||
<b-button v-if="props.record.canReimport" variant="primary" @click="reimportObject"> | ||
Reimport | ||
</b-button> | ||
</div> | ||
</div> | ||
</b-card> | ||
</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,38 @@ | ||
export class ExportRecordModel { | ||
constructor(data) { | ||
this._data = data; | ||
} | ||
|
||
get isReady() { | ||
return this._data.ready || false; | ||
} | ||
|
||
get isPreparing() { | ||
return this._data.preparing || false; | ||
} | ||
|
||
get isUpToDate() { | ||
return this._data.up_to_date || false; | ||
} | ||
|
||
get hasFailed() { | ||
return !this.isReady && !this.isPreparing; | ||
} | ||
|
||
get date() { | ||
return this._data.create_time; | ||
} | ||
|
||
get taskUUID() { | ||
return this._data.task_uuid; | ||
} | ||
|
||
// import_uri doesn't work for downloads | ||
get canReimport() { | ||
return this._data?.export_metadata?.result_data?.import_uri !== undefined; | ||
} | ||
|
||
get importLink() { | ||
return this._data?.export_metadata?.result_data?.import_uri; | ||
} | ||
} |
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,111 @@ | ||
<script setup> | ||
import { computed, ref, onMounted, watch } from "vue"; | ||
import { BAlert, BCard, BButton, BTab, BTabs } from "bootstrap-vue"; | ||
import ExportRecordDetails from "components/Common/ExportRecordDetails.vue"; | ||
import ExportToFileSourceForm from "components/Common/ExportForm.vue"; | ||
import { HistoryExportServices } from "./services"; | ||
import { useTaskMonitor } from "composables/useTaskMonitor"; | ||
import { useFileSources } from "composables/fileSources"; | ||
const service = new HistoryExportServices(); | ||
const { isRunning: isExportTaskRunning, waitForTask } = useTaskMonitor(); | ||
const { hasWritable: hasWritableFileSources } = useFileSources(); | ||
const props = defineProps({ | ||
historyId: { | ||
type: String, | ||
required: true, | ||
}, | ||
}); | ||
const isLoadingRecords = ref(true); | ||
const latestExportRecord = ref(null); | ||
const isLatestExportReady = ref(false); | ||
const previousExportRecords = ref(null); | ||
const availableRecordsMessage = computed(() => | ||
isLoadingRecords.value | ||
? "Loading export records..." | ||
: "This history has no export records yet. You can choose one of the export options above." | ||
); | ||
const errorMessage = ref(null); | ||
onMounted(async () => { | ||
updateExports(); | ||
}); | ||
watch(isExportTaskRunning, async () => { | ||
console.debug("Updating latest export after task finished"); | ||
updateLatestExport(); | ||
}); | ||
async function updateLatestExport() { | ||
isLoadingRecords.value = true; | ||
const latestExport = await service.getLatestExportRecord(props.historyId); | ||
latestExportRecord.value = latestExport; | ||
isLatestExportReady.value = latestExport?.isReady; | ||
if (latestExport?.isPreparing) { | ||
isLatestExportReady.value = false; | ||
waitForTask(latestExport.taskUUID, 3000); | ||
} | ||
isLoadingRecords.value = false; | ||
} | ||
async function updateExports() { | ||
updateLatestExport(); | ||
service.getExportRecords(props.historyId, { offset: 1, limit: 10 }).then((records) => { | ||
previousExportRecords.value = records; | ||
}); | ||
} | ||
async function exportToFileSource(exportDirectory, fileName) { | ||
await service.exportToFileSource(props.historyId, exportDirectory, fileName); | ||
updateExports(); | ||
} | ||
async function generateDownloadLink() { | ||
await service.generateDownloadLink(); | ||
updateExports(); | ||
} | ||
function reimportHistoryFromRecord(record) { | ||
return service.reimportHistoryFromRecord(record); | ||
} | ||
</script> | ||
<template> | ||
<span class="history-export-component"> | ||
<h1 class="h-lg">Export history {{ props.historyId }}</h1> | ||
<b-card no-body> | ||
<b-tabs pills card> | ||
<b-tab title="to link" title-link-class="tab-export-to-link" active> | ||
<p> | ||
Here you can generate a temporal link to download your packaged history. When your download link | ||
expires or your history changes, you can re-generate the link again. | ||
</p> | ||
<b-button variant="primary" @click="generateDownloadLink">Generate Download Link</b-button> | ||
</b-tab> | ||
<b-tab v-if="hasWritableFileSources" title="to remote file" title-link-class="tab-export-to-file"> | ||
<p> | ||
If you need a `more permanent` way of storing your exported history you can export it directly | ||
to one of the available remote file sources here. | ||
</p> | ||
<export-to-file-source-form what="history" @export="exportToFileSource" /> | ||
</b-tab> | ||
</b-tabs> | ||
</b-card> | ||
<export-record-details | ||
v-if="latestExportRecord" | ||
:record="latestExportRecord" | ||
object-type="history" | ||
class="mt-3" | ||
@onReimport="reimportHistoryFromRecord" /> | ||
<b-alert v-else-if="errorMessage" variant="danger" class="mt-3" show> | ||
{{ errorMessage }} | ||
</b-alert> | ||
<b-alert v-else variant="info" class="mt-3" show> | ||
{{ availableRecordsMessage }} | ||
</b-alert> | ||
</span> | ||
</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,63 @@ | ||
import axios from "axios"; | ||
import { rethrowSimple } from "utils/simple-error"; | ||
import { safePath } from "utils/redirect"; | ||
import { ExportRecordModel } from "components/Common/models/exportRecordModel"; | ||
|
||
export class HistoryExportServices { | ||
/** | ||
* Gets a list of export records for the given history. | ||
* @param {String} historyId | ||
* @returns {Promise<ExportRecordModel[]>} | ||
*/ | ||
async getExportRecords(historyId, params = { offset: undefined, limit: undefined }) { | ||
const url = `/api/histories/${historyId}/exports`; | ||
try { | ||
const response = await axios.get(safePath(url), { | ||
headers: { Accept: "application/vnd.galaxy.task.export+json" }, | ||
params: params, | ||
}); | ||
return response.data.map((item) => new ExportRecordModel(item)); | ||
} catch (e) { | ||
rethrowSimple(e); | ||
} | ||
} | ||
|
||
/** | ||
* Gets the latest export record for the given history. | ||
* @param {String} historyId | ||
* @returns {Promise<ExportRecordModel|null>} | ||
*/ | ||
async getLatestExportRecord(historyId) { | ||
try { | ||
const records = await this.getExportRecords(historyId, { limit: 1 }); | ||
return records.length ? records.at(0) : null; | ||
} catch (e) { | ||
rethrowSimple(e); | ||
} | ||
} | ||
|
||
async exportToFileSource( | ||
historyId, | ||
exportDirectory, | ||
fileName, | ||
options = { exportFormat: "rocrate.zip", include_files: true, include_deleted: false, include_hidden: false } | ||
) { | ||
const exportDirectoryUri = `${exportDirectory}/${fileName}.${options.exportFormat}`; | ||
const writeStoreParams = { | ||
target_uri: exportDirectoryUri, | ||
model_store_format: options.exportFormat, | ||
include_files: options.include_files, | ||
include_deleted: options.include_deleted, | ||
include_hidden: options.include_hidden, | ||
}; | ||
return axios.post(`/api/histories/${historyId}/write_store`, writeStoreParams); | ||
} | ||
|
||
async generateDownloadLink() { | ||
console.debug("TODO: Generate link"); | ||
} | ||
|
||
async reimportHistoryFromRecord(record) { | ||
console.debug("TODO: Reimport from", record.importLink); | ||
} | ||
} |
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