Skip to content

Commit

Permalink
Add export tsv to browse page
Browse files Browse the repository at this point in the history
  • Loading branch information
lukasjelonek committed Aug 22, 2024
1 parent 4d6fb7b commit de9c1ac
Show file tree
Hide file tree
Showing 4 changed files with 158 additions and 100 deletions.
28 changes: 14 additions & 14 deletions src/views/BrowseView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,7 @@ import usePageState, { State } from "@/PageState";
import AutocompleteInput from "@/components/AutocompleteInput.vue";
import Loading from "@/components/Loading.vue";
import QueryFilter from "@/components/QueryFilter.vue";
import {
empty,
toPosition,
type PaginationData,
type PositionInResult,
} from "@/components/pagination/Pagination";
import { empty, type PaginationData } from "@/components/pagination/Pagination";
import Pagination from "@/components/pagination/Pagination.vue";
import type { BakrepSearchResultEntry } from "@/model/BakrepSearchResult";
import {
Expand All @@ -21,6 +16,7 @@ import {
import ResultTable from "@/views/search/ResultTable.vue";
import { computed, onMounted, ref, watch, type Ref } from "vue";
import { useRoute, useRouter } from "vue-router";
import SearchResultNumbers from "./search/SearchResultNumbers.vue";
const pageState = usePageState();
const searchState = usePageState();
const entries: Ref<BakrepSearchResultEntry[]> = ref([]);
Expand Down Expand Up @@ -187,6 +183,7 @@ function parseFiltersFromRoute() {
pagination.value.limit = Number.parseInt(route.query.limit as string);
}
}
const query = computed(() => buildFilterQuery(filters.value));
function applyFilter(offset = 0) {
searchState.value.setState(State.Loading);
Expand All @@ -209,10 +206,6 @@ function applyFilter(offset = 0) {
const ordering: Ref<SortOption[]> = ref([{ field: "id", ord: "asc" }]);
const positionInResults: Ref<PositionInResult> = computed(() =>
toPosition(pagination.value),
);
function updateOrdering(sortkey: string, direction: SortDirection | null) {
const idx = ordering.value.findIndex((s) => s.field === sortkey);
if (direction == null) {
Expand Down Expand Up @@ -282,6 +275,8 @@ watch(
},
);
const exportInProgress = ref(false);
onMounted(init);
</script>

Expand Down Expand Up @@ -335,10 +330,15 @@ onMounted(init);
</div>
<Loading :state="searchState">
<div class="row px-3">
Showing results {{ positionInResults.firstElement }}-{{
positionInResults.lastElement
}}
of {{ pagination.total }} results
<SearchResultNumbers
ref="exportComponent"
:api="api"
:pagination="pagination"
:query="query"
@exporting="exportInProgress = true"
@export-done="exportInProgress = false"
@export-failed="exportInProgress = false"
/>
<ResultTable
:ordering="ordering"
:entries="entries"
Expand Down
61 changes: 60 additions & 1 deletion src/views/search/ExportTsv.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { type BakrepApi } from "@/BakrepApi";
import type { PaginationData } from "@/components/pagination/Pagination";
import type { Query, SearchAfter, SortOption } from "@/model/Search";
import { ref } from "vue";
import { saveAs } from "file-saver";

/**
* Downloads all entries for the query as a tsv element. The data is loaded in chunks (chunksize it hard coded to 2000 at the moment).
Expand All @@ -12,7 +15,7 @@ import type { Query, SearchAfter, SortOption } from "@/model/Search";
* @param req
* @param handler
*/
export function downloadFullTsv(
function downloadFullTsv(
api: BakrepApi,
req: ExportRequest,
handler: ExportHandler,
Expand Down Expand Up @@ -92,3 +95,59 @@ export type ProgressEvent = {
total: number;
count: number;
};

export function useExportTsv(api: BakrepApi) {
let cancelExport: AbortController | undefined = undefined;
const progress = ref<ProgressEvent>();
const exportError = ref<string>();
const exportInProgress = ref(false);
function exportTsv(query: Query, pagination: PaginationData): Promise<void> {
return new Promise((res, rej) => {
const queryCopy = JSON.parse(JSON.stringify(query));
exportError.value = undefined;
exportInProgress.value = true;
progress.value = { total: pagination.total, count: 0, progress: 0 };
cancelExport = downloadFullTsv(
api,
{
query: queryCopy,
sort: [{ field: "id", ord: "asc" }],
},
{
onError: (e) => {
exportError.value = e as string;
exportInProgress.value = false;
rej(e);
},
onFinished: (d) => {
const blob = new Blob([d], {
type: "text/tab-separated-values;charset=utf-8",
});
saveAs(blob, "bakrep-export.tsv");
exportInProgress.value = false;
cancelExport = undefined;
res();
},
onProgress: (p) => (progress.value = p),
},
);
});
}
function cancelTsvExport() {
if (cancelExport) cancelExport.abort();
}
function resetTsvExport() {
cancelTsvExport();
progress.value = undefined;
exportError.value = undefined;
exportInProgress.value = false;
}
return {
cancelTsvExport,
exportTsv,
resetTsvExport,
progress,
exportError,
exportInProgress,
};
}
70 changes: 70 additions & 0 deletions src/views/search/SearchResultNumbers.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<template>
<div class="col-12 d-flex justify-content-between align-items-end">
<div class="fs-tiny">
Showing search results {{ positionInResults.firstElement }}-{{
positionInResults.lastElement
}}
of {{ pagination.total }} results
</div>
<div v-if="pagination.total > 0">
<button
class="btn btn-sm btn-link link-secondary"
@click="performExport"
:disabled="exportInProgress"
>
Export as tsv
</button>
</div>
</div>
<div class="col-12">
<ExportProgress v-if="progress" :progress="progress" :error="exportError" />
</div>
</template>
<script setup lang="ts">
import type { BakrepApi } from "@/BakrepApi";
import {
toPosition,
type PaginationData,
type PositionInResult,
} from "@/components/pagination/Pagination";
import type { Query } from "@/model/Search";
import { useExportTsv } from "./ExportTsv";
import { computed, onUnmounted } from "vue";
import ExportProgress from "./ExportProgress.vue";
const props = defineProps<{
pagination: PaginationData;
api: BakrepApi;
query: Query;
}>();
const emit = defineEmits<{
(e: "exporting"): void;
(e: "exportDone"): void;
(e: "exportFailed", err: unknown): void;
}>();
const positionInResults = computed<PositionInResult>(() =>
toPosition(props.pagination),
);
const {
cancelTsvExport,
exportError,
exportInProgress,
exportTsv,
progress,
resetTsvExport,
} = useExportTsv(props.api);
onUnmounted(() => {
cancelTsvExport();
});
function performExport() {
emit("exporting");
exportTsv(props.query, props.pagination)
.then(() => emit("exportDone"))
.catch((err) => emit("exportFailed", err));
}
defineExpose({ resetTsvExport });
</script>
99 changes: 14 additions & 85 deletions src/views/search/SearchView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,7 @@ import { useApi } from "@/BakrepApi";
import usePageState, { State } from "@/PageState";
import type { LookupCompletionFunction } from "@/components/AutocompleteInput.vue";
import Loading from "@/components/Loading.vue";
import {
empty,
toPosition,
type PaginationData,
type PositionInResult,
} from "@/components/pagination/Pagination";
import { empty, type PaginationData } from "@/components/pagination/Pagination";
import Pagination from "@/components/pagination/Pagination.vue";
import QueryBuilder from "@/components/querybuilder/QueryBuilder.vue";
import {
Expand All @@ -26,20 +21,10 @@ import type {
SortDirection,
SortOption,
} from "@/model/Search";
import { saveAs } from "file-saver";
import {
computed,
onBeforeUnmount,
onMounted,
ref,
unref,
watch,
type Ref,
} from "vue";
import { computed, onMounted, ref, unref, watch, type Ref } from "vue";
import { useRoute, useRouter } from "vue-router";
import ExportProgress from "./ExportProgress.vue";
import { downloadFullTsv, type ProgressEvent } from "./ExportTsv";
import ResultTable from "./ResultTable.vue";
import SearchResultNumbers from "./SearchResultNumbers.vue";
const pageState = usePageState();
const searchState = usePageState();
const entries: Ref<BakrepSearchResultEntry[]> = ref([]);
Expand Down Expand Up @@ -210,7 +195,7 @@ const fieldNames: Record<string, FieldConfiguration> = {
function search(offset = 0) {
searchState.value.setState(State.Loading);
resetTsvExport();
if (exportComponent.value) exportComponent.value.resetTsvExport();
api
.search({
query: unref(query),
Expand All @@ -228,10 +213,6 @@ function search(offset = 0) {
.catch((err) => pageState.value.setError(err));
}
const positionInResults: Ref<PositionInResult> = computed(() =>
toPosition(pagination.value),
);
function updateOrdering(sortkey: string, direction: SortDirection | null) {
const idx = ordering.value.findIndex((s) => s.field === sortkey);
if (direction == null) {
Expand All @@ -244,47 +225,10 @@ function updateOrdering(sortkey: string, direction: SortDirection | null) {
search();
}
let cancelExport: AbortController | undefined = undefined;
const progress = ref<ProgressEvent>();
const exportError = ref<string>();
const exportComponent = ref<typeof SearchResultNumbers>();
const exportInProgress = ref(false);
function exportTsv() {
exportError.value = undefined;
exportInProgress.value = true;
progress.value = { total: pagination.value.total, count: 0, progress: 0 };
cancelExport = downloadFullTsv(
api,
{
query: query.value,
sort: [{ field: "id", ord: "asc" }],
},
{
onError: (e) => {
exportError.value = e as string;
exportInProgress.value = false;
},
onFinished: (d) => {
const blob = new Blob([d], {
type: "text/tab-separated-values;charset=utf-8",
});
saveAs(blob, "bakrep-export.tsv");
exportInProgress.value = false;
cancelExport = undefined;
},
onProgress: (p) => (progress.value = p),
},
);
}
function resetTsvExport() {
progress.value = undefined;
exportError.value = undefined;
exportInProgress.value = false;
}
onMounted(init);
onBeforeUnmount(() => {
if (cancelExport) cancelExport.abort();
});
function createLookupFn(r: Rule): LookupCompletionFunction {
if (r.field.startsWith("gtdbtk.classification.")) {
Expand Down Expand Up @@ -332,30 +276,15 @@ function createLookupFn(r: Rule): LookupCompletionFunction {
</div>
<Loading :state="searchState">
<div class="row">
<div class="col-12 d-flex justify-content-between align-items-end">
<div class="fs-tiny">
Showing search results {{ positionInResults.firstElement }}-{{
positionInResults.lastElement
}}
of {{ pagination.total }} results
</div>
<div v-if="pagination.total > 0">
<button
class="btn btn-sm btn-link link-secondary"
@click="exportTsv"
:disabled="exportInProgress"
>
Export as tsv
</button>
</div>
</div>
<div class="col-12">
<ExportProgress
v-if="progress"
:progress="progress"
:error="exportError"
/>
</div>
<SearchResultNumbers
ref="exportComponent"
:api="api"
:pagination="pagination"
:query="query"
@exporting="exportInProgress = true"
@export-done="exportInProgress = false"
@export-failed="exportInProgress = false"
/>
<div class="col-12">
<ResultTable
:ordering="ordering"
Expand Down

0 comments on commit de9c1ac

Please sign in to comment.