Skip to content

Commit

Permalink
feat(docs): implement order by, limit and offset in query generator #376
Browse files Browse the repository at this point in the history
  • Loading branch information
fengelniederhammer committed Dec 13, 2023
1 parent ecce91a commit 8b13930
Show file tree
Hide file tree
Showing 9 changed files with 895 additions and 52 deletions.
696 changes: 694 additions & 2 deletions lapis2-docs/package-lock.json

Large diffs are not rendered by default.

9 changes: 6 additions & 3 deletions lapis2-docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
"preview": "astro preview",
"astro": "astro",
"check-format": "prettier --check \"**/*.{ts,tsx,json,astro,md,mdx,mjs,cjs}\"",
"format": "prettier --write \"**/*.{ts,tsx,json,astro,md,mdx,mjs,cjs}\""
"format": "prettier --write \"**/*.{ts,tsx,json,astro,md,mdx,mjs,cjs}\"",
"check-types": "tsc --noEmit && CONFIG_FILE=../siloLapisTests/testData/testDatabaseConfig.yaml astro check"
},
"dependencies": {
"@astrojs/react": "^3.0.7",
Expand All @@ -29,8 +30,10 @@
"yaml": "^2.3.4"
},
"devDependencies": {
"@types/swagger-ui": "^3.52.3",
"@astrojs/check": "^0.3.2",
"@types/swagger-ui": "^3.52.4",
"prettier": "^3.0.0",
"prettier-plugin-astro": "^0.12.1"
"prettier-plugin-astro": "^0.12.1",
"typescript": "^5.3.3"
}
}
19 changes: 19 additions & 0 deletions lapis2-docs/src/components/QueryGenerator/LabelledInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
type Props = {
label: string;
value: string | number | undefined;
onChange: (value: string) => void;
};

