Skip to content

Commit

Permalink
Fix analytics API.
Browse files Browse the repository at this point in the history
Tweak analytics frontend (disable sorting which is not yet supported)
  • Loading branch information
NickPhura committed Jan 10, 2025
1 parent 22b1d76 commit f3eb70c
Show file tree
Hide file tree
Showing 6 changed files with 179 additions and 143 deletions.
218 changes: 148 additions & 70 deletions api/src/repositories/analytics-repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,11 @@ export class AnalyticsRepository extends BaseRepository {
groupByQuantitativeMeasurements: string[],
groupByQualitativeMeasurements: string[]
): Promise<ObservationCountByGroupSQLResponse[]> {
const fullyQualifiedGroupByColumnNames = this._getFullyQualifiedGroupByColumnNames(groupByColumns);

const knex = getKnex();

const allGroupByColumns = [
...groupByColumns,
...groupByQuantitativeMeasurements,
...groupByQualitativeMeasurements
];
const queryBuilder = knex.queryBuilder();

// Subquery to get the total count, used for calculating ratios
const totalCountSubquery = knex('observation_subcount')
Expand All @@ -58,7 +56,7 @@ export class AnalyticsRepository extends BaseRepository {
.toString();

// Create columns for quantitative measurements
const quantColumns = groupByQuantitativeMeasurements.map((id) =>
const quantitativeMeasurementGroupByColumnNames = groupByQuantitativeMeasurements.map((id) =>
knex.raw(
`MAX(
CASE
Expand All @@ -71,7 +69,7 @@ export class AnalyticsRepository extends BaseRepository {
);

// Create columns for qualitative measurements
const qualColumns = groupByQualitativeMeasurements.map((id) =>
const qualitativeMeasurementGroupByColumnNames = groupByQualitativeMeasurements.map((id) =>
knex.raw(
`STRING_AGG(
DISTINCT
Expand All @@ -84,91 +82,171 @@ export class AnalyticsRepository extends BaseRepository {
)
);

const fullyQualifiedGroupByColumnNames = this._getFullyQualifiedGroupByColumnNames(groupByColumns);

const queryBuilder = knex.queryBuilder();

queryBuilder.with('w_observations', (qb) => {
qb.select(

Check warning on line 86 in api/src/repositories/analytics-repository.ts

View check run for this annotation

Codecov / codecov/patch

api/src/repositories/analytics-repository.ts#L86

Added line #L86 was not covered by tests
'observation_subcount.subcount',
'observation_subcount.observation_subcount_id',
'survey_observation.survey_id',
...fullyQualifiedGroupByColumnNames.map((column) => knex.raw('??', [column])),

Check warning on line 90 in api/src/repositories/analytics-repository.ts

View check run for this annotation

Codecov / codecov/patch

api/src/repositories/analytics-repository.ts#L90

Added line #L90 was not covered by tests
...quantColumns,
...qualColumns
)
.from('observation_subcount')
.innerJoin(
'survey_observation',
'survey_observation.survey_observation_id',
'observation_subcount.survey_observation_id'
)
.leftJoin(
...quantitativeMeasurementGroupByColumnNames,
...qualitativeMeasurementGroupByColumnNames
);

if (groupByColumns.includes('survey_sample_site_id')) {
qb.select('survey_sample_site.name as survey_sample_site_name');

Check warning on line 96 in api/src/repositories/analytics-repository.ts

View check run for this annotation

Codecov / codecov/patch

api/src/repositories/analytics-repository.ts#L96

Added line #L96 was not covered by tests
}

if (groupByColumns.includes('method_technique_id')) {
qb.select('method_technique.name as method_technique_name');

Check warning on line 100 in api/src/repositories/analytics-repository.ts

View check run for this annotation

Codecov / codecov/patch

api/src/repositories/analytics-repository.ts#L100

Added line #L100 was not covered by tests
}

if (groupByColumns.includes('survey_sample_period_id')) {
qb.select(
'survey_sample_period.start_date',
'survey_sample_period.start_time',
'survey_sample_period.end_date',
'survey_sample_period.end_time'
);
}

qb.from('observation_subcount').innerJoin(

Check warning on line 112 in api/src/repositories/analytics-repository.ts

View check run for this annotation

Codecov / codecov/patch

api/src/repositories/analytics-repository.ts#L112

Added line #L112 was not covered by tests
'survey_observation',
'survey_observation.survey_observation_id',
'observation_subcount.survey_observation_id'
);

if (
groupByColumns.includes('survey_sample_period_id') ||
groupByColumns.includes('method_technique_id') ||
groupByColumns.includes('survey_sample_site_id')
) {
qb.leftJoin(

Check warning on line 123 in api/src/repositories/analytics-repository.ts

View check run for this annotation

Codecov / codecov/patch

api/src/repositories/analytics-repository.ts#L123

Added line #L123 was not covered by tests
'survey_sample_period',
'survey_sample_period.survey_sample_period_id',
'survey_observation.survey_sample_period_id'
)
.leftJoin(
);
}

if (groupByColumns.includes('method_technique_id')) {
qb.leftJoin(

Check warning on line 131 in api/src/repositories/analytics-repository.ts

View check run for this annotation

Codecov / codecov/patch

api/src/repositories/analytics-repository.ts#L131

Added line #L131 was not covered by tests
'method_technique',
'method_technique.method_technique_id',
'survey_sample_period.method_technique_id'
)
.leftJoin(
);
}

if (groupByColumns.includes('survey_sample_site_id')) {
qb.leftJoin(

Check warning on line 139 in api/src/repositories/analytics-repository.ts

View check run for this annotation

Codecov / codecov/patch

api/src/repositories/analytics-repository.ts#L139

Added line #L139 was not covered by tests
'survey_sample_site',
'survey_sample_site.survey_sample_site_id',
'survey_sample_period.survey_sample_site_id'
)
.leftJoin(
'observation_subcount_qualitative_measurement',
'observation_subcount_qualitative_measurement.observation_subcount_id',
'observation_subcount.observation_subcount_id'
)
.leftJoin(
'observation_subcount_quantitative_measurement',
'observation_subcount_quantitative_measurement.observation_subcount_id',
'observation_subcount.observation_subcount_id'
)
.whereIn('survey_observation.survey_id', surveyIds)
.groupBy(
'observation_subcount.subcount',
'observation_subcount.observation_subcount_id',
'survey_observation.survey_id',
...fullyQualifiedGroupByColumnNames
);
}

qb.leftJoin(

Check warning on line 146 in api/src/repositories/analytics-repository.ts

View check run for this annotation

Codecov / codecov/patch

api/src/repositories/analytics-repository.ts#L146

Added line #L146 was not covered by tests
'observation_subcount_qualitative_measurement',
'observation_subcount_qualitative_measurement.observation_subcount_id',
'observation_subcount.observation_subcount_id'
);

qb.leftJoin(

Check warning on line 152 in api/src/repositories/analytics-repository.ts

View check run for this annotation

Codecov / codecov/patch

api/src/repositories/analytics-repository.ts#L152

Added line #L152 was not covered by tests
'observation_subcount_quantitative_measurement',
'observation_subcount_quantitative_measurement.observation_subcount_id',
'observation_subcount.observation_subcount_id'
);

qb.whereIn('survey_observation.survey_id', surveyIds);

Check warning on line 158 in api/src/repositories/analytics-repository.ts

View check run for this annotation

Codecov / codecov/patch

api/src/repositories/analytics-repository.ts#L158

Added line #L158 was not covered by tests

qb.groupBy(

Check warning on line 160 in api/src/repositories/analytics-repository.ts

View check run for this annotation

Codecov / codecov/patch

api/src/repositories/analytics-repository.ts#L160

Added line #L160 was not covered by tests
'observation_subcount.subcount',
'observation_subcount.observation_subcount_id',
'survey_observation.survey_id',
...fullyQualifiedGroupByColumnNames
);

if (groupByColumns.includes('survey_sample_site_id')) {
qb.groupBy('survey_sample_site.name');

Check warning on line 168 in api/src/repositories/analytics-repository.ts

View check run for this annotation

Codecov / codecov/patch

api/src/repositories/analytics-repository.ts#L168

Added line #L168 was not covered by tests
}

if (groupByColumns.includes('method_technique_id')) {
qb.groupBy('method_technique.name');

Check warning on line 172 in api/src/repositories/analytics-repository.ts

View check run for this annotation

Codecov / codecov/patch

api/src/repositories/analytics-repository.ts#L172

Added line #L172 was not covered by tests
}

if (groupByColumns.includes('survey_sample_period_id')) {
qb.groupBy(

Check warning on line 176 in api/src/repositories/analytics-repository.ts

View check run for this annotation

Codecov / codecov/patch

api/src/repositories/analytics-repository.ts#L176

Added line #L176 was not covered by tests
'survey_sample_period.start_date',
'survey_sample_period.start_time',
'survey_sample_period.end_date',
'survey_sample_period.end_time'
);
}
});

queryBuilder
.select(knex.raw('public.gen_random_uuid() as id')) // Generate a unique ID for the row
.select(knex.raw('COUNT(subcount)::NUMERIC as row_count'))
.select(knex.raw('SUM(subcount)::NUMERIC as individual_count'))
.select(
knex.raw(
`ROUND(SUM(w_observations.subcount)::NUMERIC / (${totalCountSubquery}) * 100, 2) as individual_percentage`
)
queryBuilder.select(knex.raw('public.gen_random_uuid() as id')); // Generate a unique ID for the row

queryBuilder.select(knex.raw('COUNT(subcount)::NUMERIC as row_count'));

queryBuilder.select(knex.raw('SUM(subcount)::NUMERIC as individual_count'));

queryBuilder.select(
knex.raw(
`ROUND(SUM(w_observations.subcount)::NUMERIC / (${totalCountSubquery}) * 100, 2) as individual_percentage`
)
.select(groupByColumns.map((column) => knex.raw('??', [column])))
// Measurement properties are objects of {'<critterbase_taxon_measurement_id>' : '<value>', '<critterbase_taxon_measurement_id>' : '<value>'}
.select(
knex.raw(
`jsonb_build_object(${groupByQuantitativeMeasurements
.map((column) => `'${column}', ??`)
.join(', ')}) as quant_measurements`,
groupByQuantitativeMeasurements
)
);

queryBuilder.select(groupByColumns.map((column) => knex.raw('??', [column])));

// Measurement properties are objects of {'<critterbase_taxon_measurement_id>' : '<value>', '<critterbase_taxon_measurement_id>' : '<value>'}
queryBuilder.select(
knex.raw(
`jsonb_build_object(${groupByQuantitativeMeasurements
.map((column) => `'${column}', ??`)
.join(', ')}) as quant_measurements`,
groupByQuantitativeMeasurements
)
.select(
knex.raw(
`jsonb_build_object(${groupByQualitativeMeasurements
.map((column) => `'${column}', ??`)
.join(', ')}) as qual_measurements`,
groupByQualitativeMeasurements
)
);

queryBuilder.select(
knex.raw(
`jsonb_build_object(${groupByQualitativeMeasurements
.map((column) => `'${column}', ??`)
.join(', ')}) as qual_measurements`,
groupByQualitativeMeasurements
)
.select('w_observations.survey_sample_period_id')
.from('w_observations');
);

if (groupByColumns.includes('survey_sample_site_id')) {
queryBuilder.select('w_observations.survey_sample_site_name');

Check warning on line 219 in api/src/repositories/analytics-repository.ts

View check run for this annotation

Codecov / codecov/patch

api/src/repositories/analytics-repository.ts#L219

Added line #L219 was not covered by tests

queryBuilder.groupBy('w_observations.survey_sample_site_name');

Check warning on line 221 in api/src/repositories/analytics-repository.ts

View check run for this annotation

Codecov / codecov/patch

api/src/repositories/analytics-repository.ts#L221

Added line #L221 was not covered by tests
}

if (groupByColumns.includes('method_technique_id')) {
queryBuilder.select('w_observations.method_technique_name');

Check warning on line 225 in api/src/repositories/analytics-repository.ts

View check run for this annotation

Codecov / codecov/patch

api/src/repositories/analytics-repository.ts#L225

Added line #L225 was not covered by tests

queryBuilder.groupBy('w_observations.method_technique_name');

Check warning on line 227 in api/src/repositories/analytics-repository.ts

View check run for this annotation

Codecov / codecov/patch

api/src/repositories/analytics-repository.ts#L227

Added line #L227 was not covered by tests
}

if (groupByColumns.includes('survey_sample_period_id')) {
queryBuilder.select(

Check warning on line 231 in api/src/repositories/analytics-repository.ts

View check run for this annotation

Codecov / codecov/patch

api/src/repositories/analytics-repository.ts#L231

Added line #L231 was not covered by tests
'w_observations.start_date',
'w_observations.start_time',
'w_observations.end_date',
'w_observations.end_time'
);

queryBuilder.groupBy(

Check warning on line 238 in api/src/repositories/analytics-repository.ts

View check run for this annotation

Codecov / codecov/patch

api/src/repositories/analytics-repository.ts#L238

Added line #L238 was not covered by tests
'w_observations.start_date',
'w_observations.start_time',
'w_observations.end_date',
'w_observations.end_time'
);
}

queryBuilder.from('w_observations');

if (groupByColumns.length) {
queryBuilder.groupBy(allGroupByColumns);
queryBuilder.groupBy(...groupByColumns, ...groupByQuantitativeMeasurements, ...groupByQualitativeMeasurements);
}

queryBuilder.orderBy('individual_count', 'desc');
Expand All @@ -184,7 +262,7 @@ export class AnalyticsRepository extends BaseRepository {
* @example
* const groupByColumns = ['survey_sample_site_id', 'itis_tsn'];
* const fullyQualifiedGroupByColumns = _getFullyQualifiedGroupByColumnNames(groupByColumns);
* // fullyQualifiedGroupByColumns = ['survey_sample_site.survey_sample_site_id', 'observation.itis_tsn']
* // fullyQualifiedGroupByColumns = ['survey_sample_site.survey_sample_site_id', 'survey_observation.itis_tsn']
*
* @param {string[]} groupByColumns
* @memberof AnalyticsRepository
Expand Down
2 changes: 0 additions & 2 deletions app/src/contexts/dialogContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -174,8 +174,6 @@ export const DialogContextProvider: React.FC<React.PropsWithChildren> = (props)
setScoreDialogProps((prev) => ({ ...prev, ...partialProps }));
};

console.log(snackbarProps);

return (
<DialogContext.Provider
value={{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ export const SurveyObservationAnalytics = () => {
</Box>
}
isLoadingFallbackDelay={100}>
<Box minWidth="250px" display="flex" flexDirection="column">
<Box display="flex" flexDirection="column">
{/* Group by header */}
<Box flex="0 0 auto">
<Typography
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ export const ObservationAnalyticsDataTable = (props: IObservationAnalyticsDataTa
// Sorting
sortingMode="server"
// Row selection
rowSelection
checkboxSelection
rowSelection={false}
checkboxSelection={false}
// Pagination
initialState={{
pagination: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import { useBiohubApi } from 'hooks/useBioHubApi';
import { useSurveyContext, useTaxonomyContext } from 'hooks/useContext';
import useDataLoader from 'hooks/useDataLoader';
import { IObservationCountByGroup } from 'interfaces/useAnalyticsApi.interface';
import { GetSamplingPeriod } from 'interfaces/useSamplingPeriodApi.interface';
import { useEffect, useMemo } from 'react';
import {
getBasicGroupByColDefs,
Expand Down Expand Up @@ -102,10 +101,6 @@ export const ObservationAnalyticsDataTableContainer = (props: IObservationAnalyt
[analyticsDataLoader?.data]
);

// TODO: Include sampling information in the analytics response / otherwise get sampling information,
// which is now more complicated because sample sites are paginated.
const samplePeriods: GetSamplingPeriod[] = [];

const allGroupByColumns = useMemo(
() => [...groupByColumns, ...groupByQualitativeMeasurements, ...groupByQuantitativeMeasurements],
[groupByColumns, groupByQualitativeMeasurements, groupByQuantitativeMeasurements]
Expand All @@ -116,9 +111,9 @@ export const ObservationAnalyticsDataTableContainer = (props: IObservationAnalyt
getRowCountColDef(),
getIndividualCountColDef(),
getIndividualPercentageColDef(),
getSamplingSiteColDef(samplePeriods),
getMethodTechniqueColDef(samplePeriods),
getSamplingPeriodColDef(samplePeriods),
getSamplingSiteColDef(),
getMethodTechniqueColDef(),
getSamplingPeriodColDef(),
getSpeciesColDef(taxonomyContext.getCachedSpeciesTaxonomyById),
getDateColDef(),
...getBasicGroupByColDefs([...groupByQualitativeMeasurements, ...groupByQuantitativeMeasurements])
Expand Down Expand Up @@ -152,7 +147,9 @@ export const ObservationAnalyticsDataTableContainer = (props: IObservationAnalyt
<Box display="flex" flex="1 1 auto" position="relative">
<Box position="absolute" width="100%" height="100%">
<LoadingGuard
isLoading={analyticsDataLoader.isLoading || !analyticsDataLoader.isReady}
isLoading={
!analyticsDataLoader.data?.length && (analyticsDataLoader.isLoading || !analyticsDataLoader.isReady)
}
isLoadingFallback={<SkeletonTable />}
isLoadingFallbackDelay={100}
hasNoData={!analyticsDataLoader.data?.length}
Expand Down
Loading

0 comments on commit f3eb70c

Please sign in to comment.