blob: 051d99a398538439f53c72eccc7dda4c63ac4b91 [file] [log] [blame]
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview Star actions, selectors, and reducers organized into
* a single Redux "Duck" that manages updating and retrieving star state
* on the frontend.
*
* Reference: https://github.com/erikras/ducks-modular-redux
*/
import {combineReducers} from 'redux';
import {createReducer, createRequestReducer,
createKeyedRequestReducer} from './redux-helpers.js';
import {prpcClient} from 'prpc-client-instance.js';
import {projectAndUserToStarName} from 'shared/converters.js';
import 'shared/typedef.js';
/** @typedef {import('redux').AnyAction} AnyAction */
// Actions
export const LIST_PROJECTS_START = 'stars/LIST_PROJECTS_START';
export const LIST_PROJECTS_SUCCESS = 'stars/LIST_PROJECTS_SUCCESS';
export const LIST_PROJECTS_FAILURE = 'stars/LIST_PROJECTS_FAILURE';
export const STAR_PROJECT_START = 'stars/STAR_PROJECT_START';
export const STAR_PROJECT_SUCCESS = 'stars/STAR_PROJECT_SUCCESS';
export const STAR_PROJECT_FAILURE = 'stars/STAR_PROJECT_FAILURE';
export const UNSTAR_PROJECT_START = 'stars/UNSTAR_PROJECT_START';
export const UNSTAR_PROJECT_SUCCESS = 'stars/UNSTAR_PROJECT_SUCCESS';
export const UNSTAR_PROJECT_FAILURE = 'stars/UNSTAR_PROJECT_FAILURE';
/* State Shape
{
byName: Object<StarName, Star>,
requests: {
listProjects: ReduxRequestState,
},
}
*/
/**
* All star data indexed by resource name.
* @param {Object<ProjectName, Star>} state Existing Project data.
* @param {AnyAction} action
* @param {Array<Star>} action.star The Stars that were fetched.
* @param {ProjectStar} action.projectStar A single ProjectStar that was
* created.
* @param {StarName} action.starName The StarName that was mutated.
* @return {Object<ProjectName, Star>}
*/
export const byNameReducer = createReducer({}, {
[LIST_PROJECTS_SUCCESS]: (state, {stars}) => {
const newStars = {};
stars.forEach((star) => {
newStars[star.name] = star;
});
return {...state, ...newStars};
},
[STAR_PROJECT_SUCCESS]: (state, {projectStar}) => {
return {...state, [projectStar.name]: projectStar};
},
[UNSTAR_PROJECT_SUCCESS]: (state, {starName}) => {
const newState = {...state};
delete newState[starName];
return newState;
},
});
const requestsReducer = combineReducers({
listProjects: createRequestReducer(LIST_PROJECTS_START,
LIST_PROJECTS_SUCCESS, LIST_PROJECTS_FAILURE),
starProject: createKeyedRequestReducer(STAR_PROJECT_START,
STAR_PROJECT_SUCCESS, STAR_PROJECT_FAILURE),
unstarProject: createKeyedRequestReducer(UNSTAR_PROJECT_START,
UNSTAR_PROJECT_SUCCESS, UNSTAR_PROJECT_FAILURE),
});
export const reducer = combineReducers({
byName: byNameReducer,
requests: requestsReducer,
});
/**
* Returns normalized star data by name.
* @param {any} state
* @return {Object<StarName, Star>}
* @private
*/
export const byName = (state) => state.stars.byName;
/**
* Returns star requests.
* @param {any} state
* @return {Object<string, ReduxRequestState>}
*/
export const requests = (state) => state.stars.requests;
/**
* Retrieves the starred projects for a given user.
* @param {UserName} user The resource name of the user to fetch
* starred projects for.
* @return {function(function): Promise<void>}
*/
export const listProjects = (user) => async (dispatch) => {
dispatch({type: LIST_PROJECTS_START});
try {
const {projectStars} = await prpcClient.call(
'monorail.v3.Users', 'ListProjectStars', {parent: user});
dispatch({type: LIST_PROJECTS_SUCCESS, stars: projectStars});
} catch (error) {
dispatch({type: LIST_PROJECTS_FAILURE, error});
};
};
/**
* Stars a given project.
* @param {ProjectName} project The resource name of the project to star.
* @param {UserName} user The resource name of the user who is starring
* the issue. This will always be the currently logged in user.
* @return {function(function): Promise<void>}
*/
export const starProject = (project, user) => async (dispatch) => {
const requestKey = projectAndUserToStarName(project, user);
dispatch({type: STAR_PROJECT_START, requestKey});
try {
const projectStar = await prpcClient.call(
'monorail.v3.Users', 'StarProject', {project});
dispatch({type: STAR_PROJECT_SUCCESS, requestKey, projectStar});
} catch (error) {
dispatch({type: STAR_PROJECT_FAILURE, requestKey, error});
};
};
/**
* Unstars a given project.
* @param {ProjectName} project The resource name of the project to unstar.
* @param {UserName} user The resource name of the user who is unstarring
* the issue. This will always be the currently logged in user, but
* passing in the user's resource name is necessary to make it possible to
* generate the resource name of the removed star.
* @return {function(function): Promise<void>}
*/
export const unstarProject = (project, user) => async (dispatch) => {
const starName = projectAndUserToStarName(project, user);
const requestKey = starName;
dispatch({type: UNSTAR_PROJECT_START, requestKey});
try {
await prpcClient.call(
'monorail.v3.Users', 'UnStarProject', {project});
dispatch({type: UNSTAR_PROJECT_SUCCESS, requestKey, starName});
} catch (error) {
dispatch({type: UNSTAR_PROJECT_FAILURE, requestKey, error});
};
};
export const stars = {
reducer,
byName,
requests,
listProjects,
starProject,
unstarProject,
};