Skip to content

Commit 1331d79

Browse files
authored
Merge pull request #1333 from kenkoooo/chore/staging
Chore/staging
2 parents 65e7d6e + f07a582 commit 1331d79

File tree

5 files changed

+93
-36
lines changed

5 files changed

+93
-36
lines changed

atcoder-problems-frontend/src/components/SubmissionListTable.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@ import {
1414
} from "./ListPaginationPanel";
1515
import { NewTabLink } from "./NewTabLink";
1616

17+
const problemTitle = (
18+
problemName: string | undefined,
19+
{ problemIndex }: { problemIndex?: string }
20+
): string =>
21+
problemIndex ? `${problemIndex}. ${problemName || ""}` : problemName || "";
22+
1723
interface Props {
1824
submissions: Submission[];
1925
userRatingInfo?: RatingInfo;
@@ -113,7 +119,7 @@ export const SubmissionListTable: React.FC<Props> = (props) => {
113119
Date
114120
</TableHeaderColumn>
115121
<TableHeaderColumn
116-
filterFormatted
122+
filterValue={problemTitle}
117123
dataSort
118124
dataField="name"
119125
dataFormat={(

atcoder-problems-frontend/src/pages/Internal/MyAccountPage/ApiClient.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
import { PROGRESS_RESET_ADD, PROGRESS_RESET_DELETE } from "../ApiUrl";
1+
import {
2+
PROGRESS_RESET_ADD,
3+
PROGRESS_RESET_DELETE,
4+
USER_UPDATE,
5+
} from "../ApiUrl";
26
import { getCurrentUnixtimeInSecond } from "../../../utils/DateUtil";
37

48
export const addResetProgress = (problemId: string) =>
@@ -23,3 +27,14 @@ export const deleteResetProgress = (problemId: string) =>
2327
problem_id: problemId,
2428
}),
2529
});
30+
31+
export const updateUserInfo = (userId: string) =>
32+
fetch(USER_UPDATE, {
33+
method: "POST",
34+
headers: {
35+
"Content-Type": "application/json",
36+
},
37+
body: JSON.stringify({
38+
atcoder_user_id: userId,
39+
}),
40+
});

atcoder-problems-frontend/src/pages/Internal/MyAccountPage/index.tsx

