Skip to content

Commit

Permalink
add associations tsv download to frontend (#537)
Browse files Browse the repository at this point in the history
Co-authored-by: glass-ships <[email protected]>
  • Loading branch information
vincerubinetti and glass-ships authored Jan 13, 2024
1 parent 36b36d6 commit 0b6fa15
Show file tree
Hide file tree
Showing 5 changed files with 69 additions and 18 deletions.
21 changes: 20 additions & 1 deletion backend/src/monarch_py/api/entity.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
from io import StringIO
from typing import List, Union

from fastapi import APIRouter, Depends, HTTPException, Path, Query
from fastapi.responses import StreamingResponse

from monarch_py.api.additional_models import PaginationParams
from monarch_py.api.config import solr
from monarch_py.api.additional_models import OutputFormat
from monarch_py.datamodels.model import AssociationTableResults, Node
from monarch_py.datamodels.category_enums import AssociationCategory
from monarch_py.utils.format_utils import to_tsv
from monarch_py.utils.format_utils import to_json, to_tsv

router = APIRouter(tags=["entity"], responses={404: {"description": "Not Found"}})

Expand Down Expand Up @@ -69,6 +71,11 @@ def _association_table(
title="Output format for the response",
examples=["json", "tsv"],
),
download=Query(
default=False,
title="Download the results as a file",
examples=[True, False],
),
) -> Union[AssociationTableResults, str]:
"""
Retrieves association table data for a given entity and association type
Expand All @@ -85,6 +92,18 @@ def _association_table(
response = solr().get_association_table(
entity=id, category=category.value, q=query, sort=sort, offset=pagination.offset, limit=pagination.limit
)
if download:
string_response = (
to_tsv(response, print_output=False)
if format == OutputFormat.tsv
else to_json(response, print_output=False)
)
stream = StringIO(string_response)
response = StreamingResponse(
stream, media_type="text/csv" if format == OutputFormat.tsv else "application/json"
)
response.headers["Content-Disposition"] = f"attachment; filename=assoc-table-{id}.{format.value}"
return response
if format == OutputFormat.json:
return response
elif format == OutputFormat.tsv:
Expand Down
29 changes: 29 additions & 0 deletions frontend/src/api/associations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,37 @@ export const getAssociations = async (
return response;
};

/** max rows supported in single download */
export const maxDownload = 500;

/** get associations between a node and a category */
export const downloadAssociations = async (
nodeId = "",
associationCategory = "",
search?: string,
sort: Sort = null,
) => {
/** make query params */
const params = {
limit: maxDownload,
query: search || "",
sort: sort
? `${sort.key} ${sort.direction === "up" ? "asc" : "desc"}`
: null,
download: true,
format: "tsv",
};

/** make query */
const url = `${apiUrl}/entity/${nodeId}/${associationCategory}`;
await request(url, params, {}, "text", true);
};

/** get top few associations */
export const getTopAssociations = async (
nodeId = "",
associationCategory = "",
) => await getAssociations(nodeId, associationCategory, 0, 5);

/** maximum associations downloadable at once */
export const downloadLimit = 500;
6 changes: 5 additions & 1 deletion frontend/src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,12 @@ export const request = async <Response>(
* { id: [1,2,3] } -> ?id=1&id=2&id=3
*/
params: Params = {},
/** fetch options */
/** fetch/request options */
options: RequestInit = {},
/** parse response mode */
parse: "text" | "json" = "json",
/** whether open request url in new tab */
newTab = false,
): Promise<Response> => {
/** get string of url parameters/options */
const paramsObject = new URLSearchParams();
Expand All @@ -96,6 +98,8 @@ export const request = async <Response>(
const paramsString = "?" + paramsObject.toString();
const url = path + paramsString;

if (newTab) window.open(url);

/** make request object */
const request = new Request(url, options);

Expand Down
30 changes: 15 additions & 15 deletions frontend/src/pages/node/AssociationsTable.vue
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,11 @@

<script setup lang="ts">
import { computed, onMounted, ref, watch } from "vue";
import { getAssociations } from "@/api/associations";
import {
downloadAssociations,
getAssociations,
maxDownload,
} from "@/api/associations";
import { getCategoryLabel } from "@/api/categories";
import {
AssociationDirectionEnum,
Expand All @@ -108,7 +112,6 @@ import type { Cols, Sort } from "@/components/AppTable.vue";
import { snackbar } from "@/components/TheSnackbar.vue";
import { getBreadcrumbs } from "@/pages/node/AssociationsSummary.vue";
import { useQuery } from "@/util/composables";
import { downloadJson } from "@/util/download";

type Props = {
/** current node */
Expand Down Expand Up @@ -276,26 +279,23 @@ const {

/** download table data */
async function download() {
/** max rows to try to query */
const max = 100;
const total = associations.value.total;

/** warn user */
snackbar(
`Downloading data for ${total > max ? "first " : ""}${Math.min(
total,
max,
)} table entries.` + (total >= 100 ? " This may take a minute." : ""),
`Downloading data for ${
associations.value.total > maxDownload ? "first " : ""
}${Math.min(
associations.value.total,
maxDownload,
)} table rows. This may take a minute.`,
);

/** attempt to request all rows */
const response = await getAssociations(
/** download as many rows as possible */
await downloadAssociations(
props.node.id,
props.category.id,
0,
max,
search.value,
sort.value,
);
downloadJson(response, "associations");
}

/** get associations when category or table state changes */
Expand Down
1 change: 0 additions & 1 deletion frontend/src/pages/node/SectionOverview.vue
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,6 @@
</template>

<script setup lang="ts">
import type { info } from "console";
import { computed } from "vue";
import { omit } from "lodash";
import type { Node } from "@/api/model";
Expand Down

0 comments on commit 0b6fa15

Please sign in to comment.