All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ducks.pages.sagas.ts Maven / Gradle / Ivy

The newest version!
import {
    call,
    put,
    select,
    takeEvery,
    take,
    debounce,
    race,
    fork,
} from 'redux-saga/effects'
import isEmpty from 'lodash/isEmpty'
import { getLocation } from 'connected-react-router'
import queryString from 'query-string'
import { get, isEqual } from 'lodash'

import { destroyOverlay } from '../overlays/store'
import { FETCH_PAGE_METADATA } from '../../core/api'
import { dataProviderResolver } from '../../core/dataProviderResolver'
import { changeRootPage } from '../global/store'
import fetchSaga from '../../sagas/fetch'
import {
    clearModel,
    removeAllModel,
    removeModel,
    setModel,
    updateModel,
} from '../models/store'
import { modelsSelector } from '../models/selectors'
import { DefaultModels } from '../models/Models'
import { State } from '../State'
import { mergeMeta } from '../api/utils/mergeMeta'
import { DEFAULT_CONTEXT } from '../../utils/evalExpression'
import { userSelector } from '../user/selectors'
import { resolveMetadata } from '../../core/auth/resolveMetadata'
import { AuthProvider } from '../../core/auth/Provider'

import { pagesSelector } from './selectors'
import {
    metadataFail,
    metadataSuccess,
    setStatus,
    metadataRequest,
    resetPage,
} from './store'
import { flowDefaultModels } from './sagas/defaultModels'
import { MetadataRequest, Reset } from './Actions'
import { Metadata, Page } from './Pages'

function* setDefaultModels(models: DefaultModels, pageId: string) {
    yield race([
        call(flowDefaultModels, models),
        // @ts-ignore проблемы с типизацией saga
        take(({ type, payload }: Reset) => (type === resetPage.type && payload === pageId)),
    ])
}

/**
 * сага, фетчит метадату
 * @param apiProvider
 * @param action
 */
export function* getMetadata(
    apiProvider: unknown,
    authProvider: AuthProvider,
    action: MetadataRequest,
) {
    const { pageId, rootPage, pageUrl, mapping } = action.payload
    let url: string = pageUrl

    try {
        const { search } = yield select(getLocation)

        let resolveProvider: { url: string, headersParams: object } = { url: '', headersParams: {} }

        if (!isEmpty(mapping)) {
            const state: State = yield select()
            const extraQueryParams = rootPage && queryString.parse(search)

            // @ts-ignore import from js file
            resolveProvider = dataProviderResolver(state, { url, ...mapping }, extraQueryParams)

            url = resolveProvider.url
        } else if (rootPage) {
            url += search
        }

        const rawMetadata: Metadata = yield call(
            // @ts-ignore import from js file
            fetchSaga,
            FETCH_PAGE_METADATA,
            { pageUrl: url, headers: resolveProvider.headersParams },
            apiProvider,
        )
        const user: object = yield select(userSelector)
        const metadata = (yield resolveMetadata(rawMetadata, user, [
            'action', 'models', 'routes', 'datasources',
        ], authProvider)) as Metadata

        if (rootPage) {
            yield put(changeRootPage(metadata.id))
            yield put(destroyOverlay())
        }

        if (metadata.id) {
            yield put(setStatus(metadata.id, 200))
            yield put(metadataSuccess(metadata.id, metadata, pageUrl))
        }

        // @ts-ignore проблемы с типизацией saga
        yield fork(setDefaultModels, metadata.models, metadata.id)
    } catch (error: unknown) {
        const err = error as {
            message: string,
            stack: string,
            json?: { meta: Record },
            status: number | null
        }

        if (err?.status) {
            yield put(setStatus(pageId, err.status))
        }

        if (rootPage) {
            yield put(changeRootPage(pageId))
        }

        yield put(
            metadataFail(
                pageId,
                {
                    title: err.status ? err.status : 'Ошибка',
                    status: err.status,
                    text: err.message,
                    closeButton: false,
                    severity: 'danger',
                },
                err?.json?.meta || {},
            ),
        )
    }
}

/**
 * Сага наблюдения за изменением моделей для отстреливания событий
 * Повторяет логику наблюдения зависимостей из датасурсов
 * FIXME вынести на общий механизм
 * @param action
 */
let prevModels: DefaultModels

export function* watchEvents() {
    const models: DefaultModels = yield select(modelsSelector)
    const pagesMap: Page[] = yield select(pagesSelector)
    const pagesList: Page[] = Object.values(pagesMap)

    for (const { metadata } of pagesList) {
        const { events } = metadata

        if (!events || !events.length) {
            // eslint-disable-next-line no-continue
            continue
        }

        for (const { datasource, model: prefix, field, action } of events) {
            const modelLink = [prefix, datasource]
            const model = get(models, modelLink, null)
            const prevModel = get(prevModels, modelLink, null)

            let value = model
            let prevValue = prevModel

            if (field) {
                value = get(value, field)
                prevValue = get(prevValue, field)
            }

            if (!isEqual(value, prevValue)) {
                // FIXME костыльный проброс контекста
                yield put(mergeMeta(action, { evalContext: DEFAULT_CONTEXT }))
            }
        }
    }

    prevModels = models
}

/**
 * Сайд-эффекты для page редюсера
 * @ignore
 */
export default (apiProvider: unknown, security: { provider: AuthProvider }) => [
    takeEvery(metadataRequest, getMetadata, apiProvider, security.provider),
    debounce(100, [
        setModel,
        removeModel,
        removeAllModel,
        clearModel,
        updateModel,
    ], watchEvents),
]




© 2015 - 2024 Weber Informatics LLC | Privacy Policy