diff --git a/js_modules/dagster-ui/packages/ui-components/src/components/CollapsibleSection.tsx b/js_modules/dagster-ui/packages/ui-components/src/components/CollapsibleSection.tsx index 322a9c36d22f5..449db327a3989 100644 --- a/js_modules/dagster-ui/packages/ui-components/src/components/CollapsibleSection.tsx +++ b/js_modules/dagster-ui/packages/ui-components/src/components/CollapsibleSection.tsx @@ -39,11 +39,11 @@ export const CollapsibleSection = ({ name="arrow_drop_down" style={{transform: isCollapsed ? 'rotate(-90deg)' : 'rotate(0deg)'}} /> -
{header}
+
{header}
) : ( - -
{header}
+ +
{header}
void; selectNode?: (e: React.MouseEvent | React.KeyboardEvent, nodeId: string) => void; @@ -30,8 +41,8 @@ export const useAssetNodeMenu = ({ explorerPath, onChangeExplorerPath, }: AssetNodeMenuProps) => { - const upstream = Object.keys(graphData.upstream[node.id] ?? {}); - const downstream = Object.keys(graphData.downstream[node.id] ?? {}); + const upstream = graphData ? Object.keys(graphData.upstream[node.id] ?? {}) : []; + const downstream = graphData ? Object.keys(graphData.downstream[node.id] ?? {}) : []; const {executeItem, launchpadElement} = useExecuteAssetMenuItem( node.assetKey.path, @@ -80,7 +91,7 @@ export const useAssetNodeMenu = ({ {executeItem} {executeItem && (upstream.length || downstream.length) ? : null} - {upstream.length ? ( + {upstream.length && graphData ? ( ) : null} - {upstream.length ? ( + {upstream.length || !graphData ? ( showGraph(`*\"${tokenForAssetKey(node.assetKey)}\"`)} /> ) : null} - {downstream.length ? ( + {downstream.length || !graphData ? ( - + {graphData && ( + + )} {launchpadElement} ), diff --git a/js_modules/dagster-ui/packages/ui-core/src/asset-graph/AssetNodeStatusContent.tsx b/js_modules/dagster-ui/packages/ui-core/src/asset-graph/AssetNodeStatusContent.tsx index 5542fbd7bb415..0802fca5d9e10 100644 --- a/js_modules/dagster-ui/packages/ui-core/src/asset-graph/AssetNodeStatusContent.tsx +++ b/js_modules/dagster-ui/packages/ui-core/src/asset-graph/AssetNodeStatusContent.tsx @@ -49,7 +49,7 @@ const LOADING_STATUS_CONTENT = { ), }; -type StatusContentArgs = { +export type StatusContentArgs = { assetKey: AssetKeyInput; definition: {opNames: string[]; isSource: boolean; isObservable: boolean}; liveData: LiveDataForNode | null | undefined; diff --git a/js_modules/dagster-ui/packages/ui-core/src/asset-graph/ContextMenuWrapper.tsx b/js_modules/dagster-ui/packages/ui-core/src/asset-graph/ContextMenuWrapper.tsx index 3bad4ecebeb6f..94c4300f1808b 100644 --- a/js_modules/dagster-ui/packages/ui-core/src/asset-graph/ContextMenuWrapper.tsx +++ b/js_modules/dagster-ui/packages/ui-core/src/asset-graph/ContextMenuWrapper.tsx @@ -18,14 +18,24 @@ export const ContextMenuWrapper = ({ wrapperInnerStyles?: React.CSSProperties; }) => { const [menuVisible, setMenuVisible] = React.useState(false); - const [menuPosition, setMenuPosition] = React.useState<{top: number; left: number}>({ - top: 0, - left: 0, + const [menuPosition, setMenuPosition] = React.useState<{ + x: number; + y: number; + anchor: 'left' | 'right'; + }>({ + anchor: 'left', + x: 0, + y: 0, }); const showMenu = (e: React.MouseEvent) => { + const anchor = window.innerWidth - e.pageX < 240 ? 'right' : 'left'; e.preventDefault(); - setMenuPosition({top: e.pageY, left: e.pageX}); + setMenuPosition({ + x: anchor === 'left' ? e.pageX : window.innerWidth - e.pageX, + y: e.pageY, + anchor, + }); if (!menuVisible) { setMenuVisible(true); @@ -78,8 +88,9 @@ export const ContextMenuWrapper = ({
{ const evt = new MouseEvent('contextmenu', e.nativeEvent); e.target.dispatchEvent(evt); e.stopPropagation(); + e.preventDefault(); }; diff --git a/js_modules/dagster-ui/packages/ui-core/src/asset-graph/sidebar/AssetSidebarNode.tsx b/js_modules/dagster-ui/packages/ui-core/src/asset-graph/sidebar/AssetSidebarNode.tsx index ff91f04ec502f..442c5ec6f29e7 100644 --- a/js_modules/dagster-ui/packages/ui-core/src/asset-graph/sidebar/AssetSidebarNode.tsx +++ b/js_modules/dagster-ui/packages/ui-core/src/asset-graph/sidebar/AssetSidebarNode.tsx @@ -2,7 +2,7 @@ import {Box, Colors, Icon, MiddleTruncate, UnstyledButton} from '@dagster-io/ui- import * as React from 'react'; import styled from 'styled-components'; -import {StatusDot} from './StatusDot'; +import {StatusDot, StatusDotNode} from './StatusDot'; import { FolderNodeCodeLocationType, FolderNodeGroupType, @@ -11,13 +11,13 @@ import { } from './util'; import {ExplorerPath} from '../../pipelines/PipelinePathUtils'; import {AssetGroup} from '../AssetGraphExplorer'; -import {useAssetNodeMenu} from '../AssetNodeMenu'; +import {AssetNodeMenuProps, useAssetNodeMenu} from '../AssetNodeMenu'; import {useGroupNodeContextMenu} from '../CollapsedGroupNode'; import {ContextMenuWrapper, triggerContextMenu} from '../ContextMenuWrapper'; import {GraphData, GraphNode} from '../Utils'; type AssetSidebarNodeProps = { - fullAssetGraphData: GraphData; + fullAssetGraphData?: GraphData; node: GraphNode | FolderNodeNonAssetType; level: number; toggleOpen: () => void; @@ -42,10 +42,9 @@ export const AssetSidebarNode = (props: AssetSidebarNodeProps) => { const showArrow = !isAssetNode; return ( - + !e.metaKey && toggleOpen()} @@ -93,7 +92,18 @@ export const AssetSidebarNode = (props: AssetSidebarNodeProps) => { ); }; -const AssetSidebarAssetLabel = ({ +type AssetSidebarAssetLabelProps = { + fullAssetGraphData?: GraphData; + showStatus?: boolean; + node: AssetNodeMenuProps['node'] & StatusDotNode; + selectNode: (e: React.MouseEvent | React.KeyboardEvent, nodeId: string) => void; + isLastSelected: boolean; + isSelected: boolean; + explorerPath: ExplorerPath; + onChangeExplorerPath: (path: ExplorerPath, mode: 'replace' | 'push') => void; +}; + +export const AssetSidebarAssetLabel = ({ node, isSelected, isLastSelected, @@ -101,7 +111,8 @@ const AssetSidebarAssetLabel = ({ selectNode, explorerPath, onChangeExplorerPath, -}: Omit & {node: GraphNode}) => { + showStatus = true, +}: AssetSidebarAssetLabelProps) => { const {menu, dialog} = useAssetNodeMenu({ graphData: fullAssetGraphData, node, @@ -115,7 +126,7 @@ const AssetSidebarAssetLabel = ({ } + icon={showStatus ? : null} text={getDisplayName(node)} /> @@ -196,6 +207,7 @@ const FocusableLabelContainer = ({ @@ -231,7 +243,7 @@ const BoxWrapper = ({level, children}: {level: number; children: React.ReactNode const ExpandMore = styled(UnstyledButton)` position: absolute; top: 8px; - right: 20px; + right: 8px; visibility: hidden; `; @@ -240,8 +252,8 @@ const GrayOnHoverBox = styled(UnstyledButton)` user-select: none; width: 100%; display: grid; - grid-template-columns: auto minmax(0, 1fr); flex-direction: row; + height: 32px; align-items: center; padding: 5px 8px; justify-content: space-between; @@ -251,7 +263,7 @@ const GrayOnHoverBox = styled(UnstyledButton)` transition: background 100ms linear; `; -const ItemContainer = styled(Box)` +export const ItemContainer = styled(Box)` height: 32px; position: relative; cursor: pointer; diff --git a/js_modules/dagster-ui/packages/ui-core/src/asset-graph/sidebar/StatusDot.tsx b/js_modules/dagster-ui/packages/ui-core/src/asset-graph/sidebar/StatusDot.tsx index 8c358356558ac..cbfd76d983721 100644 --- a/js_modules/dagster-ui/packages/ui-core/src/asset-graph/sidebar/StatusDot.tsx +++ b/js_modules/dagster-ui/packages/ui-core/src/asset-graph/sidebar/StatusDot.tsx @@ -1,9 +1,15 @@ import {StatusCaseDot} from './util'; import {useAssetLiveData} from '../../asset-data/AssetLiveDataProvider'; -import {StatusCase, buildAssetNodeStatusContent} from '../AssetNodeStatusContent'; -import {GraphNode} from '../Utils'; +import { + StatusCase, + StatusContentArgs, + buildAssetNodeStatusContent, +} from '../AssetNodeStatusContent'; +import {AssetKeyInput} from '../../graphql/types'; -export function StatusDot({node}: {node: Pick}) { +export type StatusDotNode = {assetKey: AssetKeyInput; definition: StatusContentArgs['definition']}; + +export function StatusDot({node}: {node: StatusDotNode}) { const {liveData} = useAssetLiveData(node.assetKey); if (!liveData) { diff --git a/js_modules/dagster-ui/packages/ui-core/src/asset-graph/sidebar/util.tsx b/js_modules/dagster-ui/packages/ui-core/src/asset-graph/sidebar/util.tsx index 923fd4770a58d..f58549c7b8a2b 100644 --- a/js_modules/dagster-ui/packages/ui-core/src/asset-graph/sidebar/util.tsx +++ b/js_modules/dagster-ui/packages/ui-core/src/asset-graph/sidebar/util.tsx @@ -4,6 +4,7 @@ import styled, {keyframes} from 'styled-components'; import {StatusCase} from '../AssetNodeStatusContent'; import {GraphNode} from '../Utils'; +import {AssetKeyInput} from '../../graphql/types'; export type FolderNodeGroupType = { id: string; @@ -28,7 +29,7 @@ export function nodePathKey(node: {path: string; id: string} | {id: string}) { return 'path' in node ? node.path : node.id; } -export function getDisplayName(node: GraphNode) { +export function getDisplayName(node: {assetKey: AssetKeyInput}) { return node.assetKey.path[node.assetKey.path.length - 1]!; } diff --git a/js_modules/dagster-ui/packages/ui-core/src/assets/AssetNodeOverview.tsx b/js_modules/dagster-ui/packages/ui-core/src/assets/AssetNodeOverview.tsx index 724f2dadba3d5..52bd3e89e3232 100644 --- a/js_modules/dagster-ui/packages/ui-core/src/assets/AssetNodeOverview.tsx +++ b/js_modules/dagster-ui/packages/ui-core/src/assets/AssetNodeOverview.tsx @@ -1,5 +1,4 @@ // eslint-disable-next-line no-restricted-imports -import {Collapse} from '@blueprintjs/core'; import { Body, Body2, @@ -10,14 +9,11 @@ import { Colors, ConfigTypeSchema, Icon, - IconName, MiddleTruncate, NonIdealState, Skeleton, - Subtitle1, Subtitle2, Tag, - UnstyledButton, } from '@dagster-io/ui-components'; import dayjs from 'dayjs'; import React, {useMemo, useState} from 'react'; @@ -30,6 +26,7 @@ import {metadataForAssetNode} from './AssetMetadata'; import {insitigatorsByType} from './AssetNodeInstigatorTag'; import {AutomaterializePolicyTag} from './AutomaterializePolicyTag'; import {DependsOnSelfBanner} from './DependsOnSelfBanner'; +import {LargeCollapsibleSection} from './LargeCollapsibleSection'; import {MaterializationTag} from './MaterializationTag'; import {OverdueTag, freshnessPolicyDescription} from './OverdueTag'; import {RecentUpdatesTimeline} from './RecentUpdatesTimeline'; @@ -56,7 +53,6 @@ import {StatusDot} from '../asset-graph/sidebar/StatusDot'; import {AssetNodeForGraphQueryFragment} from '../asset-graph/types/useAssetGraphData.types'; import {DagsterTypeSummary} from '../dagstertype/DagsterType'; import {AssetComputeKindTag} from '../graph/OpTags'; -import {useStateWithStorage} from '../hooks/useStateWithStorage'; import {useLaunchPadHooks} from '../launchpad/LaunchpadHooksContext'; import {TableSchema, TableSchemaAssetContext} from '../metadata/TableSchema'; import {RepositoryLink} from '../nav/RepositoryLink'; @@ -617,55 +613,6 @@ export const AssetNodeOverviewLoading = () => ( /> ); -// BG: This should probably be moved to ui-components, but waiting to see if we -// adopt it more broadly. - -const LargeCollapsibleSection = ({ - header, - icon, - children, - right, - collapsedByDefault = false, -}: { - header: string; - icon: IconName; - children: React.ReactNode; - right?: React.ReactNode; - collapsedByDefault?: boolean; -}) => { - const [isCollapsed, setIsCollapsed] = useStateWithStorage( - `collapsible-section-${header}`, - (storedValue) => - storedValue === true || storedValue === false ? storedValue : collapsedByDefault, - ); - - return ( - - setIsCollapsed(!isCollapsed)}> - - - - {header} - - {right} - - - - - {children} - - - ); -}; - const SectionEmptyState = ({ title, description, diff --git a/js_modules/dagster-ui/packages/ui-core/src/assets/AssetsCatalogTable.tsx b/js_modules/dagster-ui/packages/ui-core/src/assets/AssetsCatalogTable.tsx index f81ab40e327f6..981bd540cb93b 100644 --- a/js_modules/dagster-ui/packages/ui-core/src/assets/AssetsCatalogTable.tsx +++ b/js_modules/dagster-ui/packages/ui-core/src/assets/AssetsCatalogTable.tsx @@ -1,8 +1,7 @@ import {gql, useApolloClient} from '@apollo/client'; -import {Box, ButtonGroup, TextInput} from '@dagster-io/ui-components'; +import {Box, ButtonGroup} from '@dagster-io/ui-components'; import * as React from 'react'; -import {useAssetGroupSelectorsForAssets} from './AssetGroupSuggest'; import {AssetTable} from './AssetTable'; import {ASSET_TABLE_DEFINITION_FRAGMENT, ASSET_TABLE_FRAGMENT} from './AssetTableFragment'; import {AssetsEmptyState} from './AssetsEmptyState'; @@ -14,31 +13,16 @@ import { AssetCatalogTableQuery, AssetCatalogTableQueryVariables, } from './types/AssetsCatalogTable.types'; -import {useAssetDefinitionFilterState} from './useAssetDefinitionFilterState'; -import {useAssetSearch} from './useAssetSearch'; +import {useAssetCatalogFiltering} from './useAssetCatalogFiltering'; import {AssetViewType, useAssetView} from './useAssetView'; -import {CloudOSSContext} from '../app/CloudOSSContext'; import {PYTHON_ERROR_FRAGMENT} from '../app/PythonErrorFragment'; import {PythonErrorInfo} from '../app/PythonErrorInfo'; import {FIFTEEN_SECONDS, useRefreshAtInterval} from '../app/QueryRefresh'; import {PythonErrorFragment} from '../app/types/PythonErrorFragment.types'; import {AssetGroupSelector} from '../graphql/types'; import {useConstantCallback} from '../hooks/useConstantCallback'; -import {useQueryPersistedState} from '../hooks/useQueryPersistedState'; import {PageLoadTrace} from '../performance'; -import {useFilters} from '../ui/Filters'; -import {useAssetGroupFilter} from '../ui/Filters/useAssetGroupFilter'; -import {useAssetOwnerFilter, useAssetOwnersForAssets} from '../ui/Filters/useAssetOwnerFilter'; -import {useAssetTagFilter, useAssetTagsForAssets} from '../ui/Filters/useAssetTagFilter'; -import {useChangedFilter} from '../ui/Filters/useChangedFilter'; -import {useCodeLocationFilter} from '../ui/Filters/useCodeLocationFilter'; -import { - useAssetKindTagsForAssets, - useComputeKindTagFilter, -} from '../ui/Filters/useComputeKindTagFilter'; -import {FilterObject} from '../ui/Filters/useFilter'; import {LoadingSpinner} from '../ui/Loading'; -import {WorkspaceContext} from '../workspace/WorkspaceContext'; type Asset = AssetTableFragment; let globalTableCache: AssetCatalogTableQuery; @@ -112,8 +96,6 @@ interface AssetCatalogTableProps { trace?: PageLoadTrace; } -const emptyArray: any[] = []; - export const AssetsCatalogTable = ({ prefixPath, setPrefixPath, @@ -121,34 +103,10 @@ export const AssetsCatalogTable = ({ trace, }: AssetCatalogTableProps) => { const [view, setView] = useAssetView(); - const [search, setSearch] = useQueryPersistedState({queryKey: 'q'}); - - const { - filters, - filterFn, - setAssetTags, - setChangedInBranch, - setComputeKindTags, - setGroups, - setOwners, - setRepos, - } = useAssetDefinitionFilterState(); - - const searchPath = (search || '') - .replace(/(( ?> ?)|\.|\/)/g, '/') - .toLowerCase() - .trim(); const {assets, query, error} = useAllAssets(groupSelector); - const pathMatches = useAssetSearch( - searchPath, - assets ?? (emptyArray as NonNullable), - ); - - const filtered = React.useMemo( - () => pathMatches.filter((a) => filterFn(a.definition ?? {})), - [filterFn, pathMatches], - ); + const {searchPath, filtered, isFiltered, filterButton, filterInput, activeFiltersJsx} = + useAssetCatalogFiltering(assets, prefixPath); const {displayPathForAsset, displayed} = view === 'flat' @@ -168,47 +126,6 @@ export const AssetsCatalogTable = ({ } }, [loaded, trace]); - const allAssetGroupOptions = useAssetGroupSelectorsForAssets(pathMatches); - const allComputeKindTags = useAssetKindTagsForAssets(pathMatches); - const allAssetOwners = useAssetOwnersForAssets(pathMatches); - - const groupsFilter = useAssetGroupFilter({ - allAssetGroups: allAssetGroupOptions, - assetGroups: filters.groups, - setGroups, - }); - const changedInBranchFilter = useChangedFilter({ - changedInBranch: filters.changedInBranch, - setChangedInBranch, - }); - const computeKindFilter = useComputeKindTagFilter({ - allComputeKindTags, - computeKindTags: filters.computeKindTags, - setComputeKindTags, - }); - const ownersFilter = useAssetOwnerFilter({ - allAssetOwners, - owners: filters.owners, - setOwners, - }); - const tagsFilter = useAssetTagFilter({ - allAssetTags: useAssetTagsForAssets(pathMatches), - tags: filters.tags, - setTags: setAssetTags, - }); - const uiFilters: FilterObject[] = [groupsFilter, computeKindFilter, ownersFilter, tagsFilter]; - const {isBranchDeployment} = React.useContext(CloudOSSContext); - if (isBranchDeployment) { - uiFilters.push(changedInBranchFilter); - } - const {allRepos} = React.useContext(WorkspaceContext); - - const reposFilter = useCodeLocationFilter({repos: filters.repos, setRepos}); - if (allRepos.length > 1) { - uiFilters.unshift(reposFilter); - } - const {button, activeFiltersJsx} = useFilters({filters: uiFilters}); - React.useEffect(() => { if (view !== 'directory' && prefixPath.length) { setView('directory'); @@ -235,15 +152,7 @@ export const AssetsCatalogTable = ({ @@ -259,17 +168,8 @@ export const AssetsCatalogTable = ({ } }} /> - {button} - ) => setSearch(e.target.value)} - /> + {filterButton} + {filterInput} } belowActionBarComponents={ diff --git a/js_modules/dagster-ui/packages/ui-core/src/assets/LargeCollapsibleSection.tsx b/js_modules/dagster-ui/packages/ui-core/src/assets/LargeCollapsibleSection.tsx new file mode 100644 index 0000000000000..d9cb24ed34002 --- /dev/null +++ b/js_modules/dagster-ui/packages/ui-core/src/assets/LargeCollapsibleSection.tsx @@ -0,0 +1,59 @@ +// eslint-disable-next-line no-restricted-imports +import {Collapse} from '@blueprintjs/core'; +import {Box, Icon, IconName, Subtitle1, UnstyledButton} from '@dagster-io/ui-components'; +import React from 'react'; + +import {useStateWithStorage} from '../hooks/useStateWithStorage'; + +export const LargeCollapsibleSection = ({ + header, + count, + icon, + children, + right, + collapsedByDefault = false, + padHeader = false, + padChildren = true, +}: { + header: string; + count?: number; + icon?: IconName; + children: React.ReactNode; + right?: React.ReactNode; + collapsedByDefault?: boolean; + padHeader?: boolean; + padChildren?: boolean; +}) => { + const [isCollapsed, setIsCollapsed] = useStateWithStorage( + `collapsible-section-${header}`, + (storedValue) => + storedValue === true || storedValue === false ? storedValue : collapsedByDefault, + ); + + return ( + + setIsCollapsed(!isCollapsed)}> + + {icon && } + + {header} + {count !== undefined ? ` (${count.toLocaleString()})` : ''} + + {right} + + + + + {children} + + + ); +}; diff --git a/js_modules/dagster-ui/packages/ui-core/src/assets/useAssetCatalogFiltering.tsx b/js_modules/dagster-ui/packages/ui-core/src/assets/useAssetCatalogFiltering.tsx new file mode 100644 index 0000000000000..e253e5c091917 --- /dev/null +++ b/js_modules/dagster-ui/packages/ui-core/src/assets/useAssetCatalogFiltering.tsx @@ -0,0 +1,126 @@ +import {TextInput} from '@dagster-io/ui-components'; +import * as React from 'react'; + +import {useAssetGroupSelectorsForAssets} from './AssetGroupSuggest'; +import {AssetTableFragment} from './types/AssetTableFragment.types'; +import {useAssetDefinitionFilterState} from './useAssetDefinitionFilterState'; +import {useAssetSearch} from './useAssetSearch'; +import {CloudOSSContext} from '../app/CloudOSSContext'; +import {useQueryPersistedState} from '../hooks/useQueryPersistedState'; +import {useFilters} from '../ui/Filters'; +import {useAssetGroupFilter} from '../ui/Filters/useAssetGroupFilter'; +import {useAssetOwnerFilter, useAssetOwnersForAssets} from '../ui/Filters/useAssetOwnerFilter'; +import {useAssetTagFilter, useAssetTagsForAssets} from '../ui/Filters/useAssetTagFilter'; +import {useChangedFilter} from '../ui/Filters/useChangedFilter'; +import {useCodeLocationFilter} from '../ui/Filters/useCodeLocationFilter'; +import { + useAssetKindTagsForAssets, + useComputeKindTagFilter, +} from '../ui/Filters/useComputeKindTagFilter'; +import {FilterObject} from '../ui/Filters/useFilter'; +import {WorkspaceContext} from '../workspace/WorkspaceContext'; + +const EMPTY_ARRAY: any[] = []; + +export function useAssetCatalogFiltering( + assets: AssetTableFragment[] | undefined, + prefixPath: string[], +) { + const [search, setSearch] = useQueryPersistedState({queryKey: 'q'}); + + const { + filters, + filterFn, + setAssetTags, + setChangedInBranch, + setComputeKindTags, + setGroups, + setOwners, + setRepos, + } = useAssetDefinitionFilterState(); + + const searchPath = (search || '') + .replace(/(( ?> ?)|\.|\/)/g, '/') + .toLowerCase() + .trim(); + + const pathMatches = useAssetSearch( + searchPath, + assets ?? (EMPTY_ARRAY as NonNullable), + ); + + const filtered = React.useMemo( + () => pathMatches.filter((a) => filterFn(a.definition ?? {})), + [filterFn, pathMatches], + ); + + const allAssetGroupOptions = useAssetGroupSelectorsForAssets(pathMatches); + const allComputeKindTags = useAssetKindTagsForAssets(pathMatches); + const allAssetOwners = useAssetOwnersForAssets(pathMatches); + + const groupsFilter = useAssetGroupFilter({ + allAssetGroups: allAssetGroupOptions, + assetGroups: filters.groups, + setGroups, + }); + const changedInBranchFilter = useChangedFilter({ + changedInBranch: filters.changedInBranch, + setChangedInBranch, + }); + const computeKindFilter = useComputeKindTagFilter({ + allComputeKindTags, + computeKindTags: filters.computeKindTags, + setComputeKindTags, + }); + const ownersFilter = useAssetOwnerFilter({ + allAssetOwners, + owners: filters.owners, + setOwners, + }); + const tagsFilter = useAssetTagFilter({ + allAssetTags: useAssetTagsForAssets(pathMatches), + tags: filters.tags, + setTags: setAssetTags, + }); + const uiFilters: FilterObject[] = [groupsFilter, computeKindFilter, ownersFilter, tagsFilter]; + const {isBranchDeployment} = React.useContext(CloudOSSContext); + if (isBranchDeployment) { + uiFilters.push(changedInBranchFilter); + } + const {allRepos} = React.useContext(WorkspaceContext); + + const reposFilter = useCodeLocationFilter({repos: filters.repos, setRepos}); + if (allRepos.length > 1) { + uiFilters.unshift(reposFilter); + } + const components = useFilters({filters: uiFilters}); + + const filterInput = ( + ) => setSearch(e.target.value)} + /> + ); + + const isFiltered: boolean = !!( + filters.changedInBranch?.length || + filters.computeKindTags?.length || + filters.groups?.length || + filters.owners?.length || + filters.repos?.length + ); + + return { + searchPath, + activeFiltersJsx: components.activeFiltersJsx, + filterValues: filters, + filterButton: components.button, + filterInput, + isFiltered, + filtered, + }; +} diff --git a/js_modules/dagster-ui/packages/ui-core/src/assets/useAssetSearch.tsx b/js_modules/dagster-ui/packages/ui-core/src/assets/useAssetSearch.tsx index d8b8f991afd9f..1b04e4aea0905 100644 --- a/js_modules/dagster-ui/packages/ui-core/src/assets/useAssetSearch.tsx +++ b/js_modules/dagster-ui/packages/ui-core/src/assets/useAssetSearch.tsx @@ -1,6 +1,7 @@ import {useMemo} from 'react'; import {tokenForAssetKey} from '../asset-graph/Utils'; +import {AssetKeyInput} from '../graphql/types'; const useSanitizedAssetSearch = (searchValue: string) => { return useMemo(() => { @@ -11,33 +12,21 @@ const useSanitizedAssetSearch = (searchValue: string) => { }, [searchValue]); }; -export const useAssetSearch = ( +export const useAssetSearch = ( searchValue: string, assets: A[], ): A[] => { const sanitizedSearch = useSanitizedAssetSearch(searchValue); - return useMemo(() => { - // If there is no search value, match everything. - if (!sanitizedSearch) { - return assets; - } - return assets.filter((a) => tokenForAssetKey(a.key).toLowerCase().includes(sanitizedSearch)); - }, [assets, sanitizedSearch]); -}; - -export const useAssetNodeSearch = ( - searchValue: string, - assetNodes: A[], -): A[] => { - const sanitizedSearch = useSanitizedAssetSearch(searchValue); return useMemo(() => { // If there is no search value, match everything. if (!sanitizedSearch) { - return assetNodes; + return assets; } - return assetNodes.filter((a) => - tokenForAssetKey(a.assetKey).toLowerCase().includes(sanitizedSearch), + return assets.filter((a) => + tokenForAssetKey('assetKey' in a ? a.assetKey : a.key) + .toLowerCase() + .includes(sanitizedSearch), ); - }, [assetNodes, sanitizedSearch]); + }, [assets, sanitizedSearch]); }; diff --git a/js_modules/dagster-ui/packages/ui-core/src/insights/InsightsLineChart.tsx b/js_modules/dagster-ui/packages/ui-core/src/insights/InsightsLineChart.tsx index 1fa803894bc0a..449072db9152d 100644 --- a/js_modules/dagster-ui/packages/ui-core/src/insights/InsightsLineChart.tsx +++ b/js_modules/dagster-ui/packages/ui-core/src/insights/InsightsLineChart.tsx @@ -39,6 +39,7 @@ interface Props { highlightKey: string | null; onHighlightKey?: (key: string | null) => void; emptyState?: React.ReactNode; + appearance?: 'minimal' | 'full'; } const SPINNER_WAIT_MSEC = 2000; @@ -57,6 +58,7 @@ export const InsightsLineChart = (props: Props) => { highlightKey, onHighlightKey, emptyState, + appearance = 'full', } = props; const dataValues = React.useMemo(() => Object.values(datapoints), [datapoints]); const dataEntries = React.useMemo(() => Object.entries(datapoints), [datapoints]); @@ -82,7 +84,7 @@ export const InsightsLineChart = (props: Props) => { const formatDateTime = useFormatDateTime(); // Don't show the y axis while loading datapoints, to avoid jumping renders. - const showYAxis = dataValues.length > 0; + const showYAxis = appearance === 'full' && dataValues.length > 0; const data = React.useMemo(() => { return { @@ -241,13 +243,14 @@ export const InsightsLineChart = (props: Props) => { x: { display: true, grid: { - display: false, + display: appearance === 'minimal', }, title: { - display: true, + display: appearance === 'full', color: Colors.textLighter(), }, ticks: { + display: appearance === 'full', color: Colors.textLighter(), font: { size: 14, @@ -267,7 +270,7 @@ export const InsightsLineChart = (props: Props) => { yCost, }, }; - }, [yCount, yCost, onHover, renderTooltipFn, formatDateTime]); + }, [yCount, yCost, onHover, renderTooltipFn, formatDateTime, appearance]); const emptyContent = () => { const anyDatapoints = Object.keys(datapoints).length > 0; diff --git a/js_modules/dagster-ui/packages/ui-core/src/overview/OverviewActivityRoot.tsx b/js_modules/dagster-ui/packages/ui-core/src/overview/OverviewActivityRoot.tsx index 46a545032205c..20a1b5dc18a81 100644 --- a/js_modules/dagster-ui/packages/ui-core/src/overview/OverviewActivityRoot.tsx +++ b/js_modules/dagster-ui/packages/ui-core/src/overview/OverviewActivityRoot.tsx @@ -6,6 +6,7 @@ import {OverviewAssetsRoot} from './OverviewAssetsRoot'; import {OverviewPageHeader} from './OverviewPageHeader'; import {OverviewTabs} from './OverviewTabs'; import {OverviewTimelineRoot} from './OverviewTimelineRoot'; +import {useFeatureFlags} from '../app/Flags'; import {useTrackPageView} from '../app/analytics'; import {useDocumentTitle} from '../hooks/useDocumentTitle'; import {useStateWithStorage} from '../hooks/useStateWithStorage'; @@ -22,13 +23,19 @@ export const OverviewActivityRoot = () => { [], ); - const [defaultTab, setDefaultTab] = useStateWithStorage<'timeline' | 'assets'>( + const [_defaultTab, setDefaultTab] = useStateWithStorage<'timeline' | 'assets'>( 'overview-activity-tab', (json) => (['timeline', 'assets'].includes(json) ? json : 'timeline'), ); + const {flagUseNewAssetHealthOverviewPage} = useFeatureFlags(); + const defaultTab = flagUseNewAssetHealthOverviewPage ? 'timeline' : _defaultTab; + const tabButton = React.useCallback( ({selected}: {selected: 'timeline' | 'assets'}) => { + if (flagUseNewAssetHealthOverviewPage) { + return null; + } if (defaultTab !== selected) { setDefaultTab(selected); } @@ -43,15 +50,17 @@ export const OverviewActivityRoot = () => { ); }, - [defaultTab, setDefaultTab], + [defaultTab, setDefaultTab, flagUseNewAssetHealthOverviewPage], ); return ( - - - + {!flagUseNewAssetHealthOverviewPage && ( + + + + )} diff --git a/js_modules/dagster-ui/packages/ui-core/src/overview/OverviewTabs.tsx b/js_modules/dagster-ui/packages/ui-core/src/overview/OverviewTabs.tsx index e031647e680f7..9b51b7737b0a1 100644 --- a/js_modules/dagster-ui/packages/ui-core/src/overview/OverviewTabs.tsx +++ b/js_modules/dagster-ui/packages/ui-core/src/overview/OverviewTabs.tsx @@ -1,6 +1,7 @@ import {QueryResult} from '@apollo/client'; import {Box, Colors, Spinner, Tabs} from '@dagster-io/ui-components'; +import {useFeatureFlags} from '../app/Flags'; import {QueryRefreshCountdown, QueryRefreshState} from '../app/QueryRefresh'; import {useAutoMaterializeSensorFlag} from '../assets/AutoMaterializeSensorFlag'; import {useAutomaterializeDaemonStatus} from '../assets/useAutomaterializeDaemonStatus'; @@ -17,11 +18,19 @@ export const OverviewTabs = >(props: Props - + {flagUseNewAssetHealthOverviewPage ? ( + + ) : ( + + )} + {flagUseNewAssetHealthOverviewPage && ( + + )} diff --git a/js_modules/dagster-ui/packages/ui-core/src/overview/OverviewTimelineRoot.tsx b/js_modules/dagster-ui/packages/ui-core/src/overview/OverviewTimelineRoot.tsx index 4ac37498b1c15..8864b29798bc3 100644 --- a/js_modules/dagster-ui/packages/ui-core/src/overview/OverviewTimelineRoot.tsx +++ b/js_modules/dagster-ui/packages/ui-core/src/overview/OverviewTimelineRoot.tsx @@ -1,6 +1,7 @@ import {Box, Button, ButtonGroup, ErrorBoundary, TextInput} from '@dagster-io/ui-components'; import * as React from 'react'; +import {useFeatureFlags} from '../app/Flags'; import {FIFTEEN_SECONDS, useQueryRefreshAtInterval} from '../app/QueryRefresh'; import {useTrackPageView} from '../app/analytics'; import {useDocumentTitle} from '../hooks/useDocumentTitle'; @@ -31,7 +32,7 @@ const hourWindowToOffset = (hourWindow: HourWindow) => { type Props = { Header: React.ComponentType<{refreshState: ReturnType}>; - TabButton: React.ComponentType<{selected: 'timeline' | 'assets'}>; + TabButton?: React.ComponentType<{selected: 'timeline' | 'assets'}>; }; export const OverviewTimelineRoot = ({Header, TabButton}: Props) => { @@ -113,7 +114,7 @@ export const OverviewTimelineRoot = ({Header, TabButton}: Props) => { flex={{alignItems: 'center', justifyContent: 'space-between'}} > - + {TabButton && } {allRepos.length > 1 && } { if (loading && !data) {