123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552 |
- /*
- * <<
- * 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 from 'react'
- import { compose, Dispatch } from 'redux'
- import { connect } from 'react-redux'
- import { createStructuredSelector } from 'reselect'
- import memoizeOne from 'memoize-one'
- import Helmet from 'react-helmet'
- import injectReducer from 'utils/injectReducer'
- import injectSaga from 'utils/injectSaga'
- import reducer from './reducer'
- import sagas from './sagas'
- import reducerSource from 'containers/Source/reducer'
- import sagasSource from 'containers/Source/sagas'
- import reducerProject from 'containers/Projects/reducer'
- import sagasProject from 'containers/Projects/sagas'
- import { RouteComponentWithParams } from 'utils/types'
- import { hideNavigator } from '../App/actions'
- import { ViewActions, ViewActionType } from './actions'
- import { SourceActions, SourceActionType } from 'containers/Source/actions'
- import { OrganizationActions, OrganizationActionType } from 'containers/Organizations/actions'
- import {
- makeSelectEditingView,
- makeSelectEditingViewInfo,
- makeSelectSources,
- makeSelectSchema,
- makeSelectSqlDataSource,
- makeSelectSqlLimit,
- makeSelectSqlValidation,
- makeSelectLoading,
- makeSelectChannels,
- makeSelectTenants,
- makeSelectBizs,
- makeSelectIsLastExecuteWholeSql
- } from './selectors'
- import { makeSelectProjectRoles } from 'containers/Projects/selectors'
- import {
- IView, IViewModel, IViewRoleRaw, IViewRole, IViewVariable, IViewInfo,
- IExecuteSqlParams, IExecuteSqlResponse, IViewLoading, ISqlValidation,
- IDacChannel, IDacTenant, IDacBiz
- } from './types'
- import { ISource, ISchema } from '../Source/types'
- import { ViewVariableTypes } from './constants'
- import { message, notification, Tooltip } from 'antd'
- import EditorSteps from './components/EditorSteps'
- import EditorContainer from './components/EditorContainer'
- import ModelAuth from './components/ModelAuth'
- import SourceTable from './components/SourceTable'
- import SqlEditor from './components/SqlEditorByAce'
- import SqlPreview from './components/SqlPreview'
- import EditorBottom from './components/EditorBottom'
- import ViewVariableList from './components/ViewVariableList'
- import VariableModal from './components/VariableModal'
- import Styles from './View.less'
- interface IViewEditorStateProps {
- editingView: IView
- editingViewInfo: IViewInfo
- sources: ISource[]
- schema: ISchema
- sqlDataSource: IExecuteSqlResponse
- sqlLimit: number
- sqlValidation: ISqlValidation
- loading: IViewLoading
- projectRoles: any[]
- channels: IDacChannel[]
- tenants: IDacTenant[]
- bizs: IDacBiz[],
- isLastExecuteWholeSql: boolean
- }
- export enum EExecuteType {
- whole,
- single
- }
- interface IViewEditorDispatchProps {
- onHideNavigator: () => void
- onLoadViewDetail: (viewId: number) => void
- onLoadSources: (projectId: number) => void
- onLoadSourceDatabases: (sourceId: number) => void
- onLoadDatabaseTables: (sourceId: number, databaseName: string) => void
- onLoadTableColumns: (sourceId: number, databaseName: string, tableName: string) => void
- onExecuteSql: (params: IExecuteSqlParams, exeType: EExecuteType) => void
- onAddView: (view: IView, resolve: () => void) => void
- onEditView: (view: IView, resolve: () => void) => void
- onUpdateEditingView: (view: IView) => void
- onUpdateEditingViewInfo: (viewInfo: IViewInfo) => void
- onSetSqlLimit: (limit: number) => void
- onLoadDacChannels: () => void,
- onLoadDacTenants: (channelName: string) => void,
- onLoadDacBizs: (channelName: string, tenantId: number) => void,
- onResetState: () => void
- onLoadProjectRoles: (projectId: number) => void
- onSetIsLastExecuteWholeSql: (isLastExecuteWholeSql: boolean) => void
- }
- type IViewEditorProps = IViewEditorStateProps & IViewEditorDispatchProps & RouteComponentWithParams
- interface IViewEditorStates {
- containerHeight: number
- sqlValidationCode: number
- init: boolean
- currentStep: number
- lastSuccessExecutedSql: string
- sqlFragment: string
- }
- export class ViewEditor extends React.Component<IViewEditorProps, IViewEditorStates> {
- public state: Readonly<IViewEditorStates> = {
- containerHeight: 0,
- currentStep: 0,
- sqlValidationCode: null,
- init: true,
- lastSuccessExecutedSql: null,
- sqlFragment: ''
- }
- public constructor(props: IViewEditorProps) {
- super(props)
- const {
- onHideNavigator,
- onLoadSources,
- onLoadViewDetail,
- onLoadProjectRoles,
- onLoadDacChannels,
- match
- } = this.props
- onHideNavigator()
- const { viewId, projectId } = match.params
- if (projectId) {
- onLoadSources(+projectId)
- onLoadProjectRoles(+projectId)
- }
- if (viewId) {
- onLoadViewDetail(+viewId)
- }
- onLoadDacChannels()
- }
- public static getDerivedStateFromProps:
- React.GetDerivedStateFromProps<IViewEditorProps, IViewEditorStates>
- = (props, state) => {
- const { match, editingView, sqlValidation } = props
- const { viewId } = match.params
- const { init, sqlValidationCode } = state
- let { lastSuccessExecutedSql } = state
- if (sqlValidationCode !== sqlValidation.code && sqlValidation.code) {
- notification.destroy()
- sqlValidation.code === 200
- ? notification.success({
- message: '执行成功',
- duration: 3
- })
- : notification.error({
- message: '执行失败',
- description: (
- <Tooltip
- placement='bottom'
- trigger='click'
- title={sqlValidation.message}
- overlayClassName={Styles.errorMessage}
- >
- <a>点击查看错误信息</a>
- </Tooltip>
- ),
- duration: null
- })
- if (sqlValidation.code === 200) {
- lastSuccessExecutedSql = editingView.sql
- }
- }
- if (editingView && editingView.id === +viewId) {
- if (init) {
- props.onLoadSourceDatabases(editingView.sourceId)
- lastSuccessExecutedSql = editingView.sql
- return {
- init: false,
- sqlValidationCode: sqlValidation.code,
- lastSuccessExecutedSql
- }
- }
- }
- return { sqlValidationCode: sqlValidation.code, lastSuccessExecutedSql }
- }
- public componentWillUnmount() {
- this.props.onResetState()
- notification.destroy()
- }
- private executeSql = () => {
- const { sqlFragment } = this.state
- const { onSetIsLastExecuteWholeSql } = this.props
- if (sqlFragment != null) {
- onSetIsLastExecuteWholeSql(false)
- }
- ViewEditor.ExecuteSql(this.props, this.state.sqlFragment)
- }
- private static ExecuteSql = (props: IViewEditorProps, sqlFragment?: string) => {
- const { onExecuteSql, editingView, editingViewInfo, sqlLimit } = props
- const { sourceId, sql } = editingView
- const { variable } = editingViewInfo
- const updatedParams: IExecuteSqlParams = {
- sourceId,
- sql: sqlFragment ?? sql,
- limit: sqlLimit,
- variables: variable
- }
- const exeType = sqlFragment == null ? EExecuteType.whole : EExecuteType.single
- onExecuteSql(updatedParams, exeType)
- }
- private stepChange = (step: number) => {
- const { currentStep } = this.state
- if (currentStep + step < 0) {
- this.goToViewList()
- return
- }
- const { editingView } = this.props
- const { name, sourceId, sql } = editingView
- const errorMessages = ['名称不能为空', '请选择数据源', 'sql 不能为空']
- const fieldsValue = [name, sourceId, sql]
- const hasError = fieldsValue.some((val, idx) => {
- if (!val) {
- message.error(errorMessages[idx])
- return true
- }
- })
- if (hasError) {
- return
- }
- this.setState({ currentStep: currentStep + step }, () => {
- if (this.state.currentStep > 1) {
- this.saveView()
- }
- })
- }
- private saveView = () => {
- const { onAddView, onEditView, editingView, editingViewInfo, projectRoles, match } = this.props
- const { projectId } = match.params
- const { model, variable, roles } = editingViewInfo
- const { id: viewId } = editingView
- const validRoles = roles.filter(({ roleId }) => projectRoles && projectRoles.findIndex(({ id }) => id === roleId) >= 0)
- const updatedView: IView = {
- ...editingView,
- projectId: +projectId,
- model: JSON.stringify(model),
- variable: JSON.stringify(variable),
- roles: validRoles.map<IViewRoleRaw>(({ roleId, columnAuth, rowAuth }) => {
- const validColumnAuth = columnAuth.filter((c) => !!model[c])
- const validRowAuth = rowAuth.filter((r) => {
- const v = variable.find((v) => v.name === r.name)
- if (!v) {
- return false
- }
- return (v.type === ViewVariableTypes.Authorization && !v.fromService)
- })
- return {
- roleId,
- columnAuth: JSON.stringify(validColumnAuth),
- rowAuth: JSON.stringify(validRowAuth)
- }
- })
- }
- viewId ? onEditView(updatedView, this.goToViewList) : onAddView(updatedView, this.goToViewList)
- }
- private goToViewList = () => {
- const { history, match } = this.props
- const { projectId } = match.params
- const prefix = window.localStorage.getItem('inDataService') ?? ''
- const prefixPath = prefix ? '/' + prefix : prefix
- history.push(`/project/${projectId}${prefixPath}/views`)
- }
- private viewChange = (propName: keyof IView, value: string | number) => {
- const { editingView, onUpdateEditingView } = this.props
- const updatedView = {
- ...editingView,
- [propName]: value
- }
- onUpdateEditingView(updatedView)
- }
- private sqlChange = (sql: string) => {
- this.viewChange('sql', sql)
- }
- private sqlSelect = (sqlFragment: string) => {
- this.setState({ sqlFragment })
- }
- private modelChange = (partialModel: IViewModel) => {
- const { editingViewInfo, onUpdateEditingViewInfo } = this.props
- const { model } = editingViewInfo
- const updatedViewInfo: IViewInfo = {
- ...editingViewInfo,
- model: { ...model, ...partialModel }
- }
- onUpdateEditingViewInfo(updatedViewInfo)
- }
- private variableChange = (updatedVariable: IViewVariable[]) => {
- const { editingViewInfo, onUpdateEditingViewInfo } = this.props
- const updatedViewInfo: IViewInfo = {
- ...editingViewInfo,
- variable: updatedVariable
- }
- onUpdateEditingViewInfo(updatedViewInfo)
- }
- /**
- * 数组长度1为单选,大于1为全选
- * @param {IViewRole[]} viewRoles
- * @private
- * @memberof ViewEditor
- */
- private viewRoleChange = (viewRoles: IViewRole[]) => {
- const { editingViewInfo, onUpdateEditingViewInfo } = this.props
- let updatedRoles: IViewRole[] = []
- if (viewRoles.length === 1) {
- const [viewRole] = viewRoles
- const { roles } = editingViewInfo
- updatedRoles = roles.filter((role) => role.roleId !== viewRole.roleId)
- updatedRoles.push(viewRole)
- } else {
- updatedRoles = viewRoles
- }
- const updatedViewInfo = {
- ...editingViewInfo,
- roles: updatedRoles
- }
- onUpdateEditingViewInfo(updatedViewInfo)
- }
- private getSqlHints = memoizeOne((sourceId: number, schema: ISchema, variables: IViewVariable[]) => {
- if (!sourceId) {
- return {}
- }
- const variableHints = variables.reduce((acc, v) => {
- acc[`$${v.name}$`] = []
- return acc
- }, {})
- const { mapDatabases, mapTables, mapColumns } = schema
- if (!mapDatabases[sourceId]) {
- return {}
- }
- const tableHints: { [tableName: string]: string[] } = Object.values(mapTables).reduce((acc, tablesInfo) => {
- if (tablesInfo.sourceId !== sourceId) {
- return acc
- }
- tablesInfo.tables.forEach(({ name: tableName }) => {
- acc[tableName] = []
- })
- return acc
- }, {})
- Object.values(mapColumns).forEach((columnsInfo) => {
- if (columnsInfo.sourceId !== sourceId) {
- return
- }
- const { tableName, columns } = columnsInfo
- if (tableHints[tableName]) {
- tableHints[tableName] = tableHints[tableName].concat(columns.map((col) => col.name))
- }
- })
- const hints = {
- ...variableHints,
- ...tableHints
- }
- return hints
- })
- public render() {
- const {
- sources, schema,
- sqlDataSource, sqlLimit, loading, projectRoles,
- channels, tenants, bizs,
- editingView, editingViewInfo,
- isLastExecuteWholeSql,
- onLoadSourceDatabases, onLoadDatabaseTables, onLoadTableColumns, onSetSqlLimit,
- onLoadDacTenants, onLoadDacBizs
- } = this.props
- const { currentStep, lastSuccessExecutedSql, sqlFragment } = this.state
- const { model, variable, roles: viewRoles } = editingViewInfo
- const sqlHints = this.getSqlHints(editingView.sourceId, schema, variable)
- const containerVisible = !currentStep
- const modelAuthVisible = !!currentStep
- const nextDisabled = (editingView.sql !== lastSuccessExecutedSql)
- return (
- <>
- <Helmet title='数据资产' />
- <div className={Styles.viewEditor}>
- <div className={Styles.header}>
- <div className={Styles.steps}>
- <EditorSteps current={currentStep} />
- </div>
- </div>
- <EditorContainer
- visible={containerVisible}
- variable={variable}
- onVariableChange={this.variableChange}
- >
- <SourceTable
- key='SourceTable'
- view={editingView}
- sources={sources}
- schema={schema}
- onViewChange={this.viewChange}
- onSourceSelect={onLoadSourceDatabases}
- onDatabaseSelect={onLoadDatabaseTables}
- onTableSelect={onLoadTableColumns}
- />
- <SqlEditor key='SqlEditor' value={editingView.sql} hints={sqlHints} onSqlChange={this.sqlChange}
- onSelect={this.sqlSelect} onCmdEnter={this.executeSql} />
- <SqlPreview key='SqlPreview' size='small' loading={loading.execute} response={sqlDataSource} />
- <EditorBottom
- key='EditorBottom'
- sqlLimit={sqlLimit}
- loading={loading.execute}
- nextDisabled={nextDisabled}
- sqlFragment={sqlFragment}
- isLastExecuteWholeSql={isLastExecuteWholeSql}
- onSetSqlLimit={onSetSqlLimit}
- onExecuteSql={this.executeSql}
- onStepChange={this.stepChange}
- />
- <ViewVariableList key='ViewVariableList' variables={variable} />
- <VariableModal
- key='VariableModal'
- channels={channels}
- tenants={tenants}
- bizs={bizs}
- onLoadDacTenants={onLoadDacTenants}
- onLoadDacBizs={onLoadDacBizs}
- />
- </EditorContainer>
- <ModelAuth
- visible={modelAuthVisible}
- model={model}
- variable={variable}
- sqlColumns={sqlDataSource.columns}
- roles={projectRoles}
- viewRoles={viewRoles}
- onModelChange={this.modelChange}
- onViewRoleChange={this.viewRoleChange}
- onStepChange={this.stepChange}
- />
- </div>
- </>
- )
- }
- }
- const mapDispatchToProps = (dispatch) => ({
- onHideNavigator: () => dispatch(hideNavigator()),
- onLoadViewDetail: (viewId: number) => dispatch(ViewActions.loadViewsDetail([viewId], null, true)),
- onLoadSources: (projectId) => dispatch(SourceActions.loadSources(projectId)),
- onLoadSourceDatabases: (sourceId) => dispatch(SourceActions.loadSourceDatabases(sourceId)),
- onLoadDatabaseTables: (sourceId, databaseName) => dispatch(SourceActions.loadDatabaseTables(sourceId, databaseName)),
- onLoadTableColumns: (sourceId, databaseName, tableName) => dispatch(SourceActions.loadTableColumns(sourceId, databaseName, tableName)),
- onExecuteSql: (params, exeType?) => dispatch(ViewActions.executeSql(params, exeType)),
- onSetIsLastExecuteWholeSql: (isLastExecuteWholeSql: boolean) => dispatch(ViewActions.setIsLastExecuteWholeSql(isLastExecuteWholeSql)),
- onAddView: (view, resolve) => dispatch(ViewActions.addView(view, resolve)),
- onEditView: (view, resolve) => dispatch(ViewActions.editView(view, resolve)),
- onUpdateEditingView: (view) => dispatch(ViewActions.updateEditingView(view)),
- onUpdateEditingViewInfo: (viewInfo: IViewInfo) => dispatch(ViewActions.updateEditingViewInfo(viewInfo)),
- onSetSqlLimit: (limit: number) => dispatch(ViewActions.setSqlLimit(limit)),
- onLoadDacChannels: () => dispatch(ViewActions.loadDacChannels()),
- onLoadDacTenants: (channelName) => dispatch(ViewActions.loadDacTenants(channelName)),
- onLoadDacBizs: (channelName, tenantId) => dispatch(ViewActions.loadDacBizs(channelName, tenantId)),
- onResetState: () => dispatch(ViewActions.resetViewState()),
- onLoadProjectRoles: (projectId) => dispatch(OrganizationActions.loadProjectRoles(projectId))
- })
- const mapStateToProps = createStructuredSelector({
- editingView: makeSelectEditingView(),
- editingViewInfo: makeSelectEditingViewInfo(),
- sources: makeSelectSources(),
- schema: makeSelectSchema(),
- sqlDataSource: makeSelectSqlDataSource(),
- sqlLimit: makeSelectSqlLimit(),
- sqlValidation: makeSelectSqlValidation(),
- loading: makeSelectLoading(),
- projectRoles: makeSelectProjectRoles(),
- channels: makeSelectChannels(),
- tenants: makeSelectTenants(),
- bizs: makeSelectBizs(),
- isLastExecuteWholeSql: makeSelectIsLastExecuteWholeSql()
- })
- const withConnect = connect(mapStateToProps, mapDispatchToProps)
- const withReducer = injectReducer({ key: 'view', reducer })
- const withSaga = injectSaga({ key: 'view', saga: sagas })
- const withReducerSource = injectReducer({ key: 'source', reducer: reducerSource })
- const withSagaSource = injectSaga({ key: 'source', saga: sagasSource })
- const withReducerProject = injectReducer({ key: 'project', reducer: reducerProject })
- const withSagaProject = injectSaga({ key: 'project', saga: sagasProject })
- export default compose(
- withReducer,
- withReducerSource,
- withSaga,
- withSagaSource,
- withReducerProject,
- withSagaProject,
- withConnect
- )(ViewEditor)
|