From bccd60631527f32a73ce5face5bd13397ce7da6d Mon Sep 17 00:00:00 2001 From: Maciej Rybaniec Date: Mon, 7 Sep 2020 12:16:42 +0200 Subject: [PATCH] =?UTF-8?q?chore:=20=F0=9F=A4=96=20funnel=20filters=20seri?= =?UTF-8?q?alize?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/js/app/modules/queries/actions.ts | 8 ++ lib/js/app/modules/queries/constants.ts | 1 + lib/js/app/modules/queries/index.ts | 2 + lib/js/app/modules/queries/reducer.ts | 6 ++ lib/js/app/modules/queries/saga.ts | 26 ++++-- lib/js/app/modules/queries/types.ts | 7 ++ .../components/Filters/Filters.tsx | 2 +- .../components/FunnelStep/FunnelStep.test.tsx | 1 + .../components/FunnelStep/FunnelStep.tsx | 1 + .../components/FunnelSteps/FunnelSteps.tsx | 6 +- .../app/queryCreator/modules/query/actions.ts | 18 ++++ .../queryCreator/modules/query/constants.ts | 2 + .../app/queryCreator/modules/query/index.ts | 4 + .../app/queryCreator/modules/query/reducer.ts | 18 ++++ .../app/queryCreator/modules/query/types.ts | 14 ++++ lib/js/app/queryCreator/saga.ts | 83 +++++++++++++------ .../queryCreator/serializers/funnelSteps.ts | 36 ++++++++ lib/js/app/queryCreator/serializers/index.ts | 3 +- 18 files changed, 201 insertions(+), 37 deletions(-) create mode 100644 lib/js/app/queryCreator/serializers/funnelSteps.ts diff --git a/lib/js/app/modules/queries/actions.ts b/lib/js/app/modules/queries/actions.ts index ca9a763fb..7053c7dba 100644 --- a/lib/js/app/modules/queries/actions.ts +++ b/lib/js/app/modules/queries/actions.ts @@ -4,6 +4,7 @@ import { APIError } from '../../types'; import { SET_QUERY_SETTINGS, RUN_QUERY, + SET_QUERY_SAVE_STATE, RUN_QUERY_ERROR, RUN_QUERY_SUCCESS, GET_SAVED_QUERIES, @@ -64,6 +65,13 @@ export const saveQuery = ( payload: { name, body }, }); +export const setQuerySaveState = (isSaving: boolean): QueriesActions => ({ + type: SET_QUERY_SAVE_STATE, + payload: { + isSaving, + }, +}); + export const saveQuerySuccess = ( queryName: string, body: Record diff --git a/lib/js/app/modules/queries/constants.ts b/lib/js/app/modules/queries/constants.ts index 2b1a76f21..7f5763c35 100644 --- a/lib/js/app/modules/queries/constants.ts +++ b/lib/js/app/modules/queries/constants.ts @@ -3,6 +3,7 @@ export const RUN_QUERY = '@queries/RUN_QUERY'; export const RUN_QUERY_SUCCESS = '@queries/RUN_QUERY_SUCCESS'; export const RUN_QUERY_ERROR = '@queries/RUN_QUERY_ERROR'; export const SAVE_QUERY = '@queries/SAVE_QUERY'; +export const SET_QUERY_SAVE_STATE = '@queries/SET_QUERY_SAVE_STATE'; export const SAVE_QUERY_SUCCESS = '@queries/SAVE_QUERY_SUCCESS'; export const SAVE_QUERY_ERROR = '@queries/SAVE_QUERY_ERROR'; export const RESET_SAVE_QUERY_ERROR = '@queries/RESET_SAVE_QUERY_ERROR'; diff --git a/lib/js/app/modules/queries/index.ts b/lib/js/app/modules/queries/index.ts index 9596d0744..4b2b37cb4 100644 --- a/lib/js/app/modules/queries/index.ts +++ b/lib/js/app/modules/queries/index.ts @@ -5,6 +5,7 @@ import { resetQueryResults, runQuery, deleteQuery, + setQuerySaveState, saveQuery, resetSavedQueryError, getOrganizationUsageLimits, @@ -48,6 +49,7 @@ export { getQueryLimitReached, getQuerySettings, setQuerySettings, + setQuerySaveState, queriesReducer, runQuery, deleteQuery, diff --git a/lib/js/app/modules/queries/reducer.ts b/lib/js/app/modules/queries/reducer.ts index 4608accb7..ce94ceece 100644 --- a/lib/js/app/modules/queries/reducer.ts +++ b/lib/js/app/modules/queries/reducer.ts @@ -16,6 +16,7 @@ import { RESET_QUERY_RESULTS, SET_QUERY_LIMIT_REACHED, RESET_SAVE_QUERY_ERROR, + SET_QUERY_SAVE_STATE, } from './constants'; export const initialState: ReducerState = { @@ -62,6 +63,11 @@ export const queriesReducer = ( ...state, saveQueryError: null, }; + case SET_QUERY_SAVE_STATE: + return { + ...state, + isSavingQuery: false, + }; case SAVE_QUERY_SUCCESS: return { ...state, diff --git a/lib/js/app/modules/queries/saga.ts b/lib/js/app/modules/queries/saga.ts index 46d6570d1..9b65bffdc 100644 --- a/lib/js/app/modules/queries/saga.ts +++ b/lib/js/app/modules/queries/saga.ts @@ -16,12 +16,14 @@ import { setCacheQueryLimitExceed, setQueryCacheLimitError, setQueryLimitReached, + setQuerySaveState, resetQueryResults, } from './actions'; import { showConfirmation, hideQuerySettingsModal, + getQuerySettingsModalVisibility, getViewMode, setViewMode, selectFirstSavedQuery, @@ -96,6 +98,7 @@ function* saveQuery({ payload }: SaveQueryAction) { const { name, body } = payload; const notificationManager = yield getContext(NOTIFICATION_MANAGER_CONTEXT); const client = yield getContext(KEEN_CLIENT_CONTEXT); + const settingsModalVisible = yield select(getQuerySettingsModalVisibility); const responseBody = yield client.put({ url: client.url('queries', 'saved', name), @@ -103,7 +106,10 @@ function* saveQuery({ payload }: SaveQueryAction) { params: body, }); - yield put(hideQuerySettingsModal()); + if (settingsModalVisible) { + yield put(hideQuerySettingsModal()); + } + yield put(saveQuerySuccess(name, responseBody)); yield notificationManager.showNotification({ type: 'success', @@ -111,12 +117,10 @@ function* saveQuery({ payload }: SaveQueryAction) { }); } catch (error) { const { status, error_code: errorCode } = error; + const notificationManager = yield getContext(NOTIFICATION_MANAGER_CONTEXT); if (status === HttpStatus.INTERNAL_SERVER_ERROR) { yield put(hideQuerySettingsModal()); - const notificationManager = yield getContext( - NOTIFICATION_MANAGER_CONTEXT - ); yield notificationManager.showNotification({ type: 'error', message: text.saveQueryError, @@ -124,7 +128,19 @@ function* saveQuery({ payload }: SaveQueryAction) { autoDismiss: false, }); } else { - yield put(saveQueryError(error)); + const settingsModalVisible = yield select( + getQuerySettingsModalVisibility + ); + if (settingsModalVisible) { + yield put(saveQueryError(error)); + } else { + yield put(setQuerySaveState(false)); + yield notificationManager.showNotification({ + type: 'error', + message: error.body, + autoDismiss: true, + }); + } } if ( diff --git a/lib/js/app/modules/queries/types.ts b/lib/js/app/modules/queries/types.ts index e55215ca9..87b34d92a 100644 --- a/lib/js/app/modules/queries/types.ts +++ b/lib/js/app/modules/queries/types.ts @@ -19,6 +19,7 @@ import { RESET_QUERY_RESULTS, GET_ORGANIZATION_USAGE_LIMITS, SET_QUERY_SETTINGS, + SET_QUERY_SAVE_STATE, } from './constants'; import { APIError } from '../../types'; @@ -95,6 +96,11 @@ export interface SaveQueryAction { }; } +export interface QuerySaveStateAction { + type: typeof SET_QUERY_SAVE_STATE; + payload: { isSaving: boolean }; +} + export interface SaveQuerySuccessAction { type: typeof SAVE_QUERY_SUCCESS; payload: { @@ -188,6 +194,7 @@ export type QueriesActions = | SetCacheQueryLimitExceedAction | SetCacheQueryLimitErrorAction | SaveQueryAction + | QuerySaveStateAction | SaveQuerySuccessAction | SaveQueryErrorAction | DeleteQueryAction diff --git a/lib/js/app/queryCreator/components/Filters/Filters.tsx b/lib/js/app/queryCreator/components/Filters/Filters.tsx index 9dcbf8850..5a95f85f9 100644 --- a/lib/js/app/queryCreator/components/Filters/Filters.tsx +++ b/lib/js/app/queryCreator/components/Filters/Filters.tsx @@ -3,7 +3,7 @@ import { useSelector } from 'react-redux'; import { v4 as uuid } from 'uuid'; import { ActionButton } from '@keen.io/ui-core'; -import { Title } from '../'; +import Title from '../Title'; import FiltersComponent from './FiltersComponent'; diff --git a/lib/js/app/queryCreator/components/FunnelStep/FunnelStep.test.tsx b/lib/js/app/queryCreator/components/FunnelStep/FunnelStep.test.tsx index 12a34c81f..dda4bd5db 100644 --- a/lib/js/app/queryCreator/components/FunnelStep/FunnelStep.test.tsx +++ b/lib/js/app/queryCreator/components/FunnelStep/FunnelStep.test.tsx @@ -6,6 +6,7 @@ import configureStore from 'redux-mock-store'; import FunnelStep from './FunnelStep'; const props = { + id: 'id', index: 0, detailsVisible: false, timeframe: 'this_14_days', diff --git a/lib/js/app/queryCreator/components/FunnelStep/FunnelStep.tsx b/lib/js/app/queryCreator/components/FunnelStep/FunnelStep.tsx index 5e512e583..245ee2c4c 100644 --- a/lib/js/app/queryCreator/components/FunnelStep/FunnelStep.tsx +++ b/lib/js/app/queryCreator/components/FunnelStep/FunnelStep.tsx @@ -161,6 +161,7 @@ const FunnelStep: FC = ({ updateStep({ eventCollection: collection, actorProperty: undefined, + filters: [], }); }} /> diff --git a/lib/js/app/queryCreator/components/FunnelSteps/FunnelSteps.tsx b/lib/js/app/queryCreator/components/FunnelSteps/FunnelSteps.tsx index fe179053d..99b8816ef 100644 --- a/lib/js/app/queryCreator/components/FunnelSteps/FunnelSteps.tsx +++ b/lib/js/app/queryCreator/components/FunnelSteps/FunnelSteps.tsx @@ -29,10 +29,8 @@ const FunnelSteps: FC<{}> = () => { const sortableRef = useRef(null); const stepsRef = useRef(null); - - useEffect(() => { - stepsRef.current = steps; - }, [steps]); + // @TODO: Check is correctly works + stepsRef.current = steps; useEffect(() => { new Sortable(sortableRef.current, { diff --git a/lib/js/app/queryCreator/modules/query/actions.ts b/lib/js/app/queryCreator/modules/query/actions.ts index d89be3fb3..09bfa91ec 100644 --- a/lib/js/app/queryCreator/modules/query/actions.ts +++ b/lib/js/app/queryCreator/modules/query/actions.ts @@ -21,6 +21,8 @@ import { SELECT_FUNNEL_STEP_EVENT_COLLECTION, UPDATE_FUNNEL_STEP, REMOVE_FUNNEL_STEP, + SET_FUNNEL_STEPS, + SET_FUNNEL_STEP_FILTERS, CHANGE_FUNNEL_STEPS_ORDER, ADD_FUNNEL_STEP_FILTER, UPDATE_FUNNEL_STEP_FILTER, @@ -226,6 +228,22 @@ export const changeFunnelStepsOrder = (steps: FunnelStep[]): QueryActions => ({ payload: { steps }, }); +export const setFunnelSteps = (steps: FunnelStep[]): QueryActions => ({ + type: SET_FUNNEL_STEPS, + payload: { steps }, +}); + +export const setFunnelStepFilters = ( + stepId: string, + filters: Filter[] +): QueryActions => ({ + type: SET_FUNNEL_STEP_FILTERS, + payload: { + stepId, + filters, + }, +}); + export const removeFunnelStep = (stepId: string): QueryActions => ({ type: REMOVE_FUNNEL_STEP, payload: { diff --git a/lib/js/app/queryCreator/modules/query/constants.ts b/lib/js/app/queryCreator/modules/query/constants.ts index c985d4f67..02ae782ed 100644 --- a/lib/js/app/queryCreator/modules/query/constants.ts +++ b/lib/js/app/queryCreator/modules/query/constants.ts @@ -27,6 +27,8 @@ export const SELECT_FUNNEL_STEP_EVENT_COLLECTION = export const REMOVE_FUNNEL_STEP = '@query-creator/REMOVE_FUNNEL_STEP'; export const UPDATE_FUNNEL_STEP = '@query-creator/UPDATE_FUNNEL_STEP'; export const CLONE_FUNNEL_STEP = '@query-creator/CLONE_FUNNEL_STEP'; +export const SET_FUNNEL_STEPS = '@query-creator/SET_FUNNEL_STEPS'; +export const SET_FUNNEL_STEP_FILTERS = '@query-creator/SET_FUNNEL_STEP_FILTERS'; export const CHANGE_FUNNEL_STEPS_ORDER = '@query-creator/CHANGE_FUNNEL_STEP_ORDER'; export const ADD_FUNNEL_STEP_FILTER = '@query-creator/ADD_FUNNEL_STEP_FILTER'; diff --git a/lib/js/app/queryCreator/modules/query/index.ts b/lib/js/app/queryCreator/modules/query/index.ts index 38469c8b5..cc5b1e621 100644 --- a/lib/js/app/queryCreator/modules/query/index.ts +++ b/lib/js/app/queryCreator/modules/query/index.ts @@ -41,6 +41,8 @@ import { setPropertyNames, setTimeframe, setFilters, + setFunnelStepFilters, + setFunnelSteps, addFunnelStep, selectFunnelStepCollection, updateFunnelStep, @@ -108,6 +110,8 @@ export { setFilters, selectTimezone, addFunnelStep, + setFunnelSteps, + setFunnelStepFilters, updateFunnelStep, removeFunnelStep, selectFunnelStepCollection, diff --git a/lib/js/app/queryCreator/modules/query/reducer.ts b/lib/js/app/queryCreator/modules/query/reducer.ts index 2c873d8f0..fe6d8fca5 100644 --- a/lib/js/app/queryCreator/modules/query/reducer.ts +++ b/lib/js/app/queryCreator/modules/query/reducer.ts @@ -19,6 +19,8 @@ import { DEFAULT_TIMEFRAME, DEFAULT_TIMEZONE, ADD_FUNNEL_STEP, + SET_FUNNEL_STEPS, + SET_FUNNEL_STEP_FILTERS, CLONE_FUNNEL_STEP, REMOVE_FUNNEL_STEP, UPDATE_FUNNEL_STEP, @@ -61,6 +63,8 @@ export const queryReducer = ( state: ReducerState = initialState, action: QueryActions ) => { + console.log(action, 'sasaas'); + switch (action.type) { case ADD_FILTER: return { @@ -183,6 +187,7 @@ export const queryReducer = ( })), ], }; + case SET_FUNNEL_STEPS: case CHANGE_FUNNEL_STEPS_ORDER: return { ...state, @@ -207,6 +212,19 @@ export const queryReducer = ( return step; }), }; + case SET_FUNNEL_STEP_FILTERS: + return { + ...state, + steps: state.steps.map((step) => { + if (step.id === action.payload.stepId) { + return { + ...step, + filters: action.payload.filters, + }; + } + return step; + }), + }; case UPDATE_FUNNEL_STEP_FILTER: return { ...state, diff --git a/lib/js/app/queryCreator/modules/query/types.ts b/lib/js/app/queryCreator/modules/query/types.ts index 8236750c4..47087612a 100644 --- a/lib/js/app/queryCreator/modules/query/types.ts +++ b/lib/js/app/queryCreator/modules/query/types.ts @@ -17,6 +17,8 @@ import { SET_FILTERS, SELECT_TIMEZONE, ADD_FUNNEL_STEP, + SET_FUNNEL_STEPS, + SET_FUNNEL_STEP_FILTERS, CLONE_FUNNEL_STEP, REMOVE_FUNNEL_STEP, UPDATE_FUNNEL_STEP, @@ -237,6 +239,16 @@ export interface ChangeFunnelStepsOrderAction { payload: { steps: FunnelStep[] }; } +export interface SetFunnelSteps { + type: typeof SET_FUNNEL_STEPS; + payload: { steps: FunnelStep[] }; +} + +export interface SetFunnelStepFilters { + type: typeof SET_FUNNEL_STEP_FILTERS; + payload: { stepId: string; filters: Filter[] }; +} + export interface AddFunnelStepFilterAction { type: typeof ADD_FUNNEL_STEP_FILTER; payload: { @@ -289,6 +301,8 @@ export type QueryActions = | SetTimeframeAction | SetFiltersAction | SelectTimezoneAction + | SetFunnelSteps + | SetFunnelStepFilters | AddFunnelStepAction | UpdateFunnelStepAction | RemoveFunnelStepAction diff --git a/lib/js/app/queryCreator/saga.ts b/lib/js/app/queryCreator/saga.ts index a95bbeec3..4d0d2c3c7 100644 --- a/lib/js/app/queryCreator/saga.ts +++ b/lib/js/app/queryCreator/saga.ts @@ -4,13 +4,14 @@ import { all, select, takeLatest, + takeEvery, getContext, fork, + spawn, take, put, } from 'redux-saga/effects'; import moment from 'moment-timezone'; -import { v4 as uuid } from 'uuid'; import { fetchProjectDetails, @@ -25,7 +26,8 @@ import { getTimeframe, setTimeframe, getFunnelSteps, - changeFunnelStepsOrder, + setFunnelSteps, + setFunnelStepFilters, updateFunnelStep, setGroupBy, setOrderBy, @@ -58,16 +60,18 @@ import { SCHEMA_COMPUTED, } from './modules/events'; -import { serializeOrderBy, serializeFilters } from './serializers'; +import { + serializeOrderBy, + serializeFilters, + serializeFunnelSteps, +} from './serializers'; import { createTree } from './utils/createTree'; import { createCollection } from './utils/createCollection'; -import { Filter, OrderBy } from './types'; +import { Filter, OrderBy, FunnelStep } from './types'; import { SetGroupByAction } from './modules/query/types'; -import { createAbstractOperator } from './utils'; - function* appStart() { yield put(fetchProjectDetails()); } @@ -117,7 +121,7 @@ function* fetchSchema(action: FetchCollectionSchemaAction) { const { properties } = yield fetch(url).then((response) => response.json()); yield put(fetchCollectionSchemaSuccess(collection, properties)); - yield fork(transformSchema, collection, properties); + yield spawn(transformSchema, collection, properties); } catch (err) { yield put(fetchCollectionSchemaError(err)); } finally { @@ -191,6 +195,31 @@ function* transformFilters(collection: string, filters: Filter[]) { yield put(setFilters(filtersSettings)); } +function* transformStepFilters( + collection: string, + filters: Filter[], + stepId: string +) { + let schemas = yield select(getSchemas); + let collectionSchema = schemas[collection]; + + if (!collectionSchema) { + while (true) { + yield take(FETCH_COLLECTION_SCHEMA_SUCCESS); + schemas = yield select(getSchemas); + if (schemas[collection]) { + collectionSchema = schemas[collection]; + break; + } + } + } + + const { schema } = collectionSchema; + + const filtersSettings = serializeFilters(filters, schema); + yield put(setFunnelStepFilters(stepId, filtersSettings)); +} + function* transformOrderBy(orderBy: string | OrderBy | OrderBy[]) { const orderBySettings = serializeOrderBy(orderBy); yield put(setOrderBy(orderBySettings)); @@ -202,30 +231,31 @@ function* serializeQuery(action: SetQueryAction) { } = action; const schemas = yield select(getSchemas); - const { filters, orderBy, ...rest } = query; + const { filters, orderBy, steps, ...rest } = query; yield put(setQuery(rest)); if (query.eventCollection && !schemas[query.eventCollection]) { yield put(fetchCollectionSchema(query.eventCollection)); } - if (query.steps) { - const steps = query.steps.map((step) => ({ - ...step, - id: uuid(), - filters: step.filters.map((filter) => ({ - ...filter, - operator: createAbstractOperator(filter), - })), - })); - - const schemasToFetch = steps.filter( - ({ eventCollection }) => !schemas[eventCollection] - ); - yield put(changeFunnelStepsOrder(steps)); + if (steps) { + const { transformedSteps, stepsFilters } = serializeFunnelSteps(steps); + yield put(setFunnelSteps(transformedSteps)); + + if (stepsFilters && stepsFilters.length) { + yield all( + stepsFilters.map(({ eventCollection, filters, id }) => + spawn(transformStepFilters, eventCollection, filters, id) + ) + ); + } + + const schemasToFetch = steps + .filter(({ eventCollection }) => !schemas[eventCollection]) + .map(({ eventCollection }) => eventCollection); yield all( - schemasToFetch.map(({ eventCollection }) => + schemasToFetch.map((eventCollection) => put(fetchCollectionSchema(eventCollection)) ) ); @@ -260,8 +290,9 @@ function* updateGroupBy(action: SetGroupByAction) { function* updateFunnelStepTimezone(action: UpdateFunnelStepTimezoneAction) { const { timezone } = action.payload; const steps = yield select(getFunnelSteps); - const timeframe = steps.filter((step) => step.id === action.payload.stepId) - .timeframe; + const timeframe = steps.filter( + (step: FunnelStep) => step.id === action.payload.stepId + ).timeframe; if (typeof timeframe !== 'string') { const { start, end } = timeframe; @@ -281,7 +312,7 @@ function* watcher() { yield takeLatest(APP_START, appStart); yield takeLatest(SERIALIZE_QUERY, serializeQuery); yield takeLatest(FETCH_PROJECT_DETAILS, fetchProject); - yield takeLatest(FETCH_COLLECTION_SCHEMA, fetchSchema); + yield takeEvery(FETCH_COLLECTION_SCHEMA, fetchSchema); yield takeLatest(SELECT_TIMEZONE, selectTimezone); yield takeLatest(SELECT_EVENT_COLLECTION, selectCollection); yield takeLatest(SCHEMA_COMPUTED, storeEventSchemas); diff --git a/lib/js/app/queryCreator/serializers/funnelSteps.ts b/lib/js/app/queryCreator/serializers/funnelSteps.ts new file mode 100644 index 000000000..407792ea8 --- /dev/null +++ b/lib/js/app/queryCreator/serializers/funnelSteps.ts @@ -0,0 +1,36 @@ +import { v4 as uuid } from 'uuid'; + +import { Filter, FunnelStep } from '../types'; + +export const serializeFunnelSteps = (steps: FunnelStep[]) => { + const stepsFilters: { + id: string; + filters: Filter[]; + eventCollection: string; + }[] = []; + const transformedSteps: FunnelStep[] = []; + + steps.forEach(({ filters, eventCollection, ...rest }) => { + const id = uuid(); + + transformedSteps.push({ + id, + filters: [], + eventCollection, + ...rest, + }); + + if (filters && filters.length) { + stepsFilters.push({ + id, + filters, + eventCollection, + }); + } + }); + + return { + transformedSteps, + stepsFilters, + }; +}; diff --git a/lib/js/app/queryCreator/serializers/index.ts b/lib/js/app/queryCreator/serializers/index.ts index b8bc19630..9dad290b9 100644 --- a/lib/js/app/queryCreator/serializers/index.ts +++ b/lib/js/app/queryCreator/serializers/index.ts @@ -1,4 +1,5 @@ import { serializeOrderBy } from './orderBy'; import { serializeFilters } from './filters'; +import { serializeFunnelSteps } from './funnelSteps'; -export { serializeFilters, serializeOrderBy }; +export { serializeFilters, serializeFunnelSteps, serializeOrderBy };