feat: Add active workflow triggers metric (no-changelog) (#11398)

This commit is contained in:
Marc Littlemore 2024-10-24 17:27:56 +01:00 committed by GitHub
parent 5341ed228d
commit 8201d322dd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 63 additions and 23 deletions

View file

@ -355,7 +355,7 @@ export type NumericLicenseFeature = ValuesOf<typeof LICENSE_QUOTAS>;
export interface ILicenseReadResponse { export interface ILicenseReadResponse {
usage: { usage: {
executions: { activeWorkflowTriggers: {
limit: number; limit: number;
value: number; value: number;
warningThreshold: number; warningThreshold: number;

View file

@ -41,7 +41,7 @@ describe('LicenseService', () => {
const data = await licenseService.getLicenseData(); const data = await licenseService.getLicenseData();
expect(data).toEqual({ expect(data).toEqual({
usage: { usage: {
executions: { activeWorkflowTriggers: {
limit: 400, limit: 400,
value: 7, value: 7,
warningThreshold: 0.8, warningThreshold: 0.8,

View file

@ -37,7 +37,7 @@ export class LicenseService {
return { return {
usage: { usage: {
executions: { activeWorkflowTriggers: {
value: triggerCount, value: triggerCount,
limit: this.license.getTriggerLimit(), limit: this.license.getTriggerLimit(),
warningThreshold: 0.8, warningThreshold: 0.8,

View file

@ -6,11 +6,14 @@ import { LicenseMetricsService } from '@/metrics/license-metrics.service';
describe('LicenseMetricsService', () => { describe('LicenseMetricsService', () => {
const workflowRepository = mock<WorkflowRepository>(); const workflowRepository = mock<WorkflowRepository>();
const licenseMetricsRespository = mock<LicenseMetricsRepository>();
const licenseMetricsService = new LicenseMetricsService( const licenseMetricsService = new LicenseMetricsService(
mock<LicenseMetricsRepository>(), licenseMetricsRespository,
workflowRepository, workflowRepository,
); );
beforeEach(() => jest.clearAllMocks());
describe('collectPassthroughData', () => { describe('collectPassthroughData', () => {
test('should return an object with active workflow IDs', async () => { test('should return an object with active workflow IDs', async () => {
/** /**
@ -30,4 +33,36 @@ describe('LicenseMetricsService', () => {
expect(result).toEqual({ activeWorkflowIds }); expect(result).toEqual({ activeWorkflowIds });
}); });
}); });
describe('collectUsageMetrics', () => {
test('should return an array of expected usage metrics', async () => {
const mockActiveTriggerCount = 1234;
workflowRepository.getActiveTriggerCount.mockResolvedValue(mockActiveTriggerCount);
const mockRenewalMetrics = {
activeWorkflows: 100,
totalWorkflows: 200,
enabledUsers: 300,
totalUsers: 400,
totalCredentials: 500,
productionExecutions: 600,
manualExecutions: 700,
};
licenseMetricsRespository.getLicenseRenewalMetrics.mockResolvedValue(mockRenewalMetrics);
const result = await licenseMetricsService.collectUsageMetrics();
expect(result).toEqual([
{ name: 'activeWorkflows', value: mockRenewalMetrics.activeWorkflows },
{ name: 'totalWorkflows', value: mockRenewalMetrics.totalWorkflows },
{ name: 'enabledUsers', value: mockRenewalMetrics.enabledUsers },
{ name: 'totalUsers', value: mockRenewalMetrics.totalUsers },
{ name: 'totalCredentials', value: mockRenewalMetrics.totalCredentials },
{ name: 'productionExecutions', value: mockRenewalMetrics.productionExecutions },
{ name: 'manualExecutions', value: mockRenewalMetrics.manualExecutions },
{ name: 'activeWorkflowTriggers', value: mockActiveTriggerCount },
]);
});
});
}); });

View file

@ -21,6 +21,8 @@ export class LicenseMetricsService {
manualExecutions, manualExecutions,
} = await this.licenseMetricsRepository.getLicenseRenewalMetrics(); } = await this.licenseMetricsRepository.getLicenseRenewalMetrics();
const activeTriggerCount = await this.workflowRepository.getActiveTriggerCount();
return [ return [
{ name: 'activeWorkflows', value: activeWorkflows }, { name: 'activeWorkflows', value: activeWorkflows },
{ name: 'totalWorkflows', value: totalWorkflows }, { name: 'totalWorkflows', value: totalWorkflows },
@ -29,6 +31,7 @@ export class LicenseMetricsService {
{ name: 'totalCredentials', value: totalCredentials }, { name: 'totalCredentials', value: totalCredentials },
{ name: 'productionExecutions', value: productionExecutions }, { name: 'productionExecutions', value: productionExecutions },
{ name: 'manualExecutions', value: manualExecutions }, { name: 'manualExecutions', value: manualExecutions },
{ name: 'activeWorkflowTriggers', value: activeTriggerCount },
]; ];
} }

View file

@ -116,7 +116,7 @@ describe('POST /license/renew', () => {
const DEFAULT_LICENSE_RESPONSE: { data: ILicenseReadResponse } = { const DEFAULT_LICENSE_RESPONSE: { data: ILicenseReadResponse } = {
data: { data: {
usage: { usage: {
executions: { activeWorkflowTriggers: {
value: 0, value: 0,
limit: -1, limit: -1,
warningThreshold: 0.8, warningThreshold: 0.8,
@ -132,7 +132,7 @@ const DEFAULT_LICENSE_RESPONSE: { data: ILicenseReadResponse } = {
const DEFAULT_POST_RESPONSE: { data: ILicensePostResponse } = { const DEFAULT_POST_RESPONSE: { data: ILicensePostResponse } = {
data: { data: {
usage: { usage: {
executions: { activeWorkflowTriggers: {
value: 0, value: 0,
limit: -1, limit: -1,
warningThreshold: 0.8, warningThreshold: 0.8,

View file

@ -1296,7 +1296,7 @@ export type UsageState = {
loading: boolean; loading: boolean;
data: { data: {
usage: { usage: {
executions: { activeWorkflowTriggers: {
limit: number; // -1 for unlimited, from license limit: number; // -1 for unlimited, from license
value: number; value: number;
warningThreshold: number; // hardcoded value in BE warningThreshold: number; // hardcoded value in BE

View file

@ -21,7 +21,7 @@ describe('Usage and plan store', () => {
const store = useUsageStore(); const store = useUsageStore();
store.setData({ store.setData({
usage: { usage: {
executions: { activeWorkflowTriggers: {
limit, limit,
value, value,
warningThreshold, warningThreshold,

View file

@ -18,7 +18,7 @@ const DEFAULT_STATE: UsageState = {
loading: true, loading: true,
data: { data: {
usage: { usage: {
executions: { activeWorkflowTriggers: {
limit: -1, limit: -1,
value: 0, value: 0,
warningThreshold: 0.8, warningThreshold: 0.8,
@ -39,9 +39,11 @@ export const useUsageStore = defineStore('usage', () => {
const planName = computed(() => state.data.license.planName || DEFAULT_PLAN_NAME); const planName = computed(() => state.data.license.planName || DEFAULT_PLAN_NAME);
const planId = computed(() => state.data.license.planId); const planId = computed(() => state.data.license.planId);
const executionLimit = computed(() => state.data.usage.executions.limit); const activeWorkflowTriggersLimit = computed(() => state.data.usage.activeWorkflowTriggers.limit);
const executionCount = computed(() => state.data.usage.executions.value); const activeWorkflowTriggersCount = computed(() => state.data.usage.activeWorkflowTriggers.value);
const executionPercentage = computed(() => (executionCount.value / executionLimit.value) * 100); const executionPercentage = computed(
() => (activeWorkflowTriggersCount.value / activeWorkflowTriggersLimit.value) * 100,
);
const instanceId = computed(() => settingsStore.settings.instanceId); const instanceId = computed(() => settingsStore.settings.instanceId);
const managementToken = computed(() => state.data.managementToken); const managementToken = computed(() => state.data.managementToken);
const appVersion = computed(() => settingsStore.settings.versionCli); const appVersion = computed(() => settingsStore.settings.versionCli);
@ -99,17 +101,17 @@ export const useUsageStore = defineStore('usage', () => {
registerCommunityEdition, registerCommunityEdition,
planName, planName,
planId, planId,
executionLimit, activeWorkflowTriggersLimit,
executionCount, activeWorkflowTriggersCount,
executionPercentage, executionPercentage,
instanceId, instanceId,
managementToken, managementToken,
appVersion, appVersion,
isCloseToLimit: computed(() => isCloseToLimit: computed(() =>
state.data.usage.executions.limit < 0 state.data.usage.activeWorkflowTriggers.limit < 0
? false ? false
: executionCount.value / executionLimit.value >= : activeWorkflowTriggersCount.value / activeWorkflowTriggersLimit.value >=
state.data.usage.executions.warningThreshold, state.data.usage.activeWorkflowTriggers.warningThreshold,
), ),
viewPlansUrl: computed( viewPlansUrl: computed(
() => `${subscriptionAppUrl.value}?${commonSubscriptionAppUrlQueryParams.value}`, () => `${subscriptionAppUrl.value}?${commonSubscriptionAppUrlQueryParams.value}`,
@ -123,8 +125,8 @@ export const useUsageStore = defineStore('usage', () => {
instance_id: instanceId.value, instance_id: instanceId.value,
action: 'view_plans', action: 'view_plans',
plan_name_current: planName.value, plan_name_current: planName.value,
usage: executionCount.value, usage: activeWorkflowTriggersCount.value,
quota: executionLimit.value, quota: activeWorkflowTriggersLimit.value,
})), })),
}; };
}); });

View file

@ -198,7 +198,7 @@ const openCommunityRegisterModal = () => {
{{ locale.baseText('settings.usageAndPlan.activeWorkflows') }} {{ locale.baseText('settings.usageAndPlan.activeWorkflows') }}
</n8n-text> </n8n-text>
<div :class="$style.chart"> <div :class="$style.chart">
<span v-if="usageStore.executionLimit > 0" :class="$style.chartLine"> <span v-if="usageStore.activeWorkflowTriggersLimit > 0" :class="$style.chartLine">
<span <span
:class="$style.chartBar" :class="$style.chartBar"
:style="{ width: `${usageStore.executionPercentage}%` }" :style="{ width: `${usageStore.executionPercentage}%` }"
@ -209,12 +209,12 @@ const openCommunityRegisterModal = () => {
:class="$style.count" :class="$style.count"
keypath="settings.usageAndPlan.activeWorkflows.count" keypath="settings.usageAndPlan.activeWorkflows.count"
> >
<template #count>{{ usageStore.executionCount }}</template> <template #count>{{ usageStore.activeWorkflowTriggersCount }}</template>
<template #limit> <template #limit>
<span v-if="usageStore.executionLimit < 0">{{ <span v-if="usageStore.activeWorkflowTriggersLimit < 0">{{
locale.baseText('settings.usageAndPlan.activeWorkflows.unlimited') locale.baseText('settings.usageAndPlan.activeWorkflows.unlimited')
}}</span> }}</span>
<span v-else>{{ usageStore.executionLimit }}</span> <span v-else>{{ usageStore.activeWorkflowTriggersLimit }}</span>
</template> </template>
</i18n-t> </i18n-t>
</div> </div>