mirror of
https://github.com/harvester/harvester-ui-extension.git
synced 2026-11-15 13:26:15 +08:00
feat: use the SUSE Virtualization product name on Prime (#1202)
* feat: switch the product name with Rancher Prime Use a [[Harvester]] token for the product name in the translations, so Rancher 2.16 fills it from the l10n global that harvester-manager registers: SUSE Virtualization on Rancher Prime, Harvester otherwise. Rancher before 2.16 shows the tokens as written, so on those hosts (and standalone) the extension resolves them to Harvester itself. Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com> * feat: switch the standalone product name with the server flavor Standalone has no Rancher Prime, so register the [[Harvester]] l10n global from the server-flavor setting instead: SUSE Virtualization on prime, Harvester otherwise. Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com> * fix: point the network policy banner to the documentation Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com> * feat: use the SUSE theme in standalone Prime Standalone Prime kept the harvester brand and only overrode the primary colour, so the rest of the palette and the fonts didn't match Rancher Prime. Switch the ui-brand setting to suse in the store when the server flavor is prime, which makes the shell load the SUSE theme. Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com> * fix: keep the SUSE theme after a brand setting change event The watcher also reads the setting's value, so a change from the server loaded into the same object switches the brand back to suse. Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com> --------- Signed-off-by: Marcelo Fukumoto <marcelo.fukumoto@suse.com>
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { load } from 'js-yaml';
|
||||
import { hostResolvesL10nGlobals, resolveL10nGlobals, setStandalonePrime, standaloneProductName } from '../l10n-globals';
|
||||
|
||||
describe('hostResolvesL10nGlobals', () => {
|
||||
it('should be true from Rancher 2.16', () => {
|
||||
expect(hostResolvesL10nGlobals('v2.16.0')).toBe(true);
|
||||
expect(hostResolvesL10nGlobals('v2.17.1')).toBe(true);
|
||||
});
|
||||
|
||||
it('should be true for 2.16 head builds', () => {
|
||||
expect(hostResolvesL10nGlobals('v2.16.0-68ddd75-head')).toBe(true);
|
||||
expect(hostResolvesL10nGlobals('v2.16-136b11a0062247c1b28f107d82283bdb29bc07ae-head')).toBe(true);
|
||||
});
|
||||
|
||||
it('should be false before Rancher 2.16', () => {
|
||||
expect(hostResolvesL10nGlobals('v2.15.1')).toBe(false);
|
||||
expect(hostResolvesL10nGlobals('v2.14.6')).toBe(false);
|
||||
});
|
||||
|
||||
it('should be false when the Rancher version is missing or unknown', () => {
|
||||
expect(hostResolvesL10nGlobals('')).toBe(false);
|
||||
expect(hostResolvesL10nGlobals(undefined)).toBe(false);
|
||||
expect(hostResolvesL10nGlobals('dev')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('standaloneProductName', () => {
|
||||
it('should be Harvester when the server flavor is not prime', () => {
|
||||
setStandalonePrime(false);
|
||||
|
||||
expect(standaloneProductName()).toBe('Harvester');
|
||||
});
|
||||
|
||||
it('should be SUSE Virtualization when the server flavor is prime', () => {
|
||||
setStandalonePrime(true);
|
||||
|
||||
expect(standaloneProductName()).toBe('SUSE Virtualization');
|
||||
setStandalonePrime(false);
|
||||
});
|
||||
|
||||
it('should go back to Harvester when the server flavor changes from prime', () => {
|
||||
setStandalonePrime(true);
|
||||
setStandalonePrime(false);
|
||||
|
||||
expect(standaloneProductName()).toBe('Harvester');
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolveL10nGlobals', () => {
|
||||
it('should replace tokens with their name when no value is given', () => {
|
||||
const translations = { support: { title: '[[Harvester]] Support' } };
|
||||
|
||||
resolveL10nGlobals(translations);
|
||||
|
||||
expect(translations).toStrictEqual({ support: { title: 'Harvester Support' } });
|
||||
});
|
||||
|
||||
it('should replace every token in a string with the given value', () => {
|
||||
const translations = { message: 'The [[Harvester]] UI only supports [[Harvester]] v1.3.0 and later' };
|
||||
|
||||
resolveL10nGlobals(translations, { Harvester: 'SUSE Virtualization' });
|
||||
|
||||
expect(translations.message).toBe('The SUSE Virtualization UI only supports SUSE Virtualization v1.3.0 and later');
|
||||
});
|
||||
|
||||
it('should keep an escaped token as a literal', () => {
|
||||
const translations = { message: 'Use \\[[Harvester]] in a string' };
|
||||
|
||||
resolveL10nGlobals(translations);
|
||||
|
||||
expect(translations.message).toBe('Use [[Harvester]] in a string');
|
||||
});
|
||||
|
||||
it('should leave strings without tokens and non-string values untouched', () => {
|
||||
const translations = {
|
||||
plain: 'harvester-seeder is an add-on',
|
||||
count: 3,
|
||||
nested: { link: '<a href="https://harvesterhci.io/">site</a>' },
|
||||
};
|
||||
|
||||
resolveL10nGlobals(translations);
|
||||
|
||||
expect(translations).toStrictEqual({
|
||||
plain: 'harvester-seeder is an add-on',
|
||||
count: 3,
|
||||
nested: { link: '<a href="https://harvesterhci.io/">site</a>' },
|
||||
});
|
||||
});
|
||||
|
||||
it('should leave no token in the extension translations', () => {
|
||||
const translations = load(fs.readFileSync(path.join(__dirname, '../../l10n/en-us.yaml'), 'utf8'));
|
||||
|
||||
expect(JSON.stringify(translations)).toContain('[[Harvester]]');
|
||||
|
||||
resolveL10nGlobals(translations);
|
||||
|
||||
expect(JSON.stringify(translations)).not.toContain('[[');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,54 @@
|
||||
import { primeBrandUpdater } from '../prime-brand';
|
||||
|
||||
describe('primeBrandUpdater', () => {
|
||||
it('should switch the brand to suse on Prime', () => {
|
||||
const setting = { value: 'harvester' };
|
||||
|
||||
primeBrandUpdater()(setting, true);
|
||||
|
||||
expect(setting.value).toBe('suse');
|
||||
});
|
||||
|
||||
it('should keep the brand of the server when not on Prime', () => {
|
||||
const setting = { value: 'harvester' };
|
||||
|
||||
primeBrandUpdater()(setting, false);
|
||||
|
||||
expect(setting.value).toBe('harvester');
|
||||
});
|
||||
|
||||
it('should put the brand of the server back when the flavor stops being Prime', () => {
|
||||
const setting = { value: 'harvester' };
|
||||
const update = primeBrandUpdater();
|
||||
|
||||
update(setting, true);
|
||||
update(setting, false);
|
||||
|
||||
expect(setting.value).toBe('harvester');
|
||||
});
|
||||
|
||||
it('should switch the brand to suse again when the server sends its brand back on Prime', () => {
|
||||
const setting = { value: 'harvester' };
|
||||
const update = primeBrandUpdater();
|
||||
|
||||
update(setting, true);
|
||||
setting.value = 'harvester';
|
||||
update(setting, true);
|
||||
|
||||
expect(setting.value).toBe('suse');
|
||||
});
|
||||
|
||||
it('should keep an empty brand of the server when not on Prime', () => {
|
||||
const setting = { value: '' };
|
||||
const update = primeBrandUpdater();
|
||||
|
||||
update(setting, true);
|
||||
update(setting, false);
|
||||
|
||||
expect(setting.value).toBe('');
|
||||
});
|
||||
|
||||
it('should do nothing before the setting is loaded', () => {
|
||||
expect(() => primeBrandUpdater()(undefined, true)).not.toThrow();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,49 @@
|
||||
import semver from 'semver';
|
||||
|
||||
// Rancher 2.16 resolves `[[name]]` tokens in translations from values registered as 'l10n-global'
|
||||
export const L10N_GLOBALS_MIN_RANCHER_VERSION = '2.16.0';
|
||||
|
||||
// Name of the product name token, `[[Harvester]]`, and its value on Prime
|
||||
export const PRODUCT_NAME_GLOBAL = 'Harvester';
|
||||
export const PRIME_PRODUCT_NAME = 'SUSE Virtualization';
|
||||
|
||||
const TOKEN = /(\\?)\[\[([\w-]+)\]\]/g;
|
||||
|
||||
// Standalone has no Rancher Prime, the server-flavor setting decides instead
|
||||
let standalonePrime = false;
|
||||
|
||||
export function setStandalonePrime(isPrime) {
|
||||
standalonePrime = !!isPrime;
|
||||
}
|
||||
|
||||
export function standaloneProductName() {
|
||||
return standalonePrime ? PRIME_PRODUCT_NAME : PRODUCT_NAME_GLOBAL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the host Rancher resolves `[[name]]` tokens itself.
|
||||
* A missing or unknown version counts as an older host.
|
||||
*/
|
||||
export function hostResolvesL10nGlobals(version) {
|
||||
const v = semver.coerce(version);
|
||||
|
||||
return !!v && semver.gte(v, L10N_GLOBALS_MIN_RANCHER_VERSION);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace `[[name]]` tokens in every string of `translations`, in place, the same way Rancher 2.16 does:
|
||||
* a known name gets its value, an unknown one falls back to the name and `\[[name]]` stays a literal `[[name]]`
|
||||
*/
|
||||
export function resolveL10nGlobals(translations, values = {}) {
|
||||
Object.keys(translations || {}).forEach((key) => {
|
||||
const value = translations[key];
|
||||
|
||||
if (typeof value === 'string') {
|
||||
translations[key] = value.replace(TOKEN, (_, escaped, name) => (escaped ? `[[${ name }]]` : (values[name] ?? name)));
|
||||
} else if (value && typeof value === 'object') {
|
||||
resolveL10nGlobals(value, values);
|
||||
}
|
||||
});
|
||||
|
||||
return translations;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { BRAND } from '@shell/config/types';
|
||||
|
||||
/**
|
||||
* Standalone Prime takes the SUSE theme of Rancher Prime: the `ui-brand` setting decides the stylesheet the shell
|
||||
* loads, and only the `suse` brand has one. Returns an updater that switches the copy of the setting in the store,
|
||||
* the server keeps its value, and puts the server's brand back when the flavor stops being Prime.
|
||||
*/
|
||||
export function primeBrandUpdater() {
|
||||
let serverBrand;
|
||||
|
||||
return (brandSetting, isPrime) => {
|
||||
if (!brandSetting) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (brandSetting.value !== BRAND.SUSE) {
|
||||
serverBrand = brandSetting.value;
|
||||
}
|
||||
|
||||
const brand = isPrime ? BRAND.SUSE : (serverBrand ?? brandSetting.value);
|
||||
|
||||
if (brandSetting.value !== brand) {
|
||||
brandSetting.value = brand;
|
||||
}
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user