DQA voting status (#1757)

* Readability / cleanup updates for DQA
* Moved from unnecessary use of useMutation to useQuery for fetching metrics
* Log API errors
* Avoid warnings in tests about updates outside of act
* Update header in DQA after vote changes quality grade

Closes #1504
This commit is contained in:
Ken-ichi
2024-07-03 10:08:47 -07:00
committed by GitHub
parent d6f8dfa453
commit 591d6dc0e5
4 changed files with 64 additions and 47 deletions

View File

@@ -18,17 +18,20 @@ import { compact, groupBy } from "lodash";
import { useCallback, useEffect, useState } from "react";
import * as React from "react";
import Observation from "realmModels/Observation";
import { log } from "sharedHelpers/logger";
import {
useAuthenticatedMutation,
useAuthenticatedQuery,
useLocalObservation
} from "sharedHooks";
import useRemoteObservation from "sharedHooks/useRemoteObservation";
const logger = log.extend( "DQAContainer" );
const DQAContainer = ( ): React.Node => {
const { isInternetReachable: isOnline } = useNetInfo( );
const { params } = useRoute( );
const { observationUUID } = params;
const [qualityMetrics, setQualityMetrics] = useState( undefined );
const [loadingAgree, setLoadingAgree] = useState( false );
const [loadingDisagree, setLoadingDisagree] = useState( false );
const [loadingMetric, setLoadingMetric] = useState( "none" );
@@ -42,8 +45,9 @@ const DQAContainer = ( ): React.Node => {
refetchRemoteObservation,
isRefetching
} = useRemoteObservation( observationUUID, fetchRemoteObservationEnabled );
const observation
= localObservation || Observation.mapApiToRealm( remoteObservation );
const observation = remoteObservation
? Observation.mapApiToRealm( remoteObservation )
: localObservation;
const fetchMetricsParams = {
id: observationUUID,
@@ -70,21 +74,12 @@ const DQAContainer = ( ): React.Node => {
}
};
// destructured mutate to pass into useEffect to prevent infinite
// rerender and disabling eslint useEffect dependency rule
const { mutate } = useAuthenticatedMutation(
( p, o ) => fetchQualityMetrics( p, o ),
{
onSuccess: response => {
setNotLoading();
setQualityMetrics( response );
},
onError: () => {
if ( !isOnline ) {
setHideOfflineSheet( false );
}
}
}
const {
data: qualityMetrics,
refetch: refetchQualityMetrics
} = useAuthenticatedQuery(
["fetchQualityMetrics", observationUUID],
optsWithAuth => fetchQualityMetrics( fetchMetricsParams, optsWithAuth )
);
const combinedQualityMetrics = {
@@ -92,14 +87,6 @@ const DQAContainer = ( ): React.Node => {
...groupBy( observation?.votes, "vote_scope" )
};
useEffect( ( ) => {
mutate( {
id: params.observationUUID,
fields: "metric,agree,user_id",
ttl: -1
} );
}, [mutate, params] );
/**
* After a success mutation of the needs_id vote we start the refetching of the remote
* observation to update the metric status of the observation. So we need to wait until
@@ -139,11 +126,13 @@ const DQAContainer = ( ): React.Node => {
const createQualityMetricMutation = useAuthenticatedMutation(
( qualityMetricParams, optsWithAuth ) => setQualityMetric( qualityMetricParams, optsWithAuth ),
{
onSuccess: () => {
// fetch updated quality metrics with updated votes
mutate( fetchMetricsParams );
onSuccess: async ( ) => {
await refetchQualityMetrics( );
await refetchRemoteObservation( );
setNotLoading( );
},
onError: () => {
onError: error => {
logger.error( "createQualityMetricMutation failure", error );
setHideErrorSheet( false );
}
}
@@ -176,14 +165,16 @@ const DQAContainer = ( ): React.Node => {
faveMutation.mutate( faveParams );
};
const createRemoveQualityMetricMutation = useAuthenticatedMutation(
( p, o ) => deleteQualityMetric( p, o ),
const removeQualityMetricMutation = useAuthenticatedMutation(
( deleteParams, options ) => deleteQualityMetric( deleteParams, options ),
{
onSuccess: () => {
// fetch updated quality metrics with updated votes
mutate( fetchMetricsParams );
onSuccess: async ( ) => {
await refetchQualityMetrics( );
await refetchRemoteObservation( );
setNotLoading( );
},
onError: () => {
onError: error => {
logger.error( "removeQualityMetricMutation failed", error );
setHideErrorSheet( false );
}
}
@@ -197,7 +188,7 @@ const DQAContainer = ( ): React.Node => {
metric,
ttyl: -1
};
createRemoveQualityMetricMutation.mutate( qualityMetricParams );
removeQualityMetricMutation.mutate( qualityMetricParams );
};
// The quality metric "needs_id" uses a fave/unfave vote with vote_scope: "needs_id"

View File

@@ -121,6 +121,8 @@ const DataQualityAssessment = ( {
);
}
// console.log( "[DEBUG DataQualityAssessment.js] qualityMetrics?.date: ", qualityMetrics?.date );
return (
<ScrollViewWrapper testID="DataQualityAssessment">
<View className="mx-[26px] my-[19px] space-y-[9px]">

View File

@@ -32,6 +32,13 @@ jest.mock( "sharedHooks/useCurrentUser", ( ) => ( {
} ) )
} ) );
jest.mock( "sharedHooks/useAuthenticatedQuery", () => ( {
__esModule: true,
default: () => ( {
data: []
} )
} ) );
const mockMutate = jest.fn();
jest.mock( "sharedHooks/useAuthenticatedMutation", () => ( {
__esModule: true,
@@ -51,31 +58,32 @@ useRoute.mockImplementation( ( ) => ( {
}
} ) );
async function expectMutateToHaveBeenCalled() {
expect( await mockMutate ).toHaveBeenCalled();
// Since we mocked the mutate() method, we're expecting mutation not to
// succeed. We want to wait for this so there are no extra things happening
// outside of act()
await screen.findByText( "ERROR LOADING IN DQA" );
}
describe( "DQA Vote Buttons", ( ) => {
test( "renders DQA vote buttons", async ( ) => {
renderComponent( <DQAContainer /> );
const emptyDisagreeButtons = await screen.findAllByTestId( "DQAVoteButton.EmptyDisagree" );
fireEvent.press( emptyDisagreeButtons[0] );
expect( await mockMutate ).toHaveBeenCalled();
expect( emptyDisagreeButtons ).toBeTruthy( );
} );
test( "calls api when DQA disagree button is pressed", async ( ) => {
renderComponent( <DQAContainer /> );
const emptyDisagreeButtons = await screen.findAllByTestId( "DQAVoteButton.EmptyDisagree" );
fireEvent.press( emptyDisagreeButtons[0] );
expect( await mockMutate ).toHaveBeenCalled();
await expectMutateToHaveBeenCalled();
} );
test( "calls api when DQA agree button is pressed", async ( ) => {
renderComponent( <DQAContainer /> );
const emptyDisagreeButtons = await screen.findAllByTestId( "DQAVoteButton.EmptyAgree" );
fireEvent.press( emptyDisagreeButtons[0] );
expect( await mockMutate ).toHaveBeenCalled();
await expectMutateToHaveBeenCalled();
} );
} );

View File

@@ -33,6 +33,13 @@ jest.mock( "sharedHooks/useCurrentUser", ( ) => ( {
} ) )
} ) );
jest.mock( "sharedHooks/useAuthenticatedQuery", () => ( {
__esModule: true,
default: () => ( {
data: []
} )
} ) );
const mockMutate = jest.fn();
jest.mock( "sharedHooks/useAuthenticatedMutation", () => ( {
__esModule: true,
@@ -46,6 +53,15 @@ jest.mock( "sharedHooks/useLocalObservation", () => ( {
default: jest.fn( ( ) => mockObservation )
} ) );
jest.mock( "sharedHooks/useRemoteObservation", ( ) => ( {
__esModule: true,
default: ( _uuid, _fetchRemoteEnabled ) => ( {
remoteObservation: mockObservation,
refetchRemoteObservation: jest.fn( ),
isRefetching: false
} )
} ) );
useRoute.mockImplementation( ( ) => ( {
params: {
observationUUID: mockObservation.uuid