Skip to content

Commit

Permalink
feat: apply filters to cancel/remind all
Browse files Browse the repository at this point in the history
ENT-8156 | Pass the appropriate content assignment filter state
to the `cancel-all` and `remind-all` endpoints.
  • Loading branch information
iloveagent57 committed Jan 4, 2024
1 parent 2d7e691 commit dbdbe10
Show file tree
Hide file tree
Showing 13 changed files with 168 additions and 36 deletions.
4 changes: 2 additions & 2 deletions src/components/forms/FormWorkflow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -138,8 +138,8 @@ const FormWorkflow = <FormConfigData extends unknown>({
const newFormFields: FormConfigData = await nextButtonConfig.onClick({
formFields,
errHandler: (error) => {
setFormError(error);
if (!!error) {
setFormError(error);
if (error) {
advance = false;
}
},
Expand Down
18 changes: 11 additions & 7 deletions src/components/learner-credit-management/AssignmentTableCancel.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import CancelAssignmentModal from './CancelAssignmentModal';
import useCancelContentAssignments from './data/hooks/useCancelContentAssignments';
import { transformSelectedRows, useBudgetId, useSubsidyAccessPolicy } from './data';
import EVENT_NAMES from '../../eventTracking';
import { getActiveTableColumnFilters } from '../../utils';

const calculateTotalToCancel = ({
assignmentUuids,
Expand Down Expand Up @@ -39,18 +38,20 @@ const AssignmentTableCancelAction = ({
totalSelectedRows,
} = transformSelectedRows(selectedFlatRows);

const activeFilters = getActiveTableColumnFilters(tableInstance.columns);

// If entire table is selected and there are NO filters, hit cancel-all endpoint. Otherwise, hit usual bulk cancel.
const shouldCancelAll = isEntireTableSelected && activeFilters.length === 0;
const { state: dataTableState } = tableInstance;

const {
cancelButtonState,
cancelContentAssignments,
close,
isOpen,
open,
} = useCancelContentAssignments(assignmentConfiguration.uuid, assignmentUuids, shouldCancelAll);
} = useCancelContentAssignments(
assignmentConfiguration.uuid,
assignmentUuids,
isEntireTableSelected,
dataTableState.filters,
);

const {
BUDGET_DETAILS_ASSIGNED_DATATABLE_OPEN_BULK_CANCEL_MODAL,
Expand Down Expand Up @@ -114,7 +115,7 @@ const AssignmentTableCancelAction = ({
const tableItemCount = tableInstance.itemCount;
const totalToCancel = calculateTotalToCancel({
assignmentUuids,
isEntireTableSelected: shouldCancelAll,
isEntireTableSelected,
tableItemCount,
});

Expand Down Expand Up @@ -146,6 +147,9 @@ AssignmentTableCancelAction.propTypes = {
tableInstance: PropTypes.shape({
itemCount: PropTypes.number.isRequired,
columns: PropTypes.arrayOf(PropTypes.shape()).isRequired,
state: PropTypes.shape({
filters: PropTypes.arrayOf(PropTypes.shape()).isRequired,
}).isRequired,
}).isRequired,
};

Expand Down
18 changes: 11 additions & 7 deletions src/components/learner-credit-management/AssignmentTableRemind.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import useRemindContentAssignments from './data/hooks/useRemindContentAssignment
import RemindAssignmentModal from './RemindAssignmentModal';
import { transformSelectedRows, useBudgetId, useSubsidyAccessPolicy } from './data';
import EVENT_NAMES from '../../eventTracking';
import { getActiveTableColumnFilters } from '../../utils';

const calculateTotalToRemind = ({
assignmentUuids,
Expand Down Expand Up @@ -41,22 +40,24 @@ const AssignmentTableRemindAction = ({
totalSelectedRows,
} = transformSelectedRows(remindableRows);

const activeFilters = getActiveTableColumnFilters(tableInstance.columns);

// If entire table is selected and there are NO filters, hit remind-all endpoint. Otherwise, hit usual bulk remind.
const shouldRemindAll = isEntireTableSelected && activeFilters.length === 0;
const { state: dataTableState } = tableInstance;

const {
remindButtonState,
remindContentAssignments,
close,
isOpen,
open,
} = useRemindContentAssignments(assignmentConfiguration.uuid, assignmentUuids, shouldRemindAll);
} = useRemindContentAssignments(
assignmentConfiguration.uuid,
assignmentUuids,
isEntireTableSelected,
dataTableState.filters,
);

const selectedRemindableRowCount = calculateTotalToRemind({
assignmentUuids,
isEntireTableSelected: shouldRemindAll,
isEntireTableSelected,
learnerStateCounts,
});

Expand Down Expand Up @@ -152,6 +153,9 @@ AssignmentTableRemindAction.propTypes = {
tableInstance: PropTypes.shape({
columns: PropTypes.arrayOf(PropTypes.shape()).isRequired,
itemCount: PropTypes.number.isRequired,
state: PropTypes.shape({
filters: PropTypes.arrayOf(PropTypes.shape()).isRequired,
}).isRequired,
}).isRequired,
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ const PendingAssignmentCancelButton = ({ row, enterpriseId }) => {
close,
isOpen,
open,
} = useCancelContentAssignments(assignmentConfiguration, [uuid]);
} = useCancelContentAssignments(assignmentConfiguration.uuid, [uuid]);

const sharedTrackEventMetadata = {
subsidyUuid,
Expand Down Expand Up @@ -117,7 +117,9 @@ PendingAssignmentCancelButton.propTypes = {
contentQuantity: PropTypes.number.isRequired,
learnerState: PropTypes.string.isRequired,
state: PropTypes.string.isRequired,
assignmentConfiguration: PropTypes.string.isRequired,
assignmentConfiguration: PropTypes.shape({
uuid: PropTypes.string.isRequired,
}).isRequired,
learnerEmail: PropTypes.string,
uuid: PropTypes.string.isRequired,
}).isRequired,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ const PendingAssignmentRemindButton = ({ row, enterpriseId }) => {
close,
isOpen,
open,
} = useRemindContentAssignments(assignmentConfiguration, [uuid]);
} = useRemindContentAssignments(assignmentConfiguration.uuid, [uuid]);

const sharedTrackEventMetadata = {
subsidyUuid,
Expand Down Expand Up @@ -115,7 +115,9 @@ PendingAssignmentRemindButton.propTypes = {
contentQuantity: PropTypes.number.isRequired,
learnerState: PropTypes.string.isRequired,
state: PropTypes.string.isRequired,
assignmentConfiguration: PropTypes.string.isRequired,
assignmentConfiguration: PropTypes.shape({
uuid: PropTypes.string.isRequired,
}).isRequired,
learnerEmail: PropTypes.string,
uuid: PropTypes.string.isRequired,
}).isRequired,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ import { useToggle } from '@edx/paragon';
import EnterpriseAccessApiService from '../../../../data/services/EnterpriseAccessApiService';
import { learnerCreditManagementQueryKeys } from '../constants';
import useBudgetId from './useBudgetId';
import { applyFiltersToOptions } from './useBudgetContentAssignments';

const useCancelContentAssignments = (
assignmentConfigurationUuid,
assignmentUuids,
cancelAll = false,
cancelAll,
tableFilters,
) => {
const [isOpen, open, close] = useToggle(false);
const [cancelButtonState, setCancelButtonState] = useState('default');
Expand All @@ -21,7 +23,9 @@ const useCancelContentAssignments = (
setCancelButtonState('pending');
try {
if (cancelAll) {
await EnterpriseAccessApiService.cancelAllContentAssignments(assignmentConfigurationUuid);
const options = {};
applyFiltersToOptions(tableFilters, options);
await EnterpriseAccessApiService.cancelAllContentAssignments(assignmentConfigurationUuid, options);
} else {
await EnterpriseAccessApiService.cancelContentAssignments(assignmentConfigurationUuid, assignmentUuids);
}
Expand All @@ -33,7 +37,7 @@ const useCancelContentAssignments = (
logError(err);
setCancelButtonState('error');
}
}, [assignmentConfigurationUuid, assignmentUuids, cancelAll, queryClient, subsidyAccessPolicyId]);
}, [assignmentConfigurationUuid, assignmentUuids, cancelAll, tableFilters, queryClient, subsidyAccessPolicyId]);

return {
cancelButtonState,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,47 @@ describe('useCancelContentAssignments', () => {
});
});

it('should send a post request to cancel all assignments with filters', async () => {
EnterpriseAccessApiService.cancelAllContentAssignments.mockResolvedValueOnce({ status: 200 });
const tableFilters = [{
id: 'learnerState',
value: ['waiting'],
}];
const cancelAll = true;
const { result } = renderHook(
() => useCancelContentAssignments(
TEST_ASSIGNMENT_CONFIGURATION_UUID,
[TEST_PENDING_ASSIGNMENT_UUID_1, TEST_PENDING_ASSIGNMENT_UUID_2],
cancelAll,
tableFilters,
),
{ wrapper },
);

expect(result.current).toEqual({
cancelButtonState: 'default',
cancelContentAssignments: expect.any(Function),
close: expect.any(Function),
isOpen: false,
open: expect.any(Function),
});

await waitFor(() => act(() => result.current.cancelContentAssignments()));
const expectedFilterParams = { learnerState: 'waiting' };
expect(
EnterpriseAccessApiService.cancelAllContentAssignments,
).toHaveBeenCalledWith(TEST_ASSIGNMENT_CONFIGURATION_UUID, expectedFilterParams);
expect(logError).toBeCalledTimes(0);

expect(result.current).toEqual({
cancelButtonState: 'complete',
cancelContentAssignments: expect.any(Function),
close: expect.any(Function),
isOpen: false,
open: expect.any(Function),
});
});

it('should handle assignment cancellation error', async () => {
const error = new Error('An error occurred');
EnterpriseAccessApiService.cancelContentAssignments.mockRejectedValueOnce(error);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ import { useToggle } from '@edx/paragon';
import EnterpriseAccessApiService from '../../../../data/services/EnterpriseAccessApiService';
import { learnerCreditManagementQueryKeys } from '../constants';
import useBudgetId from './useBudgetId';
import { applyFiltersToOptions } from './useBudgetContentAssignments';

const useRemindContentAssignments = (
assignmentConfigurationUuid,
assignmentUuids,
remindAll = false,
remindAll,
tableFilters,
) => {
const [isOpen, open, close] = useToggle(false);
const [remindButtonState, setRemindButtonState] = useState('default');
Expand All @@ -21,7 +23,9 @@ const useRemindContentAssignments = (
setRemindButtonState('pending');
try {
if (remindAll) {
await EnterpriseAccessApiService.remindAllContentAssignments(assignmentConfigurationUuid);
const options = {};
applyFiltersToOptions(tableFilters, options);
await EnterpriseAccessApiService.remindAllContentAssignments(assignmentConfigurationUuid, options);
} else {
await EnterpriseAccessApiService.remindContentAssignments(assignmentConfigurationUuid, assignmentUuids);
}
Expand All @@ -33,7 +37,7 @@ const useRemindContentAssignments = (
logError(err);
setRemindButtonState('error');
}
}, [assignmentConfigurationUuid, assignmentUuids, remindAll, queryClient, subsidyAccessPolicyId]);
}, [assignmentConfigurationUuid, assignmentUuids, remindAll, tableFilters, queryClient, subsidyAccessPolicyId]);

return {
remindButtonState,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,47 @@ describe('useRemindContentAssignments', () => {
});
});

it('should send a post request to remind all assignments with filters', async () => {
EnterpriseAccessApiService.remindAllContentAssignments.mockResolvedValueOnce({ status: 200 });
const tableFilters = [{
id: 'learnerState',
value: ['waiting'],
}];
const remindAll = true;
const { result } = renderHook(
() => useRemindContentAssignments(
TEST_ASSIGNMENT_CONFIGURATION_UUID,
[TEST_PENDING_ASSIGNMENT_UUID_1, TEST_PENDING_ASSIGNMENT_UUID_2],
remindAll,
tableFilters,
),
{ wrapper },
);

expect(result.current).toEqual({
remindButtonState: 'default',
remindContentAssignments: expect.any(Function),
close: expect.any(Function),
isOpen: false,
open: expect.any(Function),
});

await waitFor(() => result.current.remindContentAssignments());
const expectedFilterParams = { learnerState: 'waiting' };
expect(
EnterpriseAccessApiService.remindAllContentAssignments,
).toHaveBeenCalledWith(TEST_ASSIGNMENT_CONFIGURATION_UUID, expectedFilterParams);
expect(logError).toBeCalledTimes(0);

expect(result.current).toEqual({
remindButtonState: 'complete',
remindContentAssignments: expect.any(Function),
close: expect.any(Function),
isOpen: false,
open: expect.any(Function),
});
});

it('should handle assignment reminder error', async () => {
const error = new Error('An error occurred');
EnterpriseAccessApiService.remindContentAssignments.mockRejectedValueOnce(error);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ const SSOConfigConfigureStep = () => {
const returnToConnectStep = () => {
const connectStep = allSteps?.[0] as FormWorkflowStep<SSOConfigCamelCase>;
dispatch?.(
setStepAction({ step: connectStep })
setStepAction({ step: connectStep }),
);
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import React, { useState } from 'react';
import { Container, Dropzone, Form, Stack } from '@edx/paragon';
import {
Container, Dropzone, Form, Stack,
} from '@edx/paragon';

import ValidatedFormRadio from '../../../forms/ValidatedFormRadio';
import ValidatedFormControl from '../../../forms/ValidatedFormControl';
Expand Down
30 changes: 26 additions & 4 deletions src/data/services/EnterpriseAccessApiService.js
Original file line number Diff line number Diff line change
Expand Up @@ -187,8 +187,19 @@ class EnterpriseAccessApiService {
/**
* Cancel ALL content assignments for a specific AssignmentConfiguration.
*/
static cancelAllContentAssignments(assignmentConfigurationUUID) {
const url = `${EnterpriseAccessApiService.baseUrl}/assignment-configurations/${assignmentConfigurationUUID}/admin/assignments/cancel-all/`;
static cancelAllContentAssignments(assignmentConfigurationUUID, options = {}) {
const { learnerState, ...optionsRest } = options;
const params = {
...snakeCaseObject(optionsRest),
};
if (learnerState) {
params.learner_state__in = learnerState;
}
const urlParams = new URLSearchParams(params);
let url = `${EnterpriseAccessApiService.baseUrl}/assignment-configurations/${assignmentConfigurationUUID}/admin/assignments/cancel-all/`;
if (Object.keys(params).length > 0) {
url += `?${urlParams.toString()}`;
}
return EnterpriseAccessApiService.apiClient().post(url);
}

Expand All @@ -206,8 +217,19 @@ class EnterpriseAccessApiService {
/**
* Remind ALL content assignments for a specific AssignmentConfiguration.
*/
static remindAllContentAssignments(assignmentConfigurationUUID) {
const url = `${EnterpriseAccessApiService.baseUrl}/assignment-configurations/${assignmentConfigurationUUID}/admin/assignments/remind-all/`;
static remindAllContentAssignments(assignmentConfigurationUUID, options = {}) {
const { learnerState, ...optionsRest } = options;
const params = {
...snakeCaseObject(optionsRest),
};
if (learnerState) {
params.learner_state__in = learnerState;
}
const urlParams = new URLSearchParams(params);
let url = `${EnterpriseAccessApiService.baseUrl}/assignment-configurations/${assignmentConfigurationUUID}/admin/assignments/remind-all/`;
if (Object.keys(params).length > 0) {
url += `?${urlParams.toString()}`;
}
return EnterpriseAccessApiService.apiClient().post(url);
}

Expand Down
Loading

0 comments on commit dbdbe10

Please sign in to comment.