blob: 955dfeac82b47ae431e0e21e98737fdc67d6168b [file] [log] [blame]
Copybara854996b2021-09-07 19:36:02 +00001// Copyright 2019 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 Project actions, selectors, and reducers organized into
7 * a single Redux "Duck" that manages updating and retrieving project state
8 * on the frontend.
9 *
10 * Reference: https://github.com/erikras/ducks-modular-redux
11 */
12
13import {combineReducers} from 'redux';
14import {createSelector} from 'reselect';
15import {createReducer, createRequestReducer} from './redux-helpers.js';
16import {prpcClient} from 'prpc-client-instance.js';
17import 'shared/typedef.js';
18
19/** @typedef {import('redux').AnyAction} AnyAction */
20
21export const LIST_START = 'projects/LIST_START';
22export const LIST_SUCCESS = 'projects/LIST_SUCCESS';
23export const LIST_FAILURE = 'projects/LIST_FAILURE';
24
25/* State Shape
26{
27 name: string,
28
29 byName: Object<ProjectName, Project>,
30 allNames: Array<ProjectName>,
31
32 requests: {
33 list: ReduxRequestState,
34 },
35}
36*/
37
38/**
39 * All Project data indexed by Project name.
40 * @param {Object<ProjectName, Project>} state Existing Project data.
41 * @param {AnyAction} action
42 * @param {Array<Project>} action.projects The Projects that were fetched.
43 * @return {Object<ProjectName, Project>}
44 */
45export const byNameReducer = createReducer({}, {
46 [LIST_SUCCESS]: (state, {projects}) => {
47 const newProjects = {};
48 projects.forEach((proj) => {
49 newProjects[proj.name] = proj;
50 });
51 return {...state, ...newProjects};
52 },
53});
54
55/**
56 * Resource names for all Projects in Monorail.
57 * @param {Array<ProjectName>} _state Existing Project data.
58 * @param {AnyAction} action
59 * @param {Array<Project>} action.projects The Projects that were fetched.
60 * @return {Array<ProjectName>}
61 */
62export const allNamesReducer = createReducer([], {
63 [LIST_SUCCESS]: (_state, {projects}) => {
64 return projects.map((proj) => proj.name);
65 },
66});
67
68const requestsReducer = combineReducers({
69 list: createRequestReducer(
70 LIST_START, LIST_SUCCESS, LIST_FAILURE),
71});
72
73export const reducer = combineReducers({
74 byName: byNameReducer,
75 allNames: allNamesReducer,
76
77 requests: requestsReducer,
78});
79
80
81/**
82 * Returns normalized Project data by name.
83 * @param {any} state
84 * @return {Object<ProjectName, Project>}
85 * @private
86 */
87export const byName = (state) => state.projects.byName;
88
89/**
90 * Base selector for wrapping the allNames state key.
91 * @param {any} state
92 * @return {Array<ProjectName>}
93 * @private
94 */
95export const _allNames = (state) => state.projects.allNames;
96
97/**
98 * Returns all Projects on Monorail, in denormalized form, in
99 * the sort order returned by the API.
100 * @param {any} state
101 * @return {Array<Project>}
102 */
103export const all = createSelector([byName, _allNames],
104 (byName, allNames) => allNames.map((name) => byName[name]));
105
106
107/**
108 * Returns the Project requests.
109 * @param {any} state
110 * @return {Object<string, ReduxRequestState>}
111 */
112export const requests = (state) => state.projects.requests;
113
114/**
115 * Gets all projects hosted on Monorail.
116 * @return {function(function): Promise<void>}
117 */
118export const list = () => async (dispatch) => {
119 dispatch({type: LIST_START});
120 try {
121 /** @type {{projects: Array<Project>}} */
122 const {projects} = await prpcClient.call(
123 'monorail.v3.Projects', 'ListProjects', {});
124
125 dispatch({type: LIST_SUCCESS, projects});
126 } catch (error) {
127 dispatch({type: LIST_FAILURE, error});
128 }
129};