Compare commits

..

1 Commits

Author SHA1 Message Date
Claude Code
418abb4de0 test(filters): pin numeric filter values sort numerically, not lexicographically (#36775)
Closes #36775

Adds a regression test on PluginFilterSelect confirming that when no
sortMetric is configured, numeric filter-value options are ordered by
their numeric value (2, 10, 100) rather than by lexicographic
comparison of their formatted string labels (10, 100, 2).

Root cause: SelectFilterPlugin's sortComparator always compares the
formatted `label` via propertyComparator('label'). getDataRecordFormatter
(superset-frontend/src/filters/utils.ts) always returns a string, so
numeric columns hit propertyComparator's string branch (localeCompare)
instead of its numeric branch, even though the values themselves are
numeric. This is the second recurrence of the #35008/#34858 fix, which
patched SelectControl.jsx (chart-builder numeric selects like Row limit)
but never touched this native-filter value dropdown.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-29 17:54:05 -07:00
2 changed files with 85 additions and 82 deletions

View File

@@ -1,9 +1,6 @@
# This workflow automates the release process for Helm charts.
# Each run force-recreates a single 'helm-publish' branch from the tip of 'gh-pages' and
# opens (or reuses) one pull request against 'gh-pages', allowing the changes to be
# reviewed and merged manually. Because chart-releaser rebuilds index.yaml from all
# published GitHub releases, the branch always contains every chart released since the
# last merge, and the PR can never go stale or conflict with gh-pages.
# The workflow creates a new branch for the release and opens a pull request against the 'gh-pages' branch,
# allowing the changes to be reviewed and merged manually.
name: "Helm: release charts"
@@ -21,12 +18,6 @@ on:
required: false
default: "master"
# Serialize runs: concurrent runs would race on force-pushing the shared
# helm-publish branch while chart-releaser is mid-release.
concurrency:
group: helm-release
cancel-in-progress: false
jobs:
release:
runs-on: ubuntu-26.04
@@ -66,9 +57,9 @@ jobs:
echo "DEBUG TAGS"
git show-ref --tags
- name: Set pages branch name
- name: Create unique pages branch name
id: vars
run: echo "branch_name=helm-publish" >> $GITHUB_ENV
run: echo "branch_name=helm-publish-${GITHUB_SHA:0:7}" >> $GITHUB_ENV
- name: Force recreate branch from gh-pages
env:
@@ -106,17 +97,12 @@ jobs:
version: v1.6.0
charts_dir: helm
mark_as_latest: false
# A helm/** change without a Chart.yaml version bump repackages the
# already-released version; without this, cr aborts on the existing
# release tag (422 already_exists) instead of proceeding to rebuild
# the index, and the whole run fails.
skip_existing: true
pages_branch: ${{ env.branch_name }}
env:
CR_TOKEN: "${{ github.token }}"
CR_RELEASE_NAME_TEMPLATE: "superset-helm-chart-{{ .Version }}"
- name: Open or reuse Pull Request
- name: Open Pull Request
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
@@ -127,72 +113,15 @@ jobs:
throw new Error("Branch name is not defined.");
}
// The branch is force-recreated from gh-pages on every run, so an
// already-open PR for it now reflects this run's charts; opening
// another would both fail (422) and recreate the stale-PR pileup
// this fixed branch exists to avoid.
const { data: existing } = await github.rest.pulls.list({
const pr = await github.rest.pulls.create({
owner,
repo,
state: "open",
head: `${owner}:${branchName}`,
base: "gh-pages",
title: `Helm chart release for ${branchName}`,
head: branchName,
base: "gh-pages", // Adjust if the target branch is different
body: `This PR releases Helm charts to the gh-pages branch.`,
});
let current;
if (existing.length > 0) {
current = existing[0];
core.info(`Reusing existing pull request: ${current.html_url}`);
} else {
const { data: pr } = await github.rest.pulls.create({
owner,
repo,
title: "Helm chart release",
head: branchName,
base: "gh-pages", // Adjust if the target branch is different
body: [
"This PR releases Helm charts to the gh-pages branch.",
"",
"It is force-updated from the tip of `gh-pages` by every release run,",
"so it always contains every chart released since the last merge and",
"never needs to be closed as superseded.",
].join("\n"),
});
current = pr;
core.info(`Pull request created: ${current.html_url}`);
}
// Sweep release PRs left open by older runs (per-SHA helm-publish-*
// branches from the previous scheme). Their content is a subset of
// the evergreen PR, so close them with a pointer to it.
const { data: openPrs } = await github.rest.pulls.list({
owner,
repo,
state: "open",
base: "gh-pages",
per_page: 100,
});
for (const stale of openPrs) {
if (
stale.number !== current.number &&
stale.head.repo?.full_name === process.env.GITHUB_REPOSITORY &&
stale.head.ref.startsWith("helm-publish")
) {
await github.rest.issues.createComment({
owner,
repo,
issue_number: stale.number,
body: `Superseded by #${current.number}, which now carries all unreleased charts. Closing.`,
});
await github.rest.pulls.update({
owner,
repo,
pull_number: stale.number,
state: "closed",
});
core.info(`Closed superseded release PR #${stale.number}`);
}
}
core.info(`Pull request created: ${pr.data.html_url}`);
env:
BRANCH_NAME: ${{ env.branch_name }}

View File

@@ -720,6 +720,80 @@ describe('SelectFilterPlugin', () => {
expect(options[2]).toHaveTextContent('beta');
});
test('sorts numeric filter values numerically, not lexicographically, when no sortMetric is specified', () => {
// Regression for #36775: numeric filter values were sorted as strings
// (localeCompare on the formatted label), producing "1, 10, 100, 2"
// instead of the expected "1, 2, 10, 100".
const testData = [{ age: 10 }, { age: 2 }, { age: 100 }];
const testProps = {
...selectMultipleProps,
formData: {
...selectMultipleProps.formData,
groupby: ['age'],
sortMetric: undefined,
sortAscending: true,
},
queriesData: [
{
rowcount: 3,
colnames: ['age'],
coltypes: [0],
data: testData,
applied_filters: [{ column: 'age' }],
rejected_filters: [],
},
],
filterState: {
value: [],
label: '',
excludeFilterValues: true,
},
};
render(
// @ts-expect-error
<SelectFilterPlugin
// @ts-expect-error
{...transformProps(testProps)}
setDataMask={jest.fn()}
showOverflow={false}
/>,
{
useRedux: true,
initialState: {
nativeFilters: {
filters: {
'test-filter': {
name: 'Test Filter',
},
},
},
dataMask: {
'test-filter': {
extraFormData: {},
filterState: {
value: [],
label: '',
excludeFilterValues: true,
},
},
},
},
},
);
const filterSelect = screen.getAllByRole('combobox')[0];
userEvent.click(filterSelect);
// Options should appear in ascending numeric order (2, 10, 100), not
// ascending lexicographic order of their formatted labels (10, 100, 2).
const options = screen.getAllByRole('option');
expect(options[0]).toHaveTextContent('2');
expect(options[1]).toHaveTextContent('10');
expect(options[2]).toHaveTextContent('100');
});
test('shows create option for multi-select creatable filter when typing', async () => {
getWrapper({ creatable: true, multiSelect: true });
userEvent.type(screen.getByRole('combobox'), 'brand-new');