diff --git a/frontend/src/component/onboarding/dialog/snippets/dotnet.md b/frontend/src/component/onboarding/dialog/snippets/dotnet.md index e35bf4287b..9a162d025f 100644 --- a/frontend/src/component/onboarding/dialog/snippets/dotnet.md +++ b/frontend/src/component/onboarding/dialog/snippets/dotnet.md @@ -18,7 +18,6 @@ public class Program { AppName = "unleash-onboarding-dotnet", UnleashApi = new Uri(""), - SendMetricsInterval = TimeSpan.FromSeconds(1), // in production remove this or increase to >=15s CustomHttpHeaders = new Dictionary() { {"Authorization",""} // in production use environment variable diff --git a/frontend/src/component/onboarding/dialog/snippets/go.md b/frontend/src/component/onboarding/dialog/snippets/go.md index b79841f646..5a5053e50e 100644 --- a/frontend/src/component/onboarding/dialog/snippets/go.md +++ b/frontend/src/component/onboarding/dialog/snippets/go.md @@ -19,7 +19,6 @@ func init() { unleash.WithAppName("unleash-onboarding-golang"), unleash.WithUrl(""), unleash.WithCustomHeaders(http.Header{"Authorization": {""}}), // in production use environment variable - unleash.WithMetricsInterval(1*time.Second), // in production remove this or increase to >=15s ) } diff --git a/frontend/src/component/onboarding/dialog/snippets/java.md b/frontend/src/component/onboarding/dialog/snippets/java.md index 3c8033e028..c4ce7dc804 100644 --- a/frontend/src/component/onboarding/dialog/snippets/java.md +++ b/frontend/src/component/onboarding/dialog/snippets/java.md @@ -14,7 +14,6 @@ UnleashConfig config = UnleashConfig.builder() .instanceId("unleash-onboarding-instance") .unleashAPI("") .apiKey("") // in production use environment variable - .sendMetricsInterval(1) // in production remove this or increase to >=15 .build(); Unleash unleash = new DefaultUnleash(config); diff --git a/frontend/src/component/onboarding/dialog/snippets/javascript.md b/frontend/src/component/onboarding/dialog/snippets/javascript.md index 9f84489f01..ab3608cddc 100644 --- a/frontend/src/component/onboarding/dialog/snippets/javascript.md +++ b/frontend/src/component/onboarding/dialog/snippets/javascript.md @@ -11,7 +11,6 @@ const unleash = new UnleashClient({ url: '', clientKey: '', // in production use environment variable appName: 'unleash-onboarding-javascript', - metricsInterval: 1000, // in production remove this or increase to >=15000 }); unleash.start(); diff --git a/frontend/src/component/onboarding/dialog/snippets/nodejs.md b/frontend/src/component/onboarding/dialog/snippets/nodejs.md index c8e6fdb24e..13592d2301 100644 --- a/frontend/src/component/onboarding/dialog/snippets/nodejs.md +++ b/frontend/src/component/onboarding/dialog/snippets/nodejs.md @@ -13,7 +13,6 @@ const unleash = initialize({ customHeaders: { Authorization: '' // in production use environment variable }, - metricsInterval: 1000, // in production remove this or increase to >=15000 }); setInterval(() => { diff --git a/frontend/src/component/onboarding/dialog/snippets/php.md b/frontend/src/component/onboarding/dialog/snippets/php.md index b3b47ba02a..e7409d4e8f 100644 --- a/frontend/src/component/onboarding/dialog/snippets/php.md +++ b/frontend/src/component/onboarding/dialog/snippets/php.md @@ -16,7 +16,6 @@ $unleash = UnleashBuilder::create() ->withAppUrl('') ->withHeader('Authorization', '') // in production use environment variable ->withInstanceId('unleash-onboarding-instance') - ->withMetricsInterval(1000) // in production remove this or increase to >=15000 ->build(); while (true) { diff --git a/frontend/src/component/onboarding/dialog/snippets/python.md b/frontend/src/component/onboarding/dialog/snippets/python.md index 06b14998e8..cacc0db147 100644 --- a/frontend/src/component/onboarding/dialog/snippets/python.md +++ b/frontend/src/component/onboarding/dialog/snippets/python.md @@ -11,7 +11,6 @@ import asyncio client = UnleashClient( url="", app_name="unleash-onboarding-python", - metrics_interval=1, # in production remove this or increase to >=15 custom_headers={'Authorization': ''}) # in production use environment variable client.initialize_client() diff --git a/frontend/src/component/onboarding/dialog/snippets/react.md b/frontend/src/component/onboarding/dialog/snippets/react.md index 3a2ef3c746..40b4f3167e 100644 --- a/frontend/src/component/onboarding/dialog/snippets/react.md +++ b/frontend/src/component/onboarding/dialog/snippets/react.md @@ -11,7 +11,6 @@ import { FlagProvider } from '@unleash/proxy-client-react'; const config = { url: '', clientKey: '', // in production use environment variable - metricsInterval: 1, // In production use interval of >15s appName: 'unleash-onboarding-react', }; diff --git a/frontend/src/component/onboarding/dialog/snippets/ruby.md b/frontend/src/component/onboarding/dialog/snippets/ruby.md index 4e0e44d3c9..4ad630ad79 100644 --- a/frontend/src/component/onboarding/dialog/snippets/ruby.md +++ b/frontend/src/component/onboarding/dialog/snippets/ruby.md @@ -12,7 +12,6 @@ require 'unleash' custom_http_headers: { 'Authorization': "" }, # in production use environment variable app_name: 'unleash-onboarding-ruby', instance_id: 'unleash-onboarding-ruby', - metrics_interval: 3, # In production use interval of >15s ) while true diff --git a/frontend/src/component/onboarding/dialog/snippets/rust.md b/frontend/src/component/onboarding/dialog/snippets/rust.md index f518db8f73..23adc8ddd1 100644 --- a/frontend/src/component/onboarding/dialog/snippets/rust.md +++ b/frontend/src/component/onboarding/dialog/snippets/rust.md @@ -26,7 +26,6 @@ enum Flags { #[tokio::main] async fn main() -> Result<(), Box> { let client: Client = ClientBuilder::default() - .interval(1000) // in production remove this or increase to >=15000 .into_client( "", "unleash-onboarding-rust", @@ -55,7 +54,6 @@ async fn main() -> Result<(), Box> { let api_token = env::var("UNLEASH_API_TOKEN").expect("UNLEASH_API_TOKEN environment variable not set"); let client: Client = ClientBuilder::default() - .interval(1000) // Polling & metrics interval - default 15000 (ms) .into_client( "", "unleash-onboarding-rust", diff --git a/frontend/src/component/onboarding/dialog/snippets/svelte.md b/frontend/src/component/onboarding/dialog/snippets/svelte.md index fd76029871..f3740a8bdf 100644 --- a/frontend/src/component/onboarding/dialog/snippets/svelte.md +++ b/frontend/src/component/onboarding/dialog/snippets/svelte.md @@ -12,7 +12,6 @@ npm install @unleash/proxy-client-svelte url: '', clientKey: '', // in production use environment variable appName: 'unleash-onboarding-svelte', - metricsInterval: 1, // in production remove this or increase to >=15 }; diff --git a/frontend/src/component/onboarding/dialog/snippets/vue.md b/frontend/src/component/onboarding/dialog/snippets/vue.md index 13813ecd62..2c4544d74d 100644 --- a/frontend/src/component/onboarding/dialog/snippets/vue.md +++ b/frontend/src/component/onboarding/dialog/snippets/vue.md @@ -12,7 +12,6 @@ npm install @unleash/proxy-client-vue url: '', clientKey: '', // in production use environment variable appName: 'unleash-onboarding-vue', - metricsInterval: 1, // in production remove this or increase to >=15 } diff --git a/src/lib/features/onboarding/onboarding-read-model.test.ts b/src/lib/features/onboarding/onboarding-read-model.test.ts index 390d21f6a0..20c990b7e6 100644 --- a/src/lib/features/onboarding/onboarding-read-model.test.ts +++ b/src/lib/features/onboarding/onboarding-read-model.test.ts @@ -7,21 +7,43 @@ import { SYSTEM_USER, } from '../../types'; import type { IOnboardingReadModel } from './onboarding-read-model-type'; +import type ClientInstanceService from '../metrics/instance/instance-service'; +import { + type IUnleashTest, + setupAppWithCustomConfig, +} from '../../../test/e2e/helpers/test-helper'; +import { ApiTokenType } from '../../types/models/api-token'; let db: ITestDb; let onboardingReadModel: IOnboardingReadModel; let onBoardingStore: IOnboardingStore; let featureToggleStore: IFeatureToggleStore; let lastSeenStore: ILastSeenStore; +let instanceService: ClientInstanceService; +let app: IUnleashTest; beforeAll(async () => { db = await dbInit('onboarding_read_model', getLogger, { experimental: { flags: { onboardingMetrics: true } }, }); + + app = await setupAppWithCustomConfig( + db.stores, + { + experimental: { + flags: { + strictSchemaValidation: true, + }, + }, + }, + db.rawDatabase, + ); + onboardingReadModel = db.stores.onboardingReadModel; onBoardingStore = db.stores.onboardingStore; featureToggleStore = db.stores.featureToggleStore; lastSeenStore = db.stores.lastSeenStore; + instanceService = app.services.clientInstanceService; }); afterAll(async () => { @@ -178,3 +200,38 @@ test('archived feature counts as onboarded', async () => { status: 'onboarded', }); }); + +test('sdk register also onboards a project', async () => { + await featureToggleStore.create('default', { + name: 'my-flag', + createdByUserId: SYSTEM_USER.id, + }); + + const defaultProjectToken = + await app.services.apiTokenService.createApiTokenWithProjects({ + type: ApiTokenType.CLIENT, + projects: ['default'], + environment: 'default', + tokenName: 'tester', + }); + + await app.request + .post('/api/client/register') + .set('Authorization', defaultProjectToken.secret) + .send({ + appName: 'multi-project-app', + instanceId: 'instance-1', + strategies: ['default'], + started: Date.now(), + interval: 10, + }); + + await instanceService.bulkAdd(); + + const onboardedResult = + await onboardingReadModel.getOnboardingStatusForProject('default'); + + expect(onboardedResult).toMatchObject({ + status: 'onboarded', + }); +}); diff --git a/src/lib/features/onboarding/onboarding-read-model.ts b/src/lib/features/onboarding/onboarding-read-model.ts index 8e2792b0d9..534ca7f09c 100644 --- a/src/lib/features/onboarding/onboarding-read-model.ts +++ b/src/lib/features/onboarding/onboarding-read-model.ts @@ -101,11 +101,25 @@ export class OnboardingReadModel implements IOnboardingReadModel { return { status: 'onboarding-started' }; } - const lastSeen = await this.db('last_seen_at_metrics as lsm') - .select('lsm.feature_name') + // const lastSeen = await this.db('last_seen_at_metrics as lsm') + // .select('lsm.feature_name') + // .innerJoin('features as f', 'f.name', 'lsm.feature_name') + // .innerJoin('projects as p', 'p.id', 'f.project') + // .where('p.id', projectId) + // .first(); + + const db = this.db; + const lastSeen = await db + .select(db.raw('1')) + .from('last_seen_at_metrics as lsm') .innerJoin('features as f', 'f.name', 'lsm.feature_name') .innerJoin('projects as p', 'p.id', 'f.project') .where('p.id', projectId) + .union((qb) => { + qb.select(db.raw('1')) + .from('client_applications_usage as cau') + .where('cau.project', projectId); + }) .first(); if (lastSeen) {