Skip to content

Commit

Permalink
Merge pull request #446 from nttcom/topic/ui-rtkq-apply-get-actions
Browse files Browse the repository at this point in the history
refine getToipicActions using RTKQ
  • Loading branch information
mshim03 authored Nov 5, 2024
2 parents ad36fcf + c80f8c5 commit 50576f0
Show file tree
Hide file tree
Showing 7 changed files with 72 additions and 66 deletions.
26 changes: 17 additions & 9 deletions web/src/components/AnalysisTopic.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,13 @@ import { TopicEditModal } from "../components/TopicEditModal";
import { UUIDTypography } from "../components/UUIDTypography";
import { WarningTooltip } from "../components/WarningTooltip";
import styles from "../cssModule/button.module.css";
import { useSkipUntilAuthTokenIsReady } from "../hooks/auth";
import {
useCreateATeamTopicCommentMutation,
useGetTopicActionsQuery,
useUpdateATeamTopicCommentMutation,
} from "../services/tcApi";
import { getActions, getTopic } from "../slices/topics";
import { getTopic } from "../slices/topics";
import { getATeamTopicComments as apiGetATeamTopicComments } from "../utils/api";
import { rootPrefix, threatImpactNames } from "../utils/const";
import { a11yProps, dateTimeFormat, errorToString, tagsMatched } from "../utils/func.js";
Expand All @@ -66,7 +68,6 @@ export function AnalysisTopic(props) {
const [updateATeamTopicComment] = useUpdateATeamTopicCommentMutation();

const topics = useSelector((state) => state.topics.topics);
const actions = useSelector((state) => state.topics.actions);

const { enqueueSnackbar } = useSnackbar();
const dispatch = useDispatch();
Expand All @@ -77,10 +78,16 @@ export function AnalysisTopic(props) {
setActionExpanded(isExpanded ? panel : false);
};

const skip = useSkipUntilAuthTokenIsReady();
const {
data: topicActions,
error: topicActionsError,
isLoading: topicActionsIsLoading,
} = useGetTopicActionsQuery(targetTopic.topic_id, { skip });

useEffect(() => {
handleReloadComments(targetTopic.topic_id);
if (topics?.[targetTopic.topic_id] === undefined) dispatch(getTopic(targetTopic.topic_id));
if (actions?.[targetTopic.topic_id] === undefined) dispatch(getActions(targetTopic.topic_id));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [targetTopic.topic_id]);

Expand Down Expand Up @@ -163,17 +170,18 @@ export function AnalysisTopic(props) {
return `${rootPrefix}/tags/${tagId}?` + tmpParams.toString();
};
const topicDetail = topics?.[targetTopic.topic_id];
const topicActions = actions?.[targetTopic.topic_id];
const handleDetailOpen = () => setDetailOpen(!detailOpen);

/* block rendering until data ready */
if (!ateam.ateam_id || !topicDetail || !topicActions) return <Box sx={{ m: 2 }}>Loading...</Box>;
if (skip) return <></>;
if (!ateam.ateam_id || !topicDetail) return <Box sx={{ m: 2 }}>Loading...</Box>;
if (topicActionsError)
return <>{`Cannot get topicActions: ${errorToString(topicActionsError)}`}</>;
if (topicActionsIsLoading) return <>Now loading topicActions...</>;

const topicTagNames = topicDetail.tags.map((tag) => tag.tag_name);
const recommendedActions = actions?.[targetTopic.topic_id]?.filter(
(action) => action.recommended,
);
const otherActions = actions?.[targetTopic.topic_id]?.filter((action) => !action.recommended);
const recommendedActions = topicActions.filter((action) => action.recommended);
const otherActions = topicActions.filter((action) => !action.recommended);

const pteamContactInfoDict = ateam.pteams.reduce(
(dict, pteam) => ({
Expand Down
5 changes: 1 addition & 4 deletions web/src/components/TopicEditModal.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ import {
useUpdateActionMutation,
useUpdateTopicMutation,
} from "../services/tcApi";
import { getActions, getTopic } from "../slices/topics";
import { getTopic } from "../slices/topics";
import { a11yProps, errorToString, setEquals, validateNotEmpty } from "../utils/func";

import { ActionTypeIcon } from "./ActionTypeIcon";
Expand Down Expand Up @@ -186,9 +186,6 @@ export function TopicEditModal(props) {
}
enqueueSnackbar("Remofing actions succeeded", { variant: "success" });
}
if (newActions.length + updatedActions.length + removedActionIds.length > 0) {
await dispatch(getActions(currentTopic.topic_id));
}
setTopicId(""); // mark reset at next open, only if succeeded
} catch (error) {
enqueueSnackbar(`Operation failed: ${errorToString(error)}`, { variant: "error" });
Expand Down
32 changes: 17 additions & 15 deletions web/src/pages/TopicDetail.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@ import { useParams } from "react-router-dom";
import { ActionTypeIcon } from "../components/ActionTypeIcon";
import { TopicSSVCCards } from "../components/TopicSSVCCards";
import { useSkipUntilAuthTokenIsReady } from "../hooks/auth";
import { getTopic, getActions } from "../slices/topics";
import { useGetTopicActionsQuery } from "../services/tcApi";
import { getTopic } from "../slices/topics";
import { threatImpactNames, threatImpactProps } from "../utils/const";
import { errorToString } from "../utils/func";

const threatImpactColor = {
immediate: {
Expand All @@ -39,8 +41,8 @@ const artifactTagChip = (chipNumber) => {
return chipNumber <= artifactTagMax ? chipNumber : `${artifactTagMax}+`;
};

function pickAffectedVersions(actions, tagName) {
const versions = actions.reduce(
function pickAffectedVersions(topicActions, tagName) {
const versions = topicActions.reduce(
(ret, action) => [...ret, ...(action.ext?.vulnerable_versions?.[tagName] ?? [])],
[],
);
Expand All @@ -58,12 +60,10 @@ export function TopicDetail() {
const skip = useSkipUntilAuthTokenIsReady();

const topics = useSelector((state) => state.topics.topics);
const topicActions = useSelector((state) => state.topics.actions);

const dispatch = useDispatch();

const topic = topicId && topics ? topics[topicId] : undefined;
const actions = topicId && topicActions ? topicActions[topicId] : undefined;

useEffect(() => {
if (skip) return; // wait for login completed
Expand All @@ -72,15 +72,17 @@ export function TopicDetail() {
dispatch(getTopic(topicId));
}, [skip, topicId, topic, dispatch]);

useEffect(() => {
if (skip) return; // wait for login completed
if (!topicId) return; // will never happen
if (actions) return; // nothing to do any more
dispatch(getActions(topicId));
}, [skip, topicId, actions, dispatch]);
const {
data: topicActions,
error: topicActionsError,
isLoading: topicActionsIsLoading,
} = useGetTopicActionsQuery(topicId, { skip });

if (skip) return <></>;
if (!topic || !actions) return <>Now loading...</>;
if (!topic) return <>Now loading...</>;
if (topicActionsError)
return <>{`Cannot get topicActions: ${errorToString(topicActionsError)}`}</>;
if (topicActionsIsLoading) return <>Now loading topicActions...</>;

const threatImpactName = threatImpactNames[topic.threat_impact];
const baseStyle = threatImpactProps[threatImpactName].style;
Expand Down Expand Up @@ -134,7 +136,7 @@ export function TopicDetail() {
flexDirection="column"
sx={{ width: "50%", minWidth: "50%" }}
>
{pickAffectedVersions(actions, artifactTag.tag_name).map(
{pickAffectedVersions(topicActions, artifactTag.tag_name).map(
(affectedVersion) => (
<Box
key={affectedVersion}
Expand Down Expand Up @@ -234,12 +236,12 @@ export function TopicDetail() {
<Box alignItems="center" display="flex" flexDirection="row">
<Typography sx={{ fontWeight: "bold" }}>Action</Typography>
</Box>
{actions.length === 0 ? (
{topicActions.length === 0 ? (
<Typography sx={{ margin: 1 }}>No data</Typography>
) : (
<>
<Box>
{actions.map((action) => (
{topicActions.map((action) => (
<MenuItem
key={action.action_id}
sx={{
Expand Down
21 changes: 15 additions & 6 deletions web/src/pages/TopicManagement.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ import { FormattedDateTimeWithTooltip } from "../components/FormattedDateTimeWit
import { TopicSearchModal } from "../components/TopicSearchModal";
import styles from "../cssModule/button.module.css";
import { useSkipUntilAuthTokenIsReady } from "../hooks/auth";
import { useSearchTopicsQuery } from "../services/tcApi";
import { getActions, getTopic } from "../slices/topics";
import { useGetTopicActionsQuery, useSearchTopicsQuery } from "../services/tcApi";
import { getTopic } from "../slices/topics";
import { difficulty, difficultyColors } from "../utils/const";
import { errorToString } from "../utils/func";

Expand All @@ -48,17 +48,26 @@ function TopicManagementTableRow(props) {
const location = useLocation();

const topics = useSelector((state) => state.topics.topics);
const actions = useSelector((state) => state.topics.actions);
const params = new URLSearchParams(location.search);

useEffect(() => {
if (topics?.[topicId] === undefined) dispatch(getTopic(topicId));
if (actions?.[topicId] === undefined) dispatch(getActions(topicId));
/* eslint-disable-next-line react-hooks/exhaustive-deps */
}, []);

const skip = useSkipUntilAuthTokenIsReady();
const {
data: topicActions,
error: topicActionsError,
isLoading: topicActionsIsLoading,
} = useGetTopicActionsQuery(topicId, { skip });

if (skip) return <></>;
if (topicActionsError)
return <>{`Cannot get topicActions: ${errorToString(topicActionsError)}`}</>;
if (topicActionsIsLoading) return <>Now loading topicActions...</>;

const topic = topics?.[topicId];
const actionList = actions?.[topicId];

if (!topic) {
return (
Expand All @@ -84,7 +93,7 @@ function TopicManagementTableRow(props) {
<FormattedDateTimeWithTooltip utcString={topic.updated_at} />
</TableCell>
<TableCell align="center">
{actionList?.length > 0 ? (
{topicActions?.length > 0 ? (
<CheckCircleOutlineIcon color="success" />
) : (
<HorizontalRuleIcon sx={{ color: grey[500] }} />
Expand Down
14 changes: 14 additions & 0 deletions web/src/services/tcApi.js
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,19 @@ export const tcApi = createApi({
{ type: "TopicAction", id: "ALL" },
],
}),
getTopicActions: builder.query({
query: (topicId) => ({
url: `topics/${topicId}/actions/user/me`,
method: "GET",
}),
providesTags: (result, error, arg) => [
...(result?.reduce(
(ret, action) => [...ret, { type: "TopicAction", id: action.action_id }],
[],
) ?? []),
{ type: "TopicAction", id: "ALL" },
],
}),

/* Topic Comment */
createATeamTopicComment: builder.mutation({
Expand Down Expand Up @@ -544,6 +557,7 @@ export const {
useApplyATeamInvitationMutation,
useGetATeamMembersQuery,
useGetPTeamTopicActionsQuery,
useGetTopicActionsQuery,
useDeleteATeamMemberMutation,
useDeleteATeamTopicCommentMutation,
useCreateATeamTopicCommentMutation,
Expand Down
37 changes: 8 additions & 29 deletions web/src/slices/topics.js
Original file line number Diff line number Diff line change
@@ -1,27 +1,14 @@
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";

import {
getTopic as apiGetTopic,
getUserTopicActions as apiGetUserTopicActions,
} from "../utils/api";
import { getTopic as apiGetTopic } from "../utils/api";

export const getTopic = createAsyncThunk(
"topics/get",
async (topicId) => await apiGetTopic(topicId).then((response) => response.data),
);

export const getActions = createAsyncThunk(
"topics/getActions",
async (topicId) =>
await apiGetUserTopicActions(topicId).then((response) => ({
data: response.data,
topicId: topicId,
})),
);

const _initialState = {
topics: {}, // {topicId: TopicResponse}
actions: {}, // {topicId: List[ActionResponse]}
};

const topicsSlice = createSlice({
Expand All @@ -34,21 +21,13 @@ const topicsSlice = createSlice({
}),
},
extraReducers: (builder) => {
builder
.addCase(getTopic.fulfilled, (state, action) => ({
...state,
topics: {
...state.topics,
[action.payload.topic_id]: action.payload,
},
}))
.addCase(getActions.fulfilled, (state, action) => ({
...state,
actions: {
...state.actions,
[action.payload.topicId]: action.payload.data,
},
}));
builder.addCase(getTopic.fulfilled, (state, action) => ({
...state,
topics: {
...state.topics,
[action.payload.topic_id]: action.payload,
},
}));
},
});

Expand Down
3 changes: 0 additions & 3 deletions web/src/utils/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,6 @@ export const updateTopic = async (topicId, data) => axios.put(`/topics/${topicId

export const searchTopics = async (params) => axios.get("topics/search", { params: params ?? {} });

export const getUserTopicActions = async (topicId) =>
axios.get(`/topics/${topicId}/actions/user/me`);

export const fetchFlashsense = async (topicId) => axios.get(`/topics/fetch_fs/${topicId}`);

// tags
Expand Down

0 comments on commit 50576f0

Please sign in to comment.