mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-20 00:08:02 +01:00
task: Add banner encouraging edge upgrade (#6018)
Only triggers if there is any rows in client instances that have sdk_version: unleash-edge with version < 17.0.0 The function that checks this memoizes the check for 10 minutes to avoid scanning the client instances table too often.
This commit is contained in:
parent
3acdfc2cf4
commit
17d826ddf4
@ -21,6 +21,7 @@ import { styled } from '@mui/material';
|
||||
import { InitialRedirect } from './InitialRedirect';
|
||||
import { InternalBanners } from './banners/internalBanners/InternalBanners';
|
||||
import { ExternalBanners } from './banners/externalBanners/ExternalBanners';
|
||||
import { EdgeUpgradeBanner } from './banners/EdgeUpgradeBanner/EdgeUpgradeBanner';
|
||||
import { LicenseBanner } from './banners/internalBanners/LicenseBanner';
|
||||
import { Demo } from './demo/Demo';
|
||||
|
||||
@ -68,6 +69,7 @@ export const App = () => {
|
||||
<LicenseBanner />
|
||||
<ExternalBanners />
|
||||
<InternalBanners />
|
||||
<EdgeUpgradeBanner />
|
||||
<StyledContainer>
|
||||
<ToastRenderer />
|
||||
<Routes>
|
||||
|
@ -0,0 +1,22 @@
|
||||
import { useUiFlag } from '../../../hooks/useUiFlag';
|
||||
import { ConditionallyRender } from '../../common/ConditionallyRender/ConditionallyRender';
|
||||
import { Banner } from '../Banner/Banner';
|
||||
import { IBanner } from '../../../interfaces/banner';
|
||||
|
||||
export const EdgeUpgradeBanner = () => {
|
||||
const displayUpgradeEdgeBanner = useUiFlag('displayUpgradeEdgeBanner');
|
||||
const upgradeEdgeBanner: IBanner = {
|
||||
message: `We noticed that you're using an outdated Unleash Edge. To ensure you continue to receive metrics, we recommend upgrading to v17.0.0 or later.`,
|
||||
link: 'https://github.com/Unleash/unleash-edge',
|
||||
linkText: 'Get latest',
|
||||
variant: 'warning',
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<ConditionallyRender
|
||||
condition={displayUpgradeEdgeBanner}
|
||||
show={<Banner key={'upgradeEdge'} banner={upgradeEdgeBanner} />}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
@ -79,6 +79,7 @@ export type UiFlags = {
|
||||
executiveDashboard?: boolean;
|
||||
changeRequestConflictHandling?: boolean;
|
||||
feedbackComments?: Variant;
|
||||
displayUpgradeEdgeBanner?: boolean;
|
||||
};
|
||||
|
||||
export interface IVersionInfo {
|
||||
|
@ -166,6 +166,15 @@ export default class ClientInstanceStore implements IClientInstanceStore {
|
||||
return rows.map(mapRow);
|
||||
}
|
||||
|
||||
async getBySdkName(sdkName: string): Promise<IClientInstance[]> {
|
||||
const rows = await this.db
|
||||
.select()
|
||||
.from(TABLE)
|
||||
.whereRaw(`sdk_version LIKE '??%'`, [sdkName])
|
||||
.orderBy('last_seen', 'desc');
|
||||
return rows.map(mapRow);
|
||||
}
|
||||
|
||||
async getDistinctApplications(): Promise<string[]> {
|
||||
const rows = await this.db
|
||||
.distinct('app_name')
|
||||
|
@ -19,7 +19,7 @@ import { clientMetricsSchema } from '../shared/schema';
|
||||
import { PartialSome } from '../../../types/partial';
|
||||
import { IPrivateProjectChecker } from '../../private-project/privateProjectCheckerType';
|
||||
import { IFlagResolver, SYSTEM_USER } from '../../../types';
|
||||
import { ALL_PROJECTS } from '../../../util';
|
||||
import { ALL_PROJECTS, parseStrictSemVer } from '../../../util';
|
||||
import { Logger } from '../../../logger';
|
||||
|
||||
export default class ClientInstanceService {
|
||||
@ -224,4 +224,24 @@ export default class ClientInstanceService {
|
||||
async removeInstancesOlderThanTwoDays(): Promise<void> {
|
||||
return this.clientInstanceStore.removeInstancesOlderThanTwoDays();
|
||||
}
|
||||
|
||||
async usesSdkOlderThan(
|
||||
sdkName: string,
|
||||
sdkVersion: string,
|
||||
): Promise<boolean> {
|
||||
const semver = parseStrictSemVer(sdkVersion);
|
||||
const instancesOfSdk =
|
||||
await this.clientInstanceStore.getBySdkName(sdkName);
|
||||
return instancesOfSdk.some((instance) => {
|
||||
if (instance.sdkVersion) {
|
||||
const [_sdkName, sdkVersion] = instance.sdkVersion.split(':');
|
||||
const instanceUsedSemver = parseStrictSemVer(sdkVersion);
|
||||
return (
|
||||
instanceUsedSemver !== null &&
|
||||
semver !== null &&
|
||||
instanceUsedSemver < semver
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import {
|
||||
DEFAULT_STRATEGY_SEGMENTS_LIMIT,
|
||||
} from '../../util/segments';
|
||||
import TestAgent from 'supertest/lib/agent';
|
||||
import { IUnleashStores } from '../../types';
|
||||
|
||||
const uiConfig = {
|
||||
headerBackground: 'red',
|
||||
@ -28,17 +29,20 @@ async function getSetup() {
|
||||
|
||||
return {
|
||||
base,
|
||||
stores,
|
||||
request: supertest(app),
|
||||
};
|
||||
}
|
||||
|
||||
let request: TestAgent<Test>;
|
||||
let base: string;
|
||||
let stores: IUnleashStores;
|
||||
|
||||
beforeEach(async () => {
|
||||
const setup = await getSetup();
|
||||
request = setup.request;
|
||||
base = setup.base;
|
||||
stores = setup.stores;
|
||||
});
|
||||
|
||||
test('should get ui config', async () => {
|
||||
@ -52,3 +56,45 @@ test('should get ui config', async () => {
|
||||
expect(body.segmentValuesLimit).toEqual(DEFAULT_SEGMENT_VALUES_LIMIT);
|
||||
expect(body.strategySegmentsLimit).toEqual(DEFAULT_STRATEGY_SEGMENTS_LIMIT);
|
||||
});
|
||||
|
||||
describe('displayUpgradeEdgeBanner', () => {
|
||||
test('ui config should have displayUpgradeEdgeBanner to be set if an instance using edge has been seen', async () => {
|
||||
await stores.clientInstanceStore.insert({
|
||||
appName: 'my-app',
|
||||
instanceId: 'some-instance',
|
||||
sdkVersion: 'unleash-edge:16.0.0',
|
||||
});
|
||||
const { body } = await request
|
||||
.get(`${base}/api/admin/ui-config`)
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200);
|
||||
expect(body.flags).toBeTruthy();
|
||||
expect(body.flags.displayUpgradeEdgeBanner).toBeTruthy();
|
||||
});
|
||||
test('ui config should not get displayUpgradeEdgeBanner flag if edge >= 17.0.0 has been seen', async () => {
|
||||
await stores.clientInstanceStore.insert({
|
||||
appName: 'my-app',
|
||||
instanceId: 'some-instance',
|
||||
sdkVersion: 'unleash-edge:17.1.0',
|
||||
});
|
||||
const { body } = await request
|
||||
.get(`${base}/api/admin/ui-config`)
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200);
|
||||
expect(body.flags).toBeTruthy();
|
||||
expect(body.flags.displayUpgradeEdgeBanner).toEqual(false);
|
||||
});
|
||||
test('ui config should not get displayUpgradeEdgeBanner flag if java-client has been seen', async () => {
|
||||
await stores.clientInstanceStore.insert({
|
||||
appName: 'my-app',
|
||||
instanceId: 'some-instance',
|
||||
sdkVersion: 'unleash-client-java:9.1.0',
|
||||
});
|
||||
const { body } = await request
|
||||
.get(`${base}/api/admin/ui-config`)
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200);
|
||||
expect(body.flags).toBeTruthy();
|
||||
expect(body.flags.displayUpgradeEdgeBanner).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
@ -26,6 +26,9 @@ import { SetUiConfigSchema } from '../../openapi/spec/set-ui-config-schema';
|
||||
import { createRequestSchema } from '../../openapi/util/create-request-schema';
|
||||
import { ProxyService } from '../../services';
|
||||
import MaintenanceService from '../../features/maintenance/maintenance-service';
|
||||
import memoizee from 'memoizee';
|
||||
import { minutesToMilliseconds } from 'date-fns';
|
||||
import ClientInstanceService from '../../features/metrics/instance/instance-service';
|
||||
|
||||
class ConfigController extends Controller {
|
||||
private versionService: VersionService;
|
||||
@ -36,8 +39,12 @@ class ConfigController extends Controller {
|
||||
|
||||
private emailService: EmailService;
|
||||
|
||||
private clientInstanceService: ClientInstanceService;
|
||||
|
||||
private maintenanceService: MaintenanceService;
|
||||
|
||||
private usesOldEdgeFunction: () => Promise<boolean>;
|
||||
|
||||
private readonly openApiService: OpenApiService;
|
||||
|
||||
constructor(
|
||||
@ -49,6 +56,7 @@ class ConfigController extends Controller {
|
||||
openApiService,
|
||||
proxyService,
|
||||
maintenanceService,
|
||||
clientInstanceService,
|
||||
}: Pick<
|
||||
IUnleashServices,
|
||||
| 'versionService'
|
||||
@ -57,6 +65,7 @@ class ConfigController extends Controller {
|
||||
| 'openApiService'
|
||||
| 'proxyService'
|
||||
| 'maintenanceService'
|
||||
| 'clientInstanceService'
|
||||
>,
|
||||
) {
|
||||
super(config);
|
||||
@ -66,6 +75,18 @@ class ConfigController extends Controller {
|
||||
this.openApiService = openApiService;
|
||||
this.proxyService = proxyService;
|
||||
this.maintenanceService = maintenanceService;
|
||||
this.clientInstanceService = clientInstanceService;
|
||||
this.usesOldEdgeFunction = memoizee(
|
||||
async () =>
|
||||
this.clientInstanceService.usesSdkOlderThan(
|
||||
'unleash-edge',
|
||||
'17.0.0',
|
||||
),
|
||||
{
|
||||
promise: true,
|
||||
maxAge: minutesToMilliseconds(10),
|
||||
},
|
||||
);
|
||||
|
||||
this.route({
|
||||
method: 'get',
|
||||
@ -109,14 +130,17 @@ class ConfigController extends Controller {
|
||||
req: AuthedRequest,
|
||||
res: Response<UiConfigSchema>,
|
||||
): Promise<void> {
|
||||
const [frontendSettings, simpleAuthSettings, maintenanceMode] =
|
||||
await Promise.all([
|
||||
this.proxyService.getFrontendSettings(false),
|
||||
this.settingService.get<SimpleAuthSettings>(
|
||||
simpleAuthSettingsKey,
|
||||
),
|
||||
this.maintenanceService.isMaintenanceMode(),
|
||||
]);
|
||||
const [
|
||||
frontendSettings,
|
||||
simpleAuthSettings,
|
||||
maintenanceMode,
|
||||
usesOldEdge,
|
||||
] = await Promise.all([
|
||||
this.proxyService.getFrontendSettings(false),
|
||||
this.settingService.get<SimpleAuthSettings>(simpleAuthSettingsKey),
|
||||
this.maintenanceService.isMaintenanceMode(),
|
||||
this.usesOldEdgeFunction(),
|
||||
]);
|
||||
|
||||
const disablePasswordAuth =
|
||||
simpleAuthSettings?.disabled ||
|
||||
@ -126,7 +150,11 @@ class ConfigController extends Controller {
|
||||
email: req.user.email,
|
||||
});
|
||||
|
||||
const flags = { ...this.config.ui.flags, ...expFlags };
|
||||
const flags = {
|
||||
...this.config.ui.flags,
|
||||
...expFlags,
|
||||
displayUpgradeEdgeBanner: usesOldEdge,
|
||||
};
|
||||
|
||||
const response: UiConfigSchema = {
|
||||
...this.config.ui,
|
||||
|
@ -21,6 +21,7 @@ export interface IClientInstanceStore
|
||||
setLastSeen(INewClientInstance): Promise<void>;
|
||||
insert(details: INewClientInstance): Promise<void>;
|
||||
getByAppName(appName: string): Promise<IClientInstance[]>;
|
||||
getBySdkName(sdkName: string): Promise<IClientInstance[]>;
|
||||
getDistinctApplications(): Promise<string[]>;
|
||||
getDistinctApplicationsCount(daysBefore?: number): Promise<number>;
|
||||
deleteForApplication(appName: string): Promise<void>;
|
||||
|
@ -31,6 +31,14 @@ export default class FakeClientInstanceStore implements IClientInstanceStore {
|
||||
return;
|
||||
}
|
||||
|
||||
async getBySdkName(sdkName: string): Promise<IClientInstance[]> {
|
||||
return Promise.resolve(
|
||||
this.instances.filter((instance) =>
|
||||
instance.sdkVersion?.startsWith(sdkName),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
async deleteAll(): Promise<void> {
|
||||
this.instances = [];
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user