export const LabelledInput = ({ label, value, onChange }: Props) => {
return (
<div>
<label className='mr-2'>{label}</label>
<input
type='text'
className='input input-bordered w-48'
value={value}
onChange={(e) => onChange(e.target.value)}
/>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import type { Config } from '../../config.ts';
import type { Dispatch, SetStateAction } from 'react';
import type { Selection } from './QueryTypeSelection.tsx';
import { LabelledInput } from './LabelledInput.tsx';

export type OrderByLimitOffset = {
orderBy: string[] | undefined;
limit: number | undefined;
offset: number | undefined;
};

type Props = {
config: Config;
selection: Selection;
orderByLimitOffset: OrderByLimitOffset;
onOrderByLimitOffsetChange: Dispatch<SetStateAction<OrderByLimitOffset>>;
};

export const OrderLimitOffsetSelection = ({ config, orderByLimitOffset, onOrderByLimitOffsetChange }: Props) => {
return (
<div className='flex flex-col gap-4'>
{JSON.stringify(orderByLimitOffset)}
<div className='flex flex-column justify-start'>
<label className='mr-2'>Order by:</label>
<div>
{orderByLimitOffset.orderBy?.map((value, index) => (
<div key={index}>
<input
type='text'
className='input input-bordered w-48'
value={value}
onChange={(e) =>
onOrderByLimitOffsetChange((prev) => ({
...prev,
orderBy: prev.orderBy?.map((v, i) => (i === index ? e.target.value : v)),
}))
}
/>
<button
onClick={() =>
onOrderByLimitOffsetChange((prev) => ({
...prev,
orderBy: prev.orderBy?.filter((_, i) => i !== index),
}))
}
>
-
</button>
</div>
))}
<select>
<option>TODO</option>
<option>a</option>
</select>
<button>+</button>
</div>
</div>
<LabelledInput
label='Limit:'
value={orderByLimitOffset.limit}
onChange={(value) =>
onOrderByLimitOffsetChange((prev) => ({
...prev,
limit: value === '' ? undefined : parseInt(value),
}))
}
/>
<LabelledInput
label='Offset:'
value={orderByLimitOffset.offset}
onChange={(value) =>
onOrderByLimitOffsetChange((prev) => ({
...prev,
offset: value === '' ? undefined : parseInt(value),
}))
}
/>
</div>
);
};
101 changes: 75 additions & 26 deletions lapis2-docs/src/components/QueryGenerator/QueryGenerator.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { useState } from 'react';
import { QueryTypeSelection, type QueryTypeSelectionState } from './QueryTypeSelection';
import { QueryTypeSelection, type QueryTypeSelectionState, type WithResponseFields } from './QueryTypeSelection';
import { type Filters, FiltersSelection } from './FiltersSelection';
import { Result } from './Result';
import type { Config } from '../../config';
import { type OrderByLimitOffset, OrderLimitOffsetSelection } from './OrderLimitOffsetSelection.tsx';

type Props = {
config: Config;
Expand All @@ -11,31 +12,13 @@ type Props = {

export const QueryGenerator = ({ config, lapisUrl }: Props) => {
const [step, setStep] = useState(0);
const [queryType, setQueryType] = useState<QueryTypeSelectionState>({
selection: 'aggregatedAll',
aggregatedAll: {},
aggregatedStratified: {
fields: new Set<string>(),
},
mutations: {
type: 'nucleotide',
minProportion: '0.05',
},
insertions: {
type: 'nucleotide',
},
details: {
type: 'all',
fields: new Set<string>(),
},
nucleotideSequences: {
type: 'unaligned',
},
aminoAcidSequences: {
gene: '',
},
});
const [queryType, setQueryType] = useState<QueryTypeSelectionState>(getInitialQueryState(config));
const [filters, setFilters] = useState<Filters>(new Map());
const [orderByLimitOffset, setOrderByLimitOffset] = useState<OrderByLimitOffset>({
orderBy: undefined,
limit: undefined,
offset: undefined,
});

return (
<div className='not-content'>
Expand All @@ -51,7 +34,14 @@ export const QueryGenerator = ({ config, lapisUrl }: Props) => {
<div className='mt-8'>
{step === 0 && <QueryTypeSelection config={config} state={queryType} onStateChange={setQueryType} />}
{step === 1 && <FiltersSelection config={config} filters={filters} onFiltersChange={setFilters} />}
{step === 2 && <>TODO</>}
{step === 2 && (
<OrderLimitOffsetSelection
config={config}
selection={queryType.selection}
orderByLimitOffset={orderByLimitOffset}
onOrderByLimitOffsetChange={setOrderByLimitOffset}
/>
)}
{step === 3 && <Result queryType={queryType} filters={filters} config={config} lapisUrl={lapisUrl} />}
</div>

Expand All @@ -66,3 +56,62 @@ export const QueryGenerator = ({ config, lapisUrl }: Props) => {
</div>
);
};

function getInitialQueryState(config: Config): QueryTypeSelectionState {
return {
selection: 'aggregatedAll',
aggregatedAll: {},
aggregatedStratified: withResponseFields(
{
fields: new Set<string>(),
},
['count'],
),
mutations: withResponseFields(
{
type: 'nucleotide',
minProportion: '0.05',
},
['mutation', 'proportion', 'count'],
),
insertions: withResponseFields(
{
type: 'nucleotide',
},
['insertion', 'count'],
),
details: withResponseFields(
{
type: 'all',
fields: new Set<string>(),
},
[],
),
nucleotideSequences: withResponseFields(
{
type: 'unaligned',
},
[],
),
aminoAcidSequences: withResponseFields(
{
gene: '',
},
[],
),
};
}

function withResponseFields<
A extends {
fields?: Set<string>;
} & Record<string, any>,
>(initialObject: A, fieldsThatAreAlwaysPresent: string[]): WithResponseFields<A> {
return {
...initialObject,
getResponseFields: () => [
...fieldsThatAreAlwaysPresent,
...(initialObject.fields ? [...initialObject.fields] : []),
],
};
}
35 changes: 15 additions & 20 deletions lapis2-docs/src/components/QueryGenerator/QueryTypeSelection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,7 @@ import type { ReactNode } from 'react';
import type { Config } from '../../config';
import { CheckBoxesWrapper, ContainerWrapper, LabeledCheckBox, LabelWrapper } from './styled-components';

type Selection =
| 'aggregatedAll'
| 'aggregatedStratified'
| 'mutations'
| 'insertions'
| 'details'
| 'nucleotideSequences'
| 'aminoAcidSequences';
export type Selection = keyof Omit<QueryTypeSelectionState, 'selection'>;

const sequenceTypes = ['nucleotide', 'aminoAcid'] as const;
type SequenceType = (typeof sequenceTypes)[number];
Expand All @@ -20,28 +13,30 @@ type AlignmentType = (typeof alignmentTypes)[number];
export type QueryTypeSelectionState = {
selection: Selection;
aggregatedAll: {};
aggregatedStratified: {
aggregatedStratified: WithResponseFields<{
fields: Set<string>;
};
mutations: {
}>;
mutations: WithResponseFields<{
type: SequenceType;
minProportion: string;
};
insertions: {
}>;
insertions: WithResponseFields<{
type: SequenceType;
};
details: {
}>;
details: WithResponseFields<{
type: DetailsType;
fields: Set<string>;
};
nucleotideSequences: {
}>;
nucleotideSequences: WithResponseFields<{
type: AlignmentType;
};
aminoAcidSequences: {
}>;
aminoAcidSequences: WithResponseFields<{
gene: string;
};
}>;
};

export type WithResponseFields<T extends object> = { getResponseFields: () => string[] } & T;

type Props = {
config: Config;
state: QueryTypeSelectionState;
Expand Down
2 changes: 2 additions & 0 deletions lapis2-docs/src/components/TabsBox/astro/rehype-tabs.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// @ts-nocheck

// This code is largely based/copied from
// https://github.com/withastro/starlight/tree/6c387705f1019466ad177355d78eb25f58d928ec/packages/starlight/user-components
import { select } from 'hast-util-select';
Expand Down
2 changes: 1 addition & 1 deletion lapis2-docs/src/components/TabsBox/react/TabsBox.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { FC, ReactElement, ReactNode, useState } from 'react';
import React, { type FC, type ReactElement, type ReactNode, useState } from 'react';

type Props = {
children: React.ReactNode;
Expand Down
3 changes: 3 additions & 0 deletions lapis2-docs/src/content/docs/getting-started/introduction.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ to answer genomic epidemiological questions. Main features include:
To get started, you can use our [**interactive wizard**](../generate-your-request/) to generate your query. We provide
code examples for Python and R.

LAPIS is open source and available on [GitHub](https://github.com/GenSpectrum/LAPIS).
Feel free to report any issues there. Contributions are welcome.

You can find background information of LAPIS in the following publication. For up-to-date information, please rely on
this documentation.

Expand Down

0 comments on commit 8b13930

Please sign in to comment.