Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/be-develop' into develop
Browse files Browse the repository at this point in the history
  • Loading branch information
SongJSeop committed Dec 7, 2023
2 parents 4c2334a + 527f897 commit f20d968
Show file tree
Hide file tree
Showing 40 changed files with 1,006 additions and 189 deletions.
47 changes: 47 additions & 0 deletions .pnp.cjs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file modified .yarn/install-state.gz
Binary file not shown.
2 changes: 2 additions & 0 deletions packages/admin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@
"dependencies": {
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
"chart.js": "^4.4.1",
"react": "^18.2.0",
"react-chartjs-2": "^5.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.20.1"
},
Expand Down
14 changes: 10 additions & 4 deletions packages/admin/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,28 @@
import { Route, Routes } from 'react-router-dom';
import { Link, Route, Routes } from 'react-router-dom';
import './App.css';

import Board from './components/Board/Board.tsx';
import Nav from './components/Nav.tsx';
import SystemInfo from './components/SystemInfo/SystemInfo.tsx';
import ExceptionStatistics from './components/ExceptionStatistics/ExceptionStatistics.tsx';

function App() {
return (
<>
<Nav>
<a href="/admin">Home</a>
<a href="/admin/board">Board</a>
<a href="/admin/system-info">System Info</a>
<Link to={'/admin'}>Home</Link>
<Link to={'/admin/board'}>Board</Link>
<Link to={'/admin/system-info'}>System Info</Link>
<Link to={'/admin/exception-statistics'}>Exception Statistics</Link>
</Nav>
<Routes>
<Route path="/admin" element={<div>Home</div>} />
<Route path="/admin/board" element={<Board />} />
<Route path="/admin/system-info" element={<SystemInfo />} />
<Route
path="/admin/exception-statistics"
element={<ExceptionStatistics />}
/>
</Routes>
</>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import styled from '@emotion/styled';

export const Select = styled.select`
margin-top: 20px;
margin-bottom: 20px;
padding: 5px;
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import { Exception, ExceptionConditions } from '../exception.interface.ts';
import { Bar } from 'react-chartjs-2';
import { CategoryScale } from 'chart.js';
import Chart from 'chart.js/auto';
import React, { useState } from 'react';
import { Select } from './ExceptionChart.styles.tsx';
Chart.register(CategoryScale);

interface PropsType {
exceptionData: Exception[];
condition: ExceptionConditions;
}

export default function ExceptionChart({
exceptionData,
condition,
}: PropsType) {
const filteredExceptionData = filter(exceptionData, condition);
const uniqueDate = getUniqueDate(filteredExceptionData);

const [option, setOption] = useState('path');

const handleChangeOption = (event: React.ChangeEvent<HTMLSelectElement>) => {
setOption(event.target.value);
};

return (
<div style={{ width: '1024px' }}>
<Select onChange={handleChangeOption}>
<option value="path">경로 기준으로 보기</option>
<option value="error">에러 기준으로 보기</option>
</Select>
<Bar
data={{
labels: uniqueDate,
datasets: getDatasets(filteredExceptionData, uniqueDate, option),
}}
/>
</div>
);
}

function filter(
exceptionData: Exception[],
condition: ExceptionConditions,
): Exception[] {
const filterdData = exceptionData.filter((exception: Exception) => {
const { startDate, endDate } = condition;
const timestamp = new Date(exception.timestamp);
if (!startDate && !endDate) return true;
if (startDate && !endDate) return timestamp >= startDate;
if (!startDate && endDate) return timestamp <= endDate;
if (startDate && endDate)
return timestamp >= startDate && timestamp <= endDate;
});
return filterdData;
}

function getUniqueDate(exceptionData: Exception[]) {
const uniqueDate = new Set(
exceptionData.map((exception: Exception) => {
return exception.timestamp.toLocaleString().slice(0, 10);
}),
);
return [...uniqueDate];
}

function getDatasets(
exceptionData: Exception[],
uniqueDate: string[],
option: string,
) {
if (option === 'path') {
const uniquePathList = new Set(
exceptionData.map((exception: Exception) => exception.path),
);
const datasets = [...uniquePathList].map((path: string) => {
const filteredData = uniqueDate.map((date: string) => {
return exceptionData.filter(
(exception: Exception) =>
exception.path === path &&
exception.timestamp.toLocaleString().slice(0, 10) === date,
);
});
const randomColor = getRandomColor();
return {
label: path,
data: filteredData.map((data: Exception[]) => data.length),
backgroundColor: randomColor,
borderColor: randomColor,
borderWidth: 1,
};
});
return datasets;
}

const uniqueErrorList = new Set(
exceptionData.map((exception: Exception) => exception.error),
);
const datasets = [...uniqueErrorList].map((error: string) => {
const filteredData = uniqueDate.map((date: string) => {
return exceptionData.filter(
(exception: Exception) =>
exception.error === error &&
exception.timestamp.toLocaleString().slice(0, 10) === date,
);
});
const randomColor = getRandomColor();
return {
label: error,
data: filteredData.map((data: Exception[]) => data.length),
backgroundColor: randomColor,
borderColor: randomColor,
borderWidth: 1,
};
});
return datasets;
}

function getRandomColor() {
const r = Math.floor(Math.random() * 256);
const g = Math.floor(Math.random() * 256);
const b = Math.floor(Math.random() * 256);

return `rgb(${r}, ${g}, ${b})`;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { useEffect, useState } from 'react';
import Filter from './Filter/Filter.tsx';
import ExceptionChart from './ExceptionChart/ExceptionChart.tsx';
import { Exception } from './exception.interface.ts';

const baseUrl = import.meta.env.VITE_API_BASE_URL;

export default function ExceptionStatistics() {
const [condition, setCondition] = useState({});
const [exceptionData, setExceptionData] = useState([]);

const getAllExceptions = async () => {
const response = await fetch(baseUrl + '/admin/exception');
const exceptions = await response.json();
exceptions.map((exception: Exception) => {
if (exception.path.includes('?')) {
exception.path = exception.path.slice(0, exception.path.indexOf('?'));
}
});
setExceptionData(exceptions);
};

useEffect(() => {
getAllExceptions();
}, []);

return (
<div>
<Filter setCondition={setCondition} />
<ExceptionChart exceptionData={exceptionData} condition={condition} />
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import styled from '@emotion/styled';

export const FilterContainer = styled.div`
padding: 1rem;
display: flex;
align-items: center;
justify-content: center;
gap: 1rem;
`;

export const DateInputContainer = styled.div`
background-color: ${(props) => props.color};
width: 150px;
`;

export const DateInputTitle = styled.p`
font-size: 1.5rem;
border-bottom: 1px solid white;
`;

export const DateInput = styled.input`
width: 128.75px;
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import React, { useState } from 'react';
import Button from '../../shared/Button.tsx';
import { ExceptionConditions } from '../exception.interface.ts';
import {
DateInput,
DateInputContainer,
DateInputTitle,
FilterContainer,
} from './Filter.styles.tsx';

interface PropsType {
setCondition: React.Dispatch<React.SetStateAction<ExceptionConditions>>;
}

export default function Filter({ setCondition }: PropsType) {
const [defaultStartDate, defaultEndDate] = getDates();

const [startDateCondition, setStartDateCondition] =
useState(defaultStartDate);
const [endDateCondition, setEndDateCondition] = useState(defaultEndDate);

const selectStartDate = (event: React.ChangeEvent<HTMLInputElement>) => {
setStartDateCondition(event.target.value);
};
const selectEndDate = (event: React.ChangeEvent<HTMLInputElement>) => {
setEndDateCondition(event.target.value);
};

const changeCondition = () => {
setCondition({
startDate: new Date(startDateCondition),
endDate: new Date(
new Date(endDateCondition).getTime() + 60 * 60 * 24 * 1000,
),
});
};

return (
<FilterContainer>
<DateInputContainer color="#b794bd">
<DateInputTitle>시작 날짜</DateInputTitle>
<DateInput
type="date"
onChange={selectStartDate}
defaultValue={defaultStartDate}
max={endDateCondition}
/>
</DateInputContainer>
<DateInputContainer color="#94b5bd">
<DateInputTitle>종료 날짜</DateInputTitle>
<DateInput
type="date"
onChange={selectEndDate}
defaultValue={defaultEndDate}
min={startDateCondition}
/>
</DateInputContainer>
<Button onClick={changeCondition}>검색</Button>
</FilterContainer>
);
}

function getDates() {
const currentDate = new Date();
const year = currentDate.getFullYear();
const month = String(currentDate.getMonth() + 1).padStart(2, '0');
const day = String(currentDate.getDate()).padStart(2, '0');
const defaultStartDate = `${year}-${month}-01`;
const defaultEndDate = `${year}-${month}-${day}`;
return [defaultStartDate, defaultEndDate];
}
Loading

0 comments on commit f20d968

Please sign in to comment.