diff --git a/console/packages/starwhale-core/src/datastore/hooks/useFetchDatastore.ts b/console/packages/starwhale-core/src/datastore/hooks/useFetchDatastore.ts index 2dae562923..8dcd01b695 100644 --- a/console/packages/starwhale-core/src/datastore/hooks/useFetchDatastore.ts +++ b/console/packages/starwhale-core/src/datastore/hooks/useFetchDatastore.ts @@ -44,7 +44,7 @@ export function useQueryDatasetList( filter?: any[] query?: QueryTableRequest }, - rawResult = false + enabled = true ) { const { start, limit, query } = React.useMemo(() => { const { pageNum = 1, pageSize = 10 } = options || {} @@ -56,13 +56,16 @@ export function useQueryDatasetList( } }, [options]) - const columnInfo = useQueryDatastore({ - tableName, - start: 0, - limit: 0, - rawResult, - ignoreNonExistingTable: true, - }) + const columnInfo = useQueryDatastore( + { + tableName, + start: 0, + limit: 0, + rawResult: true, + ignoreNonExistingTable: true, + }, + enabled + ) const recordQuery = useMemo(() => { const column = new ColumnFilterModel(columnInfo.data?.columnTypes ?? []) @@ -72,29 +75,30 @@ export function useQueryDatasetList( tableName, start, limit, - rawResult, + rawResult: true, ignoreNonExistingTable: true, } return filter ? { ...raw, filter } : raw - }, [options?.filter, columnInfo.data?.columnTypes, limit, rawResult, start, tableName, query]) + }, [options?.filter, columnInfo.data?.columnTypes, limit, start, tableName, query]) const recordInfo = useQueryDatastore(recordQuery, columnInfo.isSuccess) React.useEffect(() => { - if (tableName) { + if (tableName && enabled) { columnInfo.refetch() } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [tableName, start, limit]) + }, [tableName, enabled]) React.useEffect(() => { - if (recordQuery.tableName) { + if (recordQuery.tableName && columnInfo.isSuccess) { recordInfo.refetch() } // eslint-disable-next-line react-hooks/exhaustive-deps }, [recordQuery.tableName]) return { + recordQuery, columnInfo, recordInfo, } diff --git a/console/packages/starwhale-core/src/store/store.ts b/console/packages/starwhale-core/src/store/store.ts index 9b55192c3b..db0ffbca5a 100644 --- a/console/packages/starwhale-core/src/store/store.ts +++ b/console/packages/starwhale-core/src/store/store.ts @@ -8,6 +8,12 @@ import WidgetFactory from '../widget/WidgetFactory' import { getTreePath } from '../utils/path' import { WidgetConfig, WidgetStoreState } from '../types' +function arrayOverride(objValue: any, srcValue: any) { + if (_.isArray(objValue)) { + return srcValue + } +} + export function createCustomStore(initState: Partial = {}) { console.log('store init') const name = 'widgets' @@ -34,8 +40,8 @@ export function createCustomStore(initState: Partial = {}) { onConfigChange: (paths: any, config: any) => set( produce((state) => { - const rawConfig = _.get(get(), paths) ?? {} - _.set(state, paths, _.merge({}, rawConfig, config)) + const rawConfig = _.merge({}, _.get(get(), paths)) + _.set(state, paths, _.mergeWith(rawConfig, config, arrayOverride)) // console.log('onConfigChange', state, paths, rawConfig, config) }) ), @@ -61,7 +67,8 @@ export function createCustomStore(initState: Partial = {}) { if (!state.defaults[type]) state.defaults[type] = defaults } - state.widgets[id] = _.merge({}, state.widgets?.[id], widgets) + const rawConfig = _.merge({}, state.widgets?.[id]) + state.widgets[id] = _.mergeWith(rawConfig, widgets, arrayOverride) }) ), onWidgetDelete: (id: string) => diff --git a/console/packages/starwhale-core/src/widget/withWidgetDynamicProps.tsx b/console/packages/starwhale-core/src/widget/withWidgetDynamicProps.tsx index d1685eb0fc..6550cf4461 100644 --- a/console/packages/starwhale-core/src/widget/withWidgetDynamicProps.tsx +++ b/console/packages/starwhale-core/src/widget/withWidgetDynamicProps.tsx @@ -1,12 +1,13 @@ import React, { useCallback, useEffect, useRef, useState } from 'react' import { Subscription } from 'rxjs' -import useSelector, { getWidget } from '../store/hooks/useSelector' +import { getWidget } from '../store/hooks/useSelector' import { useEditorContext } from '../context/EditorContextProvider' import { WidgetRendererType } from '../types' -import { useQueryDatastore } from '../datastore/hooks/useFetchDatastore' +import { useQueryDatasetList } from '../datastore/hooks/useFetchDatastore' import { useIsInViewport } from '../utils' import { exportTable } from '../datastore' import { PanelDownloadEvent, PanelReloadEvent } from '../events' +import { BusyPlaceholder } from '@starwhale/ui/BusyLoaderWrapper' function getParentPath(paths: any[]) { const curr = paths.slice() @@ -23,7 +24,10 @@ export default function withWidgetDynamicProps(WrappedWidgetRender: WidgetRender const { id, path } = props const { store, eventBus } = useEditorContext() const api = store() - const overrides = useSelector(getWidget(id)) ?? {} + const widgetIdSelector = React.useMemo(() => getWidget(id) ?? {}, [id]) + const overrides = store(widgetIdSelector) + const [loaded, setLoaded] = useState(false) + const myRef = useRef() const handleLayoutOrderChange = useCallback( (newList) => { @@ -50,27 +54,38 @@ export default function withWidgetDynamicProps(WrappedWidgetRender: WidgetRender // @FIXME show datastore be fetch at here // @FIXME refrech setting - const tableName = overrides?.fieldConfig?.data?.tableName + const tableName = React.useMemo(() => overrides?.fieldConfig?.data?.tableName, [overrides]) + const tableConfig = React.useMemo(() => overrides?.optionConfig?.currentView ?? {}, [overrides]) + const tableOptions = React.useMemo(() => { + const sorts = tableConfig.sortBy + ? [ + { + columnName: tableConfig.sortBy, + descending: tableConfig.sortDirection === 'DESC', + }, + ] + : [] - const query = React.useMemo( - () => ({ - tableName, - start: 0, - limit: 1000, - rawResult: true, - ignoreNonExistingTable: true, - }), - [tableName] - ) + sorts.push({ + columnName: 'id', + descending: true, + }) - const myRef = useRef() + return { + pageNum: 1, + pageSize: 1000, + query: { + orderBy: sorts, + }, + filter: tableConfig.queries, + } + }, [tableConfig]) const inViewport = useIsInViewport(myRef as any) - const info = useQueryDatastore(query, false) - const [loaded, setLoaded] = useState(false) + const { columnInfo, recordInfo: info, recordQuery: query } = useQueryDatasetList(tableName, tableOptions, false) useEffect(() => { if (tableName && inViewport && !loaded) { - info.refetch() + columnInfo.refetch() setLoaded(true) } // eslint-disable-next-line react-hooks/exhaustive-deps @@ -100,6 +115,17 @@ export default function withWidgetDynamicProps(WrappedWidgetRender: WidgetRender return () => subscription.unsubscribe() }, [eventBus, id, info, query]) + if (tableName && !info.isSuccess) + return ( +
+ +
+ ) + + // if (!inViewport) return
+ + // if (tableName) console.log(id, tableName, columnInfo.isSuccess, info.isSuccess) + return (
div:last-child': { + '& .table-headers-pinned > div:last-child .header-cell': { borderRight: '1px solid rgb(207, 215, 230)', }, }, @@ -66,6 +66,7 @@ function GridTable({ queryinline = false, onSave, onChange = () => {}, + storeRef, }: ITableProps) { const wrapperRef = useRef(null) const api = useTableContext() @@ -93,7 +94,11 @@ function GridTable({ return unsub }, [api, onChange]) - // console.log('store', store) + React.useEffect(() => { + if (!storeRef) return + // eslint-disable-next-line no-param-reassign + storeRef.current = store + }, [storeRef, store]) return ( <> diff --git a/console/packages/starwhale-ui/src/GridTable/types.ts b/console/packages/starwhale-ui/src/GridTable/types.ts index ef8f66d1db..7c268f7fde 100644 --- a/console/packages/starwhale-ui/src/GridTable/types.ts +++ b/console/packages/starwhale-ui/src/GridTable/types.ts @@ -22,6 +22,7 @@ export interface ITableProps { id?: string data: any[] columns: any[] + storeRef?: React.MutableRefObject } export interface IPaginationProps { diff --git a/console/packages/starwhale-ui/src/Search/FilterRenderer.tsx b/console/packages/starwhale-ui/src/Search/FilterRenderer.tsx index 52b7e5e87e..fa7eb2b55b 100644 --- a/console/packages/starwhale-ui/src/Search/FilterRenderer.tsx +++ b/console/packages/starwhale-ui/src/Search/FilterRenderer.tsx @@ -76,7 +76,7 @@ export default function FilterRenderer({ const $fieldOptions = React.useMemo(() => { return $columns .filter((tmp) => { - return tmp.label.match(value) + return tmp.label?.match(value) }) .map((tmp) => { return { diff --git a/console/packages/starwhale-ui/src/base/data-table/inner-table-element.tsx b/console/packages/starwhale-ui/src/base/data-table/inner-table-element.tsx index 57dd53bd46..8c1784489f 100644 --- a/console/packages/starwhale-ui/src/base/data-table/inner-table-element.tsx +++ b/console/packages/starwhale-ui/src/base/data-table/inner-table-element.tsx @@ -98,18 +98,20 @@ const InnerTableElement = React.forwardRef< }) )} > -
- {viewState === RENDERING && $children} -
+ {$children.length > 0 && ( +
+ {viewState === RENDERING && $children} +
+ )}
{ - const columnsMap = _.keyBy(columns, (c) => c.key) as Record - const columnIds = isFullyLoaded ? ids : Array.from(new Set([...pinnedIds, ...columns.map((c) => c.key)])) - return columnIds - .filter((id: any) => id in columnsMap) - .map((id: any) => { - return { - ...columnsMap[id], - pin: pinnedIds.includes(id) ? 'LEFT' : undefined, - } - }) as ColumnT[] - }, [columns, columnable, pinnedIds, ids, isFullyLoaded]) + const { columns: $columns, currentView, isAllRuns } = useCurrentView(useStore, { columns: props.columns }) + const { renderConfigQuery } = useConfigQuery(useStore, { columns: props.columns, queryable }) const $filters = React.useMemo(() => { return ( @@ -149,8 +134,6 @@ export function StatefulDataTable(props: StatefulDataTablePropsT) { return [sortIndex, sortDirection] }, [store, $columns]) - const { renderConfigQuery } = useConfigQuery(useStore, { columns, queryable }) - return ( - {store.currentView?.id !== 'all' && ( + {!isAllRuns && ( <>    @@ -242,7 +225,7 @@ export function StatefulDataTable(props: StatefulDataTablePropsT) { {columnable && !$rowSelectedIds.size && (
diff --git a/console/packages/starwhale-ui/src/base/data-table/store.ts b/console/packages/starwhale-ui/src/base/data-table/store.ts index 7d752cb444..c3ad4c3c25 100644 --- a/console/packages/starwhale-ui/src/base/data-table/store.ts +++ b/console/packages/starwhale-ui/src/base/data-table/store.ts @@ -92,6 +92,7 @@ const createViewSlice: IStateCreator = (set, get, store) => ({ onViewUpdate: (view) => { // view.updated = false + view.version = 0 // const $oldViewIndex = get().views?.findIndex((v) => v.id === view.id) @@ -139,20 +140,32 @@ const createViewSlice: IStateCreator = (set, get, store) => ({ getDefaultViewId: () => get().views?.find((view) => view.def)?.id ?? '', }) +const rawCurrentView = { + filters: [], + ids: [], + pinnedIds: [], + selectedIds: [], + sortBy: '', + version: 0, + updated: false, + id: 'all', +} const createCurrentViewSlice: IStateCreator = (set, get) => { const update = (updateAttrs: Partial) => { const curr = get().currentView + const version = _.isNumber(curr.version) ? curr.version + 1 : 1 set({ currentView: { ...curr, ...updateAttrs, updated: true, + version, }, }) } return { - currentView: {}, + currentView: rawCurrentView, onCurrentViewSaved: () => update({ updated: false }), onCurrentViewFiltersChange: (filters) => update({ filters }), onCurrentViewQueriesChange: (queries) => update({ queries }), @@ -196,7 +209,8 @@ const createViewInteractiveSlice: IStateCreator = (set, g const createTableStateInitSlice: IStateCreator = (set, get, store) => ({ isInit: false, key: 'table', - initStore: (obj: Record) => + initStore: (obj?: Record) => + !get().isInit && set({ ..._.pick(obj, Object.keys(rawInitialState)), isInit: true, @@ -314,5 +328,6 @@ export const useEvaluationCompareStore = createCustomStore('compare', { const stateSelector = (state: ITableState) => state const currentQueriesSelector = (state: ITableState) => state.currentView?.queries ?? [] +const currentViewSelector = (state: ITableState) => state.currentView ?? {} -export { stateSelector, currentQueriesSelector } +export { stateSelector, currentQueriesSelector, currentViewSelector } diff --git a/console/packages/starwhale-ui/src/base/data-table/types.tsx b/console/packages/starwhale-ui/src/base/data-table/types.tsx index ca873378d8..6564414270 100644 --- a/console/packages/starwhale-ui/src/base/data-table/types.tsx +++ b/console/packages/starwhale-ui/src/base/data-table/types.tsx @@ -113,6 +113,7 @@ export type QueryT = { export type ConfigT = { updated?: boolean + version?: number updatedTime?: number id?: string def?: boolean diff --git a/console/packages/starwhale-ui/src/base/data-table/view/useCurrentView.tsx b/console/packages/starwhale-ui/src/base/data-table/view/useCurrentView.tsx new file mode 100644 index 0000000000..68d4b1c48d --- /dev/null +++ b/console/packages/starwhale-ui/src/base/data-table/view/useCurrentView.tsx @@ -0,0 +1,55 @@ +import _ from 'lodash' +import React from 'react' +import { currentViewSelector, IStore } from '../store' +import { ColumnT, ConfigT } from '../types' + +function useConfigView(store: IStore, { columns }: { columns: ColumnT[] }) { + const view = store(currentViewSelector) + + const columnIds = React.useMemo(() => { + return columns.map((c) => c.key) + }, [columns]) + + const $ids = React.useMemo(() => { + const { pinnedIds = [], ids = [] }: ConfigT = view + + // NOTICE: used full columns when + if (!view.id || (view.id === 'all' && ids.length === 0)) { + return Array.from(new Set([...pinnedIds, ...columnIds])) + } + return ids + }, [view, columnIds]) + + const $columns = React.useMemo(() => { + const { pinnedIds = [] }: ConfigT = view + const columnsMap = _.keyBy(columns, (c) => c.key) as Record + return $ids + .filter((id: any) => id in columnsMap) + .map((id: any) => { + return { + ...columnsMap[id], + pin: pinnedIds.includes(id) ? 'LEFT' : undefined, + } + }) as ColumnT[] + }, [view, columns, $ids]) + + const $view = React.useMemo(() => { + return { + ...view, + ids: $ids, + } + }, [view]) + + const isAllRuns = React.useMemo(() => { + return view.id === 'all' + }, [view]) + + return { + ids: $ids, + columns: $columns, + currentView: $view, + isAllRuns, + } +} + +export default useConfigView diff --git a/console/packages/starwhale-widgets/src/PanelTableWidget/component/Table.tsx b/console/packages/starwhale-widgets/src/PanelTableWidget/component/Table.tsx index 949fb739c8..c2aa550f96 100644 --- a/console/packages/starwhale-widgets/src/PanelTableWidget/component/Table.tsx +++ b/console/packages/starwhale-widgets/src/PanelTableWidget/component/Table.tsx @@ -2,7 +2,16 @@ import React from 'react' import { GridTable, useDatastoreColumns } from '@starwhale/ui/GridTable' // @ts-ignore -export default function PanelTable({ columnTypes, data, storeKey }) { +export default function PanelTable({ columnTypes, data, storeKey, onChange, storeRef }) { const $columns = useDatastoreColumns(columnTypes) - return + return ( + + ) } diff --git a/console/packages/starwhale-widgets/src/PanelTableWidget/index.tsx b/console/packages/starwhale-widgets/src/PanelTableWidget/index.tsx index 4bbe873d7d..a9e8885545 100644 --- a/console/packages/starwhale-widgets/src/PanelTableWidget/index.tsx +++ b/console/packages/starwhale-widgets/src/PanelTableWidget/index.tsx @@ -2,11 +2,13 @@ import React from 'react' import { WidgetRendererProps, WidgetConfig, WidgetGroupType } from '@starwhale/core/types' import { WidgetPlugin } from '@starwhale/core/widget' import PanelTable from './component/Table' +import { ITableState } from '@starwhale/ui/base/data-table/store' export const CONFIG: WidgetConfig = { type: 'ui:panel:table', group: WidgetGroupType.PANEL, name: 'Table', + optionConfig: {}, fieldConfig: { uiSchema: {}, schema: {}, @@ -14,10 +16,26 @@ export const CONFIG: WidgetConfig = { } function PanelTableWidget(props: WidgetRendererProps) { - const { data = {}, id } = props + const { optionConfig, data = {}, id, onOptionChange } = props const { columnTypes = [], records = [] } = data + const [state, setState] = React.useState(null) + const storeRef = React.useRef(null) - return + React.useEffect(() => { + if (state?.isInit || !storeRef.current) return + // console.log('--paneltable--', storeRef.current, optionConfig) + storeRef.current.initStore(optionConfig as ITableState) + setState(optionConfig as ITableState) + }, [optionConfig, storeRef, state]) + + const onChange = React.useCallback( + (newState: ITableState) => { + onOptionChange?.(newState.getRawConfigs()) + }, + [onOptionChange] + ) + + return } const widget = new WidgetPlugin(PanelTableWidget, CONFIG) diff --git a/console/src/pages/Evaluation/EvaluationListCard.tsx b/console/src/pages/Evaluation/EvaluationListCard.tsx index 380b348a5d..eff1e144ab 100644 --- a/console/src/pages/Evaluation/EvaluationListCard.tsx +++ b/console/src/pages/Evaluation/EvaluationListCard.tsx @@ -131,8 +131,8 @@ export default function EvaluationListCard() { return evaluationsInfo.data?.records?.filter((r) => store.rowSelectedIds.includes(r.id)) ?? [] }, [store.rowSelectedIds, evaluationsInfo.data?.records]) const $ready = React.useMemo(() => { - return columnInfo.isSuccess && evaluationViewConfig.isSuccess && store.isInit - }, [columnInfo.isSuccess, evaluationViewConfig.isSuccess, store.isInit]) + return columnInfo.isSuccess && evaluationViewConfig.isSuccess + }, [columnInfo.isSuccess, evaluationViewConfig.isSuccess]) const doSave = async () => { await setEvaluationViewConfig(projectId, { @@ -163,13 +163,11 @@ export default function EvaluationListCard() { let $rawConfig try { $rawConfig = JSON.parse(evaluationViewConfig.data?.content, undefined) ?? {} - // console.log('upcoming state', $rawConfig) - store.initStore($rawConfig) } catch (e) { // console.log(e) } - store.initStore($rawConfig) + // store should not be used as a deps, it's will trigger cycle render // eslint-disable-next-line react-hooks/exhaustive-deps }, [store.isInit, evaluationViewConfig.isSuccess]) diff --git a/console/src/pages/Evaluation/EvaluationWidgetResults.tsx b/console/src/pages/Evaluation/EvaluationWidgetResults.tsx index 2ff4714166..1d7b2d9394 100644 --- a/console/src/pages/Evaluation/EvaluationWidgetResults.tsx +++ b/console/src/pages/Evaluation/EvaluationWidgetResults.tsx @@ -5,23 +5,99 @@ import { showTableName, tableNameOfSummary } from '@starwhale/core/datastore/uti import { useQueryDatastore } from '@starwhale/core/datastore/hooks/useFetchDatastore' import { useProject } from '@/domain/project/hooks/useProject' import Table from '@/components/Table' -import { Panel } from 'baseui/accordion' -import Accordion from '@starwhale/ui/Accordion' +import { Panel, StatelessAccordion } from 'baseui/accordion' import { QueryTableRequest } from '@starwhale/core/datastore' import { FullTablesEditor } from '@/components/Editor/FullTablesEditor' import { useParams } from 'react-router-dom' +import { Button, IconFont } from '@starwhale/ui' const PAGE_TABLE_SIZE = 100 function Summary({ fetch }: any) { const record: Record = fetch?.data?.records?.[0] ?? {} + const [expanded, setExpanded] = React.useState(false) return (
- - + , + }, + }} + > + setExpanded(!expanded)} + as='transparent' + overrides={{ + BaseButton: { + style: { + flex: 1, + justifyContent: 'flex-start', + }, + }, + }} + isFull + > +
+ Summary + +
+ + } + key='summary' + > {fetch?.data?.records.length === 0 && ( - + )}
{Object.keys(record) @@ -61,7 +139,7 @@ function Summary({ fetch }: any) { })}
-
+
) } @@ -119,10 +197,10 @@ function EvaluationWidgetResults() { const tables = React.useMemo(() => { const names = [] - if (project?.name) names.push(tableNameOfSummary(project?.name as string)) + if (project?.name) names.push(tableNameOfSummary(project?.name)) return [...names] - }, [project]) + }, [project?.name]) return (
@@ -141,10 +219,10 @@ function EvaluationWidgetResults() { operator: 'EQUAL', operands: [ { - stringValue: jobId, + intValue: jobId, }, { - columnName: 'id', + columnName: 'sys/id', }, ], }