feat(editor): Implement new banners framework (#6603)

*  Implemented new grid row - banners
*  Fixing node creator and executions sidebar position after layout update
* 💄 Added configurable round corners to the Callout component
*  Fixing mouse position detection and main tab bar position
*  Implemented basic banner component structure
*  Implemented banner state and dismiss logic
*  Fixing grid layout. Updating banners height state dynamically
*  Fix zoom to fit position, mouse position in demo mode and callout vertical alignment
*  Implementing proper trial banners logic
* 💄 Only showing execution usage data once the sidebar is fully expanded
*  Implemented permanent/temporary dismiss logic for v1 flag
*  Minor refactoring of banner logic
*  Updating permanent dismiss logic to work with all banners
* 👕 Fixing linting errors
* ✔️ Updating Callout component test snapshots
* 💄 Tweaking zoom to fit position
* ✔️ Updating testing endpoints to use new store data
*  Added banners unit tests
* ✔️ Fixing failing banner tests
*  Added more banner tests
*  Updating banners dimensions on resize, removing leftover code
* ✔️ Removing store import from API file
* 👕 Fixing lint errors
*  Updating migration files
*  Using query parameters in migrations
* 👌 Addressing design review feedback
*  Updating upgrade plan button click
*  Updating the migrations syntax
* 👌 Updating permanent banner dismiss endpoint and back-end logic
* 👌 Refactoring trial banner component and ui store
* 👌 Addressing more points from code review
* 👌 Moving DOM logic from the store
* ✔️ Updated callout component snapshots
* 👌 Updating mysql migration file
* ✔️ Updating e2e test canvas coordinates after setting it's position to absolute
* 👌 Addressing back-end review feedback
* 👌 Improving typing around banners
* 👕 Fixing lint errors
This commit is contained in:
Milorad FIlipović
2023-07-14 15:36:17 +02:00
committed by GitHub
parent ff0759530d
commit 4240e76253
47 changed files with 637 additions and 221 deletions

View File

@@ -31,6 +31,7 @@ import { useUIStore } from './ui.store';
import { useUsersStore } from './users.store';
import { useVersionsStore } from './versions.store';
import { makeRestApiRequest } from '@/utils';
import { useCloudPlanStore } from './cloudPlan.store';
export const useSettingsStore = defineStore(STORES.SETTINGS, {
state: (): ISettingsState => ({
@@ -170,6 +171,9 @@ export const useSettingsStore = defineStore(STORES.SETTINGS, {
isDefaultAuthenticationSaml(): boolean {
return this.userManagement.authenticationMethod === UserManagementAuthenticationMethod.Saml;
},
permanentlyDismissedBanners(): string[] {
return this.settings.banners?.dismissed ?? [];
},
isBelowUserQuota(): boolean {
const userStore = useUsersStore();
return (
@@ -219,8 +223,14 @@ export const useSettingsStore = defineStore(STORES.SETTINGS, {
rootStore.setN8nMetadata(settings.n8nMetadata || {});
rootStore.setDefaultLocale(settings.defaultLocale);
rootStore.setIsNpmAvailable(settings.isNpmAvailable);
if (settings.banners.v1.dismissed) {
useUIStore().setBanners({ v1: { dismissed: true, mode: 'permanent' } });
const isV1BannerDismissedPermanently = settings.banners.dismissed.includes('V1');
if (
!isV1BannerDismissedPermanently &&
useRootStore().versionCli.startsWith('1.') &&
!useCloudPlanStore().userIsTrialing
) {
useUIStore().showBanner('V1');
}
useVersionsStore().setVersionNotificationSettings(settings.versionNotifications);

View File

@@ -34,6 +34,7 @@ import {
SOURCE_CONTROL_PULL_MODAL_KEY,
} from '@/constants';
import type {
CloudUpdateLinkSourceType,
CurlToJSONResponse,
IFakeDoorLocation,
IMenuItem,
@@ -41,6 +42,7 @@ import type {
IOnboardingCallPrompt,
IUser,
UIState,
UTMCampaign,
XYPosition,
} from '@/Interface';
import { defineStore } from 'pinia';
@@ -53,7 +55,9 @@ import type { BaseTextKey } from '@/plugins/i18n';
import { i18n as locale } from '@/plugins/i18n';
import type { Modals, NewCredentialsModal } from '@/Interface';
import { useTelemetryStore } from '@/stores/telemetry.store';
import { dismissV1BannerPermanently } from '@/api/ui';
import { getStyleTokenValue } from '@/utils';
import { dismissBannerPermanently } from '@/api/ui';
import type { Banners } from 'n8n-workflow';
export const useUIStore = defineStore(STORES.UI, {
state: (): UIState => ({
@@ -144,12 +148,6 @@ export const useUIStore = defineStore(STORES.UI, {
},
modalStack: [],
sidebarMenuCollapsed: true,
banners: {
v1: {
dismissed: false,
mode: 'temporary',
},
},
isPageLoading: true,
currentView: '',
mainPanelPosition: 0.5,
@@ -191,6 +189,12 @@ export const useUIStore = defineStore(STORES.UI, {
nodeViewInitialized: false,
addFirstStepOnLoad: false,
executionSidebarAutoRefresh: true,
banners: {
V1: { dismissed: true },
TRIAL: { dismissed: true },
TRIAL_OVER: { dismissed: true },
},
bannersHeight: 0,
}),
getters: {
contextBasedTranslationKeys() {
@@ -341,6 +345,9 @@ export const useUIStore = defineStore(STORES.UI, {
return linkUrl;
};
},
headerHeight() {
return Number(getStyleTokenValue('--header-height'));
},
},
actions: {
setBanners(banners: UIState['banners']): void {
@@ -525,27 +532,15 @@ export const useUIStore = defineStore(STORES.UI, {
toggleSidebarMenuCollapse(): void {
this.sidebarMenuCollapsed = !this.sidebarMenuCollapsed;
},
async dismissBanner(bannerType: 'v1', mode: 'temporary' | 'permanent'): Promise<void> {
if (mode === 'permanent') {
await dismissV1BannerPermanently(useRootStore().getRestApiContext);
this.banners[bannerType].dismissed = true;
this.banners[bannerType].mode = 'permanent';
return;
}
this.banners[bannerType].dismissed = true;
this.banners[bannerType].mode = 'temporary';
},
restoreBanner(bannerType: 'v1'): void {
if (this.banners[bannerType].dismissed && this.banners[bannerType].mode === 'temporary') {
this.banners[bannerType].dismissed = false;
}
},
async getCurlToJson(curlCommand: string): Promise<CurlToJSONResponse> {
const rootStore = useRootStore();
return getCurlToJson(rootStore.getRestApiContext, curlCommand);
},
goToUpgrade(source: string, utm_campaign: string, mode: 'open' | 'redirect' = 'open'): void {
goToUpgrade(
source: CloudUpdateLinkSourceType,
utm_campaign: UTMCampaign,
mode: 'open' | 'redirect' = 'open',
): void {
const { usageLeft, trialDaysLeft, userIsTrialing } = useCloudPlanStore();
const { executionsLeft, workflowsLeft } = usageLeft;
useTelemetryStore().track('User clicked upgrade CTA', {
@@ -562,5 +557,27 @@ export const useUIStore = defineStore(STORES.UI, {
location.href = this.upgradeLinkUrl(source, utm_campaign);
}
},
async dismissBanner(
name: Banners,
type: 'temporary' | 'permanent' = 'temporary',
): Promise<void> {
if (type === 'permanent') {
await dismissBannerPermanently(useRootStore().getRestApiContext, {
bannerName: name,
dismissedBanners: useSettingsStore().permanentlyDismissedBanners,
});
this.banners[name].dismissed = true;
this.banners[name].type = 'permanent';
return;
}
this.banners[name].dismissed = true;
this.banners[name].type = 'temporary';
},
showBanner(name: Banners): void {
this.banners[name].dismissed = false;
},
updateBannersHeight(newHeight: number): void {
this.bannersHeight = newHeight;
},
},
});