Fakultas Ilmu Komputer UI

Commit 9e40fcf6 authored by Nabilah Adani's avatar Nabilah Adani
Browse files

Merge branch 'sean/tampilan-log-aktivitas' into 'PBI-14-Log-Aktivitas'

PBI 14 Tampilan baru Log Aktivitas

See merge request !44
parents 3310736c 2048883b
Pipeline #79984 passed with stages
in 10 minutes and 25 seconds
......@@ -25,6 +25,10 @@ const navigators = [
name: 'Manajemen Akun',
to: '/account-management',
},
{
name: 'Log Aktivitas',
to: '/activity-log',
},
];
const Layout = (props: LayoutProps) => {
......
......@@ -6,6 +6,7 @@ import { Home, AccountManagement, Login, CaseRecapitulation, PositiveCaseInput }
import { LocalStorage } from 'services';
import { DEFAULT_SECRET_KEY } from 'constant';
import Layout from './components/Layout';
import ActivityLog from 'scenes/ActivityLog';
export default function Routes() {
const { user, setUser, setToken, token, services, alert } = useContext(
......@@ -78,6 +79,11 @@ export default function Routes() {
path="/positive-case-input"
component={PositiveCaseInput}
/>
<Route
exact
path="/activity-log"
component={ActivityLog}
/>
</Layout>
</>
) : (
......
......@@ -9,7 +9,6 @@ import Button from 'components/Button';
import Loading from 'components/Loading';
import Icon from 'components/Icon';
type ValueType = string | number;
interface TableProps {
......@@ -17,10 +16,13 @@ interface TableProps {
header: Array<string>;
data?: Array<Array<ValueType>>;
setData?: any;
onChange?: (value: string, pageNumber: number) => Promise<Array<Array<ValueType>>>;
onChange?: (
value: string,
pageNumber: number
) => Promise<Array<Array<ValueType>>>;
searchPlaceholder?: string;
maximumData?: number;
rowOnClick?: (row: Array<ValueType>) => void
rowOnClick?: (row: Array<ValueType>) => void;
}
const Click = styled.div`
......@@ -68,7 +70,7 @@ const TableHeader = styled.th`
const LoadingContainer = styled.tr`
height: 400px;
`
`;
const DEFAULT_THEME = {
colors: {
......@@ -124,7 +126,9 @@ export default function Table({
if (newData.length === 0 || newData[0].length === 0) return;
setPageNumber(newPageNumber);
setPreviousDataTotal(previousDataTotal + (sign === 1? data.length: -newData.length));
setPreviousDataTotal(
previousDataTotal + (sign === 1 ? data.length : -newData.length)
);
setData([...newData]);
};
......@@ -136,7 +140,7 @@ export default function Table({
useEffect(() => {
setData(data);
}, [JSON.stringify(data)]);
useEffect(() => {
const initializeData = async () => {
setIsLoading(true);
......@@ -149,7 +153,7 @@ export default function Table({
initializeData();
}, []);
if (data[0] && (header.length > data[0].length)) {
if (data[0] && header.length > data[0].length) {
throw new Error('The shape of Header and data are not consistent');
}
......@@ -218,7 +222,7 @@ export default function Table({
})}
</tr>
</thead>
{isLoading? (
{isLoading ? (
<tbody>
<LoadingContainer>
<Loading isLoading={isLoading} shouldSetBlackTheme={true} />
......@@ -235,20 +239,22 @@ export default function Table({
index % 2 === 0 ? 'transparent' : colors.almostWhite
}
>
{row.filter((_, index) => index < header.length).map((value: ValueType, index: number) => {
return (
<td key={index}>
<Text
width="100%"
type={Text.StyleType.Small}
align="center"
>
{value.toString()}
</Text>
</td>
);
})}
{rowOnClick? (
{row
.filter((_, index) => index < header.length)
.map((value: ValueType, index: number) => {
return (
<td key={index}>
<Text
width="100%"
type={Text.StyleType.Small}
align="center"
>
{value.toString()}
</Text>
</td>
);
})}
{rowOnClick ? (
<td>
<Box
height="100%"
......@@ -269,34 +275,33 @@ export default function Table({
crossAxis="center"
background={colors.green}
>
<Icon src="/assets/icons/zoom.svg" height="50%" cursor="pointer" />
<Icon
src="/assets/icons/zoom.svg"
height="50%"
cursor="pointer"
/>
</Box>
</Click>
</Box>
</td>
) : <></>}
) : (
<></>
)}
</Row>
);
})}
</tbody>
)}
</StyledTable>
{!isLoading && data.length === 0
? (
<Box
width="100%"
height="300px"
mainAxis='center'
crossAxis='center'
>
<Text
type={Text.StyleType.Medium}
color={colors.mediumGray}
>
Tidak ada data
</Text>
</Box>
) : <></>}
{!isLoading && data.length === 0 ? (
<Box width="100%" height="300px" mainAxis="center" crossAxis="center">
<Text type={Text.StyleType.Medium} color={colors.mediumGray}>
Tidak ada data
</Text>
</Box>
) : (
<></>
)}
<Gap gap={20} axis={Gap.Axis.Vertical} />
<Box width="100%" mainAxis="flex-end">
<Box crossAxis="center">
......@@ -318,7 +323,10 @@ export default function Table({
transform="rotateZ(180deg)"
/>
<Gap gap={12} axis={Gap.Axis.Horizontal} />
<Text id='page-number' type={Text.StyleType.Small}>{`${pageNumber}`}</Text>
<Text
id="page-number"
type={Text.StyleType.Small}
>{`${pageNumber}`}</Text>
<Gap gap={12} axis={Gap.Axis.Horizontal} />
<Icon
data-test-id="next-button"
......
import React from 'react';
import axios from 'axios';
import renderer from 'react-test-renderer';
import ActivityList from '.';
import { useMainService } from 'services';
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: "12346",
recorded_at: "2020-05-31T23:03:35.854615+07:00",
},
]
}
}
jest.mock('axios');
const mockedAxios = axios as jest.Mocked<typeof axios>;
const testProps = {
services: {
main: useMainService('dummyToken'),
},
};
it('ActivityList renders correctly', () => {
const instance = renderer.create(
<ActivityList />
);
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(
<AppContext.Provider value={testProps}>
<ActivityList />
</AppContext.Provider>
);
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"
}
}
});
const instance = mount(
<AppContext.Provider value={testProps}>
<ActivityList />
</AppContext.Provider>
);
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",
},
],
}
});
const instance = mount(
<AppContext.Provider value={testProps}>
<ActivityList />
</AppContext.Provider>
);
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",
},
]
}
});
const instance = mount(
<AppContext.Provider value={testProps}>
<ActivityList />
</AppContext.Provider>
);
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(
<AppContext.Provider value={testProps}>
<ActivityList />
</AppContext.Provider>
);
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",
},
]
}
});
const instance = mount(
<AppContext.Provider value={testProps}>
<ActivityList />
</AppContext.Provider>
);
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(
<AppContext.Provider value={testProps}>
<ActivityList />
</AppContext.Provider>
);
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(
<AppContext.Provider value={testProps}>
<ActivityList />
</AppContext.Provider>
);
expect(mockedAxios.request).toBeCalled();
});
});
it('fetch new logs and change page number when press next or previous button', () => {
mockedAxios.request.mockResolvedValue(dummyLog);
const instance = mount(
<AppContext.Provider value={testProps}>
<ActivityList />
</AppContext.Provider>
);
expect(mockedAxios.request).toBeCalled();
const prevButton = instance.find('#prev-button');
const nextButton = instance.find('#next-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();
})
it('filter are displayed and clickable', () => {
mockedAxios.request.mockResolvedValue(dummyLog);
const instance = mount(
<AppContext.Provider value={testProps}>
<ActivityList />
</AppContext.Provider>
);
expect(mockedAxios.request).toBeCalled();
let filter = instance.find("#filter");
filter.simulate("click");
let list = instance.find("#list-activity");
expect(list).toBeTruthy();
});
\ No newline at end of file
import React, { useContext, useEffect, useState } from 'react';
import styled, { ThemeContext } from 'styled-components';
import { Box, Text, Gap, Icon, Field, Button, Checkbox } from 'components';
import { AppContext } from 'contexts';
import Loading from 'components/Loading';
import { DateProps } from 'contexts/AppContext/types';
import { Log } from 'scenes/ActivityLog/types/types';
import {
generateLogDetail,
generateLogMessage,
} from 'scenes/ActivityLog/utilities/utils';
import { DEFAULT_THEME } from 'scenes/ActivityLog';
export default function ActivityList() {
const { colors } = useContext(ThemeContext) || DEFAULT_THEME;
const { services } = useContext(AppContext);
const [logList, setLogList] = useState<Log[]>([]);
const [page, setPage] = useState(1);
const [next, setNext] = useState(false);
const [prev, setPrev] = useState(false);
const [totalLog, setTotalLog] = useState(0);
const [isLoading, setIsLoading] = useState(false);
const verticalLineHeight = (logList.length - 1) * 52;
const [roleFilterQuery, setRoleFilterQuery] = useState<String>('Semua Peran');
const [ignoreDateFilterQuery, setIgnoredateFilterQuery] = useState<boolean>(
true
);
const [dateFilterQuery, setDateFilterQuery] = useState<DateProps>({
start_date: new Date(),
end_date: new Date(),
});
const VerticalLine = styled.div`
border-left: 2px solid ${colors.mediumGray};
height: ${verticalLineHeight}px;
position: absolute;
left: 12px;
top: 5px;
`;
const Circle = styled.div`
left: 12px;
width: 24px;
height: 24px;
border-radius: 12px;
background: ${colors.mediumGray};
margin-right: 20px;
`;
const fetchLog = async (
page: number,
roleFilterQuery: String,
dateFilterQuery: DateProps,
ignoreDateFilterQuery: boolean
) => {
let dateOnly = {
start_date: dateFilterQuery.start_date.toISOString().split('T')[0],
end_date: dateFilterQuery.end_date.toISOString().split('T')[0],
};
setIsLoading(true);
const logResponse = await services.main.getLog(
page,
roleFilterQuery,
dateOnly.start_date,
dateOnly.end_date,
ignoreDateFilterQuery
);
console.log(logResponse);