mirror of
https://github.com/apache/superset.git
synced 2026-05-21 15:55:10 +00:00
feat(plugin-chart-country-map): friendlier labels + auto-migrate from legacy plugin
Two small UX wins on top of the previous control-panel fixes:
1. Rename admin_level options to "World" / "Country" / "Aggregated
regions" (from the technical "Countries (Admin 0)" / "Subdivisions
(Admin 1)"). The control's own label becomes "Map view" with an
explanatory description. Underlying form_data values stay 0 / 1 /
aggregated so saved charts don't break.
2. Auto-migrate form_data when a user switches a saved legacy
country_map chart's viz type to country_map_v2:
- select_country: 'france' → admin_level=1, country=FRA
- select_country: 'france_overseas' → composite=france_overseas
- select_country: 'turkey_regions' → admin_level=aggregated,
country=TUR, region_set=nuts_1
- …same pattern for france_regions / italy_regions /
philippines_regions and ~180 other legacy country files.
The mapping lives in src/plugin/migrateFromLegacy.ts (covered by
12 unit tests). Migration only fills fields the user hasn't already
set on the new chart, so explicit edits win over inferred values.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -25,6 +25,7 @@ import {
|
||||
getStandardizedControls,
|
||||
} from '@superset-ui/chart-controls';
|
||||
import manifest from '../data/manifest.json';
|
||||
import migrateFromLegacy from './migrateFromLegacy';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Choice tables — sourced from the build pipeline's manifest.json
|
||||
@@ -96,9 +97,11 @@ const WORLDVIEW_CHOICES: Array<[string, string]> = M.worldviews.map(wv => [
|
||||
WORLDVIEW_LABELS[wv] || wv,
|
||||
]);
|
||||
|
||||
// Map scope choices. The underlying values stay 0/1/aggregated so saved
|
||||
// charts and form_data don't break — only the labels change for clarity.
|
||||
const ADMIN_LEVEL_CHOICES: Array<[string, string]> = [
|
||||
[String(0), t('Countries (Admin 0)')],
|
||||
[String(1), t('Subdivisions (Admin 1)')],
|
||||
[String(0), t('World')],
|
||||
[String(1), t('Country')],
|
||||
['aggregated', t('Aggregated regions')],
|
||||
];
|
||||
|
||||
@@ -316,10 +319,13 @@ const config: ControlPanelConfig = {
|
||||
name: 'admin_level',
|
||||
config: {
|
||||
type: 'SelectControl',
|
||||
label: t('Admin level'),
|
||||
label: t('Map view'),
|
||||
description: t(
|
||||
'Choose the geographic level: countries (world map), ' +
|
||||
'subdivisions of one country, or an aggregated regional layer.',
|
||||
'World shows all countries; Country shows subdivisions of ' +
|
||||
'one country (states/provinces/departments); Aggregated ' +
|
||||
"regions dissolves a country's subdivisions into coarser " +
|
||||
'administrative regions (e.g. French regions, Turkish ' +
|
||||
'NUTS-1 regions). Stored as admin_level (0 / 1 / aggregated).',
|
||||
),
|
||||
choices: ADMIN_LEVEL_CHOICES,
|
||||
default: String(0),
|
||||
@@ -528,11 +534,31 @@ const config: ControlPanelConfig = {
|
||||
renderTrigger: true,
|
||||
},
|
||||
},
|
||||
formDataOverrides: formData => ({
|
||||
...formData,
|
||||
entity: getStandardizedControls().shiftColumn(),
|
||||
metric: getStandardizedControls().shiftMetric(),
|
||||
}),
|
||||
// formDataOverrides runs when the user switches a chart's viz_type to
|
||||
// this plugin. We use it for two jobs:
|
||||
// 1. Standard control hand-off (entity, metric) via getStandardizedControls
|
||||
// 2. Migration from the legacy country_map plugin — translate the
|
||||
// legacy `select_country` value into admin_level / country /
|
||||
// composite / region_set so the new chart lands pre-populated
|
||||
// rather than dumping the user back to an empty Country dropdown.
|
||||
formDataOverrides: formData => {
|
||||
const fromLegacy =
|
||||
typeof formData.select_country === 'string' && formData.select_country
|
||||
? migrateFromLegacy(formData)
|
||||
: {};
|
||||
return {
|
||||
...formData,
|
||||
entity: getStandardizedControls().shiftColumn(),
|
||||
metric: getStandardizedControls().shiftMetric(),
|
||||
// Only fill fields the user has not already set on the new chart;
|
||||
// explicit user edits on the new viz win over legacy migration.
|
||||
...Object.fromEntries(
|
||||
Object.entries(fromLegacy).filter(
|
||||
([k]) => formData[k as keyof typeof formData] == null,
|
||||
),
|
||||
),
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
||||
@@ -0,0 +1,308 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Translate a legacy `country_map` chart's form_data into the new
|
||||
* `country_map_v2` form_data shape.
|
||||
*
|
||||
* Triggered from controlPanel.formDataOverrides whenever a user switches
|
||||
* a saved chart's viz type to country_map_v2. We try to preserve as much
|
||||
* intent as possible:
|
||||
*
|
||||
* - Legacy `select_country: 'france'` → admin_level=1, country='FRA'
|
||||
* - Legacy `select_country: 'france_overseas'` → composite='france_overseas'
|
||||
* - Legacy `select_country: 'turkey_regions'` → admin_level='aggregated',
|
||||
* country='TUR', region_set='nuts_1'
|
||||
* - Legacy `select_country: 'italy_regions'` → ITA/regions
|
||||
* - Legacy `select_country: 'philippines_regions'` → PHL/regions
|
||||
* - Legacy `select_country: 'france_regions'` → FRA/regions
|
||||
*
|
||||
* Worldview defaults to 'ukr' (the new plugin's default editorial choice).
|
||||
* Standard controls (entity, metric, color scheme, number format) flow
|
||||
* through the standard `formDataOverrides` path; this migration only
|
||||
* touches the country/admin/composite/region_set quartet.
|
||||
*/
|
||||
|
||||
interface PartialFormData {
|
||||
select_country?: string;
|
||||
[k: string]: unknown;
|
||||
}
|
||||
|
||||
interface MigrationOutput {
|
||||
admin_level?: string;
|
||||
country?: string;
|
||||
composite?: string;
|
||||
region_set?: string;
|
||||
worldview?: string;
|
||||
}
|
||||
|
||||
// Composite outputs — legacy keys that should map to the new plugin's
|
||||
// composite_maps.yaml-driven composite control rather than to a country.
|
||||
const LEGACY_TO_COMPOSITE: Record<string, string> = {
|
||||
france_overseas: 'france_overseas',
|
||||
};
|
||||
|
||||
// Aggregated region outputs — legacy keys that should map to the new
|
||||
// plugin's regional_aggregations.yaml-driven country+region_set pair.
|
||||
const LEGACY_TO_AGGREGATED: Record<
|
||||
string,
|
||||
{ country: string; region_set: string }
|
||||
> = {
|
||||
france_regions: { country: 'FRA', region_set: 'regions' },
|
||||
italy_regions: { country: 'ITA', region_set: 'regions' },
|
||||
philippines_regions: { country: 'PHL', region_set: 'regions' },
|
||||
turkey_regions: { country: 'TUR', region_set: 'nuts_1' },
|
||||
};
|
||||
|
||||
// Per-country subdivisions — legacy snake_case keys mapped to ISO 3166-1
|
||||
// alpha-3 codes used by the new plugin's country control. Coverage is
|
||||
// intentionally broad — every legacy country file maps to a sibling
|
||||
// entry in the new build's admin 1 outputs.
|
||||
const LEGACY_TO_ISO_A3: Record<string, string> = {
|
||||
afghanistan: 'AFG',
|
||||
aland: 'ALD',
|
||||
albania: 'ALB',
|
||||
algeria: 'DZA',
|
||||
american_samoa: 'ASM',
|
||||
andorra: 'AND',
|
||||
angola: 'AGO',
|
||||
anguilla: 'AIA',
|
||||
antarctica: 'ATA',
|
||||
antigua_and_barbuda: 'ATG',
|
||||
argentina: 'ARG',
|
||||
armenia: 'ARM',
|
||||
australia: 'AUS',
|
||||
austria: 'AUT',
|
||||
azerbaijan: 'AZE',
|
||||
bahrain: 'BHR',
|
||||
bangladesh: 'BGD',
|
||||
barbados: 'BRB',
|
||||
belarus: 'BLR',
|
||||
belgium: 'BEL',
|
||||
belize: 'BLZ',
|
||||
benin: 'BEN',
|
||||
bermuda: 'BMU',
|
||||
bhutan: 'BTN',
|
||||
bolivia: 'BOL',
|
||||
bosnia_and_herzegovina: 'BIH',
|
||||
botswana: 'BWA',
|
||||
brazil: 'BRA',
|
||||
brunei: 'BRN',
|
||||
bulgaria: 'BGR',
|
||||
burkina_faso: 'BFA',
|
||||
burundi: 'BDI',
|
||||
cambodia: 'KHM',
|
||||
cameroon: 'CMR',
|
||||
canada: 'CAN',
|
||||
cape_verde: 'CPV',
|
||||
central_african_republic: 'CAF',
|
||||
chad: 'TCD',
|
||||
chile: 'CHL',
|
||||
china: 'CHN',
|
||||
colombia: 'COL',
|
||||
comoros: 'COM',
|
||||
cook_islands: 'COK',
|
||||
costa_rica: 'CRI',
|
||||
croatia: 'HRV',
|
||||
cuba: 'CUB',
|
||||
cyprus: 'CYP',
|
||||
czech_republic: 'CZE',
|
||||
democratic_republic_of_the_congo: 'COD',
|
||||
denmark: 'DNK',
|
||||
djibouti: 'DJI',
|
||||
dominica: 'DMA',
|
||||
dominican_republic: 'DOM',
|
||||
ecuador: 'ECU',
|
||||
egypt: 'EGY',
|
||||
el_salvador: 'SLV',
|
||||
equatorial_guinea: 'GNQ',
|
||||
eritrea: 'ERI',
|
||||
estonia: 'EST',
|
||||
ethiopia: 'ETH',
|
||||
fiji: 'FJI',
|
||||
finland: 'FIN',
|
||||
france: 'FRA',
|
||||
french_polynesia: 'PYF',
|
||||
gabon: 'GAB',
|
||||
gambia: 'GMB',
|
||||
germany: 'DEU',
|
||||
ghana: 'GHA',
|
||||
greece: 'GRC',
|
||||
greenland: 'GRL',
|
||||
grenada: 'GRD',
|
||||
guatemala: 'GTM',
|
||||
guinea: 'GIN',
|
||||
guyana: 'GUY',
|
||||
haiti: 'HTI',
|
||||
honduras: 'HND',
|
||||
hungary: 'HUN',
|
||||
iceland: 'ISL',
|
||||
india: 'IND',
|
||||
indonesia: 'IDN',
|
||||
iran: 'IRN',
|
||||
israel: 'ISR',
|
||||
italy: 'ITA',
|
||||
ivory_coast: 'CIV',
|
||||
japan: 'JPN',
|
||||
jordan: 'JOR',
|
||||
kazakhstan: 'KAZ',
|
||||
kenya: 'KEN',
|
||||
korea: 'KOR',
|
||||
kuwait: 'KWT',
|
||||
kyrgyzstan: 'KGZ',
|
||||
laos: 'LAO',
|
||||
latvia: 'LVA',
|
||||
lebanon: 'LBN',
|
||||
lesotho: 'LSO',
|
||||
liberia: 'LBR',
|
||||
libya: 'LBY',
|
||||
liechtenstein: 'LIE',
|
||||
lithuania: 'LTU',
|
||||
luxembourg: 'LUX',
|
||||
macedonia: 'MKD',
|
||||
madagascar: 'MDG',
|
||||
malawi: 'MWI',
|
||||
malaysia: 'MYS',
|
||||
maldives: 'MDV',
|
||||
mali: 'MLI',
|
||||
malta: 'MLT',
|
||||
marshall_islands: 'MHL',
|
||||
mauritania: 'MRT',
|
||||
mauritius: 'MUS',
|
||||
mexico: 'MEX',
|
||||
moldova: 'MDA',
|
||||
mongolia: 'MNG',
|
||||
montenegro: 'MNE',
|
||||
montserrat: 'MSR',
|
||||
morocco: 'MAR',
|
||||
mozambique: 'MOZ',
|
||||
myanmar: 'MMR',
|
||||
namibia: 'NAM',
|
||||
nauru: 'NRU',
|
||||
nepal: 'NPL',
|
||||
netherlands: 'NLD',
|
||||
new_caledonia: 'NCL',
|
||||
new_zealand: 'NZL',
|
||||
nicaragua: 'NIC',
|
||||
niger: 'NER',
|
||||
nigeria: 'NGA',
|
||||
northern_mariana_islands: 'MNP',
|
||||
norway: 'NOR',
|
||||
oman: 'OMN',
|
||||
pakistan: 'PAK',
|
||||
palau: 'PLW',
|
||||
panama: 'PAN',
|
||||
papua_new_guinea: 'PNG',
|
||||
paraguay: 'PRY',
|
||||
peru: 'PER',
|
||||
philippines: 'PHL',
|
||||
poland: 'POL',
|
||||
portugal: 'PRT',
|
||||
qatar: 'QAT',
|
||||
republic_of_serbia: 'SRB',
|
||||
romania: 'ROU',
|
||||
russia: 'RUS',
|
||||
rwanda: 'RWA',
|
||||
saint_lucia: 'LCA',
|
||||
saint_pierre_and_miquelon: 'SPM',
|
||||
saint_vincent_and_the_grenadines: 'VCT',
|
||||
samoa: 'WSM',
|
||||
san_marino: 'SMR',
|
||||
sao_tome_and_principe: 'STP',
|
||||
saudi_arabia: 'SAU',
|
||||
senegal: 'SEN',
|
||||
seychelles: 'SYC',
|
||||
sierra_leone: 'SLE',
|
||||
singapore: 'SGP',
|
||||
slovakia: 'SVK',
|
||||
slovenia: 'SVN',
|
||||
solomon_islands: 'SLB',
|
||||
somalia: 'SOM',
|
||||
south_africa: 'ZAF',
|
||||
spain: 'ESP',
|
||||
sri_lanka: 'LKA',
|
||||
sudan: 'SDN',
|
||||
suriname: 'SUR',
|
||||
sweden: 'SWE',
|
||||
switzerland: 'CHE',
|
||||
syria: 'SYR',
|
||||
taiwan: 'TWN',
|
||||
tajikistan: 'TJK',
|
||||
tanzania: 'TZA',
|
||||
thailand: 'THA',
|
||||
the_bahamas: 'BHS',
|
||||
timorleste: 'TLS',
|
||||
togo: 'TGO',
|
||||
tonga: 'TON',
|
||||
trinidad_and_tobago: 'TTO',
|
||||
tunisia: 'TUN',
|
||||
turkey: 'TUR',
|
||||
turkmenistan: 'TKM',
|
||||
turks_and_caicos_islands: 'TCA',
|
||||
uganda: 'UGA',
|
||||
uk: 'GBR',
|
||||
ukraine: 'UKR',
|
||||
united_arab_emirates: 'ARE',
|
||||
united_states_minor_outlying_islands: 'UMI',
|
||||
united_states_virgin_islands: 'VIR',
|
||||
uruguay: 'URY',
|
||||
usa: 'USA',
|
||||
uzbekistan: 'UZB',
|
||||
vanuatu: 'VUT',
|
||||
venezuela: 'VEN',
|
||||
vietnam: 'VNM',
|
||||
wallis_and_futuna: 'WLF',
|
||||
yemen: 'YEM',
|
||||
zambia: 'ZMB',
|
||||
zimbabwe: 'ZWE',
|
||||
};
|
||||
|
||||
export default function migrateFromLegacy(
|
||||
formData: PartialFormData,
|
||||
): MigrationOutput {
|
||||
const legacy = String(formData.select_country ?? '').toLowerCase();
|
||||
if (!legacy) return {};
|
||||
|
||||
if (LEGACY_TO_COMPOSITE[legacy]) {
|
||||
return {
|
||||
admin_level: '1',
|
||||
composite: LEGACY_TO_COMPOSITE[legacy],
|
||||
worldview: 'ukr',
|
||||
};
|
||||
}
|
||||
if (LEGACY_TO_AGGREGATED[legacy]) {
|
||||
const { country, region_set } = LEGACY_TO_AGGREGATED[legacy];
|
||||
return {
|
||||
admin_level: 'aggregated',
|
||||
country,
|
||||
region_set,
|
||||
worldview: 'ukr',
|
||||
};
|
||||
}
|
||||
if (LEGACY_TO_ISO_A3[legacy]) {
|
||||
return {
|
||||
admin_level: '1',
|
||||
country: LEGACY_TO_ISO_A3[legacy],
|
||||
worldview: 'ukr',
|
||||
};
|
||||
}
|
||||
// Unknown legacy code — leave the country control empty so the user
|
||||
// can re-pick. Worldview defaults flow from the control's own default.
|
||||
return {};
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
/**
|
||||
* 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 migrateFromLegacy from '../../src/plugin/migrateFromLegacy';
|
||||
|
||||
test('legacy "france" → Country view with FRA pre-selected', () => {
|
||||
expect(migrateFromLegacy({ select_country: 'france' })).toEqual({
|
||||
admin_level: '1',
|
||||
country: 'FRA',
|
||||
worldview: 'ukr',
|
||||
});
|
||||
});
|
||||
|
||||
test('legacy "usa" → Country view with USA pre-selected', () => {
|
||||
expect(migrateFromLegacy({ select_country: 'usa' })).toEqual({
|
||||
admin_level: '1',
|
||||
country: 'USA',
|
||||
worldview: 'ukr',
|
||||
});
|
||||
});
|
||||
|
||||
test('legacy "uk" maps to GBR (the ISO 3166 code)', () => {
|
||||
expect(migrateFromLegacy({ select_country: 'uk' })).toEqual({
|
||||
admin_level: '1',
|
||||
country: 'GBR',
|
||||
worldview: 'ukr',
|
||||
});
|
||||
});
|
||||
|
||||
test('legacy "france_overseas" maps to composite, not country', () => {
|
||||
expect(migrateFromLegacy({ select_country: 'france_overseas' })).toEqual({
|
||||
admin_level: '1',
|
||||
composite: 'france_overseas',
|
||||
worldview: 'ukr',
|
||||
});
|
||||
});
|
||||
|
||||
test('legacy "france_regions" maps to aggregated regions for France', () => {
|
||||
expect(migrateFromLegacy({ select_country: 'france_regions' })).toEqual({
|
||||
admin_level: 'aggregated',
|
||||
country: 'FRA',
|
||||
region_set: 'regions',
|
||||
worldview: 'ukr',
|
||||
});
|
||||
});
|
||||
|
||||
test('legacy "turkey_regions" maps to TUR / nuts_1', () => {
|
||||
expect(migrateFromLegacy({ select_country: 'turkey_regions' })).toEqual({
|
||||
admin_level: 'aggregated',
|
||||
country: 'TUR',
|
||||
region_set: 'nuts_1',
|
||||
worldview: 'ukr',
|
||||
});
|
||||
});
|
||||
|
||||
test('legacy "italy_regions" maps to ITA / regions', () => {
|
||||
expect(migrateFromLegacy({ select_country: 'italy_regions' })).toEqual({
|
||||
admin_level: 'aggregated',
|
||||
country: 'ITA',
|
||||
region_set: 'regions',
|
||||
worldview: 'ukr',
|
||||
});
|
||||
});
|
||||
|
||||
test('legacy "philippines_regions" maps to PHL / regions', () => {
|
||||
expect(migrateFromLegacy({ select_country: 'philippines_regions' })).toEqual({
|
||||
admin_level: 'aggregated',
|
||||
country: 'PHL',
|
||||
region_set: 'regions',
|
||||
worldview: 'ukr',
|
||||
});
|
||||
});
|
||||
|
||||
test('uppercase / mixed case legacy values still match', () => {
|
||||
expect(migrateFromLegacy({ select_country: 'France' })).toEqual({
|
||||
admin_level: '1',
|
||||
country: 'FRA',
|
||||
worldview: 'ukr',
|
||||
});
|
||||
});
|
||||
|
||||
test('unknown legacy code → empty migration (user re-picks)', () => {
|
||||
expect(migrateFromLegacy({ select_country: 'atlantis' })).toEqual({});
|
||||
});
|
||||
|
||||
test('missing select_country → empty migration', () => {
|
||||
expect(migrateFromLegacy({})).toEqual({});
|
||||
});
|
||||
|
||||
test('every legacy "_regions" key resolves to an existing region_set', () => {
|
||||
// Smoke-check that the four legacy aggregated keys all map to
|
||||
// (country, region_set) pairs the build pipeline actually emits.
|
||||
const cases = [
|
||||
'france_regions',
|
||||
'italy_regions',
|
||||
'philippines_regions',
|
||||
'turkey_regions',
|
||||
];
|
||||
cases.forEach(name => {
|
||||
const m = migrateFromLegacy({ select_country: name });
|
||||
expect(m.admin_level).toBe('aggregated');
|
||||
expect(typeof m.country).toBe('string');
|
||||
expect(typeof m.region_set).toBe('string');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user