mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-24 04:04:06 -08:00
feat: Add active workflow triggers metric (no-changelog) (#11398)
This commit is contained in:
parent
5341ed228d
commit
8201d322dd
|
@ -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;
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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 },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -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 },
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
})),
|
})),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in a new issue