Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(editor): Add cloud ExecutionsUsage and API blocking using licenses #6159

Merged
merged 57 commits into from
May 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
a87b0b6
Add ExecutionsUsage component
RicardoE105 May 3, 2023
1d8955c
set $sidebar-expanded-width back to 200px
RicardoE105 May 3, 2023
399060c
add days using interpolation
RicardoE105 May 3, 2023
f57d085
Rename PlanData type to CloudPlanData
RicardoE105 May 3, 2023
43209c9
Rename Metadata type to PlanMetadata
RicardoE105 May 3, 2023
0ceaf34
Make prop block in the update button
RicardoE105 May 3, 2023
a67feae
Use variable in line-height
RicardoE105 May 3, 2023
835292a
Remove progressBarSection class
RicardoE105 May 3, 2023
1db1f3f
fix trial expiration calculation
RicardoE105 May 3, 2023
0d6729c
mock expirationDate and fix issue with days left
RicardoE105 May 3, 2023
d834ec3
Remove unnecesary property from class .container
RicardoE105 May 4, 2023
0429314
inject component data via props
RicardoE105 May 4, 2023
bcfd76c
Check for plan data during app mounting and keep data in the store
RicardoE105 May 4, 2023
b48adc6
Remove mounted hook
RicardoE105 May 4, 2023
78424c9
redirect when upgrade plan is clicked
RicardoE105 May 4, 2023
0148832
Remove computed properties
RicardoE105 May 4, 2023
efb615b
Remove instance property as it's not needed anymore
RicardoE105 May 4, 2023
c8a2fb4
Flatten plan object
RicardoE105 May 4, 2023
d624c71
remove console.log
RicardoE105 May 4, 2023
17294c8
Add all cloud types within its own namespace
RicardoE105 May 6, 2023
6eef985
keep redirection inside component
RicardoE105 May 6, 2023
3d0d2ba
get computed properties back
RicardoE105 May 6, 2023
90c8556
Improve polling logic
RicardoE105 May 6, 2023
2fbb1e7
Move cloudData to its own store
RicardoE105 May 6, 2023
b765867
Remove commented interfaces
RicardoE105 May 6, 2023
807a797
remove cloudPlan from user store
RicardoE105 May 6, 2023
95f1a2e
Sync master
RicardoE105 May 6, 2023
b782e0e
fix imports
RicardoE105 May 6, 2023
cfb921d
update logic for userIsTrialing method
RicardoE105 May 9, 2023
ae0949c
centralize userIsTrialing method
RicardoE105 May 9, 2023
e1353b4
redirect to production change plan page always
RicardoE105 May 9, 2023
be626dc
Call staging or production cloud api depending on base URL
RicardoE105 May 9, 2023
34fee42
remove setting store form ExecutionUsage.vue
RicardoE105 May 9, 2023
dee8651
fix linting issue
RicardoE105 May 9, 2023
48191cd
Add trial group to PlanMetadata group
RicardoE105 May 9, 2023
bb22b9c
Move helpers into the store
RicardoE105 May 9, 2023
f8cc32a
make staging url check more specific
RicardoE105 May 9, 2023
99c51b0
make cloud state nullable
RicardoE105 May 9, 2023
4a4aa83
fix linting issue
RicardoE105 May 9, 2023
903557a
swap mockup date for endpoint
RicardoE105 May 10, 2023
aefa08a
Merge branch 'master' into ado-638-in-app-build-ui-for-upgrade-path
RicardoE105 May 10, 2023
6b48ad5
Make getCurrentPlan async
RicardoE105 May 10, 2023
fabd05c
asas
RicardoE105 May 11, 2023
765dd60
Improvements
RicardoE105 May 12, 2023
1f1ff74
small improvements
RicardoE105 May 12, 2023
4766591
chore: resolve conflicts
mutdmour May 12, 2023
b09bcdc
chore: resolve conflicts
mutdmour May 12, 2023
9b5fbba
make sure there is data before calculating trial expiration
RicardoE105 May 12, 2023
454659f
Fix issue with component not loading on first page load
RicardoE105 May 12, 2023
25b6f91
type safety improvements
RicardoE105 May 13, 2023
827e1dd
Sync master
RicardoE105 May 13, 2023
5b9c5e8
apply component ui feedback
RicardoE105 May 14, 2023
d30632e
fix linting issue
RicardoE105 May 14, 2023
d3a64a1
chore: clean up unnecessary change from merge conflict
mutdmour May 15, 2023
d2a77ab
feat: Block api feature using licenses, show notice page for trial cl…
mutdmour May 15, 2023
a745424
add pluralization to days left text
RicardoE105 May 15, 2023
9c8f61e
sync master
RicardoE105 May 15, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/cli/src/License.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,10 @@ export class License {
return this.isFeatureEnabled(LICENSE_FEATURES.VERSION_CONTROL);
}

