Skip to content
Snippets Groups Projects
Commit bd2ce6b1 authored by Tom Robinson's avatar Tom Robinson
Browse files

Fix flow

parent 8ea5f8bd
No related branches found
No related tags found
No related merge requests found
......@@ -26,6 +26,10 @@ declare module icepick {
path: Array<K>,
value: V,
): O;
declare function dissocIn<O: Object | Array<any>, K: Key>(
object: O,
path: Array<K>,
): O;
declare function updateIn<O: Object | Array<any>, K: Key, V: Value>(
object: O,
path: Array<K>,
......
......@@ -15,16 +15,20 @@ const Collections = createEntity({
schema: CollectionSchema,
objectActions: {
@undo("collection", (o, archived) => (archived ? "archived" : "unarchived"))
setArchived: ({ id }, archived) =>
Collections.actions.update({ id, archived }),
setArchived: ({ id }, archived, opts) =>
Collections.actions.update(
{ id, archived },
undo(opts, "collection", archived ? "archived" : "unarchived"),
),
@undo("collection", "moved")
setCollection: ({ id }, collection) =>
Collections.actions.update({
id,
parent_id: collection && collection.id,
}),
setCollection: ({ id }, collection, opts) =>
Collections.actions.update(
{
id,
parent_id: collection && collection.id,
},
undo(opts, "collection", "moved"),
),
},
objectSelectors: {
......
......@@ -74,6 +74,7 @@ export default class EntityListLoader extends React.Component {
// transitioned from loaded to not loaded, and isn't yet loading again
// this typically means the list request state was cleared by a
// create/update/delete action
// $FlowFixMe: provided by @connect
nextProps.fetchList(nextProps.entityQuery);
}
}
......
......@@ -20,11 +20,13 @@ const Dashboards = createEntity({
},
objectActions: {
@undo("dashboard", (o, archived) => (archived ? "archived" : "unarchived"))
setArchived: ({ id }, archived, opts) =>
Dashboards.actions.update({ id }, { archived }, opts),
Dashboards.actions.update(
{ id },
{ archived },
undo(opts, "dashboard", archived ? "archived" : "unarchived"),
),
@undo("dashboard", "moved")
setCollection: ({ id }, collection, opts) =>
Dashboards.actions.update(
{ id },
......@@ -33,7 +35,7 @@ const Dashboards = createEntity({
collection_id:
!collection || collection.id === "root" ? null : collection.id,
},
opts,
undo(opts, "dashboard", "moved"),
),
setPinned: ({ id }, pinned, opts) =>
......
......@@ -12,17 +12,18 @@ const Pulses = createEntity({
objectActions: {
// FIXME: not implemented in backend
// @undo("pulse", (o, archived) => archived ? "archived" : "unarchived")
// setArchived: ({ id }, archived) => Pulses.actions.update({ id, archived }),
@undo("pulse", "moved")
setCollection: ({ id }, collection) =>
Pulses.actions.update({
id,
// TODO - would be dope to make this check in one spot instead of on every movable item type
collection_id:
collection && collection.id === "root" ? null : collection.id,
}),
setCollection: ({ id }, collection, opts) =>
Pulses.actions.update(
{
id,
// TODO - would be dope to make this check in one spot instead of on every movable item type
collection_id:
collection && collection.id === "root" ? null : collection.id,
},
undo(opts, "pulse", "moved"),
),
},
objectSelectors: {
......
......@@ -23,11 +23,13 @@ const Questions = createEntity({
},
objectActions: {
@undo("question", (o, archived) => (archived ? "archived" : "unarchived"))
setArchived: ({ id }, archived, opts) =>
Questions.actions.update({ id }, { archived }, opts),
Questions.actions.update(
{ id },
{ archived },
undo(opts, "question", archived ? "archived" : "unarchived"),
),
@undo("question", "moved")
setCollection: ({ id }, collection, opts) =>
Questions.actions.update(
{ id },
......@@ -36,7 +38,7 @@ const Questions = createEntity({
collection_id:
!collection || collection.id === "root" ? null : collection.id,
},
opts,
undo(opts, "question", "moved"),
),
setPinned: ({ id }, pinned, opts) =>
......
......@@ -29,10 +29,14 @@ import type { APIMethod } from "metabase/lib/api";
type EntityName = string;
type ActionType = string;
type ActionCreator = Function;
type ObjectActionCreator = Function;
type ObjectSelector = Function;
type Action = any;
export type Reducer = (state: any, action: Action) => any;
type EntityDefinition = {
name: EntityName,
schema?: schema.Entity,
......@@ -41,6 +45,9 @@ type EntityDefinition = {
actions?: {
[name: string]: ActionCreator,
},
selectors?: {
[name: string]: Function,
},
objectActions?: {
[name: string]: ObjectActionCreator,
},
......@@ -50,10 +57,26 @@ type EntityDefinition = {
reducer?: Reducer,
wrapEntity?: (object: EntityObject) => any,
form?: any,
actionShouldInvalidateLists?: (action: Action) => boolean,
};
type EntityObject = any;
type EntityQuery = {
[name: string]: string | number | boolean | null,
};
type FetchOptions = {
reload?: boolean,
};
type UpdateOptions = {
notify?:
| { verb?: string, subject?: string, undo?: boolean, message?: any }
| false,
};
type Result = any; // FIXME
export type Entity = {
name: EntityName,
path?: string,
......@@ -63,9 +86,24 @@ export type Entity = {
get: APIMethod,
update: APIMethod,
delete: APIMethod,
[method: string]: APIMethod,
},
schema: schema.Entity,
actions: { [name: string]: ActionCreator },
actionTypes: {
[name: string]: ActionType,
CREATE: ActionType,
FETCH: ActionType,
UPDATE: ActionType,
DELETE: ActionType,
FETCH_LIST: ActionType,
},
actions: {
[name: string]: ActionCreator,
fetchList: (
entityQuery?: EntityQuery,
options?: FetchOptions,
) => Promise<Result>,
},
reducers: { [name: string]: Reducer },
selectors: {
getList: Function,
......@@ -74,18 +112,31 @@ export type Entity = {
getLoaded: Function,
getFetched: Function,
getError: Function,
[name: string]: Function,
},
objectActions: {
[name: string]: ObjectActionCreator,
create: (entityObject: EntityObject) => Promise<Result>,
fetch: (
entityObject: EntityObject,
options?: FetchOptions,
) => Promise<Result>,
update: (
entityObject: EntityObject,
updatedObject: EntityObject,
options?: UpdateOptions,
) => Promise<Result>,
delete: (entityObject: EntityObject) => Promise<Result>,
},
objectSelectors: {
[name: string]: ObjectSelector,
},
wrapEntity: (object: EntityObject) => any,
form?: any,
};
type Reducer = (state: any, action: any) => any;
requestsReducer: Reducer,
actionShouldInvalidateLists: (action: Action) => boolean,
};
export function createEntity(def: EntityDefinition): Entity {
// $FlowFixMe
......@@ -203,13 +254,18 @@ export function createEntity(def: EntityDefinition): Entity {
dispatch(setRequestState({ statePath, state: "LOADED" }));
if (notify) {
if (notify.undo) {
// pick only the attributes that were updated
// $FlowFixMe
const undoObject = _.pick(
originalObject,
...Object.keys(updatedObject || {}),
);
dispatch(
addUndo({
actions: [
entity.objectActions.update(
entityObject,
// pick only the attributes that were updated
_.pick(originalObject, ..._.keys(updatedObject)),
undoObject,
// don't show an undo for the undo
{ notify: false },
),
......@@ -417,7 +473,7 @@ export function createEntity(def: EntityDefinition): Entity {
action.type === UPDATE_ACTION;
}
entity.requestReducer = (state, action) => {
entity.requestsReducer = (state, action) => {
// reset all list request states when creating, deleting, or updating
// to force a reload
if (entity.actionShouldInvalidateLists(action)) {
......@@ -479,6 +535,7 @@ type CombinedEntities = {
entities: { [key: EntityName]: Entity },
reducers: { [name: string]: Reducer },
reducer: Reducer,
requestsReducer: Reducer,
};
export function combineEntities(entities: Entity[]): CombinedEntities {
......@@ -494,10 +551,10 @@ export function combineEntities(entities: Entity[]): CombinedEntities {
}
}
const entitiesRequestsReducer = (state, action) => {
const requestsReducer = (state, action) => {
for (const entity of entities) {
if (entity.requestReducer) {
state = entity.requestReducer(state, action);
if (entity.requestsReducer) {
state = entity.requestsReducer(state, action);
}
}
return state;
......@@ -507,37 +564,45 @@ export function combineEntities(entities: Entity[]): CombinedEntities {
entities: entitiesMap,
reducers: reducersMap,
reducer: combineReducers(reducersMap),
entitiesRequestsReducer,
requestsReducer,
};
}
// OBJECT ACTION DECORATORS
// merges in options to give an object action a notification
export function notify(subject, verb, undo = false) {
return function(target, name, descriptor) {
// https://github.com/loganfsmyth/babel-plugin-transform-decorators-legacy/issues/34
const original = descriptor.initializer
? descriptor.initializer()
: descriptor.value;
delete descriptor.initializer;
descriptor.value = function(o, arg, opts = {}) {
opts = merge(
{
notify: {
subject: typeof subject === "function" ? subject(o, arg) : subject,
verb: typeof verb === "function" ? verb(o, arg) : verb,
undo,
},
},
opts,
);
return original(o, arg, opts);
};
};
}
export const notify = (opts: any = {}, subject: string, verb: string) =>
merge({ notify: { subject, verb, undo: false } }, opts || {});
// merges in options to give make object action undo-able
export function undo(subject, verb) {
return notify(subject, verb, true);
}
export const undo = (opts: any = {}, subject: string, verb: string) =>
merge({ notify: { subject, verb, undo: true } }, opts || {});
// decorator versions disabled due to incompatibility with current version of flow
//
// // merges in options to give an object action a notification
// export function notify(subject: string, verb: string, undo: boolean = false) {
// return function(target: Object, name: string, descriptor: any) {
// // https://github.com/loganfsmyth/babel-plugin-transform-decorators-legacy/issues/34
// const original = descriptor.initializer
// ? descriptor.initializer()
// : descriptor.value;
// delete descriptor.initializer;
// descriptor.value = function(o, arg, opts = {}) {
// opts = merge(
// {
// notify: {
// subject: typeof subject === "function" ? subject(o, arg) : subject,
// verb: typeof verb === "function" ? verb(o, arg) : verb,
// undo,
// },
// },
// opts,
// );
// return original(o, arg, opts);
// };
// };
// }
//
// // merges in options to give make object action undo-able
// export function undo(subject: string, verb: string) {
// return notify(subject, verb, true);
// }
/* @flow */
import { combineEntities } from "metabase/lib/entities";
import type { Entity } from "metabase/lib/entities";
import type { Entity, Reducer } from "metabase/lib/entities";
import * as entitiesMap from "metabase/entities";
// $FlowFixMe
const entitiesArray: Entity[] = Object.values(entitiesMap);
export const { entities, reducer, entitiesRequestsReducer } = combineEntities(
export const { entities, reducer, requestsReducer } = combineEntities(
entitiesArray,
);
export default reducer;
export const enhanceRequestsReducer = originalRequestsReducer => {
export const enhanceRequestsReducer = (
originalRequestsReducer: Reducer,
): Reducer => {
return (state, action) =>
originalRequestsReducer(entitiesRequestsReducer(state, action), action);
originalRequestsReducer(requestsReducer(state, action), action);
};
(window.Metabase = window.Metabase || {}).entities = entities;
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment