Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(website): SSR on initial load of search page #2290

Merged
merged 35 commits into from
Jul 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
094b5b3
refactor query parsing
theosanderson Jul 11, 2024
4d381ac
wip
theosanderson Jul 11, 2024
4eba0bd
comment out
theosanderson Jul 11, 2024
052caa9
type fixes
theosanderson Jul 11, 2024
0bb409a
[skip CI]
theosanderson Jul 11, 2024
c198cc9
update
theosanderson Jul 11, 2024
6e2bbf7
clean up
theosanderson Jul 11, 2024
2194f41
move parseMutationString
theosanderson Jul 11, 2024
dc3ffd9
update
theosanderson Jul 11, 2024
1dc60c1
Update KeycloakClientManager.ts
theosanderson Jul 11, 2024
9b36fa8
wip - need to sort out: pagination, ordering, flash reload
theosanderson Jul 12, 2024
19056fd
fix not actually searching
theosanderson Jul 12, 2024
ebcd89f
fix longstanding issue where adding random query parameters would bor…
theosanderson Jul 12, 2024
b925128
bring page into url client side
theosanderson Jul 12, 2024
c38e0b7
provide initial query dict
theosanderson Jul 12, 2024
135dbd8
Merge branch 'main' into search-ssr
theosanderson Jul 12, 2024
2eaebbc
working quite nicely
theosanderson Jul 12, 2024
843cb96
format
theosanderson Jul 12, 2024
2cfdb54
linted
theosanderson Jul 12, 2024
2f6f882
delete file
theosanderson Jul 12, 2024
6f2274a
types are fixed
theosanderson Jul 12, 2024
47cc221
update
theosanderson Jul 12, 2024
eaa8eca
oops
theosanderson Jul 12, 2024
f0771cc
fix
theosanderson Jul 12, 2024
24605ec
format
theosanderson Jul 12, 2024
9a152ac
don't set page to "1" ever in URL
theosanderson Jul 12, 2024
a09a4b3
format
theosanderson Jul 12, 2024
7b986f7
locale
theosanderson Jul 12, 2024
34a2d2f
Update SearchFullUI.spec.tsx
theosanderson Jul 12, 2024
0e38724
Merge branch 'main' into search-ssr
theosanderson Jul 16, 2024
90a5ed2
Fix missing accession, mutation fields
theosanderson Jul 18, 2024
5170130
Merge branch 'main' into search-ssr
theosanderson Jul 18, 2024
06e13a6
Merge branch 'main' into search-ssr
theosanderson Jul 21, 2024
05832eb
Automated code formatting
Jul 21, 2024
999815b
empty
theosanderson Jul 21, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion website/src/components/SearchPage/SearchForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,6 @@ export const SearchForm = ({
value={'mutation' in fieldValues ? (fieldValues.mutation as string) : ''}
onChange={(value) => setAFieldValue('mutation', value)}
/>

{visibleFields.map((filter) => (
<SearchField
field={filter}
Expand Down
3 changes: 3 additions & 0 deletions website/src/components/SearchPage/SearchFullUI.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,9 @@ function renderSearchFullUI({
tableColumns: ['field1', 'field3'],
primaryKey: 'accession',
} as Schema,
initialData: [],
initialCount: 0,
initialQueryDict: {},
};

render(
Expand Down
196 changes: 105 additions & 91 deletions website/src/components/SearchPage/SearchFullUI.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
VISIBILITY_PREFIX,
COLUMN_VISIBILITY_PREFIX,
getLapisSearchParameters,
getMetadataSchemaWithExpandedRanges,
} from '../../utils/search.ts';
import ErrorBox from '../common/ErrorBox.tsx';

Expand All @@ -42,6 +43,9 @@ interface InnerSearchFullUIProps {
clientConfig: ClientConfig;
schema: Schema;
hiddenFieldValues?: FieldValues;
initialData: TableSequenceData[];
initialCount: number;
initialQueryDict: QueryState;
}
interface QueryState {
[key: string]: string;
Expand All @@ -55,45 +59,25 @@ export const InnerSearchFullUI = ({
clientConfig,
schema,
hiddenFieldValues,
initialData,
initialCount,
initialQueryDict,
}: InnerSearchFullUIProps) => {
if (!hiddenFieldValues) {
hiddenFieldValues = {};
}

const metadataSchema = schema.metadata;

const [isColumnModalOpen, setIsColumnModalOpen] = useState(false);

const metadataSchemaWithExpandedRanges = useMemo(() => {
const result = [];
for (const field of metadataSchema) {
if (field.rangeSearch === true) {
const fromField = {
...field,
name: `${field.name}From`,
label: `From`,
fieldGroup: field.name,
fieldGroupDisplayName: field.displayName ?? sentenceCase(field.name),
};
const toField = {
...field,
name: `${field.name}To`,
label: `To`,
fieldGroup: field.name,
fieldGroupDisplayName: field.displayName ?? sentenceCase(field.name),
};
result.push(fromField);
result.push(toField);
} else {
result.push(field);
}
}
return result;
return getMetadataSchemaWithExpandedRanges(metadataSchema);
}, [metadataSchema]);

const [previewedSeqId, setPreviewedSeqId] = useState<string | null>(null);
const [previewHalfScreen, setPreviewHalfScreen] = useState(false);
const [state, setState] = useQueryAsState({});
const [page, setPage] = useState(1);
const [state, setState] = useQueryAsState(initialQueryDict);

const searchVisibilities = useMemo(() => {
return getFieldVisibilitiesFromQuery(schema, state);
Expand All @@ -116,6 +100,23 @@ export const InnerSearchFullUI = ({

const orderDirection = state.order ?? schema.defaultOrder ?? 'ascending';

const page = parseInt(state.page ?? '1', 10);

const setPage = (newPage: number) => {
setState((prev: QueryState) => {
if (newPage === 1) {
const withoutPageSet = { ...prev };
delete withoutPageSet.page;
return withoutPageSet;
} else {
return {
...prev,
page: newPage.toString(),
};
}
});
};

const setOrderByField = (field: string) => {
setState((prev: QueryState) => ({
...prev,
Expand All @@ -130,8 +131,8 @@ export const InnerSearchFullUI = ({
};

const fieldValues = useMemo(() => {
return getFieldValuesFromQuery(state, hiddenFieldValues);
}, [state, hiddenFieldValues]);
return getFieldValuesFromQuery(state, hiddenFieldValues, schema);
}, [state, hiddenFieldValues, schema]);

const setAFieldValue: SetAFieldValue = (fieldName, value) => {
setState((prev: any) => {
Expand Down Expand Up @@ -203,16 +204,20 @@ export const InnerSearchFullUI = ({

const [oldData, setOldData] = useState<TableSequenceData[] | null>(null);
const [oldCount, setOldCount] = useState<number | null>(null);
const [firstClientSideLoadOfDataCompleted, setFirstClientSideLoadOfDataCompleted] = useState(false);
const [firstClientSideLoadOfCountCompleted, setFirstClientSideLoadOfCountCompleted] = useState(false);

useEffect(() => {
if (detailsHook.data?.data && oldData !== detailsHook.data.data) {
setOldData(detailsHook.data.data);
setFirstClientSideLoadOfDataCompleted(true);
}
}, [detailsHook.data?.data, oldData]);

useEffect(() => {
if (aggregatedHook.data?.data && oldCount !== aggregatedHook.data.data[0].count) {
setOldCount(aggregatedHook.data.data[0].count);
setFirstClientSideLoadOfCountCompleted(true);
}
}, [aggregatedHook.data?.data, oldCount]);

Expand Down Expand Up @@ -281,74 +286,83 @@ export const InnerSearchFullUI = ({
(!detailsHook.isSuccess || !aggregatedHook.isSuccess) && (
<ErrorBox title='Connection problem'>Please check your internet connection</ErrorBox>
)}
{!(totalSequences === undefined && oldCount === null) && (
<div
className={`
${detailsHook.isLoading || aggregatedHook.isLoading ? 'opacity-50 pointer-events-none' : ''}

<div
className={`
${
!(firstClientSideLoadOfCountCompleted && firstClientSideLoadOfDataCompleted)
? 'cursor-wait pointer-events-none'
: detailsHook.isLoading || aggregatedHook.isLoading
? 'opacity-50 pointer-events-none'
: ''
}
`}
>
<div className='text-sm text-gray-800 mb-6 justify-between flex md:px-6 items-baseline'>
<div className='mt-auto'>
Search returned{' '}
{totalSequences !== undefined
? totalSequences.toLocaleString()
: oldCount !== null
? oldCount.toLocaleString()
: ''}{' '}
sequence
{totalSequences === 1 ? '' : 's'}
{detailsHook.isLoading || aggregatedHook.isLoading ? (
<span className='loading loading-spinner loading-xs ml-3 appearSlowly'></span>
) : null}
</div>
<div className='flex'>
<button
className='text-gray-800 hover:text-gray-600 mr-4 underline text-primary-700 hover:text-primary-500'
onClick={() => setIsColumnModalOpen(true)}
>
Customize columns
</button>

<DownloadDialog
lapisUrl={lapisUrl}
lapisSearchParameters={lapisSearchParameters}
referenceGenomesSequenceNames={referenceGenomesSequenceNames}
hiddenFieldValues={hiddenFieldValues}
/>
</div>
>
<div className='text-sm text-gray-800 mb-6 justify-between flex md:px-6 items-baseline'>
<div className='mt-auto'>
Search returned{' '}
{totalSequences !== undefined
? totalSequences.toLocaleString()
: oldCount !== null
? oldCount.toLocaleString()
: initialCount.toLocaleString()}{' '}
sequence
{totalSequences === 1 ? '' : 's'}
{detailsHook.isLoading ||
aggregatedHook.isLoading ||
!firstClientSideLoadOfCountCompleted ||
!firstClientSideLoadOfDataCompleted ? (
<span className='loading loading-spinner loading-xs ml-3 appearSlowly'></span>
) : null}
</div>

<Table
schema={schema}
data={
detailsHook.data?.data !== undefined
? (detailsHook.data.data as TableSequenceData[])
: (oldData ?? [])
}
setPreviewedSeqId={setPreviewedSeqId}
previewedSeqId={previewedSeqId}
orderBy={
{
field: orderByField,
type: orderDirection,
} as OrderBy
}
setOrderByField={setOrderByField}
setOrderDirection={setOrderDirection}
columnsToShow={columnsToShow}
/>

<div className='mt-4 flex justify-center'>
{totalSequences !== undefined && (
<SearchPagination
count={Math.ceil(totalSequences / pageSize)}
page={page}
setPage={setPage}
/>
)}
<div className='flex'>
<button
className='text-gray-800 hover:text-gray-600 mr-4 underline text-primary-700 hover:text-primary-500'
onClick={() => setIsColumnModalOpen(true)}
>
Customize columns
</button>

<DownloadDialog
lapisUrl={lapisUrl}
lapisSearchParameters={lapisSearchParameters}
referenceGenomesSequenceNames={referenceGenomesSequenceNames}
hiddenFieldValues={hiddenFieldValues}
/>
</div>
</div>
)}

<Table
schema={schema}
data={
detailsHook.data?.data !== undefined
? (detailsHook.data.data as TableSequenceData[])
: (oldData ?? initialData)
}
setPreviewedSeqId={setPreviewedSeqId}
previewedSeqId={previewedSeqId}
orderBy={
{
field: orderByField,
type: orderDirection,
} as OrderBy
}
setOrderByField={setOrderByField}
setOrderDirection={setOrderDirection}
columnsToShow={columnsToShow}
/>

<div className='mt-4 flex justify-center'>
{totalSequences !== undefined && (
<SearchPagination
count={Math.ceil(totalSequences / pageSize)}
page={page}
setPage={setPage}
/>
)}
</div>
</div>
</div>
</div>
);
Expand Down
4 changes: 1 addition & 3 deletions website/src/components/SearchPage/fields/TextField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -112,9 +112,7 @@ export const TextField = forwardRef<HTMLInputElement | HTMLTextAreaElement, Text
rows={hasFocus || (fieldValue !== undefined && fieldValue.toString().split('\n').length > 1) ? 4 : 1}
className={`rounded-md block px-2.5 pb-1.5 pt-3 w-full text-sm text-gray-900 bg-transparent border-1 border-gray-300 appearance-none dark:text-white dark:border-gray-600 dark:focus:border-blue-500 focus:outline-none focus:ring-0 focus:border-blue-600 peer ${className}`}
placeholder=''
>
{props.fieldValue}
</textarea>
></textarea>

<label
htmlFor={id}
Expand Down
6 changes: 5 additions & 1 deletion website/src/components/SearchPage/useQueryAsState.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@ export default function useQueryAsState(defaultDict) {
for (const [key, value] of urlParams) {
newDict[key] = value;
}
setValueDict(newDict);

setValueDict( // only change if actually different
(prev) =>
JSON.stringify(prev) === JSON.stringify(newDict) ? prev : newDict
);
}, []);

useEffect(() => {
Expand Down
14 changes: 14 additions & 0 deletions website/src/pages/[organism]/search/index.astro
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import { siloVersionStatuses } from '../../../types/lapis';
import { getAccessToken } from '../../../utils/getAccessToken';
import { getMyGroups } from '../../../utils/getMyGroups';
import { getReferenceGenomesSequenceNames } from '../../../utils/search';
import { performLapisSearchQueries } from '../../../utils/serversideSearch';

const hiddenFieldValues = {
[VERSION_STATUS_FIELD]: siloVersionStatuses.latestVersion,
[IS_REVOCATION_FIELD]: 'false',
Expand All @@ -29,6 +31,15 @@ const accessToken = getAccessToken(Astro.locals.session);
const myGroups = accessToken !== undefined ? await getMyGroups(accessToken) : [];

const referenceGenomeSequenceNames = getReferenceGenomesSequenceNames(cleanedOrganism.key);

const initialQueryDict = Object.fromEntries(Astro.url.searchParams.entries());
const { data, totalCount } = await performLapisSearchQueries(
initialQueryDict,
schema,
referenceGenomeSequenceNames,
hiddenFieldValues,
cleanedOrganism.key,
);
---

<BaseLayout title={`${cleanedOrganism.displayName} - Browse`} noHorizontalPadding>
Expand All @@ -43,5 +54,8 @@ const referenceGenomeSequenceNames = getReferenceGenomesSequenceNames(cleanedOrg
accessToken={accessToken}
referenceGenomesSequenceNames={referenceGenomeSequenceNames}
hiddenFieldValues={hiddenFieldValues}
initialData={data}
initialCount={totalCount}
initialQueryDict={initialQueryDict}
/>
</BaseLayout>
16 changes: 16 additions & 0 deletions website/src/pages/[organism]/submission/[groupId]/released.astro
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import { GROUP_ID_FIELD, VERSION_STATUS_FIELD } from '../../../../settings';
import { siloVersionStatuses } from '../../../../types/lapis';
import { getAccessToken } from '../../../../utils/getAccessToken';
import { getReferenceGenomesSequenceNames } from '../../../../utils/search';
import { performLapisSearchQueries } from '../../../../utils/serversideSearch';
import { getGroupsAndCurrentGroup } from '../../../../utils/submissionPages';

const groupsResult = await getGroupsAndCurrentGroup(Astro.params, Astro.locals.session);
if (groupsResult.isErr()) {
return new Response(undefined, { status: groupsResult.error.status });
Expand All @@ -34,9 +36,20 @@ const hiddenFieldValues = {
[VERSION_STATUS_FIELD]: siloVersionStatuses.latestVersion,
[GROUP_ID_FIELD]: group.groupId,
};

const initialQueryDict = Object.fromEntries(Astro.url.searchParams.entries());
const { data, totalCount } = await performLapisSearchQueries(
initialQueryDict,
schema,
referenceGenomeSequenceNames,
hiddenFieldValues,
cleanedOrganism.key,
);
---

<SubmissionPageWrapper groupsResult={groupsResult} title='Released sequences'>
<h1 class='title px-3 py-2 ml-1'>Search</h1>

<SearchFullUI
client:load
clientConfig={clientConfig}
Expand All @@ -46,5 +59,8 @@ const hiddenFieldValues = {
accessToken={accessToken}
referenceGenomesSequenceNames={referenceGenomeSequenceNames}
hiddenFieldValues={hiddenFieldValues}
initialData={data}
initialCount={totalCount}
initialQueryDict={initialQueryDict}
/>
</SubmissionPageWrapper>
Loading
Loading