From 042314f5626719cc34e1708d288066b4357c8a91 Mon Sep 17 00:00:00 2001 From: Dariusz Szymczyk Date: Fri, 31 Jul 2026 11:20:57 +0200 Subject: [PATCH 1/2] feat(stacklet): show exact image SHA in the user menu The Stacklet image is tagged with the full fork commit SHA, so surfacing the untruncated VERSION_SHA under the Superset version in the sidebar's user menu identifies the exact running image from the UI. Co-Authored-By: Claude Fable 5 --- STACKLET_IMAGE.md | 4 + .../src/stacklet/StackletSidebar.test.tsx | 77 +++++++++++++++++++ .../src/stacklet/StackletSidebar.tsx | 15 +++- .../src/stacklet/__mocks__/stackletUiMock.tsx | 17 +++- superset/config.py | 4 +- 5 files changed, 110 insertions(+), 7 deletions(-) create mode 100644 superset-frontend/src/stacklet/StackletSidebar.test.tsx diff --git a/STACKLET_IMAGE.md b/STACKLET_IMAGE.md index 28c4742f4af7..7d1b8ffb99f6 100644 --- a/STACKLET_IMAGE.md +++ b/STACKLET_IMAGE.md @@ -38,3 +38,7 @@ skipped when an image already exists for that SHA. Each image also carries OCI labels: `org.opencontainers.image.source`, `org.opencontainers.image.revision`, `io.stacklet.superset.upstream_version`, and `io.stacklet.superset.stacklet_ref`. + +The full commit SHA baked into the image is shown in the sidebar's user menu +(under the Superset version), so the exact running image can be identified +from the UI. diff --git a/superset-frontend/src/stacklet/StackletSidebar.test.tsx b/superset-frontend/src/stacklet/StackletSidebar.test.tsx new file mode 100644 index 000000000000..43325fe4d5d7 --- /dev/null +++ b/superset-frontend/src/stacklet/StackletSidebar.test.tsx @@ -0,0 +1,77 @@ +/** + * 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. + */ +import { render, screen } from 'spec/helpers/testing-library'; +import type { MenuData } from 'src/types/bootstrapTypes'; +import StackletSidebar from './StackletSidebar'; + +const FULL_SHA = '56cb0a6d107bc5f7e732b270d7d0e66ed5bb434a'; + +function menuData(navbarRightOverrides: Record): MenuData { + return { + brand: { path: '/', icon: '', alt: 'Superset', tooltip: '', text: '' }, + navbar_right: { + show_watermark: false, + languages: {}, + show_language_picker: false, + user_is_anonymous: false, + user_info_url: '/users/userinfo/', + user_login_url: '/login/', + user_logout_url: '/logout/', + locale: 'en', + ...navbarRightOverrides, + }, + menu: [], + settings: [], + environment_tag: { text: '', color: '' }, + } as unknown as MenuData; +} + +function renderSidebar(navbarRightOverrides: Record) { + return render( + , + ); +} + +test('shows the version and the full image SHA in the user menu', () => { + renderSidebar({ version_string: '6.1.0', version_sha: FULL_SHA }); + + expect(screen.getByText('Superset 6.1.0')).toBeInTheDocument(); + expect(screen.getByText(`SHA: ${FULL_SHA}`)).toBeInTheDocument(); +}); + +test('shows the SHA even without a version string', () => { + renderSidebar({ version_sha: FULL_SHA }); + + expect(screen.getByText(`SHA: ${FULL_SHA}`)).toBeInTheDocument(); + expect(screen.queryByText(/^Superset /)).not.toBeInTheDocument(); +}); + +test('omits the version block when neither version nor SHA is set', () => { + renderSidebar({}); + + expect(screen.queryByText(/^Superset /)).not.toBeInTheDocument(); + expect(screen.queryByText(/SHA:/)).not.toBeInTheDocument(); +}); diff --git a/superset-frontend/src/stacklet/StackletSidebar.tsx b/superset-frontend/src/stacklet/StackletSidebar.tsx index 479be2957913..dbaa81e09530 100644 --- a/superset-frontend/src/stacklet/StackletSidebar.tsx +++ b/superset-frontend/src/stacklet/StackletSidebar.tsx @@ -184,9 +184,20 @@ function useCurrentUserMenu( {t('User info')} {themeControl} - {navbarRight.version_string ? ( + {navbarRight.version_string || navbarRight.version_sha ? (
- Superset {navbarRight.version_string} + {navbarRight.version_string ? ( +
{t('Superset %s', navbarRight.version_string)}
+ ) : null} + {navbarRight.version_sha ? ( + // The full commit SHA doubles as the image tag, so it identifies + // the exact running image. It is longer than the menu, hence the + // inline word-break (the scoped stylesheet ships only the + // utilities the design system itself uses). +
+ {t('SHA')}: {navbarRight.version_sha} +
+ ) : null}
) : null} diff --git a/superset-frontend/src/stacklet/__mocks__/stackletUiMock.tsx b/superset-frontend/src/stacklet/__mocks__/stackletUiMock.tsx index b4b88ed94d6c..87e68012293e 100644 --- a/superset-frontend/src/stacklet/__mocks__/stackletUiMock.tsx +++ b/superset-frontend/src/stacklet/__mocks__/stackletUiMock.tsx @@ -17,13 +17,22 @@ * under the License. */ +import type { ReactNode } from 'react'; + // Jest stand-in for the prebuilt (ESM-only) @stacklet/ui package, mapped via // moduleNameMapper in jest.config.js. Only the exports consumed by -// src/stacklet need to exist here. -export const Sidebar = () => null; -export const UserMenu = () => null; +// src/stacklet need to exist here; the user-menu chain renders its content +// so tests can assert on it. +export const Sidebar = ({ SignInMenu }: { SignInMenu?: ReactNode }) => ( + <>{SignInMenu} +); +export const UserMenu = ({ children }: { children?: ReactNode }) => ( + <>{children} +); export const AppSelector = () => null; -export const Button = () => null; +export const Button = ({ children }: { children?: ReactNode }) => ( + +); // @stacklet/ui/icons (see src/stacklet/menuItems.ts) const MockIcon = () => null; diff --git a/superset/config.py b/superset/config.py index 7477f7f4f9ec..cd2319fc47be 100644 --- a/superset/config.py +++ b/superset/config.py @@ -149,7 +149,9 @@ def _try_json_readsha(filepath: str, length: int) -> str | None: PACKAGE_JSON_FILE ) -VERSION_SHA_LENGTH = 8 +# Stacklet: keep the full 40-character SHA — images are tagged with the exact +# fork commit SHA, so the untruncated value identifies the running image. +VERSION_SHA_LENGTH = 40 VERSION_SHA = _try_json_readsha(VERSION_INFO_FILE, VERSION_SHA_LENGTH) # Build number is shown in the About section if available. This From 2ccfd709a5ce251459871edc53f41ca15ae1eeec Mon Sep 17 00:00:00 2001 From: Dariusz Szymczyk Date: Fri, 31 Jul 2026 11:23:23 +0200 Subject: [PATCH 2/2] refactor(stacklet): reduce to a UI-only change Drop the unit tests, the VERSION_SHA_LENGTH bump and the STACKLET_IMAGE.md note; the user menu simply shows the default 8-character SHA. Co-Authored-By: Claude Fable 5 --- STACKLET_IMAGE.md | 4 - .../src/stacklet/StackletSidebar.test.tsx | 77 ------------------- .../src/stacklet/StackletSidebar.tsx | 6 +- .../src/stacklet/__mocks__/stackletUiMock.tsx | 17 +--- superset/config.py | 4 +- 5 files changed, 6 insertions(+), 102 deletions(-) delete mode 100644 superset-frontend/src/stacklet/StackletSidebar.test.tsx diff --git a/STACKLET_IMAGE.md b/STACKLET_IMAGE.md index 7d1b8ffb99f6..28c4742f4af7 100644 --- a/STACKLET_IMAGE.md +++ b/STACKLET_IMAGE.md @@ -38,7 +38,3 @@ skipped when an image already exists for that SHA. Each image also carries OCI labels: `org.opencontainers.image.source`, `org.opencontainers.image.revision`, `io.stacklet.superset.upstream_version`, and `io.stacklet.superset.stacklet_ref`. - -The full commit SHA baked into the image is shown in the sidebar's user menu -(under the Superset version), so the exact running image can be identified -from the UI. diff --git a/superset-frontend/src/stacklet/StackletSidebar.test.tsx b/superset-frontend/src/stacklet/StackletSidebar.test.tsx deleted file mode 100644 index 43325fe4d5d7..000000000000 --- a/superset-frontend/src/stacklet/StackletSidebar.test.tsx +++ /dev/null @@ -1,77 +0,0 @@ -/** - * 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. - */ -import { render, screen } from 'spec/helpers/testing-library'; -import type { MenuData } from 'src/types/bootstrapTypes'; -import StackletSidebar from './StackletSidebar'; - -const FULL_SHA = '56cb0a6d107bc5f7e732b270d7d0e66ed5bb434a'; - -function menuData(navbarRightOverrides: Record): MenuData { - return { - brand: { path: '/', icon: '', alt: 'Superset', tooltip: '', text: '' }, - navbar_right: { - show_watermark: false, - languages: {}, - show_language_picker: false, - user_is_anonymous: false, - user_info_url: '/users/userinfo/', - user_login_url: '/login/', - user_logout_url: '/logout/', - locale: 'en', - ...navbarRightOverrides, - }, - menu: [], - settings: [], - environment_tag: { text: '', color: '' }, - } as unknown as MenuData; -} - -function renderSidebar(navbarRightOverrides: Record) { - return render( - , - ); -} - -test('shows the version and the full image SHA in the user menu', () => { - renderSidebar({ version_string: '6.1.0', version_sha: FULL_SHA }); - - expect(screen.getByText('Superset 6.1.0')).toBeInTheDocument(); - expect(screen.getByText(`SHA: ${FULL_SHA}`)).toBeInTheDocument(); -}); - -test('shows the SHA even without a version string', () => { - renderSidebar({ version_sha: FULL_SHA }); - - expect(screen.getByText(`SHA: ${FULL_SHA}`)).toBeInTheDocument(); - expect(screen.queryByText(/^Superset /)).not.toBeInTheDocument(); -}); - -test('omits the version block when neither version nor SHA is set', () => { - renderSidebar({}); - - expect(screen.queryByText(/^Superset /)).not.toBeInTheDocument(); - expect(screen.queryByText(/SHA:/)).not.toBeInTheDocument(); -}); diff --git a/superset-frontend/src/stacklet/StackletSidebar.tsx b/superset-frontend/src/stacklet/StackletSidebar.tsx index dbaa81e09530..91ea1ed03ac4 100644 --- a/superset-frontend/src/stacklet/StackletSidebar.tsx +++ b/superset-frontend/src/stacklet/StackletSidebar.tsx @@ -190,11 +190,7 @@ function useCurrentUserMenu(
{t('Superset %s', navbarRight.version_string)}
) : null} {navbarRight.version_sha ? ( - // The full commit SHA doubles as the image tag, so it identifies - // the exact running image. It is longer than the menu, hence the - // inline word-break (the scoped stylesheet ships only the - // utilities the design system itself uses). -
+
{t('SHA')}: {navbarRight.version_sha}
) : null} diff --git a/superset-frontend/src/stacklet/__mocks__/stackletUiMock.tsx b/superset-frontend/src/stacklet/__mocks__/stackletUiMock.tsx index 87e68012293e..b4b88ed94d6c 100644 --- a/superset-frontend/src/stacklet/__mocks__/stackletUiMock.tsx +++ b/superset-frontend/src/stacklet/__mocks__/stackletUiMock.tsx @@ -17,22 +17,13 @@ * under the License. */ -import type { ReactNode } from 'react'; - // Jest stand-in for the prebuilt (ESM-only) @stacklet/ui package, mapped via // moduleNameMapper in jest.config.js. Only the exports consumed by -// src/stacklet need to exist here; the user-menu chain renders its content -// so tests can assert on it. -export const Sidebar = ({ SignInMenu }: { SignInMenu?: ReactNode }) => ( - <>{SignInMenu} -); -export const UserMenu = ({ children }: { children?: ReactNode }) => ( - <>{children} -); +// src/stacklet need to exist here. +export const Sidebar = () => null; +export const UserMenu = () => null; export const AppSelector = () => null; -export const Button = ({ children }: { children?: ReactNode }) => ( - -); +export const Button = () => null; // @stacklet/ui/icons (see src/stacklet/menuItems.ts) const MockIcon = () => null; diff --git a/superset/config.py b/superset/config.py index cd2319fc47be..7477f7f4f9ec 100644 --- a/superset/config.py +++ b/superset/config.py @@ -149,9 +149,7 @@ def _try_json_readsha(filepath: str, length: int) -> str | None: PACKAGE_JSON_FILE ) -# Stacklet: keep the full 40-character SHA — images are tagged with the exact -# fork commit SHA, so the untruncated value identifies the running image. -VERSION_SHA_LENGTH = 40 +VERSION_SHA_LENGTH = 8 VERSION_SHA = _try_json_readsha(VERSION_INFO_FILE, VERSION_SHA_LENGTH) # Build number is shown in the About section if available. This