Lines changed: 16 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import React, { useEffect, useState } from "react";
2-
import { connect, PromiseState, PropsMapInner } from "react-refetch";
32
import { Nav, NavItem, NavLink, Spinner } from "reactstrap";
43
import {
54
Redirect,
@@ -10,25 +9,29 @@ import {
109
useLocation,
1110
} from "react-router-dom";
1211
import { useLoginState } from "../../../api/InternalAPIClient";
13-
import { USER_UPDATE } from "../ApiUrl";
1412
import { UserProblemListPage } from "../UserProblemListPage";
1513
import { MyContestList } from "./MyContestList";
1614
import { ResetProgress } from "./ResetProgress";
1715
import { UserIdUpdate } from "./UserIdUpdate";
16+
import { updateUserInfo } from "./ApiClient";
1817

19-
interface InnerProps {
20-
updateUserInfo: (atcoderUser: string) => void;
21-
updateUserInfoResponse: PromiseState<Record<string, unknown> | null>;
22-
}
23-
24-
const InnerMyAccountPage = (props: InnerProps): JSX.Element => {
25-
const { updateUserInfoResponse } = props;
18+
export const MyAccountPage = (): JSX.Element => {
2619
const loginState = useLoginState();
2720

2821
const [userId, setUserId] = useState("");
22+
const [isUpdating, setIsUpdating] = useState(false);
23+
const [isValidResponse, setIsValidResponse] = useState<boolean>();
2924
const { path } = useRouteMatch();
3025
const { pathname } = useLocation();
3126

27+
const handleSubmit = async (userId: string) => {
28+
setIsUpdating(true);
29+
await updateUserInfo(userId).then((response) => {
30+
setIsValidResponse(response.status === 200);
31+
});
32+
setIsUpdating(false);
33+
};
34+
3235
useEffect(() => {
3336
if (loginState.data) {
3437
setUserId(loginState.data.atcoder_user_id ?? "");
@@ -37,16 +40,12 @@ const InnerMyAccountPage = (props: InnerProps): JSX.Element => {
3740
// eslint-disable-next-line react-hooks/exhaustive-deps
3841
}, [!!loginState.data]);
3942

40-
if (loginState.error || updateUserInfoResponse.rejected) {
43+
if (loginState.error || isValidResponse === false) {
4144
return <Redirect to="/" />;
4245
} else if (!loginState.data) {
4346
return <Spinner style={{ width: "3rem", height: "3rem" }} />;
4447
} else {
45-
const updating = updateUserInfoResponse.refreshing;
46-
const updated =
47-
!updating &&
48-
updateUserInfoResponse.fulfilled &&
49-
updateUserInfoResponse.value !== null;
48+
const updated = !isUpdating && isValidResponse;
5049

5150
return (
5251
<>
@@ -90,8 +89,8 @@ const InnerMyAccountPage = (props: InnerProps): JSX.Element => {
9089
<UserIdUpdate
9190
userId={userId}
9291
setUserId={setUserId}
93-
onSubmit={() => props.updateUserInfo(userId)}
94-
status={updating ? "updating" : updated ? "updated" : "open"}
92+
onSubmit={() => handleSubmit(userId)}
93+
status={isUpdating ? "updating" : updated ? "updated" : "open"}
9594
/>
9695
</Route>
9796
<Route exact path={`${path}/contests`}>
@@ -108,20 +107,3 @@ const InnerMyAccountPage = (props: InnerProps): JSX.Element => {
108107
);
109108
}
110109
};
111-
112-
export const MyAccountPage = connect<unknown, InnerProps>(() => ({
113-
updateUserInfo: (
114-
atcoderUser: string
115-
): PropsMapInner<Pick<InnerProps, "updateUserInfoResponse">> => ({
116-
updateUserInfoResponse: {
117-
force: true,
118-
refreshing: true,
119-
url: USER_UPDATE,
120-
method: "POST",
121-
body: JSON.stringify({ atcoder_user_id: atcoderUser }),
122-
},
123-
}),
124-
updateUserInfoResponse: {
125-
value: null,
126-
},
127-
}))(InnerMyAccountPage);

atcoder-problems-frontend/src/pages/UserPage/ProgressChartBlock/index.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import { useLocalStorage } from "../../../utils/LocalStorage";
2727
import {
2828
countUniqueAcByDate,
2929
countTeeByDate,
30+
countTeeMovingAverage,
3031
} from "../../../utils/StreakCounter";
3132
import Submission from "../../../interfaces/Submission";
3233
import { ProblemId } from "../../../interfaces/Status";
@@ -146,6 +147,8 @@ export const ProgressChartBlock: React.FC<Props> = (props) => {
146147
return list;
147148
}, [] as { dateSecond: number; count: number }[]);
148149

150+
const teeMovingAverage = countTeeMovingAverage(dailyTeeCount);
151+
149152
const dateColorCountMap = Array.from(submissionsByProblem.values())
150153
.map((submissionsOfProblem) => {
151154
const accepted = submissionsOfProblem
@@ -277,6 +280,11 @@ export const ProgressChartBlock: React.FC<Props> = (props) => {
277280
</Row>
278281
<TeeChart climbingData={teeClimbing} />
279282

283+
<Row className="my-2 border-bottom">
284+
<h1>TEE Moving Average (30 days)</h1>
285+
</Row>
286+
<TeeChart climbingData={teeMovingAverage} />
287+
280288
<Row className="my-2 border-bottom">
281289
<h1>Heatmap</h1>
282290
</Row>

atcoder-problems-frontend/src/utils/StreakCounter.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,3 +111,49 @@ export const countTeeByDate = (
111111
.map(([dateLabel, count]) => ({ dateLabel, count }))
112112
.sort((a, b) => a.dateLabel.localeCompare(b.dateLabel));
113113
};
114+
115+
export const countTeeMovingAverage = (
116+
dailyTeeCount: {
117+
dateLabel: string;
118+
count: number;
119+
}[]
120+
) => {
121+
const DURATION = 30;
122+
if (!Array.isArray(dailyTeeCount) || dailyTeeCount.length === 0) {
123+
return [];
124+
}
125+
126+
const minDateLabel = dailyTeeCount[0].dateLabel;
127+
const maxDateLabel = dailyTeeCount[dailyTeeCount.length - 1].dateLabel;
128+
const dateDelta =
129+
1 +
130+
(new Date(maxDateLabel).getTime() - new Date(minDateLabel).getTime()) /
131+
1000 /
132+
86400;
133+
134+
const differentiatedTees = Array.from(Array(dateDelta)).map((__, i) => {
135+
const nextDate = new Date(minDateLabel);
136+
nextDate.setDate(nextDate.getDate() + i);
137+
const nextDateLabel = nextDate.toISOString().substring(0, 10);
138+
const found = dailyTeeCount.find((tee) => tee.dateLabel === nextDateLabel);
139+
if (found) {
140+
return found;
141+
} else {
142+
return { dateLabel: nextDateLabel, count: 0 };
143+
}
144+
});
145+
146+
return differentiatedTees
147+
.map(({ dateLabel }, i) => {
148+
const dateSecond = parseDateLabel(dateLabel).unix();
149+
const begin = Math.max(i - (DURATION - 1), 0);
150+
const total = differentiatedTees
151+
.slice(begin, i + 1)
152+
.reduce((tot, data) => data.count + tot, 0);
153+
if (!total) {
154+
return null;
155+
}
156+
return { dateSecond, count: total / DURATION };
157+
})
158+
.filter((data): data is { dateSecond: number; count: number } => !!data);
159+
};

0 commit comments

Comments
 (0)