diff --git a/superset-frontend/package-lock.json b/superset-frontend/package-lock.json index d80c4535cd8b..f65873c10df0 100644 --- a/superset-frontend/package-lock.json +++ b/superset-frontend/package-lock.json @@ -43,7 +43,7 @@ "@rjsf/utils": "^5.24.3", "@rjsf/validator-ajv8": "^5.24.13", "@scarf/scarf": "^1.4.0", - "@stacklet/ui": "^2.15.0", + "@stacklet/ui": "^2.16.0", "@superset-ui/chart-controls": "file:./packages/superset-ui-chart-controls", "@superset-ui/core": "file:./packages/superset-ui-core", "@superset-ui/legacy-plugin-chart-calendar": "file:./plugins/legacy-plugin-chart-calendar", @@ -10471,9 +10471,9 @@ } }, "node_modules/@stacklet/ui": { - "version": "2.15.0", - "resolved": "https://stacklet-653993915282.d.codeartifact.us-east-1.amazonaws.com/npm/stacklet.client.ui/@stacklet/ui/-/ui-2.15.0.tgz", - "integrity": "sha512-QMHi0UPl0X2K74g929xR6luaBrAM+Dfegljgx3rIrPBShBkPmV6keqXMTWpwJLBMUJY0T2ulN2USEiyB6VQ3Pw==", + "version": "2.16.0", + "resolved": "https://stacklet-653993915282.d.codeartifact.us-east-1.amazonaws.com/npm/stacklet.client.ui/@stacklet/ui/-/ui-2.16.0.tgz", + "integrity": "sha512-sGKpXtMKpIFXr6H1mrgeoxFOhMhZvq0hI08YQvaZbFoPUcJSg/NC45qJz23gE7lxdBJNjRJ7b6FsTsdCFT4OWg==", "hasInstallScript": true, "license": "UNLICENSED", "dependencies": { diff --git a/superset-frontend/package.json b/superset-frontend/package.json index 69e1616907ac..ee4032c8588f 100644 --- a/superset-frontend/package.json +++ b/superset-frontend/package.json @@ -125,6 +125,7 @@ "@rjsf/utils": "^5.24.3", "@rjsf/validator-ajv8": "^5.24.13", "@scarf/scarf": "^1.4.0", + "@stacklet/ui": "^2.16.0", "@superset-ui/chart-controls": "file:./packages/superset-ui-chart-controls", "@superset-ui/core": "file:./packages/superset-ui-core", "@superset-ui/legacy-plugin-chart-calendar": "file:./plugins/legacy-plugin-chart-calendar", @@ -146,7 +147,6 @@ "@superset-ui/plugin-chart-pivot-table": "file:./plugins/plugin-chart-pivot-table", "@superset-ui/plugin-chart-table": "file:./plugins/plugin-chart-table", "@superset-ui/plugin-chart-word-cloud": "file:./plugins/plugin-chart-word-cloud", - "@stacklet/ui": "^2.15.0", "@superset-ui/switchboard": "file:./packages/superset-ui-switchboard", "@types/d3-format": "^3.0.1", "@types/d3-selection": "^3.0.11", diff --git a/superset-frontend/src/stacklet/StackletSidebar.tsx b/superset-frontend/src/stacklet/StackletSidebar.tsx index 91ea1ed03ac4..6227ef52bbc4 100644 --- a/superset-frontend/src/stacklet/StackletSidebar.tsx +++ b/superset-frontend/src/stacklet/StackletSidebar.tsx @@ -35,13 +35,17 @@ import '@fontsource-variable/dm-sans'; import getBootstrapData from 'src/utils/getBootstrapData'; import type { MenuData } from 'src/types/bootstrapTypes'; -import { getAppSelectorOptions, SUPERSET_APP_NAME } from './appSelector'; +import { getAppSelectorOptions, getSupersetAppName } from './appSelector'; import { buildNavItems, resolveActiveNavToken } from './menuItems'; // The design system's stylesheet with every rule pre-scoped under // `.stacklet-ui-scope` — the raw dist/ui.css would restyle all of Superset // (Tailwind preflight + global html/body rules). import '@stacklet/ui/dist/ui.scoped.css'; +// Superset-local layout/type tweaks so "AssetDB (Preview)" fits the sidebar; +// must come after the scoped stylesheet to win the cascade. +import './stackletSidebarOverrides.css'; + /** Matches the sidebar widths baked into the V2Sidebar component. */ export const SIDEBAR_WIDTH = '13.625rem'; export const SIDEBAR_COLLAPSED_WIDTH = '4.5rem'; @@ -258,6 +262,7 @@ export default function StackletSidebar({ [data, user], ); const appSelectorOptions = useMemo(() => getAppSelectorOptions(), []); + const supersetAppName = useMemo(() => getSupersetAppName(), []); const signInMenu = useCurrentUserMenu(data, collapsed, themeControl); // See resolveActiveNavToken: a virtual token stands in for the pathname so // exactly one nav item is highlighted, query-aware for filtered presets. @@ -266,8 +271,16 @@ export default function StackletSidebar({ [navItems, pathname, search], ); + // TEMPORARY — remove together with stackletSidebarOverrides.css once the + // Redash-backed AssetDB is retired: only the longer "AssetDB (Preview)" + // branding needs the sizing overrides that class scopes. + const scopeClassName = + supersetAppName === 'AssetDB (Preview)' + ? 'stacklet-ui-scope stacklet-preview-branding' + : 'stacklet-ui-scope'; + return ( -
+
diff --git a/superset-frontend/src/stacklet/appSelector.test.ts b/superset-frontend/src/stacklet/appSelector.test.ts index 3eb84b4b8368..c8480d312336 100644 --- a/superset-frontend/src/stacklet/appSelector.test.ts +++ b/superset-frontend/src/stacklet/appSelector.test.ts @@ -17,7 +17,7 @@ * under the License. */ import getBootstrapData from 'src/utils/getBootstrapData'; -import { getAppSelectorOptions, SUPERSET_APP_NAME } from './appSelector'; +import { getAppSelectorOptions, getSupersetAppName } from './appSelector'; jest.mock('src/utils/getBootstrapData', () => jest.fn()); @@ -26,18 +26,25 @@ const mockedBootstrap = getBootstrapData as jest.Mock; const bootstrapWith = (common: object) => mockedBootstrap.mockReturnValue({ common }); -test('builds entries from the platform-injected stacklet.urls', () => { +test('brands itself "AssetDB (Preview)" while the Redash-backed AssetDB exists', () => { bootstrapWith({ stacklet: { urls: { console: 'https://console.acme.stacklet.io', + redash: 'https://redash.acme.stacklet.io', sinistral: 'https://sinistral.acme.stacklet.io', superset: 'https://superset.acme.stacklet.io', }, }, }); + expect(getSupersetAppName()).toBe('AssetDB (Preview)'); expect(getAppSelectorOptions()).toEqual([ - { label: SUPERSET_APP_NAME, href: '', isBeta: false }, + { label: 'AssetDB (Preview)', href: '', isBeta: false }, + { + label: 'AssetDB', + href: 'https://redash.acme.stacklet.io', + isBeta: false, + }, { label: 'Console', href: 'https://console.acme.stacklet.io', @@ -47,20 +54,36 @@ test('builds entries from the platform-injected stacklet.urls', () => { ]); }); -test('omits siblings whose URL is not configured', () => { +test('brands itself plain "AssetDB" when Redash is not deployed', () => { bootstrapWith({ - stacklet: { urls: { console: 'https://console.acme.stacklet.io' } }, + stacklet: { + urls: { + console: 'https://console.acme.stacklet.io', + superset: 'https://superset.acme.stacklet.io', + }, + }, }); - expect(getAppSelectorOptions().map(option => option.label)).toEqual([ - SUPERSET_APP_NAME, - 'Console', + expect(getSupersetAppName()).toBe('AssetDB'); + expect(getAppSelectorOptions()).toEqual([ + { label: 'AssetDB', href: '', isBeta: false }, + { + label: 'Console', + href: 'https://console.acme.stacklet.io', + isBeta: false, + }, ]); }); test('falls back to the dev platform when the payload has no stacklet.urls', () => { bootstrapWith({}); + // The dev fallback includes a Redash URL, so the Preview name applies expect(getAppSelectorOptions()).toEqual([ - { label: SUPERSET_APP_NAME, href: '', isBeta: false }, + { label: 'AssetDB (Preview)', href: '', isBeta: false }, + { + label: 'AssetDB', + href: 'https://redash.dev.stacklet.dev', + isBeta: false, + }, { label: 'Console', href: 'https://console.dev.stacklet.dev', diff --git a/superset-frontend/src/stacklet/appSelector.ts b/superset-frontend/src/stacklet/appSelector.ts index 9923da89014d..b299d665e34c 100644 --- a/superset-frontend/src/stacklet/appSelector.ts +++ b/superset-frontend/src/stacklet/appSelector.ts @@ -18,13 +18,6 @@ */ import getBootstrapData from 'src/utils/getBootstrapData'; -/** - * How this Superset deployment is branded in the Stacklet platform's app - * switcher (Design Kit v2, node 6820-6796). Also part of @stacklet/ui's - * `V2AppName` union, so it can be passed to the Sidebar without casts. - */ -export const SUPERSET_APP_NAME = 'AssetDB'; - export interface StackletAppOption { label: string; href: string; @@ -50,19 +43,40 @@ interface StackletBootstrapExtras { */ const DEV_FALLBACK_URLS: Partial> = { console: 'https://console.dev.stacklet.dev', + redash: 'https://redash.dev.stacklet.dev', sinistral: 'https://sinistral.dev.stacklet.dev', }; +function getStackletUrls(): Partial> { + const common = getBootstrapData().common as StackletBootstrapExtras; + return common?.stacklet?.urls ?? DEV_FALLBACK_URLS; +} + +/** + * How this Superset deployment is branded in the Stacklet platform's app + * switcher (Design Kit v2, node 6820-6796). Plain "AssetDB" once it is the + * only AssetDB in the environment; "(Preview)" while the Redash-backed + * AssetDB is still deployed alongside it. Both names are part of + * @stacklet/ui's `V2AppName` union, so the result can be passed to the + * Sidebar without casts. + */ +export function getSupersetAppName(): 'AssetDB' | 'AssetDB (Preview)' { + return getStackletUrls().redash ? 'AssetDB (Preview)' : 'AssetDB'; +} + /** * Builds the app switcher entries for the Stacklet sidebar. Superset itself - * (branded AssetDB) is always present and selected; siblings appear only - * when their URL is configured. + * is always present and selected; siblings — including the Redash-backed + * "AssetDB" — appear only when their URL is configured. The Sidebar renders + * the selected app first regardless of this order. */ export function getAppSelectorOptions(): StackletAppOption[] { - const common = getBootstrapData().common as StackletBootstrapExtras; - const urls = common?.stacklet?.urls ?? DEV_FALLBACK_URLS; + const urls = getStackletUrls(); return [ - { label: SUPERSET_APP_NAME, href: '', isBeta: false }, + { label: getSupersetAppName(), href: '', isBeta: false }, + ...(urls.redash + ? [{ label: 'AssetDB', href: urls.redash, isBeta: false }] + : []), ...(urls.console ? [{ label: 'Console', href: urls.console, isBeta: false }] : []), diff --git a/superset-frontend/src/stacklet/stackletSidebarOverrides.css b/superset-frontend/src/stacklet/stackletSidebarOverrides.css new file mode 100644 index 000000000000..151e3756ccf3 --- /dev/null +++ b/superset-frontend/src/stacklet/stackletSidebarOverrides.css @@ -0,0 +1,64 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +/* + * TEMPORARY — delete this file (and its import plus the + * `stacklet-preview-branding` class in StackletSidebar.tsx) once the + * Redash-backed AssetDB is retired: the app is then branded plain "AssetDB", + * which fits the sidebar with the component defaults. + * + * Superset-local tweaks on top of @stacklet/ui's scoped stylesheet (imported + * right before this file in StackletSidebar.tsx), so the longer + * "AssetDB (Preview)" pill fits the sidebar without touching the shared + * component. `stacklet-preview-branding` is stamped on the scope wrapper only + * while the Redash URL is configured — the only case that gets the long name. + * Selectors anchor on the trigger's aria-label rather than the dist's utility + * class names, which are minified build artifacts. + */ + +/* Header row holding the logo and the app selector: tighter padding and gap + than the component defaults, freeing width for the longer name. */ +.stacklet-ui-scope.stacklet-preview-branding + div:has(> div > button[aria-label^='App selector']) { + padding: 16px; + column-gap: 6px; +} + +/* Slightly smaller logo, matching the reduced type next to it. */ +.stacklet-ui-scope.stacklet-preview-branding + div:has(> div > button[aria-label^='App selector']) + > a + > svg { + width: 24px; + height: auto; +} + +/* The trigger lays out label and tile icon on the text baseline; with the + smaller type below the icon rides too high, so center-align instead. */ +.stacklet-ui-scope.stacklet-preview-branding + button[aria-label^='App selector'] { + align-items: center; +} + +/* Smaller label type so the full name fits the expanded sidebar width. */ +.stacklet-ui-scope.stacklet-preview-branding + button[aria-label^='App selector'] + .whitespace-nowrap { + font-size: 12px; +}