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 {
|
||||
usage: {
|
||||
executions: {
|
||||
activeWorkflowTriggers: {
|
||||
limit: number;
|
||||
value: number;
|
||||
warningThreshold: number;
|
||||
|
|
|
@ -41,7 +41,7 @@ describe('LicenseService', () => {
|
|||
const data = await licenseService.getLicenseData();
|
||||
expect(data).toEqual({
|
||||
usage: {
|
||||
executions: {
|
||||
activeWorkflowTriggers: {
|
||||
limit: 400,
|
||||
value: 7,
|
||||
warningThreshold: 0.8,
|
||||
|
|
|
@ -37,7 +37,7 @@ export class LicenseService {
|
|||
|
||||
return {
|
||||
usage: {
|
||||
executions: {
|
||||
activeWorkflowTriggers: {
|
||||
value: triggerCount,
|
||||
limit: this.license.getTriggerLimit(),
|
||||
warningThreshold: 0.8,
|
||||
|
|
|
@ -6,11 +6,14 @@ import { LicenseMetricsService } from '@/metrics/license-metrics.service';
|
|||
|
||||
describe('LicenseMetricsService', () => {
|
||||
const workflowRepository = mock<WorkflowRepository>();
|
||||
const licenseMetricsRespository = mock<LicenseMetricsRepository>();
|
||||
const licenseMetricsService = new LicenseMetricsService(
|
||||
mock<LicenseMetricsRepository>(),
|
||||
licenseMetricsRespository,
|
||||
workflowRepository,
|
||||
);
|
||||
|
||||
beforeEach(() => jest.clearAllMocks());
|
||||
|
||||
describe('collectPassthroughData', () => {
|
||||
test('should return an object with active workflow IDs', async () => {
|
||||
/**
|
||||
|
@ -30,4 +33,36 @@ describe('LicenseMetricsService', () => {
|
|||
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,
|
||||
} = await this.licenseMetricsRepository.getLicenseRenewalMetrics();
|
||||
|
||||
const activeTriggerCount = await this.workflowRepository.getActiveTriggerCount();
|
||||
|
||||
return [
|
||||
{ name: 'activeWorkflows', value: activeWorkflows },
|
||||
{ name: 'totalWorkflows', value: totalWorkflows },
|
||||
|
@ -29,6 +31,7 @@ export class LicenseMetricsService {
|
|||
{ name: 'totalCredentials', value: totalCredentials },
|
||||
{ name: 'productionExecutions', value: productionExecutions },
|
||||
{ name: 'manualExecutions', value: manualExecutions },
|
||||
{ name: 'activeWorkflowTriggers', value: activeTriggerCount },
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
@ -116,7 +116,7 @@ describe('POST /license/renew', () => {
|
|||
const DEFAULT_LICENSE_RESPONSE: { data: ILicenseReadResponse } = {
|
||||
data: {
|
||||
usage: {
|
||||
executions: {
|
||||
activeWorkflowTriggers: {
|
||||
value: 0,
|
||||
limit: -1,
|
||||
warningThreshold: 0.8,
|
||||
|
@ -132,7 +132,7 @@ const DEFAULT_LICENSE_RESPONSE: { data: ILicenseReadResponse } = {
|
|||
const DEFAULT_POST_RESPONSE: { data: ILicensePostResponse } = {
|
||||
data: {
|
||||
usage: {
|
||||
executions: {
|
||||
activeWorkflowTriggers: {
|
||||
value: 0,
|
||||
limit: -1,
|
||||
warningThreshold: 0.8,
|
||||
|
|
|
@ -1296,7 +1296,7 @@ export type UsageState = {
|
|||
loading: boolean;
|
||||
data: {
|
||||
usage: {
|
||||
executions: {
|
||||
activeWorkflowTriggers: {
|
||||
limit: number; // -1 for unlimited, from license
|
||||
value: number;
|
||||
warningThreshold: number; // hardcoded value in BE
|
||||
|
|
|
@ -21,7 +21,7 @@ describe('Usage and plan store', () => {
|
|||
const store = useUsageStore();
|
||||
store.setData({
|
||||
usage: {
|
||||
executions: {
|
||||
activeWorkflowTriggers: {
|
||||
limit,
|
||||
value,
|
||||
warningThreshold,
|
||||
|
|
|
@ -18,7 +18,7 @@ const DEFAULT_STATE: UsageState = {
|
|||
loading: true,
|
||||
data: {
|
||||
usage: {
|
||||
executions: {
|
||||
activeWorkflowTriggers: {
|
||||
limit: -1,
|
||||
value: 0,
|
||||
warningThreshold: 0.8,
|
||||
|
@ -39,9 +39,11 @@ export const useUsageStore = defineStore('usage', () => {
|
|||
|
||||
const planName = computed(() => state.data.license.planName || DEFAULT_PLAN_NAME);
|
||||
const planId = computed(() => state.data.license.planId);
|
||||
const executionLimit = computed(() => state.data.usage.executions.limit);
|
||||
const executionCount = computed(() => state.data.usage.executions.value);
|
||||
const executionPercentage = computed(() => (executionCount.value / executionLimit.value) * 100);
|
||||
const activeWorkflowTriggersLimit = computed(() => state.data.usage.activeWorkflowTriggers.limit);
|
||||
const activeWorkflowTriggersCount = computed(() => state.data.usage.activeWorkflowTriggers.value);
|
||||
const executionPercentage = computed(
|
||||
() => (activeWorkflowTriggersCount.value / activeWorkflowTriggersLimit.value) * 100,
|
||||
);
|
||||
const instanceId = computed(() => settingsStore.settings.instanceId);
|
||||
const managementToken = computed(() => state.data.managementToken);
|
||||
const appVersion = computed(() => settingsStore.settings.versionCli);
|
||||
|
@ -99,17 +101,17 @@ export const useUsageStore = defineStore('usage', () => {
|
|||
registerCommunityEdition,
|
||||
planName,
|
||||
planId,
|
||||
executionLimit,
|
||||
executionCount,
|
||||
activeWorkflowTriggersLimit,
|
||||
activeWorkflowTriggersCount,
|
||||
executionPercentage,
|
||||
instanceId,
|
||||
managementToken,
|
||||
appVersion,
|
||||
isCloseToLimit: computed(() =>
|
||||
state.data.usage.executions.limit < 0
|
||||
state.data.usage.activeWorkflowTriggers.limit < 0
|
||||
? false
|
||||
: executionCount.value / executionLimit.value >=
|
||||
state.data.usage.executions.warningThreshold,
|
||||
: activeWorkflowTriggersCount.value / activeWorkflowTriggersLimit.value >=
|
||||
state.data.usage.activeWorkflowTriggers.warningThreshold,
|
||||
),
|
||||
viewPlansUrl: computed(
|
||||
() => `${subscriptionAppUrl.value}?${commonSubscriptionAppUrlQueryParams.value}`,
|
||||
|
@ -123,8 +125,8 @@ export const useUsageStore = defineStore('usage', () => {
|
|||
instance_id: instanceId.value,
|
||||
action: 'view_plans',
|
||||
plan_name_current: planName.value,
|
||||
usage: executionCount.value,
|
||||
quota: executionLimit.value,
|
||||
usage: activeWorkflowTriggersCount.value,
|
||||
quota: activeWorkflowTriggersLimit.value,
|
||||
})),
|
||||
};
|
||||
});
|
||||
|
|
|
@ -198,7 +198,7 @@ const openCommunityRegisterModal = () => {
|
|||
{{ locale.baseText('settings.usageAndPlan.activeWorkflows') }}
|
||||
</n8n-text>
|
||||
<div :class="$style.chart">
|
||||
<span v-if="usageStore.executionLimit > 0" :class="$style.chartLine">
|
||||
<span v-if="usageStore.activeWorkflowTriggersLimit > 0" :class="$style.chartLine">
|
||||
<span
|
||||
:class="$style.chartBar"
|
||||
:style="{ width: `${usageStore.executionPercentage}%` }"
|
||||
|
@ -209,12 +209,12 @@ const openCommunityRegisterModal = () => {
|
|||
:class="$style.count"
|
||||
keypath="settings.usageAndPlan.activeWorkflows.count"
|
||||
>
|
||||
<template #count>{{ usageStore.executionCount }}</template>
|
||||
<template #count>{{ usageStore.activeWorkflowTriggersCount }}</template>
|
||||
<template #limit>
|
||||
<span v-if="usageStore.executionLimit < 0">{{
|
||||
<span v-if="usageStore.activeWorkflowTriggersLimit < 0">{{
|
||||
locale.baseText('settings.usageAndPlan.activeWorkflows.unlimited')
|
||||
}}</span>
|
||||
<span v-else>{{ usageStore.executionLimit }}</span>
|
||||
<span v-else>{{ usageStore.activeWorkflowTriggersLimit }}</span>
|
||||
</template>
|
||||
</i18n-t>
|
||||
</div>
|
||||
|
|
Loading…
Reference in a new issue