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

Feature/improve filter messaging #840

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
2 changes: 1 addition & 1 deletion src/components/Query/Rules/EditEntityRule.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ const EditEntityRule = ({
>
<DetachedField
component={RadioGroup}
options={entityRuleTypeOptions}
options={entityRuleTypeOptions(entityType)}
value={entityRuleType}
onChange={handleChangeEntityRuleType}
/>
Expand Down
6 changes: 3 additions & 3 deletions src/components/Query/Rules/withEntityRuleType.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ const entityRuleTypes = {
TYPE_RULE,
};

const entityRuleTypeOptions = [
{ label: 'Attribute - rule based on the value of this alter type\'s attributes.', value: VARIABLE_RULE },
{ label: 'Presence - based on the presence or absence of this alter type in the interview network.', value: TYPE_RULE },
const entityRuleTypeOptions = (entityType) => [
{ label: `Attribute - rule based on the value of this ${entityType} type's attributes.`, value: VARIABLE_RULE },
{ label: `Presence - based on the presence or absence of this ${entityType} type in the interview network.`, value: TYPE_RULE },
buckhalt marked this conversation as resolved.
Show resolved Hide resolved
];

const withEntityRuleType = compose(
Expand Down
37 changes: 35 additions & 2 deletions src/components/sections/Filter.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useCallback } from 'react';
import React, { useCallback, useMemo } from 'react';
import { change, Field, formValueSelector } from 'redux-form';
import { useDispatch, useSelector } from 'react-redux';
import { Section } from '@components/EditorLayout';
Expand All @@ -7,6 +7,8 @@ import { getFieldId } from '../../utils/issues';
import {
Filter as FilterQuery, withFieldConnector, withStoreConnector, ruleValidator,
} from '../Query';
import Tip from '../Tip';
import getEdgeFilteringWarning from './SociogramPrompts/utils';

const FilterField = withFieldConnector(withStoreConnector(FilterQuery));

Expand All @@ -30,6 +32,29 @@ const Filter = () => {
[dispatch],
);

// get edge creation and display values for edges across all prompts
const prompts = useSelector((state) => getFormValue(state, 'prompts'));

const { edgeCreationValues, edgeDisplayValues } = useMemo(() => {
if (!prompts) return { edgeCreationValues: [], edgeDisplayValues: [] };
const creationValues = [];
const displayValues = [];
prompts.forEach((prompt) => {
if (prompt?.edges?.create) creationValues.push(prompt.edges.create);
if (prompt?.edges?.display) displayValues.push(...prompt.edges.display);
});
return { edgeCreationValues: creationValues, edgeDisplayValues: displayValues };
}, [prompts]);
const shouldShowWarning = useMemo(() => {
if (edgeCreationValues.length > 0 || edgeDisplayValues.length > 0) {
return getEdgeFilteringWarning(
currentValue.rules,
[...edgeCreationValues, ...edgeDisplayValues],
);
}
return false;
}, [currentValue, edgeCreationValues, edgeDisplayValues]);

const handleToggleChange = useCallback(
async (newState) => {
if (!currentValue || newState === true) {
Expand All @@ -54,13 +79,21 @@ const Filter = () => {
toggleable
summary={(
<p>
You can optionally filter which nodes are shown on this stage, by creating
You can optionally filter which nodes or edges are shown on this stage, by creating
one or more rules using the options below.
</p>
)}
startExpanded={!!currentValue}
handleToggleChange={handleToggleChange}
>
{shouldShowWarning && (
<Tip type="warning">
<p>
This stage has edge creation or display values that will not be shown
buckhalt marked this conversation as resolved.
Show resolved Hide resolved
based on the current filter rules.
</p>
</Tip>
)}
<div id={getFieldId('filter')} data-name="Filter text" />
<Field
name="filter"
Expand Down
17 changes: 16 additions & 1 deletion src/components/sections/SociogramPrompts/PromptFieldsEdges.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import { change, Field, formValueSelector } from 'redux-form';
import * as Fields from '@codaco/ui/lib/components/Fields';
import { Section, Row } from '@components/EditorLayout';
import Tip from '../../Tip';
import { getEdgesForSubject } from './selectors';
import { getEdgeFilters, getEdgesForSubject } from './selectors';
import getEdgeFilteringWarning from './utils';

const DisplayEdges = ({ form, entity, type }) => {
const dispatch = useDispatch();
Expand All @@ -30,6 +31,9 @@ const DisplayEdges = ({ form, entity, type }) => {
dispatch(change(form, 'edges.display', displayEdgesWithCreatedEdge));
}, [createEdge]);

const edgeFilters = useSelector((state) => getEdgeFilters(state));
const shouldShowNetworkFilterWarning = getEdgeFilteringWarning(edgeFilters, displayEdges);

return (
<>
<Section
Expand Down Expand Up @@ -59,6 +63,17 @@ const DisplayEdges = ({ form, entity, type }) => {
}}
>
<Row>
{shouldShowNetworkFilterWarning && (
<Tip type="warning">
<p>
Stage level network filtering is enabled, but one or more of the edge types
you have configured to display on this prompt are not currently included in the
filter. This means that these edges may not be displayed. Either remove the
stage-level network filtering, or add these edge types to the filter to resolve this
issue.
</p>
</Tip>
)}
{ hasDisabledEdgeOption && (
<Tip>
<p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ import { change, formValueSelector } from 'redux-form';
import * as Fields from '@codaco/ui/lib/components/Fields';
import { Section, Row } from '@components/EditorLayout';
import ValidatedField from '@components/Form/ValidatedField';
import Tip from '@components/Tip';
import EntitySelectField from '../fields/EntitySelectField/EntitySelectField';
import DetachedField from '../../DetachedField';
import VariablePicker from '../../Form/Fields/VariablePicker/VariablePicker';
import { getHighlightVariablesForSubject } from './selectors';
import { getHighlightVariablesForSubject, getEdgeFilters } from './selectors';
import { actionCreators as codebookActions } from '../../../ducks/modules/protocol/codebook';
import getEdgeFilteringWarning from './utils';

// TODO: Move this somewhere else!
// This was created as part of removing the HOC pattern used throughout the app.
Expand Down Expand Up @@ -101,6 +103,11 @@ const TapBehaviour = ({
return true;
};

const selectedValue = useSelector((state) => getFormValue(state, 'edges.create'));

const edgeFilters = useSelector(getEdgeFilters);
const showNetworkFilterWarning = getEdgeFilteringWarning(edgeFilters, [selectedValue]);

return (
<Section
group
Expand Down Expand Up @@ -168,13 +175,26 @@ const TapBehaviour = ({
/>
)}
{ tapBehaviour === TAP_BEHAVIOURS.CREATE_EDGES && (
<>
{showNetworkFilterWarning && (
<Tip type="warning">
<p>
Stage level network filtering is enabled, but the edge type you want to create
on this prompt is not currently included in the filter. This means that these
edges may not be displayed. Either remove the stage-level network filtering,
or add these edge types to the filter to resolve this issue.
</p>
</Tip>
)}

<ValidatedField
entityType="edge"
name="edges.create"
component={EntitySelectField}
label="Create edges of the following type"
validation={{ required: true }}
/>
</>
)}
</Row>
</Section>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`SociogramPrompts selectors get edge filters 1`] = `
Array [
Object {
"color": "blue",
"label": "an edge",
"value": "1234-5",
},
]
`;

exports[`SociogramPrompts selectors get edges for node type 1`] = `
Array [
Object {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
/* eslint-env jest */

import mockState from '../../../../__tests__/testState.json';

import {
getLayoutVariablesForSubject,
getHighlightVariablesForSubject,
Expand Down Expand Up @@ -42,5 +41,11 @@ describe('SociogramPrompts', () => {

expect(result).toMatchSnapshot();
});

it('get edge filters', () => {
const result = getEdgesForSubject(mockState, subject);

expect(result).toMatchSnapshot();
});
});
});
70 changes: 70 additions & 0 deletions src/components/sections/SociogramPrompts/__tests__/utils.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/* eslint-env jest */
import getEdgeFilteringWarning from '../utils';

describe('getEdgeFilteringWarning', () => {
// Case 1: Selected edge is in EXISTS filters - no warning
it('returns false when selected edges match EXISTS filters', () => {
const filters = [
{ options: { operator: 'EXISTS', type: '1' } },
{ options: { operator: 'EXISTS', type: '2' } },
];
const edges = ['1', '2'];

const result = getEdgeFilteringWarning(filters, edges);
expect(result).toBe(false);
});

// Case 2: Selected edge is not in EXISTS filters - show warning
it('returns true when selected edges do not match EXISTS filters', () => {
const filters = [
{ options: { operator: 'EXISTS', type: '1' } },
];
const edges = ['2', '3'];

const result = getEdgeFilteringWarning(filters, edges);
expect(result).toBe(true);
});

// Case 3: Selected edge is in DOES_NOT_EXIST filters - show warning
it('returns true when selected edges match DOES_NOT_EXIST filters', () => {
const filters = [
{ options: { operator: 'NOT_EXISTS', type: '1' } },
];
const edges = ['1'];

const result = getEdgeFilteringWarning(filters, edges);
expect(result).toBe(true);
});

// Case 4: Selected edge is not in DOES_NOT_EXIST filters - no warning
it('returns false when selected edges do not match DOES_NOT_EXIST filters', () => {
const filters = [
{ options: { operator: 'NOT_EXISTS', type: '1' } },
];
const edges = ['2', '3'];

const result = getEdgeFilteringWarning(filters, edges);
expect(result).toBe(false);
});

// Mixed filters
it('handles mixed filter scenarios correctly', () => {
const filters = [
{ options: { operator: 'EXISTS', type: '1' } },
{ options: { operator: 'NOT_EXISTS', type: '2' } },
];
const edges = ['3', '2'];

const result = getEdgeFilteringWarning(filters, edges);
expect(result).toBe(true);
});

// Empty filter
it('returns false when no filters and no edges', () => {
const filters = [];
const edges = [];

const result = getEdgeFilteringWarning(filters, edges);
expect(result).toBe(false);
});
});
13 changes: 13 additions & 0 deletions src/components/sections/SociogramPrompts/selectors.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { getCodebook } from '@selectors/protocol';
import { getVariableOptionsForSubject } from '@selectors/codebook';
import { asOptions } from '@selectors/utils';
import { formValueSelector } from 'redux-form';

export const getLayoutVariablesForSubject = (state, { entity, type }) => {
const variableOptions = getVariableOptionsForSubject(state, { entity, type });
Expand Down Expand Up @@ -32,3 +33,15 @@ export const getEdgesForSubject = (state) => {

return codebookOptions;
};

export const getEdgeFilters = (state) => {
const getStageValue = formValueSelector('edit-stage');
const currentFilters = getStageValue(state, 'filter');

if (!currentFilters || !currentFilters.rules) {
return [];
}
const edgeFilters = currentFilters.rules.filter((rule) => rule.type === 'edge');

return edgeFilters;
};
43 changes: 43 additions & 0 deletions src/components/sections/SociogramPrompts/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/**
* Compare selected edges to edge filters to determine if a warning should be shown
* @param {Array} filters - edge filters
* @param {Array} edges - selected edges
* There are four main cases to consider:
* 1. Selected edge is in the filters with rule EXISTS -- no warning
* 2. Selected edge is not in the filters with rule EXISTS -- show a warning
* 3. Selected edge is in the filters with rule DOES_NOT_EXIST -- show a warning
* 4. Selected edge is not in the filters with rule DOES_NOT_EXIST -- no warning
*/

const getEdgeFilteringWarning = (filters, edges) => {
const existFilters = filters.filter((rule) => rule.options.operator === 'EXISTS');
const doesNotExistFilters = filters.filter((rule) => rule.options.operator === 'NOT_EXISTS');

// if any edge should show a warning, return true
return edges.some((edge) => {
const isEdgeInExistFilters = existFilters.some((rule) => rule.options.type === edge);
const isEdgeInDoesNotExistFilters = doesNotExistFilters.some(
(rule) => rule.options.type === edge,
);

// case 1
if (isEdgeInExistFilters) {
return false;
}

// case 2
if (!isEdgeInExistFilters && existFilters.length > 0) {
return true;
}

// case 3
if (isEdgeInDoesNotExistFilters) {
return true;
}

// No warning in other cases
return false;
});
};

export default getEdgeFilteringWarning;
Loading