isAPIDisabled() {
return this.isFeatureEnabled(LICENSE_FEATURES.API_DISABLED);
}

getCurrentEntitlements() {
return this.manager?.getCurrentEntitlements() ?? [];
}
Expand Down
10 changes: 10 additions & 0 deletions packages/cli/src/PublicApi/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import * as Db from '@/Db';
import { getInstanceBaseUrl } from '@/UserManagement/UserManagementHelper';
import { Container } from 'typedi';
import { InternalHooks } from '@/InternalHooks';
import { License } from '@/License';

async function createApiRouter(
version: string,
Expand Down Expand Up @@ -151,3 +152,12 @@ export const loadPublicApiVersions = async (
apiLatestVersion: Number(versions.pop()?.charAt(1)) ?? 1,
};
};

function isApiEnabledByLicense(): boolean {
const license = Container.get(License);
return !license.isAPIDisabled();
}

export function isApiEnabled(): boolean {
return !config.get('publicApi.disabled') && isApiEnabledByLicense();
}
8 changes: 4 additions & 4 deletions packages/cli/src/Server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ import {

import { executionsController } from '@/executions/executions.controller';
import { workflowStatsController } from '@/api/workflowStats.api';
import { loadPublicApiVersions } from '@/PublicApi';
import { isApiEnabled, loadPublicApiVersions } from '@/PublicApi';
import {
getInstanceBaseUrl,
isEmailSetUp,
Expand Down Expand Up @@ -277,7 +277,7 @@ export class Server extends AbstractServer {
},
},
publicApi: {
enabled: !config.getEnv('publicApi.disabled'),
enabled: isApiEnabled(),
latestVersion: 1,
path: config.getEnv('publicApi.path'),
swaggerUi: {
Expand Down Expand Up @@ -538,7 +538,7 @@ export class Server extends AbstractServer {
this.endpointWebhook,
this.endpointWebhookTest,
this.endpointPresetCredentials,
config.getEnv('publicApi.disabled') ? publicApiEndpoint : '',
isApiEnabled() ? '' : publicApiEndpoint,
...excludeEndpoints.split(':'),
].filter((u) => !!u);

Expand All @@ -564,7 +564,7 @@ export class Server extends AbstractServer {
// Public API
// ----------------------------------------

if (!config.getEnv('publicApi.disabled')) {
if (isApiEnabled()) {
const { apiRouters, apiLatestVersion } = await loadPublicApiVersions(publicApiEndpoint);
this.app.use(...apiRouters);
this.frontendSettings.publicApi.latestVersion = apiLatestVersion;
Expand Down
3 changes: 2 additions & 1 deletion packages/cli/src/audit/risks/instance.risk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
import { getN8nPackageJson, inDevelopment } from '@/constants';
import type { WorkflowEntity } from '@db/entities/WorkflowEntity';
import type { Risk, n8n } from '@/audit/types';
import { isApiEnabled } from '@/PublicApi';

function getSecuritySettings() {
if (config.getEnv('deployment.type') === 'cloud') return null;
Expand All @@ -34,7 +35,7 @@ function getSecuritySettings() {
communityPackagesEnabled: config.getEnv('nodes.communityPackages.enabled'),
versionNotificationsEnabled: config.getEnv('versionNotifications.enabled'),
templatesEnabled: config.getEnv('templates.enabled'),
publicApiEnabled: !config.getEnv('publicApi.disabled'),
publicApiEnabled: isApiEnabled(),
userManagementEnabled,
};

Expand Down
1 change: 1 addition & 0 deletions packages/cli/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ export const enum LICENSE_FEATURES {
ADVANCED_EXECUTION_FILTERS = 'feat:advancedExecutionFilters',
VARIABLES = 'feat:variables',
VERSION_CONTROL = 'feat:versionControl',
API_DISABLED = 'feat:apiDisabled',
}

export const enum LICENSE_QUOTAS {
Expand Down
13 changes: 12 additions & 1 deletion packages/design-system/src/components/N8nButton/Button.vue
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export default defineComponent({
type: String,
default: 'medium',
validator: (value: string): boolean =>
['mini', 'small', 'medium', 'large', 'xlarge'].includes(value),
['xmini', 'mini', 'small', 'medium', 'large', 'xlarge'].includes(value),
},
loading: {
type: Boolean,
Expand Down Expand Up @@ -278,6 +278,17 @@ $loading-overlay-background-color: rgba(255, 255, 255, 0);
* Sizes
*/

.xmini {
--button-padding-vertical: var(--spacing-4xs);
--button-padding-horizontal: var(--spacing-3xs);
--button-font-size: var(--font-size-3xs);

&.square {
height: 22px;
width: 22px;
}
}

.mini {
--button-padding-vertical: var(--spacing-4xs);
--button-padding-horizontal: var(--spacing-2xs);
Expand Down
1 change: 1 addition & 0 deletions packages/design-system/src/components/N8nMenu/Menu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
</el-menu>
</div>
<div :class="[$style.lowerContent, 'pb-2xs']">
<slot name="beforeLowerMenu"></slot>
<el-menu :defaultActive="defaultActive" :collapse="collapsed" v-on="$listeners">
<n8n-menu-item
v-for="item in lowerMenuItems"
Expand Down
26 changes: 25 additions & 1 deletion packages/editor-ui/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
import Modals from '@/components/Modals.vue';
import LoadingView from '@/views/LoadingView.vue';
import Telemetry from '@/components/Telemetry.vue';
import { HIRING_BANNER, LOCAL_STORAGE_THEME, VIEWS } from '@/constants';
import { CLOUD_TRIAL_CHECK_INTERVAL, HIRING_BANNER, LOCAL_STORAGE_THEME, VIEWS } from '@/constants';

import { userHelpers } from '@/mixins/userHelpers';
import { loadLanguage } from '@/plugins/i18n';
Expand All @@ -42,10 +42,12 @@ import { useUsersStore } from '@/stores/users.store';
import { useRootStore } from '@/stores/n8nRoot.store';
import { useTemplatesStore } from '@/stores/templates.store';
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
import { useCloudPlanStore } from './stores/cloudPlan.store';
import { useHistoryHelper } from '@/composables/useHistoryHelper';
import { newVersions } from '@/mixins/newVersions';
import { useRoute } from 'vue-router/composables';
import { useVersionControlStore } from '@/stores/versionControl.store';
import { useUsageStore } from '@/stores/usage.store';
import { useExternalHooks } from '@/composables';
import { defineComponent } from 'vue';

Expand Down Expand Up @@ -75,6 +77,8 @@ export default defineComponent({
useUIStore,
useUsersStore,
useVersionControlStore,
useCloudPlanStore,
useUsageStore,
),
defaultLocale(): string {
return this.rootStore.defaultLocale;
Expand Down Expand Up @@ -185,6 +189,25 @@ export default defineComponent({
window.document.body.classList.add(`theme-${theme}`);
}
},
async checkForCloudPlanData(): Promise<void> {
try {
await this.cloudPlanStore.getOwnerCurrentPLan();
if (!this.cloudPlanStore.userIsTrialing) return;
await this.cloudPlanStore.getInstanceCurrentUsage();
this.startPollingInstanceUsageData();
} catch {}
},
startPollingInstanceUsageData() {
const interval = setInterval(async () => {
RicardoE105 marked this conversation as resolved.
Show resolved Hide resolved
try {
await this.cloudPlanStore.getInstanceCurrentUsage();
if (this.cloudPlanStore.trialExpired || this.cloudPlanStore.allExecutionsUsed) {
clearTimeout(interval);
return;
}
} catch {}
RicardoE105 marked this conversation as resolved.
Show resolved Hide resolved
}, CLOUD_TRIAL_CHECK_INTERVAL);
},
},
async mounted() {
this.setTheme();
Expand All @@ -193,6 +216,7 @@ export default defineComponent({
this.authenticate();
this.redirectIfNecessary();
void this.checkForNewVersions();
void this.checkForCloudPlanData();

this.loading = false;

Expand Down
39 changes: 39 additions & 0 deletions packages/editor-ui/src/Interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1450,3 +1450,42 @@ export type VersionControlPreferences = {
branchColor: string;
publicKey?: string;
};

export declare namespace Cloud {
RicardoE105 marked this conversation as resolved.
Show resolved Hide resolved
export interface PlanData {
planId: number;
monthlyExecutionsLimit: number;
activeWorkflowsLimit: number;
credentialsLimit: number;
isActive: boolean;
displayName: string;
expirationDate: string;
metadata: PlanMetadata;
}

export interface PlanMetadata {
version: 'v1';
group: 'opt-out' | 'opt-in' | 'trial';
slug: 'pro-1' | 'pro-2' | 'starter' | 'trial-1';
trial?: Trial;
}

interface Trial {
length: number;
gracePeriod: number;
}
}

export interface CloudPlanState {
data: Cloud.PlanData | null;
usage: InstanceUsage | null;
loadingPlan: boolean;
}

export interface InstanceUsage {
timeframe?: string;
executions: number;
activeWorkflows: number;
}

export type CloudPlanAndUsageData = Cloud.PlanData & { usage: InstanceUsage };
13 changes: 13 additions & 0 deletions packages/editor-ui/src/api/cloudPlans.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import type { Cloud, IRestApiContext, InstanceUsage } from '@/Interface';
import { get } from '@/utils';

export async function getCurrentPlan(
context: IRestApiContext,
cloudUserId: string,
): Promise<Cloud.PlanData> {
return get(context.baseUrl, `/user/${cloudUserId}/plan`);
}

export async function getCurrentUsage(context: IRestApiContext): Promise<InstanceUsage> {
return get(context.baseUrl, '/limits');
}
Loading