blob: b67ff9d0edf79e9d753ae8fc83c759466f359fc8 [file] [log] [blame]
Copybara854996b2021-09-07 19:36:02 +00001// Copyright 2020 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5/**
6 * @fileoverview Star actions, selectors, and reducers organized into
7 * a single Redux "Duck" that manages updating and retrieving star state
8 * on the frontend.
9 *
10 * Reference: https://github.com/erikras/ducks-modular-redux
11 */
12
13import {combineReducers} from 'redux';
14import {createReducer, createRequestReducer,
15 createKeyedRequestReducer} from './redux-helpers.js';
16import {prpcClient} from 'prpc-client-instance.js';
17import {projectAndUserToStarName} from 'shared/converters.js';
18import 'shared/typedef.js';
19
20/** @typedef {import('redux').AnyAction} AnyAction */
21
22// Actions
23export const LIST_PROJECTS_START = 'stars/LIST_PROJECTS_START';
24export const LIST_PROJECTS_SUCCESS = 'stars/LIST_PROJECTS_SUCCESS';
25export const LIST_PROJECTS_FAILURE = 'stars/LIST_PROJECTS_FAILURE';
26
27export const STAR_PROJECT_START = 'stars/STAR_PROJECT_START';
28export const STAR_PROJECT_SUCCESS = 'stars/STAR_PROJECT_SUCCESS';
29export const STAR_PROJECT_FAILURE = 'stars/STAR_PROJECT_FAILURE';
30
31export const UNSTAR_PROJECT_START = 'stars/UNSTAR_PROJECT_START';
32export const UNSTAR_PROJECT_SUCCESS = 'stars/UNSTAR_PROJECT_SUCCESS';
33export const UNSTAR_PROJECT_FAILURE = 'stars/UNSTAR_PROJECT_FAILURE';
34
35/* State Shape
36{
37 byName: Object<StarName, Star>,
38
39 requests: {
40 listProjects: ReduxRequestState,
41 },
42}
43*/
44
45/**
46 * All star data indexed by resource name.
47 * @param {Object<ProjectName, Star>} state Existing Project data.
48 * @param {AnyAction} action
49 * @param {Array<Star>} action.star The Stars that were fetched.
50 * @param {ProjectStar} action.projectStar A single ProjectStar that was
51 * created.
52 * @param {StarName} action.starName The StarName that was mutated.
53 * @return {Object<ProjectName, Star>}
54 */
55export const byNameReducer = createReducer({}, {
56 [LIST_PROJECTS_SUCCESS]: (state, {stars}) => {
57 const newStars = {};
58 stars.forEach((star) => {
59 newStars[star.name] = star;
60 });
61 return {...state, ...newStars};
62 },
63 [STAR_PROJECT_SUCCESS]: (state, {projectStar}) => {
64 return {...state, [projectStar.name]: projectStar};
65 },
66 [UNSTAR_PROJECT_SUCCESS]: (state, {starName}) => {
67 const newState = {...state};
68 delete newState[starName];
69 return newState;
70 },
71});
72
73
74const requestsReducer = combineReducers({
75 listProjects: createRequestReducer(LIST_PROJECTS_START,
76 LIST_PROJECTS_SUCCESS, LIST_PROJECTS_FAILURE),
77 starProject: createKeyedRequestReducer(STAR_PROJECT_START,
78 STAR_PROJECT_SUCCESS, STAR_PROJECT_FAILURE),
79 unstarProject: createKeyedRequestReducer(UNSTAR_PROJECT_START,
80 UNSTAR_PROJECT_SUCCESS, UNSTAR_PROJECT_FAILURE),
81});
82
83
84export const reducer = combineReducers({
85 byName: byNameReducer,
86 requests: requestsReducer,
87});
88
89
90/**
91 * Returns normalized star data by name.
92 * @param {any} state
93 * @return {Object<StarName, Star>}
94 * @private
95 */
96export const byName = (state) => state.stars.byName;
97
98/**
99 * Returns star requests.
100 * @param {any} state
101 * @return {Object<string, ReduxRequestState>}
102 */
103export const requests = (state) => state.stars.requests;
104
105/**
106 * Retrieves the starred projects for a given user.
107 * @param {UserName} user The resource name of the user to fetch
108 * starred projects for.
109 * @return {function(function): Promise<void>}
110 */
111export const listProjects = (user) => async (dispatch) => {
112 dispatch({type: LIST_PROJECTS_START});
113
114 try {
115 const {projectStars} = await prpcClient.call(
116 'monorail.v3.Users', 'ListProjectStars', {parent: user});
117 dispatch({type: LIST_PROJECTS_SUCCESS, stars: projectStars});
118 } catch (error) {
119 dispatch({type: LIST_PROJECTS_FAILURE, error});
120 };
121};
122
123/**
124 * Stars a given project.
125 * @param {ProjectName} project The resource name of the project to star.
126 * @param {UserName} user The resource name of the user who is starring
127 * the issue. This will always be the currently logged in user.
128 * @return {function(function): Promise<void>}
129 */
130export const starProject = (project, user) => async (dispatch) => {
131 const requestKey = projectAndUserToStarName(project, user);
132 dispatch({type: STAR_PROJECT_START, requestKey});
133 try {
134 const projectStar = await prpcClient.call(
135 'monorail.v3.Users', 'StarProject', {project});
136 dispatch({type: STAR_PROJECT_SUCCESS, requestKey, projectStar});
137 } catch (error) {
138 dispatch({type: STAR_PROJECT_FAILURE, requestKey, error});
139 };
140};
141
142/**
143 * Unstars a given project.
144 * @param {ProjectName} project The resource name of the project to unstar.
145 * @param {UserName} user The resource name of the user who is unstarring
146 * the issue. This will always be the currently logged in user, but
147 * passing in the user's resource name is necessary to make it possible to
148 * generate the resource name of the removed star.
149 * @return {function(function): Promise<void>}
150 */
151export const unstarProject = (project, user) => async (dispatch) => {
152 const starName = projectAndUserToStarName(project, user);
153 const requestKey = starName;
154 dispatch({type: UNSTAR_PROJECT_START, requestKey});
155
156 try {
157 await prpcClient.call(
158 'monorail.v3.Users', 'UnStarProject', {project});
159 dispatch({type: UNSTAR_PROJECT_SUCCESS, requestKey, starName});
160 } catch (error) {
161 dispatch({type: UNSTAR_PROJECT_FAILURE, requestKey, error});
162 };
163};
164
165export const stars = {
166 reducer,
167 byName,
168 requests,
169 listProjects,
170 starProject,
171 unstarProject,
172};