123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444 |
- /*
- * <<
- * Davinci
- * ==
- * Copyright (C) 2016 - 2017 EDP
- * ==
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * >>
- */
- import React, { useEffect, useState, useCallback } from 'react'
- import moment, { Moment } from 'moment'
- import Helmet from 'react-helmet'
- import { connect } from 'react-redux'
- import { compose } from 'redux'
- import { RouteComponentWithParams } from 'utils/types'
- import injectReducer from 'utils/injectReducer'
- import injectSaga from 'utils/injectSaga'
- import { createStructuredSelector } from 'reselect'
- import {
- makeSelectLoading,
- makeSelectEditingSchedule,
- makeSelectSuggestMails,
- makeSelectPortalDashboards
- } from './selectors'
- import {
- makeSelectPortals,
- makeSelectDisplays,
- makeSelectDisplaySlides
- } from 'containers/Viz/selectors'
- import { checkNameUniqueAction } from 'containers/App/actions'
- import { ScheduleActions } from './actions'
- import { hideNavigator } from 'containers/App/actions'
- import { VizActions } from 'containers/Viz/actions'
- import reducer from './reducer'
- import saga, { editSchedule } from './sagas'
- import vizReducer from 'containers/Viz/reducer'
- import vizSaga from 'containers/Viz/sagas'
- import dashboardSaga from 'containers/Dashboard/sagas'
- import { Row, Col, Card, Button, Icon, Tooltip, message } from 'antd'
- import { FormComponentProps } from 'antd/lib/form/Form'
- import ScheduleBaseConfig, {
- ScheduleBaseFormProps
- } from './components/ScheduleBaseConfig'
- import ScheduleMailConfig from './components/ScheduleMailConfig'
- import ScheduleVizConfig from './components/ScheduleVizConfig'
- import {
- IPortal,
- IDashboard,
- IDisplayFormed,
- ISlideFormed
- } from 'containers/Viz/types'
- import { IProject } from 'containers/Projects/types'
- import { ISchedule, IScheduleLoading } from './types'
- import {
- IUserInfo,
- IScheduleMailConfig,
- SchedulePeriodUnit,
- ICronExpressionPartition,
- IScheduleVizConfigItem,
- IScheduleWeChatWorkConfig,
- JobType
- } from './components/types'
- import { serialize } from 'components/RichText/Serializer'
- import { RichTextNode } from 'app/components/RichText'
- import Styles from './Schedule.less'
- import StylesHeader from 'components/EditorHeader/EditorHeader.less'
- import ScheduleWeChatWorkConfig from './components/ScheduleWeChatWorkConfig'
- const getCronExpressionByPartition = (partition: ICronExpressionPartition) => {
- const { periodUnit, minute, hour, day, weekDay, month } = partition
- let cronExpression = ''
- switch (periodUnit as SchedulePeriodUnit) {
- case 'Minute':
- cronExpression = `0 */${minute} * * * ?`
- break
- case 'Hour':
- cronExpression = `0 ${minute} * * * ?`
- break
- case 'Day':
- cronExpression = `0 ${minute} ${hour} * * ?`
- break
- case 'Week':
- cronExpression = `0 ${minute} ${hour} ? * ${weekDay}`
- break
- case 'Month':
- cronExpression = `0 ${minute} ${hour} ${day} * ?`
- break
- case 'Year':
- cronExpression = `0 ${minute} ${hour} ${day} ${month} ?`
- break
- }
- return cronExpression
- }
- interface IScheduleEditorStateProps {
- displays: IDisplayFormed[]
- portals: IPortal[]
- portalDashboards: { [key: number]: IDashboard[] }
- displaySlides: { [key: number]: ISlideFormed[] }
- loading: IScheduleLoading
- editingSchedule: ISchedule
- suggestMails: IUserInfo[]
- currentProject: IProject
- }
- interface IScheduleEditorDispatchProps {
- onHideNavigator: () => void
- onLoadDisplays: (projectId: number) => void
- onLoadPortals: (projectId: number) => void
- onLoadDisplaySlides: (displayId: number) => void
- onLoadDashboards: (portalId: number) => void
- onLoadScheduleDetail: (scheduleId: number) => void
- onAddSchedule: (schedule: ISchedule, resolve: () => void) => any
- onEditSchedule: (schedule: ISchedule, resolve: () => void) => any
- onResetState: () => void
- onCheckUniqueName: (
- data: any,
- resolve: () => any,
- reject: (error: string) => any
- ) => any
- onLoadSuggestMails: (keyword: string) => any
- onChangeJobType: (jobType: JobType) => any
- }
- type ScheduleEditorProps = IScheduleEditorStateProps &
- IScheduleEditorDispatchProps &
- RouteComponentWithParams
- const ScheduleEditor: React.FC<ScheduleEditorProps> = (props) => {
- const {
- onHideNavigator,
- onLoadDisplays,
- onLoadPortals,
- onLoadScheduleDetail,
- onResetState,
- match,
- history
- } = props
- const { projectId, scheduleId } = match.params
- useEffect(() => {
- onHideNavigator()
- onLoadDisplays(+projectId)
- onLoadPortals(+projectId)
- if (+scheduleId) {
- onLoadScheduleDetail(+scheduleId)
- }
- return () => {
- onResetState()
- }
- }, [])
- const goBack = useCallback(() => {
- const prefix = window.localStorage.getItem('inDataService') ?? ''
- const prefixPath = prefix ? '/' + prefix : prefix
- history.push(`/project/${projectId}${prefixPath}/schedules`)
- }, [])
- const {
- portals,
- displays,
- loading,
- editingSchedule,
- onLoadDisplaySlides,
- onLoadDashboards
- } = props
- const loadVizDetail = useCallback(
- (
- type: IScheduleVizConfigItem['contentType'],
- schedule: ISchedule,
- vizs: IPortal[] | IDisplayFormed[]
- ) => {
- if (!schedule.id || !vizs.length) {
- return
- }
- const { contentList } = schedule.config
- // initial Viz loading by contentList Portal or Display setting
- contentList.forEach(({ contentType, id: vizId }) => {
- if (contentType !== type) {
- return
- }
- if (~vizs.findIndex(({ id }) => id === vizId)) {
- switch (type) {
- case 'portal':
- onLoadDashboards(vizId)
- break
- case 'display':
- onLoadDisplaySlides(vizId)
- break
- }
- }
- })
- },
- []
- )
- useEffect(() => {
- loadVizDetail('portal', editingSchedule, portals)
- }, [portals, editingSchedule])
- useEffect(() => {
- loadVizDetail('display', editingSchedule, displays)
- }, [displays, editingSchedule])
- const {
- suggestMails,
- portalDashboards,
- displaySlides,
- onAddSchedule,
- onEditSchedule,
- onCheckUniqueName,
- onLoadSuggestMails,
- onChangeJobType
- } = props
- const { jobStatus, config } = editingSchedule
- const { contentList } = config
- const [localContentList, setLocalContentList] = useState(contentList)
- useEffect(() => {
- setLocalContentList([...contentList])
- }, [contentList])
- let baseConfigForm: FormComponentProps<ScheduleBaseFormProps> = null
- let mailConfigForm: FormComponentProps<IScheduleMailConfig> = null
- let weChatWorkConfigForm: FormComponentProps<IScheduleWeChatWorkConfig> = null
- const saveSchedule = () => {
- if (!localContentList.length) {
- message.error('请勾选发送内容')
- return
- }
- baseConfigForm.form.validateFieldsAndScroll((err1, value1) => {
- if (err1) {
- return
- }
- const { setCronExpressionManually, ...scheduleBase } = value1
- const [startDate, endDate] = baseConfigForm.form.getFieldValue(
- 'dateRange'
- ) as ScheduleBaseFormProps['dateRange']
- delete scheduleBase.dateRange
- const schedule: ISchedule = {
- ...scheduleBase,
- cronExpression: setCronExpressionManually
- ? scheduleBase.cronExpression
- : getCronExpressionByPartition(scheduleBase),
- startDate: moment(startDate).format('YYYY-MM-DD HH:mm:ss'),
- endDate: moment(endDate).format('YYYY-MM-DD HH:mm:ss'),
- projectId: +projectId
- }
- if (editingSchedule.jobType === 'email') {
- mailConfigForm.form.validateFieldsAndScroll((err2, value2) => {
- if (err2) {
- return
- }
- schedule.config = {
- ...value2,
- contentList: localContentList,
- setCronExpressionManually
- }
- schedule.config.content = serialize(
- schedule.config.content as RichTextNode[]
- )
- })
- } else {
- weChatWorkConfigForm.form.validateFieldsAndScroll((err3, value3) => {
- if (err3) {
- return
- }
- schedule.config = {
- ...value3,
- contentList: localContentList,
- setCronExpressionManually
- }
- })
- }
- if (editingSchedule.id) {
- schedule.id = editingSchedule.id
- onEditSchedule(schedule, goBack)
- } else {
- onAddSchedule(schedule, goBack)
- }
- })
- }
- return (
- <>
- <Helmet title="Schedule" />
- <div className={Styles.scheduleEditor}>
- <div className={StylesHeader.editorHeader}>
- <Icon type="left" className={StylesHeader.back} onClick={goBack} />
- <div className={StylesHeader.title}>
- <span className={StylesHeader.name}>{`${
- scheduleId ? '修改' : '新增'
- } Schedule`}</span>
- </div>
- <div className={StylesHeader.actions}>
- <Tooltip
- placement="bottom"
- title={jobStatus === 'started' ? '停止后允许修改' : ''}
- >
- <Button
- type="primary"
- disabled={loading.edit || jobStatus === 'started'}
- onClick={saveSchedule}
- >
- 保存
- </Button>
- </Tooltip>
- </div>
- </div>
- <div className={Styles.containerVertical}>
- <Row gutter={8}>
- <Col span={12}>
- <Card title="基本设置" size="small">
- <ScheduleBaseConfig
- wrappedComponentRef={(inst) => {
- baseConfigForm = inst
- }}
- schedule={editingSchedule}
- loading={loading.schedule}
- onCheckUniqueName={onCheckUniqueName}
- onChangeJobType={onChangeJobType}
- />
- </Card>
- {editingSchedule.jobType === 'email' ? (
- <Card title="邮件设置" size="small" style={{ marginTop: 8 }}>
- <ScheduleMailConfig
- wrappedComponentRef={(inst) => {
- mailConfigForm = inst
- }}
- config={config as IScheduleMailConfig}
- loading={loading.schedule}
- mailList={suggestMails}
- onLoadMailList={onLoadSuggestMails}
- />
- </Card>
- ) : (
- <Card
- title="企业微信设置"
- size="small"
- style={{ marginTop: 8 }}
- >
- <ScheduleWeChatWorkConfig
- wrappedComponentRef={(inst) => {
- weChatWorkConfigForm = inst
- }}
- config={config as IScheduleWeChatWorkConfig}
- />
- </Card>
- )}
- </Col>
- <Col span={12}>
- <Card title="发送内容设置" size="small">
- <ScheduleVizConfig
- displays={displays}
- portals={portals}
- portalDashboards={portalDashboards}
- displaySlides={displaySlides}
- value={localContentList}
- onLoadDisplaySlides={onLoadDisplaySlides}
- onLoadPortalDashboards={onLoadDashboards}
- onChange={setLocalContentList}
- />
- </Card>
- </Col>
- </Row>
- </div>
- </div>
- </>
- )
- }
- const mapStateToProps = createStructuredSelector({
- displays: makeSelectDisplays(),
- portals: makeSelectPortals(),
- portalDashboards: makeSelectPortalDashboards(),
- displaySlides: makeSelectDisplaySlides(),
- loading: makeSelectLoading(),
- editingSchedule: makeSelectEditingSchedule(),
- suggestMails: makeSelectSuggestMails()
- })
- const mapDispatchToProps = (dispatch) => ({
- onHideNavigator: () => dispatch(hideNavigator()),
- onLoadDisplays: (projectId) => dispatch(VizActions.loadDisplays(projectId)),
- onLoadPortals: (projectId) => dispatch(VizActions.loadPortals(projectId)),
- onLoadDisplaySlides: (displayId) =>
- dispatch(VizActions.loadDisplaySlides(displayId)),
- // @REFACTOR to use viz reducer portalDashboards
- onLoadDashboards: (portalId) =>
- dispatch(
- VizActions.loadPortalDashboards(
- portalId,
- (dashboards) => {
- dispatch(ScheduleActions.portalDashboardsLoaded(portalId, dashboards))
- },
- false
- )
- ),
- onLoadScheduleDetail: (scheduleId) =>
- dispatch(ScheduleActions.loadScheduleDetail(scheduleId)),
- onAddSchedule: (schedule, resolve) =>
- dispatch(ScheduleActions.addSchedule(schedule, resolve)),
- onEditSchedule: (schedule, resolve) =>
- dispatch(ScheduleActions.editSchedule(schedule, resolve)),
- onResetState: () => dispatch(ScheduleActions.resetScheduleState()),
- onCheckUniqueName: (data, resolve, reject) =>
- dispatch(checkNameUniqueAction('cronjob', data, resolve, reject)),
- onLoadSuggestMails: (keyword) =>
- dispatch(ScheduleActions.loadSuggestMails(keyword)),
- onChangeJobType: (jobType) =>
- dispatch(ScheduleActions.changeScheduleJobType(jobType))
- })
- const withConnect = connect(mapStateToProps, mapDispatchToProps)
- const withReducer = injectReducer({ key: 'schedule', reducer })
- const withSaga = injectSaga({ key: 'schedule', saga })
- const withVizReducer = injectReducer({
- key: 'viz',
- reducer: vizReducer
- })
- const withVizSaga = injectSaga({ key: 'viz', saga: vizSaga })
- const withDashboardSaga = injectSaga({ key: 'dashboard', saga: dashboardSaga })
- export default compose(
- withReducer,
- withSaga,
- withVizReducer,
- withVizSaga,
- withDashboardSaga,
- withConnect
- )(ScheduleEditor)
|