From f074b35c7a5d9ed9aa93b9dc2a77951268668de4 Mon Sep 17 00:00:00 2001 From: Sean Zeliq Urian Date: Sun, 6 Jun 2021 00:48:52 +0700 Subject: [PATCH 1/5] [RED] implement test for indexing item --- .../components/ActivityTabel/index.test.tsx | 548 +++++++++--------- 1 file changed, 276 insertions(+), 272 deletions(-) diff --git a/src/scenes/ActivityLog/components/ActivityTabel/index.test.tsx b/src/scenes/ActivityLog/components/ActivityTabel/index.test.tsx index 22660dc..a9ab717 100644 --- a/src/scenes/ActivityLog/components/ActivityTabel/index.test.tsx +++ b/src/scenes/ActivityLog/components/ActivityTabel/index.test.tsx @@ -7,314 +7,318 @@ import { AppContext } from 'contexts'; import { mount } from 'enzyme'; const dummyLog = { - status: 200, - data: { - username: "Test", - is_admin: false, - previous: true, - next: true, - count: 1, - results: [ - { - action_type: "Delete", - model_name: "Account", - object_id: "12345", - recorded_at: "2020-05-31T23:03:35.854615+07:00", - }, - ] - } -} + status: 200, + data: { + username: 'Test', + is_admin: false, + previous: true, + next: true, + count: 1, + results: [ + { + action_type: 'Delete', + model_name: 'Account', + object_id: '12345', + recorded_at: '2020-05-31T23:03:35.854615+07:00', + }, + ], + }, +}; jest.mock('axios'); const mockedAxios = axios as jest.Mocked; const testProps = { - services: { - main: useMainService('dummyToken'), - }, + services: { + main: useMainService('dummyToken'), + }, }; it('ActivityTabel renders correctly', () => { - const instance = renderer.create( - - ); + const instance = renderer.create(); - expect(instance).toBeTruthy(); + expect(instance).toBeTruthy(); }); describe('load logs and generate log messages correctly', () => { - it('Monitoring case', () => { - mockedAxios.request.mockResolvedValue({ - status: 200, - data: { - previous: null, - next: null, - count: 1, - results: [ - { - action_type: "Create", - model_name: "Monitoring Case", - object_id: "1234", - recorded_at: "2020-05-31T23:03:35.854615+07:00", - }, - ], - investigation_case: { - case_subject: { - name: "Test" - } - } - } - }); - - const instance = mount( - - - - ); - - expect(mockedAxios.request).toBeCalled(); + it('Monitoring case', () => { + mockedAxios.request.mockResolvedValue({ + status: 200, + data: { + previous: null, + next: null, + count: 1, + results: [ + { + action_type: 'Create', + model_name: 'Monitoring Case', + object_id: '1234', + recorded_at: '2020-05-31T23:03:35.854615+07:00', + }, + ], + investigation_case: { + case_subject: { + name: 'Test', + }, + }, + }, }); - it('Investigation case', () => { - mockedAxios.request.mockResolvedValue({ - status: 200, - data: { - previous: null, - next: null, - count: 1, - results: [ - { - action_type: "Create", - model_name: "Investigation Case", - object_id: "1234", - recorded_at: "2020-05-31T23:03:35.854615+07:00", - }, - ], - case_subject: { - name: "Test" - } - } - }); - - const instance = mount( - - - - ); - - expect(mockedAxios.request).toBeCalled(); - }); + const instance = mount( + + + + ); - it('Case subject', () => { - mockedAxios.request.mockResolvedValue({ - status: 200, - data: { - previous: null, - next: null, - count: 1, - results: [ - { - action_type: "Create", - model_name: "Case Subject", - object_id: "1234", - recorded_at: "2020-05-31T23:03:35.854615+07:00", - }, - ], - } - }); - - const instance = mount( - - - - ); - - expect(mockedAxios.request).toBeCalled(); + expect(mockedAxios.request).toBeCalled(); + }); + + it('Investigation case', () => { + mockedAxios.request.mockResolvedValue({ + status: 200, + data: { + previous: null, + next: null, + count: 1, + results: [ + { + action_type: 'Create', + model_name: 'Investigation Case', + object_id: '1234', + recorded_at: '2020-05-31T23:03:35.854615+07:00', + }, + ], + case_subject: { + name: 'Test', + }, + }, }); - it('Create account', () => { - mockedAxios.request.mockResolvedValue({ - status: 200, - data: { - username: "Test", - is_admin: false, - previous: null, - next: null, - count: 1, - results: [ - { - action_type: "Create", - model_name: "Account", - object_id: "1234", - recorded_at: "2020-05-31T23:03:35.854615+07:00", - }, - ] - } - }); - - const instance = mount( - - - - ); - - expect(mockedAxios.request).toBeCalled(); - }); + const instance = mount( + + + + ); - it('Create account, but account deleted', () => { - mockedAxios.request.mockResolvedValue({ - status: 200, - data: { - previous: null, - next: null, - count: 1, - results: [ - { - action_type: "Create", - model_name: "Account", - object_id: "1234", - recorded_at: "2020-05-31T23:03:35.854615+07:00", - }, - ] - } - }); - - const instance = mount( - - - - ); - - expect(mockedAxios.request).toBeCalled(); + expect(mockedAxios.request).toBeCalled(); + }); + + it('Case subject', () => { + mockedAxios.request.mockResolvedValue({ + status: 200, + data: { + previous: null, + next: null, + count: 1, + results: [ + { + action_type: 'Create', + model_name: 'Case Subject', + object_id: '1234', + recorded_at: '2020-05-31T23:03:35.854615+07:00', + }, + ], + }, }); - it('Edit account', () => { - mockedAxios.request.mockResolvedValue({ - status: 200, - data: { - username: "Test", - is_admin: false, - previous: null, - next: null, - count: 1, - results: [ - { - action_type: "Edit", - model_name: "Account", - object_id: "1234", - recorded_at: "2020-05-31T23:03:35.854615+07:00", - }, - ] - } - }); - - const instance = mount( - - - - ); - - expect(mockedAxios.request).toBeCalled(); - }); + const instance = mount( + + + + ); - it('Edit account, but account deleted', () => { - mockedAxios.request.mockResolvedValue({ - status: 404, - data: { - previous: null, - next: null, - count: 1, - results: [ - { - action_type: "Edit", - model_name: "Account", - object_id: "1234", - recorded_at: "2020-05-31T23:03:35.854615+07:00", - }, - ] - } - }); - - const instance = mount( - - - - ); - - expect(mockedAxios.request).toBeCalled(); - }); + expect(mockedAxios.request).toBeCalled(); + }); - it('Delete account', () => { - mockedAxios.request.mockResolvedValue({ - status: 200, - data: { - username: "Test", - is_admin: false, - previous: null, - next: null, - count: 1, - results: [ - { - action_type: "Delete", - model_name: "Account", - object_id: "1234", - recorded_at: "2020-05-31T23:03:35.854615+07:00", - }, - ] - } - }); - - const instance = mount( - - - - ); - - expect(mockedAxios.request).toBeCalled(); + it('Create account', () => { + mockedAxios.request.mockResolvedValue({ + status: 200, + data: { + username: 'Test', + is_admin: false, + previous: null, + next: null, + count: 1, + results: [ + { + action_type: 'Create', + model_name: 'Account', + object_id: '1234', + recorded_at: '2020-05-31T23:03:35.854615+07:00', + }, + ], + }, }); -}); -it('fetch new logs and change page number when press next or previous button', () => { - mockedAxios.request.mockResolvedValue(dummyLog); + const instance = mount( + + + + ); + + expect(mockedAxios.request).toBeCalled(); + }); + + it('Create account, but account deleted', () => { + mockedAxios.request.mockResolvedValue({ + status: 200, + data: { + previous: null, + next: null, + count: 1, + results: [ + { + action_type: 'Create', + model_name: 'Account', + object_id: '1234', + recorded_at: '2020-05-31T23:03:35.854615+07:00', + }, + ], + }, + }); const instance = mount( - - - + + + ); + expect(mockedAxios.request).toBeCalled(); + }); + + it('Edit account', () => { + mockedAxios.request.mockResolvedValue({ + status: 200, + data: { + username: 'Test', + is_admin: false, + previous: null, + next: null, + count: 1, + results: [ + { + action_type: 'Edit', + model_name: 'Account', + object_id: '1234', + recorded_at: '2020-05-31T23:03:35.854615+07:00', + }, + ], + }, + }); - let nextButton = instance.find('[data-test-id="next-button"]'); - let prevButton = instance.find('[data-test-id="prev-button"]'); + const instance = mount( + + + + ); - let pageNumber = instance.find('Text').findWhere(elem => elem.prop('id') === 'page-number'); - expect(pageNumber.text()).toBe('1'); - nextButton.at(0).simulate('click'); expect(mockedAxios.request).toBeCalled(); + }); + + it('Edit account, but account deleted', () => { + mockedAxios.request.mockResolvedValue({ + status: 404, + data: { + previous: null, + next: null, + count: 1, + results: [ + { + action_type: 'Edit', + model_name: 'Account', + object_id: '1234', + recorded_at: '2020-05-31T23:03:35.854615+07:00', + }, + ], + }, + }); + + const instance = mount( + + + + ); - prevButton.at(0).simulate('click'); - pageNumber = instance.find('Text').findWhere(elem => elem.prop('id') === 'page-number'); - expect(pageNumber.text()).toBe('1'); expect(mockedAxios.request).toBeCalled(); -}) + }); -it('Sort and filter are displayed and clickable', () => { - mockedAxios.request.mockResolvedValue(dummyLog); + it('Delete account', () => { + mockedAxios.request.mockResolvedValue({ + status: 200, + data: { + username: 'Test', + is_admin: false, + previous: null, + next: null, + count: 1, + results: [ + { + action_type: 'Delete', + model_name: 'Account', + object_id: '1234', + recorded_at: '2020-05-31T23:03:35.854615+07:00', + }, + ], + }, + }); const instance = mount( - - - + + + ); - expect(mockedAxios.request).toBeCalled(); - let sortByDate = instance.find("#sortbydate"); - let sortByObj = instance.find("#sortbyobj"); - let filter = instance.find("#filter"); + expect(mockedAxios.request).toBeCalled(); + }); +}); - sortByDate.simulate("click"); - sortByObj.simulate("click"); - filter.simulate("click"); +it('fetch new logs and change page number when press next or previous button', () => { + mockedAxios.request.mockResolvedValue(dummyLog); + + const instance = mount( + + + + ); + expect(mockedAxios.request).toBeCalled(); + + let nextButton = instance.find('[data-test-id="next-button"]'); + let prevButton = instance.find('[data-test-id="prev-button"]'); + + let pageNumber = instance + .find('Text') + .findWhere((elem) => elem.prop('id') === 'page-number'); + expect(pageNumber.text()).toBe('1'); + nextButton.at(0).simulate('click'); + expect(mockedAxios.request).toBeCalled(); + + prevButton.at(0).simulate('click'); + pageNumber = instance + .find('Text') + .findWhere((elem) => elem.prop('id') === 'page-number'); + expect(pageNumber.text()).toBe('1'); + expect(mockedAxios.request).toBeCalled(); +}); - let table = instance.find("Table"); - expect(table).toBeTruthy(); -}); \ No newline at end of file +it('Sort and filter are displayed and clickable', () => { + mockedAxios.request.mockResolvedValue(dummyLog); + + const instance = mount( + + + + ); + expect(mockedAxios.request).toBeCalled(); + + let sortByIndex = instance.find('#sortbyindex'); + let sortByDate = instance.find('#sortbydate'); + let sortByObj = instance.find('#sortbyobj'); + let filter = instance.find('#filter'); + + sortByIndex.at(0).simulate('click'); + sortByDate.at(0).simulate('click'); + sortByObj.at(0).simulate('click'); + filter.at(0).simulate('click'); + + let table = instance.find('Table'); + expect(table).toBeTruthy(); +}); -- GitLab From 962e6b6be4080e79c02d3eec04d94289643bbf4b Mon Sep 17 00:00:00 2001 From: Sean Zeliq Urian Date: Sun, 6 Jun 2021 00:49:14 +0700 Subject: [PATCH 2/5] [GREEN] implement indexing item --- .../components/ActivityTabel/index.tsx | 53 +++++++++++++++++-- src/scenes/ActivityLog/types/types.tsx | 17 +++--- src/scenes/ActivityLog/utilities/utils.tsx | 1 + 3 files changed, 58 insertions(+), 13 deletions(-) diff --git a/src/scenes/ActivityLog/components/ActivityTabel/index.tsx b/src/scenes/ActivityLog/components/ActivityTabel/index.tsx index d766dd9..97cd7fd 100644 --- a/src/scenes/ActivityLog/components/ActivityTabel/index.tsx +++ b/src/scenes/ActivityLog/components/ActivityTabel/index.tsx @@ -1,5 +1,4 @@ import React, { useContext, useEffect, useState } from 'react'; -import { ThemeContext } from 'styled-components'; import { Box, Gap, Icon, Table, Field, Button, Checkbox } from 'components'; import { AppContext } from 'contexts'; import { DateProps } from 'contexts/AppContext/types'; @@ -10,11 +9,13 @@ import { } from 'scenes/ActivityLog/utilities/utils'; export default function ActivityTabel() { + const itemPerPage = 10; const { services } = useContext(AppContext); const [logList, setLogList] = useState([]); const [logTable, setLogTable] = useState([]); const [page, setPage] = useState(1); const [totalLog, setTotalLog] = useState(0); + const [indexAscending, setIndexAscending] = useState(false); const [dateAscending, setDateAscending] = useState(true); const [objAscending, setObjAscending] = useState(true); const [filterToggle, setFilterToggle] = useState(true); @@ -27,6 +28,13 @@ export default function ActivityTabel() { end_date: new Date(), }); + const addNumber = (page: number, logs: any) => { + for (let i = 0; i < logs.length; i++) { + logs[i]['detail']['index'] = itemPerPage * (page - 1) + i + 1; + } + return logs; + }; + const fetchLog = async ( page: number, roleFilterQuery: String, @@ -69,11 +77,31 @@ export default function ActivityTabel() { setFilterToggle(!filterToggle); setPage(1); } + logs = addNumber(page, logs); setLogList(logs); setLogTable(logs); setTotalLog(logResponse.data.count); }; + const sortLogByIndex = () => { + if (indexAscending) { + setLogTable( + logList.sort((a, b) => { + if (a.detail.index === b.detail.index) return 0; + return a.detail.index > b.detail.index ? 1 : -1; + }) + ); + } else { + setLogTable( + logList.sort((a, b) => { + if (a.detail.index === b.detail.index) return 0; + return a.detail.index < b.detail.index ? 1 : -1; + }) + ); + } + setIndexAscending(!indexAscending); + }; + const sortLogByDate = () => { if (dateAscending) { setLogTable( @@ -112,8 +140,9 @@ export default function ActivityTabel() { setObjAscending(!objAscending); }; - const getTableData = () => - logTable.map(({ timestamp, detail }) => [ + const getTableData = () => { + let tempData = logTable.map(({ timestamp, detail }) => [ + detail.index, timestamp.toLocaleString('id-ID', { weekday: 'long', year: 'numeric', @@ -131,6 +160,8 @@ export default function ActivityTabel() { ? detail.object.slice(0, 77) + '...' : detail.object, ]); + return tempData; + }; useEffect(() => { fetchLog(page, roleFilterQuery, dateFilterQuery, ignoreDateFilterQuery); @@ -201,6 +232,17 @@ export default function ActivityTabel() { Urutkan dengan + + + No + { + setPage(pageInput); setLogTable( - logList.filter((e) => (val ? e.detail.object === val : true)) + logList.filter((e) => (val ? e.detail.object.includes(val) : true)) ); - setPage(pageInput); return getTableData(); }} /> diff --git a/src/scenes/ActivityLog/types/types.tsx b/src/scenes/ActivityLog/types/types.tsx index b1f2028..b98486f 100644 --- a/src/scenes/ActivityLog/types/types.tsx +++ b/src/scenes/ActivityLog/types/types.tsx @@ -1,12 +1,13 @@ export interface Log { - timestamp: Date; - message: string; - detail: LogDetail; + timestamp: Date; + message: string; + detail: LogDetail; } export interface LogDetail { - activity: string; - authorRole: 'admin' | 'kader'; - authorName: string; - object: string; -} \ No newline at end of file + index: number; + activity: string; + authorRole: 'admin' | 'kader'; + authorName: string; + object: string; +} diff --git a/src/scenes/ActivityLog/utilities/utils.tsx b/src/scenes/ActivityLog/utilities/utils.tsx index 380c24b..e84766b 100644 --- a/src/scenes/ActivityLog/utilities/utils.tsx +++ b/src/scenes/ActivityLog/utilities/utils.tsx @@ -85,6 +85,7 @@ const generateLogDetail = async ( services: any ): Promise => { let logDetail: LogDetail = { + index: log.index, activity: log.model_name, authorRole: log.author.is_admin ? 'admin' : 'kader', authorName: log.author.username, -- GitLab From c75f7ef0095759f38b3d9396b603e77348fffc75 Mon Sep 17 00:00:00 2001 From: Sean Zeliq Urian Date: Sun, 6 Jun 2021 12:16:49 +0700 Subject: [PATCH 3/5] [REFACTOR] fix inconsistency total item when change page --- src/components/Table/index.tsx | 50 ++++++++++++------- .../components/ActivityTabel/index.tsx | 30 ++++++++--- 2 files changed, 55 insertions(+), 25 deletions(-) diff --git a/src/components/Table/index.tsx b/src/components/Table/index.tsx index 0ade5ed..6298a6f 100644 --- a/src/components/Table/index.tsx +++ b/src/components/Table/index.tsx @@ -23,6 +23,7 @@ interface TableProps { searchPlaceholder?: string; maximumData?: number; rowOnClick?: (row: Array) => void; + itemPerPage?: number; } const Click = styled.div` @@ -93,6 +94,7 @@ export default function Table({ onChange = async () => new Promise>>(() => []), maximumData = -1, rowOnClick, + itemPerPage, }: TableProps) { const { colors } = useContext(ThemeContext) || DEFAULT_THEME; const [previousDataTotal, setPreviousDataTotal] = useState(0); @@ -118,18 +120,24 @@ export default function Table({ return; setIsLoading(true); - const newData: Array> = await onChange( - searchValue, - newPageNumber - ); - setIsLoading(false); + await onChange(searchValue, newPageNumber).then((newData) => { + setIsLoading(false); - if (newData.length === 0 || newData[0].length === 0) return; - setPageNumber(newPageNumber); - setPreviousDataTotal( - previousDataTotal + (sign === 1 ? data.length : -newData.length) - ); - setData([...newData]); + if (newData.length === 0 || newData[0].length === 0) return; + setPageNumber(newPageNumber); + setPreviousDataTotal( + previousDataTotal + (sign === 1 ? data.length : -newData.length) + ); + if ( + action === PageAction.Prev && + previousDataTotal + data.length === maximumData + ) { + setPreviousDataTotal( + previousDataTotal - (itemPerPage ? itemPerPage : 10) + ); + } + setData([...newData]); + }); }; const [couldGoBack, couldGoNext] = [ @@ -175,14 +183,14 @@ export default function Table({