diff --git a/.env.schema b/.env.schema index d3496328..83f1bf5a 100644 --- a/.env.schema +++ b/.env.schema @@ -24,3 +24,6 @@ NEXT_PUBLIC_SCORE_API_URL= ######## Jbrowse NEXT_PUBLIC_JBROWSE_GENOME_URL_ROOT= NEXT_PUBLIC_JBROWSE_GENOME_ALIASES_URL_ROOT= +NEXT_PUBLIC_JBROWSE_DATA_MODEL= +NEXT_PUBLIC_JBROWSE_NODE_QUERY= +NEXT_PUBLIC_JBROWSE_FILE_QUERY= diff --git a/.env.test b/.env.test index 1739341a..e6f906f7 100644 --- a/.env.test +++ b/.env.test @@ -9,4 +9,9 @@ NEXT_PUBLIC_ARRANGER_DOCUMENT_TYPE=file NEXT_PUBLIC_ARRANGER_INDEX=file-centric # ######## Optional features/functionalities -NEXT_PUBLIC_DEBUG=true \ No newline at end of file +NEXT_PUBLIC_DEBUG=true + +# Testing Jbrowse Env Queries +NEXT_PUBLIC_JBROWSE_DATA_MODEL=analysis { hits { edges { node { analysis_id files { hits { edges { node { data_type object_id name size fileType file_access } } } } id } } total } } +NEXT_PUBLIC_JBROWSE_NODE_QUERY=edges +NEXT_PUBLIC_JBROWSE_FILE_QUERY=edges \ No newline at end of file diff --git a/components/pages/explorer/Jbrowse/JbrowseWrapper.tsx b/components/pages/explorer/Jbrowse/JbrowseWrapper.tsx index ef16ec9f..d0a77e53 100644 --- a/components/pages/explorer/Jbrowse/JbrowseWrapper.tsx +++ b/components/pages/explorer/Jbrowse/JbrowseWrapper.tsx @@ -28,6 +28,7 @@ import { css, useTheme } from '@emotion/react'; import { useTableContext } from '@overture-stack/arranger-components'; import { JbrowseCircular, JbrowseLinear } from '@overture-stack/dms-jbrowse-components'; import SQON from '@overture-stack/sqon-builder'; +import jsonpath from 'jsonpath'; import { find } from 'lodash'; import { useEffect, useState } from 'react'; import urlJoin from 'url-join'; @@ -40,42 +41,43 @@ import { JbrowseQueryNode, ScoreDownloadParams, ScoreDownloadResult, + TableData, } from './types'; import useJbrowseCompatibility from './useJbrowseCompatibility'; import { checkJbrowseCompatibility, + fileQuery, jbrowseAssemblyName, jbrowseErrors, + JbrowseFileAccess, + JbrowseFileTypes, JbrowseTypeName, JbrowseTypeNames, } from './utils'; -const { NEXT_PUBLIC_SCORE_API_URL } = getConfig(); + +const { + NEXT_PUBLIC_SCORE_API_URL, + NEXT_PUBLIC_JBROWSE_DATA_MODEL, + NEXT_PUBLIC_JBROWSE_NODE_QUERY, + NEXT_PUBLIC_JBROWSE_FILE_QUERY, +} = getConfig(); const arrangerFetcher = createArrangerFetcher({}); +type Filters = { + filters: { + first: number; + offset: number; + score: string; + sort: [{ fieldName: string; order: string }]; + sqon: SQON; + }; +}; + // request data for jbrowse display and // score /download request to get signed URLs -const jbrowseInputQuery = ` -query jbrowseInput($filters:JSON){ - file { - hits (filters: $filters){ - total - edges { - node { - file_access - file_type - object_id - file { - name - size - index_file { - object_id - size - } - } - } - } - } - } +const jbrowseInputQuery = (dataQuery: string) => ` +query jbrowseInput ($filters: JSON) { + ${dataQuery} } `; @@ -128,53 +130,96 @@ const JbrowseEl = ({ activeJbrowseType }: { activeJbrowseType: JbrowseTypeName } console.error(error); }; + const jBrowseCompatibilityFilter = ({ + file_access, + file_type, + file: { index_file }, + }: JbrowseQueryNode) => + checkJbrowseCompatibility({ + file_access, + file_type, + index_file, + jbrowseType: activeJbrowseType, + }); + + const mapJbrowseFiles = ({ + object_id, + file, + file_type, + }: JbrowseQueryNode): JbrowseCompatibleFile => ({ + fileId: object_id, + fileName: file.name, + fileSize: file.size, + fileType: file_type, + // files without an index were filtered out above. + // falsey handling is for typescript only. + indexId: file.index_file?.object_id || '', + indexSize: file.index_file?.size || 0, + }); + useEffect(() => { // step 1: get compatible files - setLoading(true); + const variables: Filters = { + filters: { + first: 20, + offset: 0, + score: '', + sort: [{ fieldName: 'analysis_id', order: 'asc' }], + sqon: SQON.in('object_id', selectedRows), + }, + }; + const dataQuery = NEXT_PUBLIC_JBROWSE_DATA_MODEL || fileQuery; + const query = jbrowseInputQuery(dataQuery); // fetch metadata from arranger for selected files arrangerFetcher({ endpoint: 'graphql/JBrowseDataQuery', body: JSON.stringify({ - variables: { - filters: SQON.in('object_id', selectedRows), - }, - query: jbrowseInputQuery, + variables, + query, }), }) .then(({ data }) => { + const nodeQuery = NEXT_PUBLIC_JBROWSE_NODE_QUERY + ? `$..${NEXT_PUBLIC_JBROWSE_NODE_QUERY}` + : '$..edges'; + const fileQuery = NEXT_PUBLIC_JBROWSE_FILE_QUERY + ? `$..${NEXT_PUBLIC_JBROWSE_FILE_QUERY}` + : '$..edges'; + + const nodes = jsonpath.query(data, nodeQuery)[0]; + const indexFiles = jsonpath.query(nodes, fileQuery); + const files = indexFiles + .map((files: TableData[]) => { + const mappedFiles = files.map((data) => { + // Map for Compatibility + // Based on Table Data Query + const { object_id, name, size, fileType, file_access } = data.node; + const jbrowseFile: JbrowseQueryNode = { + file_type: fileType as JbrowseFileTypes, + file_access: file_access as JbrowseFileAccess, + object_id, + file: { + name, + size, + index_file: { + object_id, + size, + }, + }, + }; + return jbrowseFile; + }); + return mappedFiles; + }) + .flat(); + // restructure compatible files list for jbrowse's API - const nextJbrowseCompatibleFiles = (data.file?.hits?.edges || []) - .filter( - ({ - node: { - file_access, - file_type, - file: { index_file }, - }, - }: { - node: JbrowseQueryNode; - }) => - checkJbrowseCompatibility({ - file_access, - file_type, - index_file, - jbrowseType: activeJbrowseType, - }), - ) - .map( - ({ node }: { node: JbrowseQueryNode }): JbrowseCompatibleFile => ({ - fileId: node.object_id, - fileName: node.file.name, - fileSize: node.file.size, - fileType: node.file_type, - // files without an index were filtered out above. - // falsey handling is for typescript only. - indexId: node.file.index_file?.object_id || '', - indexSize: node.file.index_file?.size || 0, - }), - ); + const nextJbrowseCompatibleFiles = files + .filter(jBrowseCompatibilityFilter) + .map(mapJbrowseFiles); + setCompatibleFiles(nextJbrowseCompatibleFiles); }) .catch((error: Error) => handleError(error)); diff --git a/components/pages/explorer/Jbrowse/types.ts b/components/pages/explorer/Jbrowse/types.ts index ef325a5b..273f5f62 100644 --- a/components/pages/explorer/Jbrowse/types.ts +++ b/components/pages/explorer/Jbrowse/types.ts @@ -94,3 +94,25 @@ export type JbrowseInput = JbrowseFileType & }; export type JbrowseCompatibleFile = ScoreDownloadJbrowseInput & JbrowseFileName & JbrowseFileType; + +// Example Query Objects for configurable env queries +export type TableData = { + node: { + data_type: string; + object_id: string; + name: string; + size: number; + fileType: string; + file_access: string; + }; +}; + +export type TableNodes = { + node: { + files: { + hits: { + edges: TableData[]; + }; + }; + }; +}; diff --git a/components/pages/explorer/Jbrowse/utils.tsx b/components/pages/explorer/Jbrowse/utils.tsx index 7d6861d5..58d63199 100644 --- a/components/pages/explorer/Jbrowse/utils.tsx +++ b/components/pages/explorer/Jbrowse/utils.tsx @@ -104,6 +104,27 @@ export const jbrowseFileMetadataQuery = ` } `; +export const fileQuery = `file { + hits (filters: $filters){ + total + edges { + node { + file_access + file_type + object_id + file { + name + size + index_file { + object_id + size + } + } + } + } + } +}`; + // check if file is the right type for jbrowse // and that it has an index // MVP: restrict controlled access files diff --git a/global/config.ts b/global/config.ts index 32702d1e..0eb98895 100644 --- a/global/config.ts +++ b/global/config.ts @@ -45,6 +45,9 @@ export const getConfig = () => { NEXT_PUBLIC_JBROWSE_GENOME_URL_ROOT: publicConfig.NEXT_PUBLIC_JBROWSE_GENOME_URL_ROOT || '', NEXT_PUBLIC_JBROWSE_GENOME_ALIASES_URL_ROOT: publicConfig.NEXT_PUBLIC_JBROWSE_GENOME_ALIASES_URL_ROOT || '', + NEXT_PUBLIC_JBROWSE_DATA_MODEL: publicConfig.NEXT_PUBLIC_JBROWSE_DATA_MODEL || '', + NEXT_PUBLIC_JBROWSE_NODE_QUERY: publicConfig.NEXT_PUBLIC_JBROWSE_NODE_QUERY || '', + NEXT_PUBLIC_JBROWSE_FILE_QUERY: publicConfig.NEXT_PUBLIC_JBROWSE_FILE_QUERY || '', NEXT_PUBLIC_SHOW_MOBILE_WARNING: publicConfig.NEXT_PUBLIC_SHOW_MOBILE_WARNING === 'true', } as { NEXT_PUBLIC_EGO_API_ROOT: string; @@ -64,6 +67,9 @@ export const getConfig = () => { NEXT_PUBLIC_SCORE_API_URL: string; NEXT_PUBLIC_JBROWSE_GENOME_URL_ROOT: string; NEXT_PUBLIC_JBROWSE_GENOME_ALIASES_URL_ROOT: string; + NEXT_PUBLIC_JBROWSE_DATA_MODEL: string; + NEXT_PUBLIC_JBROWSE_NODE_QUERY: string; + NEXT_PUBLIC_JBROWSE_FILE_QUERY: string; NEXT_PUBLIC_SHOW_MOBILE_WARNING: boolean; }; }; diff --git a/next.config.js b/next.config.js index b9771887..1b550912 100644 --- a/next.config.js +++ b/next.config.js @@ -62,6 +62,7 @@ module.exports = withPlugins([withTranspileModules], { // Optional features/functionalities NEXT_PUBLIC_DEBUG: process.env.NEXT_PUBLIC_DEBUG, NEXT_PUBLIC_SHOW_MOBILE_WARNING: process.env.NEXT_PUBLIC_SHOW_MOBILE_WARNING, + NEXT_PUBLIC_JBROWSE_DATA_MODEL: process.env.NEXT_PUBLIC_JBROWSE_DATA_MODEL, }, assetPrefix: process.env.ASSET_PREFIX || '', optimizeFonts: false, diff --git a/package-lock.json b/package-lock.json index c586f9cf..4ed58650 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "@overture-stack/dms-jbrowse-components": "^0.1.0-beta.4", "@overture-stack/sqon-builder": "^0.0.0", "axios": "^0.27.2", + "jsonpath": "^1.1.1", "jsonwebtoken": "^8.5.1", "jwt-decode": "^3.1.2", "lodash": "^4.17.21", @@ -32,6 +33,7 @@ "devDependencies": { "@babel/preset-typescript": "^7.21.0", "@emotion/babel-plugin": "^11.9.2", + "@types/jsonpath": "^0.2.4", "@types/lodash": "^4.14.182", "@types/node": "^17.0.35", "@types/react": "^17.0.63", @@ -3186,6 +3188,12 @@ "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", "dev": true }, + "node_modules/@types/jsonpath": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@types/jsonpath/-/jsonpath-0.2.4.tgz", + "integrity": "sha512-K3hxB8Blw0qgW6ExKgMbXQv2UPZBoE2GqLpVY+yr7nMD2Pq86lsuIzyAaiQ7eMqFL5B6di6pxSkogLJEyEHoGA==", + "dev": true + }, "node_modules/@types/lodash": { "version": "4.14.182", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.182.tgz", @@ -13322,6 +13330,12 @@ "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", "dev": true }, + "@types/jsonpath": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@types/jsonpath/-/jsonpath-0.2.4.tgz", + "integrity": "sha512-K3hxB8Blw0qgW6ExKgMbXQv2UPZBoE2GqLpVY+yr7nMD2Pq86lsuIzyAaiQ7eMqFL5B6di6pxSkogLJEyEHoGA==", + "dev": true + }, "@types/lodash": { "version": "4.14.182", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.182.tgz", diff --git a/package.json b/package.json index 03fcd8b0..eed24b52 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "@overture-stack/dms-jbrowse-components": "^0.1.0-beta.4", "@overture-stack/sqon-builder": "^0.0.0", "axios": "^0.27.2", + "jsonpath": "^1.1.1", "jsonwebtoken": "^8.5.1", "jwt-decode": "^3.1.2", "lodash": "^4.17.21", @@ -36,6 +37,7 @@ "devDependencies": { "@babel/preset-typescript": "^7.21.0", "@emotion/babel-plugin": "^11.9.2", + "@types/jsonpath": "^0.2.4", "@types/lodash": "^4.14.182", "@types/node": "^17.0.35", "@types/react": "^17.0.63",