From 837e4741ce27a2d85641a452c3ac9ab2d5f5490e Mon Sep 17 00:00:00 2001 From: Fabian Engelniederhammer Date: Tue, 20 Feb 2024 11:05:03 +0100 Subject: [PATCH] feat(docs): references/additional-request-properties #604 --- lapis2-docs/astro.config.mjs | 4 + .../QueryGenerator/OutputFormatSelection.tsx | 2 +- .../QueryGenerator/QueryTypeSelection.tsx | 24 ++-- .../QueryGenerator/QueryTypeSelectionState.ts | 7 + .../src/components/QueryGenerator/Result.tsx | 120 ++++++++++++++---- .../src/components/TabsBox/react/TabsBox.tsx | 2 +- .../content/docs/concepts/response-format.mdx | 4 +- .../additional-request-properties.mdx | 112 ++++++++++++++++ .../src/content/docs/references/filters.mdx | 2 +- ...ample_nucleotide_mutation_response_200.png | Bin .../example_nucleotide_mutation_schema.png | Bin .../src/images/references/media_type.png | Bin 0 -> 30791 bytes lapis2-docs/tests/docs.spec.ts | 1 + lapis2-docs/tests/queryGenerator.page.ts | 8 ++ lapis2-docs/tests/queryGenerator.spec.ts | 6 + .../controller/ControllerDescriptions.kt | 9 +- 16 files changed, 254 insertions(+), 47 deletions(-) create mode 100644 lapis2-docs/src/content/docs/references/additional-request-properties.mdx rename lapis2-docs/{public/images/docs => src/images}/concepts/example_nucleotide_mutation_response_200.png (100%) rename lapis2-docs/{public/images/docs => src/images}/concepts/example_nucleotide_mutation_schema.png (100%) create mode 100644 lapis2-docs/src/images/references/media_type.png diff --git a/lapis2-docs/astro.config.mjs b/lapis2-docs/astro.config.mjs index 38212ffb..86e1d669 100644 --- a/lapis2-docs/astro.config.mjs +++ b/lapis2-docs/astro.config.mjs @@ -46,6 +46,10 @@ export default defineConfig({ label: 'Filters', link: '/references/filters/', }, + { + label: 'Additional Request Properties', + link: '/references/additional-request-properties/', + }, { label: 'Open API / Swagger', link: '/references/open-api-definition/', diff --git a/lapis2-docs/src/components/QueryGenerator/OutputFormatSelection.tsx b/lapis2-docs/src/components/QueryGenerator/OutputFormatSelection.tsx index 46fc7671..b5df9de6 100644 --- a/lapis2-docs/src/components/QueryGenerator/OutputFormatSelection.tsx +++ b/lapis2-docs/src/components/QueryGenerator/OutputFormatSelection.tsx @@ -12,7 +12,7 @@ type Props = { export const OutputFormatSelection = ({ queryType, format, onFormatChange }: Props) => { return ( -
+
{queryType.selection === 'nucleotideSequences' || queryType.selection === 'aminoAcidSequences' ? ( <> For sequences, only FASTA is available as output format. diff --git a/lapis2-docs/src/components/QueryGenerator/QueryTypeSelection.tsx b/lapis2-docs/src/components/QueryGenerator/QueryTypeSelection.tsx index 0caa746a..0f8a6e99 100644 --- a/lapis2-docs/src/components/QueryGenerator/QueryTypeSelection.tsx +++ b/lapis2-docs/src/components/QueryGenerator/QueryTypeSelection.tsx @@ -113,17 +113,19 @@ const AggregatedStratified = ({ config, state, onStateChange }: Props) => {
By which field(s) would you like to stratify? - {config.schema.metadata.map((m) => ( - changeAggregatedStratifiedField(m.name)} - /> - ))} + {config.schema.metadata + .filter((metadata) => metadata.type !== 'insertion' && metadata.type !== 'aaInsertion') + .map((metadata) => ( + changeAggregatedStratifiedField(metadata.name)} + /> + ))}
diff --git a/lapis2-docs/src/components/QueryGenerator/QueryTypeSelectionState.ts b/lapis2-docs/src/components/QueryGenerator/QueryTypeSelectionState.ts index d11f16e9..c878a112 100644 --- a/lapis2-docs/src/components/QueryGenerator/QueryTypeSelectionState.ts +++ b/lapis2-docs/src/components/QueryGenerator/QueryTypeSelectionState.ts @@ -125,5 +125,12 @@ function mapMetadataTypeToResultFieldType(type: MetadataType): ResultFieldType { case 'date': case 'string': return 'string'; + case 'int': + return 'integer'; + case 'float': + return 'float'; + case 'insertion': + case 'aaInsertion': + throw Error('Insertion and aaInsertion are not supported as result field types'); } } diff --git a/lapis2-docs/src/components/QueryGenerator/Result.tsx b/lapis2-docs/src/components/QueryGenerator/Result.tsx index 3ac6d842..d07b654b 100644 --- a/lapis2-docs/src/components/QueryGenerator/Result.tsx +++ b/lapis2-docs/src/components/QueryGenerator/Result.tsx @@ -5,12 +5,26 @@ import { CodeBlock } from '../CodeBlock'; import { Tab, TabsBox } from '../TabsBox/react/TabsBox'; import { generateNonFastaQuery } from '../../utils/code-generators/python/generator'; import type { Config } from '../../config'; -import { useState } from 'react'; +import React, { useState } from 'react'; import type { ResultField } from '../../utils/code-generators/types'; -import { ContainerWrapper, LabelWrapper } from './styled-components'; +import { CheckBoxesWrapper, ContainerWrapper, LabeledCheckBox, LabelWrapper } from './styled-components'; import { getResultFields, MULTI_SEGMENTED, type QueryTypeSelectionState } from './QueryTypeSelectionState.ts'; import type { OrderByLimitOffset } from './OrderLimitOffsetSelection.tsx'; +const compressionOptions = [ + { value: undefined, label: 'No compression' }, + { value: 'gzip', label: 'gzip' }, + { value: 'zstd', label: 'zstd' }, +]; + +type CompressionValues = (typeof compressionOptions)[number]['value']; + +type AdditionalProperties = { + downloadAsFile: boolean; + tabularOutputFormat: TabularOutputFormat; + compression: CompressionValues; +}; + type Props = { queryType: QueryTypeSelectionState; filters: Filters; @@ -19,23 +33,60 @@ type Props = { lapisUrl: string; }; +type TabProps = Props & AdditionalProperties; + export const Result = (props: Props) => { + const [additionalProperties, setAdditionalProperties] = useState({ + downloadAsFile: false, + tabularOutputFormat: 'json', + compression: undefined, + }); + + const tabProps = { ...props, ...additionalProperties }; + const tabs = [ - { name: 'Query URL', content: }, - { name: 'R code', content: }, - { name: 'Python code', content: }, + { name: 'Query URL', content: }, + { name: 'R code', content: }, + { name: 'Python code', content: }, ]; return ( - - {tabs.map((tab) => ( - {tab.content} - ))} - + <> + setAdditionalProperties((prev) => ({ ...prev, downloadAsFile: !prev.downloadAsFile }))} + /> + setAdditionalProperties((prev) => ({ ...prev, tabularOutputFormat: value }))} + /> +
+ Do you want to fetch compressed data? + + {compressionOptions.map(({ value, label }) => ( + setAdditionalProperties((prev) => ({ ...prev, compression: value }))} + /> + ))} + +
+ + {tabs.map((tab) => ( + {tab.content} + ))} + + ); }; -function constructGetQueryUrl(props: Props, tabularOutputFormat: TabularOutputFormat) { +function constructGetQueryUrl(props: TabProps) { const { lapisUrl, endpoint, body } = constructPostQuery(props); const params = new URLSearchParams(); for (let [name, value] of Object.entries(body)) { @@ -51,25 +102,24 @@ function constructGetQueryUrl(props: Props, tabularOutputFormat: TabularOutputFo if ( queryType.selection !== 'nucleotideSequences' && queryType.selection !== 'aminoAcidSequences' && - tabularOutputFormat !== 'json' + props.tabularOutputFormat !== 'json' ) { - params.set('dataFormat', tabularOutputFormat); + params.set('dataFormat', props.tabularOutputFormat); + } + if (props.downloadAsFile) { + params.set('downloadAsFile', 'true'); + } + if (props.compression !== undefined) { + params.set('compression', props.compression); } return `${lapisUrl}${endpoint}?${params}`; } -const QueryUrlTab = (props: Props) => { - const [tabularOutputFormat, setTabularOutputFormat] = useState('json'); - - const queryUrl = constructGetQueryUrl(props, tabularOutputFormat); +const QueryUrlTab = (props: TabProps) => { + const queryUrl = constructGetQueryUrl(props); return ( -
Query URL: { ); }; -const RTab = (props: Props) => { +const RTab = (props: TabProps) => { return TODO R code; }; -const PythonTab = (props: Props) => { +const PythonTab = (props: TabProps) => { if (props.queryType.selection === 'nucleotideSequences' || props.queryType.selection === 'aminoAcidSequences') { return TODO Code for fetching sequences; } - const propsWithJson: Props = { + const propsWithJson: TabProps = { ...props, }; const { lapisUrl, endpoint, body, resultFields } = constructPostQuery(propsWithJson); @@ -103,7 +153,16 @@ const PythonTab = (props: Props) => { return {code}; }; -function constructPostQuery({ queryType, filters, config, lapisUrl, orderByLimitOffset }: Props): { +function constructPostQuery({ + queryType, + filters, + config, + lapisUrl, + orderByLimitOffset, + downloadAsFile, + tabularOutputFormat, + compression, +}: TabProps): { lapisUrl: string; endpoint: string; body: object; @@ -165,5 +224,14 @@ function constructPostQuery({ queryType, filters, config, lapisUrl, orderByLimit if (orderByLimitOffset.offset !== undefined) { body.offset = orderByLimitOffset.offset; } + if (downloadAsFile) { + body.downloadAsFile = true; + } + if (tabularOutputFormat !== 'json') { + body.dataFormat = tabularOutputFormat; + } + if (compression !== undefined) { + body.compression = compression; + } return { lapisUrl, endpoint, body, resultFields }; } diff --git a/lapis2-docs/src/components/TabsBox/react/TabsBox.tsx b/lapis2-docs/src/components/TabsBox/react/TabsBox.tsx index 574e7ca8..577be1e0 100644 --- a/lapis2-docs/src/components/TabsBox/react/TabsBox.tsx +++ b/lapis2-docs/src/components/TabsBox/react/TabsBox.tsx @@ -18,7 +18,7 @@ export const TabsBox = ({ children }: Props) => { const [activeTab, setActiveTab] = useState(0); return ( -
+
{tabs.map((tab, index) => ( `. +This will prompt browsers to download the data as file. + +```http +GET /sample/aggregated?downloadAsFile=true +``` + +## Compression + +LAPIS supports gzip and Zstd compression. +You can request compressed data via the `Accept-Encoding` header. + +```http +GET /sample/aggregated +Accept-Encoding: gzip +``` + +```http +POST /sample/aggregated +Accept-Encoding: zstd +``` + +LAPIS will set the `Content-Encoding` header in the response to indicate the compression used. + +:::note +Alternatively, you can use the `compression` property in the request. +Refer to the Swagger UI for allowed values. + +```http +GET /sample/aggregated?compression=gzip +``` + +::: diff --git a/lapis2-docs/src/content/docs/references/filters.mdx b/lapis2-docs/src/content/docs/references/filters.mdx index ec4822c2..e8619d7b 100644 --- a/lapis2-docs/src/content/docs/references/filters.mdx +++ b/lapis2-docs/src/content/docs/references/filters.mdx @@ -5,6 +5,6 @@ description: Filters import FiltersTable from '../../../components/FiltersTable/FiltersTable.astro'; -This instance of LAPIS supports the following filters: +This instance of LAPIS supports the following sequence filters: diff --git a/lapis2-docs/public/images/docs/concepts/example_nucleotide_mutation_response_200.png b/lapis2-docs/src/images/concepts/example_nucleotide_mutation_response_200.png similarity index 100% rename from lapis2-docs/public/images/docs/concepts/example_nucleotide_mutation_response_200.png rename to lapis2-docs/src/images/concepts/example_nucleotide_mutation_response_200.png diff --git a/lapis2-docs/public/images/docs/concepts/example_nucleotide_mutation_schema.png b/lapis2-docs/src/images/concepts/example_nucleotide_mutation_schema.png similarity index 100% rename from lapis2-docs/public/images/docs/concepts/example_nucleotide_mutation_schema.png rename to lapis2-docs/src/images/concepts/example_nucleotide_mutation_schema.png diff --git a/lapis2-docs/src/images/references/media_type.png b/lapis2-docs/src/images/references/media_type.png new file mode 100644 index 0000000000000000000000000000000000000000..83510b4279fa99285fd4a74ee49633aff652f334 GIT binary patch literal 30791 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 literal 0 HcmV?d00001 diff --git a/lapis2-docs/tests/docs.spec.ts b/lapis2-docs/tests/docs.spec.ts index 0f44b1a2..7010773e 100644 --- a/lapis2-docs/tests/docs.spec.ts +++ b/lapis2-docs/tests/docs.spec.ts @@ -30,6 +30,7 @@ const referencesPages = prependToRelativeUrl( { title: 'Introduction', relativeUrl: '/introduction' }, { title: 'Fields', relativeUrl: '/fields' }, { title: 'Filters', relativeUrl: '/filters' }, + { title: 'Additional Request Properties', relativeUrl: '/additional-request-properties' }, { title: 'Open API / Swagger', relativeUrl: '/open-api-definition' }, { title: 'Database Config', relativeUrl: '/database-config' }, { title: 'Reference Genomes', relativeUrl: '/reference-genomes' }, diff --git a/lapis2-docs/tests/queryGenerator.page.ts b/lapis2-docs/tests/queryGenerator.page.ts index 4fbfe7ba..dfadfd48 100644 --- a/lapis2-docs/tests/queryGenerator.page.ts +++ b/lapis2-docs/tests/queryGenerator.page.ts @@ -69,6 +69,14 @@ export class QueryGeneratorPage { await this.page.getByLabel(format).check(); } + public async selectCompressionFormat(format: 'gzip' | 'zstd') { + await this.page.getByLabel(format).check(); + } + + public async checkDownloadAsFile() { + await this.page.getByLabel('Download as file').check(); + } + public async expectQueryUrlContains(expected: string) { await expect(this.page.getByRole('textbox', {})).toHaveValue( new RegExp(`^http://localhost:8080/sample.*${expected}`), diff --git a/lapis2-docs/tests/queryGenerator.spec.ts b/lapis2-docs/tests/queryGenerator.spec.ts index 2b29c11a..f73a17f8 100644 --- a/lapis2-docs/tests/queryGenerator.spec.ts +++ b/lapis2-docs/tests/queryGenerator.spec.ts @@ -20,6 +20,12 @@ test.describe('The query generator wizard', () => { await queryGeneratorPage.selectOutputFormat('CSV'); await queryGeneratorPage.expectQueryUrlContains('dataFormat=csv'); + await queryGeneratorPage.selectCompressionFormat('zstd'); + await queryGeneratorPage.expectQueryUrlContains('compression=zstd'); + + await queryGeneratorPage.checkDownloadAsFile(); + await queryGeneratorPage.expectQueryUrlContains('downloadAsFile=true'); + await queryGeneratorPage.viewPythonCode(); await queryGeneratorPage.expectCodeContains( '@dataclass class DataEntry: count: int primaryKey: Optional[str] date: Optional[str]', diff --git a/lapis2/src/main/kotlin/org/genspectrum/lapis/controller/ControllerDescriptions.kt b/lapis2/src/main/kotlin/org/genspectrum/lapis/controller/ControllerDescriptions.kt index 172e30fb..a08140b0 100644 --- a/lapis2/src/main/kotlin/org/genspectrum/lapis/controller/ControllerDescriptions.kt +++ b/lapis2/src/main/kotlin/org/genspectrum/lapis/controller/ControllerDescriptions.kt @@ -49,11 +49,10 @@ const val LIMIT_DESCRIPTION = """The maximum number of entries to return in the const val OFFSET_DESCRIPTION = """The offset of the first entry to return in the response. This is useful for pagination in combination with \"limit\".""" -const val FORMAT_DESCRIPTION = - """The data format of the response. - Alternatively, the data format can be specified by setting the \"Accept\"-header. - You can include the parameter to return the CSV/TSV without headers: "$TEXT_CSV_WITHOUT_HEADERS_HEADER". - When both are specified, this parameter takes precedence.""" +const val FORMAT_DESCRIPTION = """The data format of the response. +Alternatively, the data format can be specified by setting the \"Accept\"-header. +You can include the parameter to return the CSV/TSV without headers: "$TEXT_CSV_WITHOUT_HEADERS_HEADER". +When both are specified, the request parameter takes precedence over the header.""" private const val MAYBE_DESCRIPTION = """ A mutation can be wrapped in a maybe expression "MAYBE(\)"