in src/amo/reducers/addons.js [368:573]
export default function addonsReducer(
// eslint-disable-next-line default-param-last
state: AddonsState = initialState,
action: Action,
): AddonsState {
switch (action.type) {
case SET_LANG:
return {
...state,
lang: action.payload.lang,
};
case FETCH_ADDON: {
const { slug } = action.payload;
return {
...state,
loadingByIdInURL: {
...state.loadingByIdInURL,
[slug]: true,
},
};
}
case LOAD_ADDON: {
const { addon: loadedAddon, slug } = action.payload;
const byID = { ...state.byID };
const byGUID = { ...state.byGUID };
const bySlug = { ...state.bySlug };
const byIdInURL = { ...state.byIdInURL };
const loadingByIdInURL = { ...state.loadingByIdInURL };
const addon = createInternalAddon(loadedAddon, state.lang);
// Flow wants hash maps with string keys.
// See: https://zhenyong.github.io/flowtype/docs/objects.html#objects-as-maps
byID[`${addon.id}`] = addon;
byIdInURL[slug] = addon.id;
loadingByIdInURL[slug] = false;
if (addon.slug) {
bySlug[addon.slug.toLowerCase()] = addon.id;
}
if (addon.guid) {
byGUID[addon.guid] = addon.id;
}
return {
...state,
byID,
byGUID,
bySlug,
byIdInURL,
loadingByIdInURL,
};
}
case UNLOAD_ADDON_REVIEWS: {
const { addonId } = action.payload;
const addon = getAddonByID(state, addonId);
if (addon) {
return {
...state,
byID: {
...state.byID,
[`${addonId}`]: undefined,
},
byGUID: {
...state.byGUID,
[addon.guid]: undefined,
},
bySlug: {
...state.bySlug,
[addon.slug.toLowerCase()]: undefined,
},
loadingByIdInURL: {
...state.loadingByIdInURL,
[addon.slug]: undefined,
},
};
}
return state;
}
case UPDATE_RATING_COUNTS: {
const { addonId, oldReview, newReview } = action.payload;
const addon = getAddonByID(state, addonId);
if (!addon) {
return state;
}
const { ratings } = addon;
let average = ratings ? ratings.average : 0;
let ratingCount = ratings ? ratings.count : 0;
let reviewCount = ratings ? ratings.text_count : 0;
const newGroupedRatings = ratings
? { ...ratings.grouped_counts }
: createGroupedRatings();
if (
oldReview &&
oldReview.score &&
newGroupedRatings[oldReview.score] > 0
) {
newGroupedRatings[oldReview.score] -= 1;
}
if (newReview && newReview.score) {
newGroupedRatings[newReview.score] += 1;
}
let countForAverage = ratingCount;
if (average && countForAverage && oldReview && oldReview.score) {
// If average and countForAverage are defined and greater than 0,
// begin by subtracting the old rating to reset the baseline.
const countAfterRemoval = countForAverage - 1;
if (countAfterRemoval === 0) {
// There are no ratings left.
average = 0;
} else {
// Expand all existing rating scores, take away the old score,
// and recalculate the average.
average =
(average * countForAverage - oldReview.score) / countAfterRemoval;
}
countForAverage = countAfterRemoval;
}
// Expand all existing rating scores, add in the new score,
// and recalculate the average.
average =
(average * countForAverage + Number(newReview.score)) /
(countForAverage + 1);
// Adjust rating / review counts.
if (!oldReview) {
// A new rating / review was added.
ratingCount += 1;
if (newReview.body) {
reviewCount += 1;
}
} else if (!oldReview.body && newReview.body) {
// A rating was converted into a review.
reviewCount += 1;
}
return {
...state,
byID: {
...state.byID,
[addonId]: {
...addon,
ratings: {
...ratings,
average,
// It's impossible to recalculate the bayesian_average
// (i.e. median) so we set it to the average as an
// approximation.
bayesian_average: average,
count: ratingCount,
grouped_counts: newGroupedRatings,
text_count: reviewCount,
},
},
},
};
}
case FETCH_ADDON_INFO: {
const { slug } = action.payload;
return {
...state,
infoBySlug: {
...state.infoBySlug,
[slug]: {
info: undefined,
loading: true,
},
},
};
}
case LOAD_ADDON_INFO: {
const { slug, info } = action.payload;
return {
...state,
infoBySlug: {
...state.infoBySlug,
[slug]: {
info: createInternalAddonInfo(info, state.lang),
loading: false,
},
},
};
}
default:
return state;
}
}