Skip to content

Commit

Permalink
Front end for Fixed/delete action dropdown (#2041)
Browse files Browse the repository at this point in the history
* back end updates for feedback api

* update tests

* update feedback test

* add new migration files & update tests for backend

* update enums && lint

* update migrations

* add missing enum

* update tests

* update resource

* fix migration error

* add dropdown

* push updates

* touch ups to ui

* make button only visible to superusers

* fix import error

* add when component

* update front end tests

* update feedback modal test

* add use effect to reload when switching between archived & regular listing

* duplkicate imports
  • Loading branch information
djnunez-aot authored Aug 23, 2023
1 parent d7ed49b commit a7cb0ab
Show file tree
Hide file tree
Showing 8 changed files with 165 additions and 12 deletions.
2 changes: 1 addition & 1 deletion met-api/src/met_api/resources/feedback.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ def post():
return str(err), HTTPStatus.INTERNAL_SERVER_ERROR


@cors_preflight('DELETE, PATCH, OPTIONS')
@cors_preflight('DELETE, PATCH')
@API.route('/<int:feedback_id>', methods=['DELETE', 'PATCH'])
class FeedbackById(Resource):
"""Feedback Id Resource."""
Expand Down
4 changes: 2 additions & 2 deletions met-web/src/apiManager/endpoints/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,8 @@ const Endpoints = {
Feedback: {
GET_LIST: `${AppConfig.apiUrl}/feedbacks/`,
CREATE: `${AppConfig.apiUrl}/feedbacks/`,
UPDATE: `${AppConfig.apiUrl}/feedbacks/`,
DELETE: `${AppConfig.apiUrl}/feedbacks/`,
UPDATE: `${AppConfig.apiUrl}/feedbacks/feedback_id`,
DELETE: `${AppConfig.apiUrl}/feedbacks/feedback_id`,
},
EmailVerification: {
GET: `${AppConfig.apiUrl}/email_verification/verification_token`,
Expand Down
103 changes: 103 additions & 0 deletions met-web/src/components/feedback/actionDropdown.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import React, { useMemo } from 'react';
import { MenuItem, Select } from '@mui/material';
import { Feedback, FeedbackStatusEnum } from 'models/feedback';
import { useAppDispatch } from 'hooks';
import { Palette } from 'styles/Theme';
import { updateFeedback, deleteFeedback } from 'services/feedbackService';
import { openNotification } from 'services/notificationService/notificationSlice';
import { openNotificationModal } from 'services/notificationModalService/notificationModalSlice';

interface ActionDropDownItem {
value: number;
label: string;
action?: () => void;
condition?: boolean;
}
export const ActionsDropDown = ({ feedback, reload }: { feedback: Feedback; reload: () => void }) => {
const dispatch = useAppDispatch();
const isArchived = feedback.status == FeedbackStatusEnum.Archived;
const canEditFeedback = (): boolean => {
return true;
};

const archiveFeedback = async () => {
try {
await updateFeedback(feedback.id, { ...feedback, status: FeedbackStatusEnum.Archived });
dispatch(openNotification({ severity: 'success', text: 'Feedback has been archived.' }));
reload();
} catch (error) {
dispatch(openNotification({ severity: 'error', text: 'Error occurred while archiving feedback.' }));
}
};

const removeFeedback = async () => {
dispatch(
openNotificationModal({
open: true,
data: {
header: 'Delete Feedback',
subText: [
{ text: 'You will be permanently deleting this feedback.' },
{ text: 'Please click the Cancel or Confirm button to continue.', bold: true },
],
handleConfirm: async () => {
try {
await deleteFeedback(feedback.id);
dispatch(openNotification({ severity: 'success', text: 'Feedback has been deleted.' }));
reload();
} catch (error) {
dispatch(
openNotification({
severity: 'error',
text: 'Error occurred while deleting feedback.',
}),
);
}
},
},
type: 'confirm',
}),
);
};

const ITEMS: ActionDropDownItem[] = useMemo(
() => [
{
value: 1,
label: 'Mark As Resolved',
action: () => {
archiveFeedback();
},
condition: canEditFeedback() && !isArchived,
},
{
value: 2,
label: 'Delete Feedback',
action: () => {
removeFeedback();
},
condition: canEditFeedback(),
},
],
[feedback.id],
);

return (
<Select
id={`action-drop-down-${feedback.id}`}
value={0}
fullWidth
size="small"
sx={{ backgroundColor: 'white', color: Palette.info.main }}
>
<MenuItem value={0} sx={{ fontStyle: 'italic', height: '2em' }} color="info" disabled>
{'(Select One)'}
</MenuItem>
{ITEMS.filter((item) => item.condition).map((item) => (
<MenuItem key={item.value} value={item.value} onClick={item.action}>
{item.label}
</MenuItem>
))}
</Select>
);
};
48 changes: 42 additions & 6 deletions met-web/src/components/feedback/listing.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import React, { useState, useEffect } from 'react';
import Grid from '@mui/material/Grid';
import { MetPageGridContainer } from 'components/common';
import { CommentTypeEnum, Feedback, SourceTypeEnum } from 'models/feedback';
import { useAppDispatch } from 'hooks';
import { MetPageGridContainer, PrimaryButton } from 'components/common';
import { CommentTypeEnum, Feedback, FeedbackStatusEnum, SourceTypeEnum } from 'models/feedback';
import { useAppDispatch, useAppSelector } from 'hooks';
import { createDefaultPageInfo, HeadCell, PageInfo, PaginationOptions } from 'components/common/Table/types';
import Stack from '@mui/material/Stack';
import { openNotification } from 'services/notificationService/notificationSlice';
Expand All @@ -12,9 +12,12 @@ import { formatDate } from 'components/common/dateHelper';
import { customRatings } from 'components/feedback/FeedbackModal/constants';
import { useLocation } from 'react-router-dom';
import { updateURLWithPagination } from 'components/common/Table/utils';

import { ActionsDropDown } from './actionDropdown';
import { USER_ROLES } from 'services/userService/constants';
import { When } from 'react-if';
const FeedbackListing = () => {
const [feedbacks, setFeedbacks] = useState<Feedback[]>([]);
const { roles } = useAppSelector((state) => state.user);
const location = useLocation();
const searchParams = new URLSearchParams(location.search);
const pageFromURL = searchParams.get('page');
Expand All @@ -28,6 +31,8 @@ const FeedbackListing = () => {
});
const [pageInfo, setPageInfo] = useState<PageInfo>(createDefaultPageInfo());
const [tableLoading, setTableLoading] = useState(true);
const [statusFilter, setStatusFilter] = useState(FeedbackStatusEnum.NotReviewed);
const authorized = roles.includes(USER_ROLES.CREATE_ADMIN_USER);
const dispatch = useAppDispatch();

const { page, size, sort_key, nested_sort_key, sort_order } = paginationOptions;
Expand All @@ -37,6 +42,10 @@ const FeedbackListing = () => {
updateURLWithPagination(paginationOptions);
}, [paginationOptions]);

useEffect(() => {
loadFeedbacks();
}, [authorized]);

const loadFeedbacks = async () => {
try {
setTableLoading(true);
Expand Down Expand Up @@ -104,6 +113,19 @@ const FeedbackListing = () => {
allowSort: true,
renderCell: (row: Feedback) => row.comment,
},
{
key: 'id',
numeric: true,
disablePadding: false,
label: 'Actions',
allowSort: false,
renderCell: (row: Feedback) => {
return <ActionsDropDown reload={() => loadFeedbacks()} feedback={row} />;
},
customStyle: {
minWidth: '200px',
},
},
];

return (
Expand All @@ -122,12 +144,26 @@ const FeedbackListing = () => {
width="100%"
justifyContent="flex-end"
sx={{ p: 2 }}
></Stack>
>
<When condition={authorized}>
<PrimaryButton
onClick={() =>
setStatusFilter(
statusFilter == FeedbackStatusEnum.NotReviewed
? FeedbackStatusEnum.Archived
: FeedbackStatusEnum.NotReviewed,
)
}
>
{statusFilter == FeedbackStatusEnum.NotReviewed ? 'View Archive' : 'View Feedback'}
</PrimaryButton>
</When>
</Stack>
</Grid>
<Grid item xs={12} lg={10}>
<MetTable
headCells={headCells}
rows={feedbacks}
rows={feedbacks.filter((feedback) => feedback.status == statusFilter)}
handleChangePagination={(paginationOptions: PaginationOptions<Feedback>) =>
setPaginationOptions(paginationOptions)
}
Expand Down
2 changes: 2 additions & 0 deletions met-web/src/models/feedback.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export interface Feedback {
id: number;
created_date: string;
rating: number;
comment: string;
Expand Down Expand Up @@ -30,6 +31,7 @@ export enum SourceTypeEnum {

export const createDefaultFeedback = (): Feedback => {
return {
id: 1,
rating: 0,
comment: '',
comment_type: CommentTypeEnum.None,
Expand Down
6 changes: 3 additions & 3 deletions met-web/src/services/feedbackService/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import Endpoints from 'apiManager/endpoints';
import { Page } from 'services/type';
import { Feedback } from 'models/feedback';
import { GetFeedbackRequest, PostFeedbackRequest, UpdateFeedbackRequest } from './types';

import { replaceUrl } from 'helper';
export const getFeedbacksPage = async ({
page,
size,
Expand Down Expand Up @@ -35,7 +35,7 @@ export const createFeedback = async (feedback: PostFeedbackRequest): Promise<Fee
};

export const updateFeedback = async (feedback_id: number, feedback: UpdateFeedbackRequest): Promise<Feedback> => {
const url = `${Endpoints.Feedback.UPDATE}/${feedback_id}`;
const url = replaceUrl(Endpoints.Feedback.UPDATE, 'feedback_id', String(feedback_id));
const response = await http.PatchRequest<Feedback>(url, feedback);
if (response.data) {
return response.data;
Expand All @@ -44,7 +44,7 @@ export const updateFeedback = async (feedback_id: number, feedback: UpdateFeedba
};

export const deleteFeedback = async (feedback_id: number): Promise<void> => {
const url = `${Endpoints.Feedback.DELETE}/${feedback_id}`;
const url = replaceUrl(Endpoints.Feedback.DELETE, 'feedback_id', String(feedback_id));
const response = await http.DeleteRequest(url);
if (response.status !== 200) {
return Promise.reject('Failed to delete feedback');
Expand Down
11 changes: 11 additions & 0 deletions met-web/tests/unit/components/feedback/feedbackListing.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import * as feedbackService from 'services/feedbackService/index';
import * as notificationSlice from 'services/notificationService/notificationSlice';
import { createDefaultFeedback, CommentTypeEnum, SourceTypeEnum } from 'models/feedback';
import FeedbackListing from 'components/feedback/listing';
import { USER_ROLES } from 'services/userService/constants';

const mockFeedbackOne = {
...createDefaultFeedback(),
Expand Down Expand Up @@ -49,6 +50,15 @@ jest.mock('react-router-dom', () => ({
})),
}));

jest.mock('react-redux', () => ({
...jest.requireActual('react-redux'),
useSelector: jest.fn(() => {
return {
roles: [USER_ROLES.CREATE_ENGAGEMENT],
};
}),
}));

describe('Feedback Listing tests', () => {
jest.spyOn(reactRedux, 'useDispatch').mockImplementation(() => jest.fn());
jest.spyOn(notificationSlice, 'openNotification').mockImplementation(jest.fn());
Expand Down Expand Up @@ -77,6 +87,7 @@ describe('Feedback Listing tests', () => {
});

test('Feedback table is empty', async () => {
jest.spyOn(reactRedux, 'useDispatch').mockImplementation(() => jest.fn());
render(<FeedbackListing />);
const feedbackListing = screen.getByTestId('listing-table');

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ describe('Feedback modal tests', () => {
test('Submit shows thank you message', async () => {
createFeedbackMock.mockReturnValue(
Promise.resolve({
id: 1,
status: FeedbackStatusEnum.NotReviewed,
comment_type: CommentTypeEnum.None,
comment: '',
Expand Down

0 comments on commit a7cb0ab

Please sign in to comment.