diff --git a/js_modules/dagster-ui/packages/ui-components/src/palettes/Color.tsx b/js_modules/dagster-ui/packages/ui-components/src/palettes/Color.tsx index 535e02bfb1ad8..d24b096aec83b 100644 --- a/js_modules/dagster-ui/packages/ui-components/src/palettes/Color.tsx +++ b/js_modules/dagster-ui/packages/ui-components/src/palettes/Color.tsx @@ -116,4 +116,5 @@ export { colorDataVizVioletAlt as dataVizVioletAlt, colorDataVizYellow as dataVizYellow, colorDataVizYellowAlt as dataVizYellowAlt, + replaceAlpha, } from '../theme/color'; diff --git a/js_modules/dagster-ui/packages/ui-components/src/theme/color.tsx b/js_modules/dagster-ui/packages/ui-components/src/theme/color.tsx index 742ae56f8fb27..24cecc2a4746d 100644 --- a/js_modules/dagster-ui/packages/ui-components/src/theme/color.tsx +++ b/js_modules/dagster-ui/packages/ui-components/src/theme/color.tsx @@ -8,6 +8,29 @@ const getColor = memoize((semanticName: ColorName): string => { return palette[semanticName]; }); +export function replaceAlpha(rgbaString: string, newAlpha: number) { + // Check if the input string is in the correct format + const rgbaRegex = /^rgba?\((\s*\d+\s*),(\s*\d+\s*),(\s*\d+\s*),(\s*[\d.]+\s*)\)$/; + const match = rgbaString.match(rgbaRegex); + if (!match) { + console.error('Invalid RGBA string format.'); + return null; + } + + // Extract RGB values + const red = parseInt(match[1]!.trim(), 10); + const green = parseInt(match[2]!.trim(), 10); + const blue = parseInt(match[3]!.trim(), 10); + + // Ensure alpha value is within the range [0, 1] + const clampedAlpha = Math.max(0, Math.min(newAlpha, 1)); + + // Construct the new RGBA string with the updated alpha value + const newRgbaString = `rgba(${red},${green},${blue},${clampedAlpha})`; + + return newRgbaString; +} + export const browserColorScheme = () => getColor(ColorName.BrowserColorScheme); export const colorKeylineDefault = () => getColor(ColorName.KeylineDefault); export const colorLinkDefault = () => getColor(ColorName.LinkDefault); diff --git a/js_modules/dagster-ui/packages/ui-core/src/asset-graph/AssetNode.tsx b/js_modules/dagster-ui/packages/ui-core/src/asset-graph/AssetNode.tsx index 2148119e451cf..009c015246377 100644 --- a/js_modules/dagster-ui/packages/ui-core/src/asset-graph/AssetNode.tsx +++ b/js_modules/dagster-ui/packages/ui-core/src/asset-graph/AssetNode.tsx @@ -7,7 +7,6 @@ import styled, {CSSObject} from 'styled-components'; import {AssetNodeMenuProps, useAssetNodeMenu} from './AssetNodeMenu'; import {buildAssetNodeStatusContent} from './AssetNodeStatusContent'; -import {AssetLatestRunSpinner} from './AssetRunLinking'; import {ContextMenuWrapper} from './ContextMenuWrapper'; import {LiveDataForNode} from './Utils'; import {ASSET_NODE_NAME_MAX_LENGTH} from './layout'; @@ -32,7 +31,6 @@ export const AssetNode = React.memo(({definition, selected}: Props) => { const isSource = definition.isSource; const {liveData} = useAssetLiveData(definition.assetKey); - return ( @@ -179,6 +177,9 @@ export const AssetNodeMinimal = ({ const isChanged = definition.changedReasons.length; const isStale = isAssetStale(assetKey, liveData, 'upstream'); + const queuedRuns = liveData?.unstartedRunIds.length; + const inProgressRuns = liveData?.inProgressRunIds.length; + return ( @@ -193,6 +194,8 @@ export const AssetNodeMinimal = ({ $isSource={isSource} $background={background} $border={border} + $inProgress={!!inProgressRuns} + $isQueued={!!queuedRuns} > {isChanged ? ( ) : null} - - - {withMiddleTruncation(displayName, {maxLength: 20})} @@ -323,12 +323,6 @@ const AssetName = styled.div<{$isSource: boolean}>` border-top-right-radius: 8px; `; -const AssetNodeSpinnerContainer = styled.div` - top: 50%; - position: absolute; - transform: translate(8px, -16px); -`; - const MinimalAssetNodeContainer = styled(AssetNodeContainer)` height: 100%; `; @@ -338,13 +332,61 @@ const MinimalAssetNodeBox = styled.div<{ $selected: boolean; $background: string; $border: string; + $inProgress: boolean; + $isQueued: boolean; }>` background: ${(p) => p.$background}; + overflow: hidden; ${(p) => p.$isSource ? `border: 4px dashed ${p.$selected ? Colors.accentGray() : p.$border}` : `border: 4px solid ${p.$selected ? Colors.lineageNodeBorderSelected() : p.$border}`}; - + ${(p) => + p.$inProgress + ? ` + background-color: ${p.$background}; + &::after { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + transform: translateX(-100%); + background-image: linear-gradient(90deg, ${Colors.replaceAlpha( + p.$background, + 0, + )} 0, ${Colors.replaceAlpha(p.$background, 0)} 0%, ${Colors.replaceAlpha( + p.$background, + 0.2, + )}); + animation: shimmer 1.5s infinite; + content: ''; + } + + @keyframes shimmer { + 100% { + transform: translateX(100%); + } + } + ` + : ''} + + ${(p) => { + if (p.$isQueued) { + return ` + animation: pulse 0.5s infinite alternate; + @keyframes pulse { + 0% { + border-color: ${Colors.replaceAlpha(p.$border, 0.2)}; + } + 100% { + border-color: ${Colors.replaceAlpha(p.$border, 1)}; + } + } + `; + } + return ''; + }} border-radius: 16px; position: relative; padding: 2px;