From 3fb2a8793ad337554d5937312b9c73e1e834c85d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82?= Date: Mon, 28 Aug 2023 11:53:02 +0200 Subject: [PATCH 01/95] chore(web): eslint react hooks part 3 --- apps/web/.eslintrc.js | 2 +- apps/web/src/components/utils/ProductLead.tsx | 42 +++++----- .../src/components/workflow/FlowEditor.tsx | 1 + .../icons/actions/PlusFilled.tsx | 10 +-- apps/web/src/hooks/useVariablesManager.ts | 1 + apps/web/src/pages/auth/InvitationPage.tsx | 4 +- apps/web/src/pages/auth/LoginPage.tsx | 1 + .../components/CreateOrganizationForm.tsx | 2 +- .../pages/auth/components/useAcceptInvite.ts | 43 +++++----- .../web/src/pages/brand/tabs/BrandingForm.tsx | 57 +++++--------- .../web/src/pages/brand/tabs/LayoutEditor.tsx | 6 +- .../src/pages/brand/tabs/LayoutsListPage.tsx | 6 +- .../components/ConnectIntegrationForm.tsx | 2 +- .../Modal/ConnectIntegrationForm.tsx | 2 +- .../CreateProviderInstanceSidebar.tsx | 2 +- .../multi-provider/UpdateProviderSidebar.tsx | 4 +- .../src/pages/invites/MembersInvitePage.tsx | 26 +++---- .../components/ProjectRow.tsx | 2 +- .../components/QuickStartWrapper.tsx | 15 +--- .../quick-start/components/SetupStatus.tsx | 6 +- .../quick-start/components/SetupTimeline.tsx | 2 +- .../components/layout/GetStartedLayout.tsx | 12 +-- .../pages/quick-start/steps/DigestPreview.tsx | 2 +- .../quick-start/steps/FrameworkSetup.tsx | 2 +- .../pages/quick-start/steps/GetStarted.tsx | 2 +- .../pages/quick-start/steps/InAppSuccess.tsx | 2 +- .../quick-start/steps/NotificationCenter.tsx | 2 +- .../web/src/pages/quick-start/steps/Setup.tsx | 2 +- .../src/pages/settings/tabs/EmailSettings.tsx | 8 +- .../components/ChannelStepEditor.tsx | 2 +- .../components/TemplateEditorFormProvider.tsx | 3 +- .../templates/components/TestWorkflow.tsx | 5 +- .../components/VariableManager.cy.tsx | 1 + .../email-editor/EmailInboxContent.tsx | 14 ++-- .../email-editor/TextRowContent.tsx | 1 + .../in-app-editor/AvatarFeedFields.tsx | 12 +-- .../src/pages/templates/editor/Preview.tsx | 3 +- .../templates/editor/TemplateEditorPage.tsx | 2 +- .../src/pages/templates/hooks/useCreate.ts | 1 + apps/web/src/pages/templates/hooks/useSave.ts | 78 ------------------- .../pages/templates/hooks/useTourStorage.ts | 9 +-- .../templates/workflow/SideBar/Sidebar.tsx | 1 - .../templates/workflow/WorkflowEditor.tsx | 2 +- .../workflow/node-types/ChannelNode.tsx | 2 +- .../workflow/node-types/WorkflowNode.tsx | 4 +- apps/web/src/pages/tenants/TenantsPage.tsx | 2 +- .../components/CreateTenantSidebar.tsx | 1 + .../components/UpdateTenantSidebar.tsx | 2 +- 48 files changed, 150 insertions(+), 263 deletions(-) delete mode 100644 apps/web/src/pages/templates/hooks/useSave.ts diff --git a/apps/web/.eslintrc.js b/apps/web/.eslintrc.js index be5f6182901..178bfc319bf 100644 --- a/apps/web/.eslintrc.js +++ b/apps/web/.eslintrc.js @@ -36,7 +36,7 @@ module.exports = { }, ], "react-hooks/rules-of-hooks": 'error', - "react-hooks/exhaustive-deps": 'off' + "react-hooks/exhaustive-deps": 'warn' }, env: { 'cypress/globals': true, diff --git a/apps/web/src/components/utils/ProductLead.tsx b/apps/web/src/components/utils/ProductLead.tsx index 0d3a3d94719..ac9b87d2c89 100644 --- a/apps/web/src/components/utils/ProductLead.tsx +++ b/apps/web/src/components/utils/ProductLead.tsx @@ -1,6 +1,8 @@ import { ActionIcon, Group, Title, useMantineTheme } from '@mantine/core'; import { useLocalStorage } from '@mantine/hooks'; import { CSSProperties, ReactNode, useEffect } from 'react'; +import styled from '@emotion/styled'; + import { IS_DOCKER_HOSTED } from '../../config'; import { Button, colors, Text } from '../../design-system'; import { Calendar, Close } from '../../design-system/icons'; @@ -13,23 +15,12 @@ export enum ProductLeadVariants { COLUMN = 'column', } -const Wrapper = ({ children, variant, id }: { children: any; variant: ProductLeadVariants; id: string }) => { - const segment = useSegment(); - - useEffect(() => { - segment.track('Banner seen - [Product lead]', { - id, - }); - }, []); - - return variant === ProductLeadVariants.COLUMN ? ( - - {children} - - ) : ( - children - ); -}; +const WrapperHolder = styled.div<{ variant: ProductLeadVariants }>` + display: flex; + flex-direction: ${({ variant }) => (variant === ProductLeadVariants.COLUMN ? 'column' : 'row')}; + justify-content: space-between; + gap: 24px; +`; export const ProductLead = ({ title, @@ -54,11 +45,16 @@ export const ProductLead = ({ defaultValue: true, getInitialValueInEffect: true, }); - + const segment = useSegment(); const theme = useMantineTheme(); const dark = theme.colorScheme === 'dark'; const isSelfHosted = IS_DOCKER_HOSTED; - const segment = useSegment(); + + useEffect(() => { + segment.track('Banner seen - [Product lead]', { + id, + }); + }, [segment, id]); if (open === false) { return null; @@ -74,7 +70,7 @@ export const ProductLead = ({ ...style, }} > - +
@@ -86,6 +82,7 @@ export const ProductLead = ({ { setOpen(false); segment.track('Banner hidden - [Product lead]', { @@ -101,9 +98,8 @@ export const ProductLead = ({ {text}
- + + + + } + > + {fields.map((item, index) => { + const filterFieldOn = (fields[index] as any).on; + console.log('item', item); + + return ( +
+ + + {index > 0 ? ( + + { + return ( + + ); + }} + /> + + + { + return ( + + ); + }} + /> + + + { + return ( + +
+ +
+ + } + required + disabled={field.value === 'IS_DEFINED'} + error={fieldState.error?.message} + placeholder="Value" + data-test-id="filter-value-input" + /> + ); + }} + /> +
+ + + + + } + middlewares={{ flip: false, shift: false }} + position="bottom-end" + > + { + insert(index + 1, getValues(`conditions.0.children.${index}`)); + }} + icon={} + > + Duplicate + + { + remove(index); + }} + icon={} + > + Delete + + + +
+
+ ); + })} + + + + + + ); +} +export function Conditions1({ + isOpened, + conditions, + onClose, + setConditions, +}: { + isOpened: boolean; + onClose: () => void; + setConditions: (data: any) => void; + conditions: any; +}) { + const { + control, + setValue, + getValues, + handleSubmit: handleSubmit1, + watch, + } = useForm({ + defaultValues: { conditions }, + }); + const { fields, append, update, remove, insert } = useFieldArray({ + control, + name: `conditions.0.children`, + }); + + const watchConditions = watch(`conditions.0.children`); + + const FilterPartTypeList = [ + { value: FilterPartTypeEnum.TENANT, label: FILTER_TO_LABEL[FilterPartTypeEnum.TENANT] }, + { value: FilterPartTypeEnum.SUBSCRIBER, label: FILTER_TO_LABEL[FilterPartTypeEnum.SUBSCRIBER] }, + ]; + function handleOnChildOnChange(index: number) { + return (data) => { + const newField = Object.assign({}, fields[index], { on: data }); + update(index, newField); + }; + } + + useEffect(() => { + console.log(watchConditions); + }, [watchConditions]); + // console.log('conditions', conditions); + + function updateConditions(data) { + console.log('data 1', data); + setConditions(data); + } + + return ( + { + e.preventDefault(); + console.log(e); + handleSubmit1(updateConditions)(e); + onClose(); + + // e.stopPropagation(); + }} + customHeader={ +
+ + + Condition for + +
+ } + customFooter={ + + + + + } + > + {fields.map((item, index) => { + const filterFieldOn = (fields[index] as any).on; + console.log('item', item); + + return ( +
+ + + {index > 0 ? ( + + { + return ( + + ); + }} + /> + + + { + return ( + + ); + }} + /> + + + { + return ( + +
+ +
+ + } + required + disabled={field.value === 'IS_DEFINED'} + error={fieldState.error?.message} + placeholder="Value" + data-test-id="filter-value-input" + /> + ); + }} + /> +
+ + + + + } + middlewares={{ flip: false, shift: false }} + position="bottom-end" + > + { + insert(index + 1, getValues(`conditions.0.children.${index}`)); + }} + icon={} + > + Duplicate + + { + remove(index); + }} + icon={} + > + Delete + + + +
+
+ ); + })} + + + + +
+ ); +} +export function Conditions2({ + isOpened, + onClose, + control, + setValue, + getValues, +}: { + isOpened: boolean; + onClose: () => void; + control: any; + setValue: any; + getValues: any; +}) { + const { fields, append, update, remove, insert } = useFieldArray({ + control, + name: `conditions.0.children`, + }); + + const FilterPartTypeList = [ + { value: FilterPartTypeEnum.TENANT, label: FILTER_TO_LABEL[FilterPartTypeEnum.TENANT] }, + { value: FilterPartTypeEnum.SUBSCRIBER, label: FILTER_TO_LABEL[FilterPartTypeEnum.SUBSCRIBER] }, + ]; + console.log(28 / 4); + function handleOnChildOnChange(index: number) { + return (data) => { + const newField = Object.assign({}, fields[index], { on: data }); + update(index, newField); + }; + } + + return ( + + + + Condition for + + + } + customFooter={ + + + + + } + > + {fields.map((item, index) => { + const filterFieldOn = (fields[index] as any).on; + + return ( +
+ + + {index > 0 ? ( + + { + return ( + + ); + }} + /> + + + { + return ( + + ); + }} + /> + + + { + return ( + + ); + }} + /> + + + + + + } + middlewares={{ flip: false, shift: false }} + position="bottom-end" + > + { + insert(index + 1, getValues(`conditions.0.children.${index}`)); + }} + icon={} + > + Duplicate + + { + remove(index); + }} + icon={} + > + Delete + + + + +
+ ); + })} + + + + +
+ ); +} + +const ItemName = () => { + return
bla
; +}; + +const Wrapper = styled.div` + .mantine-Select-wrapper:not(:hover) { + .mantine-Select-input { + border-color: transparent; + color: ${colors.B60}; + } + .mantine-Input-rightSection.mantine-Select-rightSection { + svg { + display: none; + } + } + } +`; diff --git a/apps/web/src/design-system/icons/actions/ConditionPlus.tsx b/apps/web/src/design-system/icons/actions/ConditionPlus.tsx new file mode 100644 index 00000000000..1877a995c0b --- /dev/null +++ b/apps/web/src/design-system/icons/actions/ConditionPlus.tsx @@ -0,0 +1,29 @@ +import React from 'react'; + +export function ConditionPlus(props: React.ComponentPropsWithoutRef<'svg'>) { + return ( + + + + + + + + + + ); +} diff --git a/apps/web/src/design-system/icons/actions/Duplicate.tsx b/apps/web/src/design-system/icons/actions/Duplicate.tsx new file mode 100644 index 00000000000..10eee70d2ca --- /dev/null +++ b/apps/web/src/design-system/icons/actions/Duplicate.tsx @@ -0,0 +1,12 @@ +import React from 'react'; +/* eslint-disable */ +export function Duplicate(props: React.ComponentPropsWithoutRef<'svg'>) { + return ( + + + + ); +} diff --git a/apps/web/src/design-system/icons/actions/PlusFilled.tsx b/apps/web/src/design-system/icons/actions/PlusFilled.tsx index 6087e41ca02..8c1eac0e7ea 100644 --- a/apps/web/src/design-system/icons/actions/PlusFilled.tsx +++ b/apps/web/src/design-system/icons/actions/PlusFilled.tsx @@ -7,14 +7,14 @@ export function PlusFilled(props: React.ComponentPropsWithoutRef<'svg'>) { - - + + diff --git a/apps/web/src/design-system/icons/general/Condition.tsx b/apps/web/src/design-system/icons/general/Condition.tsx index 73ec7b5f743..1593e16d602 100644 --- a/apps/web/src/design-system/icons/general/Condition.tsx +++ b/apps/web/src/design-system/icons/general/Condition.tsx @@ -1,5 +1,5 @@ import React from 'react'; -/* eslint-disable */ + export function Condition(props: React.ComponentPropsWithoutRef<'svg'>) { return ( @@ -19,7 +19,7 @@ export function Condition(props: React.ComponentPropsWithoutRef<'svg'>) { - + diff --git a/apps/web/src/design-system/icons/index.ts b/apps/web/src/design-system/icons/index.ts index dcc7189b1c6..a209a84f806 100644 --- a/apps/web/src/design-system/icons/index.ts +++ b/apps/web/src/design-system/icons/index.ts @@ -94,6 +94,8 @@ export { Edit } from './actions/Edit'; export { Upload } from './actions/Upload'; export { Invite } from './actions/Invite'; export { PlusFilled } from './actions/PlusFilled'; +export { ConditionPlus } from './actions/ConditionPlus'; +export { Duplicate } from './actions/Duplicate'; export { ArrowDown } from './arrows/ArrowDown'; export { DoubleArrowRight } from './arrows/DoubleArrowRight'; diff --git a/apps/web/src/design-system/sidebar/Sidebar.tsx b/apps/web/src/design-system/sidebar/Sidebar.tsx index 59508b2f91f..20f7f4485ad 100644 --- a/apps/web/src/design-system/sidebar/Sidebar.tsx +++ b/apps/web/src/design-system/sidebar/Sidebar.tsx @@ -1,5 +1,5 @@ import styled from '@emotion/styled'; -import { ActionIcon, createStyles, Drawer, Loader, MantineTheme, Stack } from '@mantine/core'; +import { ActionIcon, Box, createStyles, Drawer, Loader, MantineTheme, Stack } from '@mantine/core'; import { ReactNode } from 'react'; import { HEADER_HEIGHT } from '../../components/layout/constants'; @@ -46,10 +46,11 @@ const useDrawerStyles = createStyles((theme: MantineTheme) => { return { root: { position: 'absolute', - zIndex: 1, + // zIndex: 1, }, drawer: { position: 'fixed', + // zIndex: 9999, top: `${INTEGRATION_SETTING_TOP}px`, right: 0, bottom: 0, @@ -122,11 +123,12 @@ export const Sidebar = ({ closeOnEscape={false} withinPortal={false} trapFocus={false} + zIndex={999} data-expanded={isExpanded} >
- {isExpanded && ( + {isExpanded && onBack && ( diff --git a/apps/web/src/pages/integrations/components/multi-provider/CreateProviderInstanceSidebar.tsx b/apps/web/src/pages/integrations/components/multi-provider/CreateProviderInstanceSidebar.tsx index 39f08a02096..ebd17b528a1 100644 --- a/apps/web/src/pages/integrations/components/multi-provider/CreateProviderInstanceSidebar.tsx +++ b/apps/web/src/pages/integrations/components/multi-provider/CreateProviderInstanceSidebar.tsx @@ -1,12 +1,21 @@ import { ActionIcon, Group, Radio, Text } from '@mantine/core'; -import { useEffect, useMemo } from 'react'; +import { useEffect, useMemo, useState } from 'react'; import { useMutation, useQueryClient } from '@tanstack/react-query'; import { Controller, useForm } from 'react-hook-form'; import styled from '@emotion/styled'; -import { ChannelTypeEnum, ICreateIntegrationBodyDto, InAppProviderIdEnum, providers } from '@novu/shared'; +import { + BuilderFieldType, + BuilderGroupValues, + ChannelTypeEnum, + FilterParts, + FilterPartTypeEnum, + ICreateIntegrationBodyDto, + InAppProviderIdEnum, + providers, +} from '@novu/shared'; import { Button, colors, NameInput, Sidebar } from '../../../../design-system'; -import { ArrowLeft } from '../../../../design-system/icons'; +import { ArrowLeft, ConditionPlus } from '../../../../design-system/icons'; import { inputStyles } from '../../../../design-system/config/inputs.styles'; import { useFetchEnvironments } from '../../../../hooks/useFetchEnvironments'; import { useSegment } from '../../../../components/providers/SegmentProvider'; @@ -19,10 +28,17 @@ import { CHANNEL_TYPE_TO_STRING } from '../../../../utils/channels'; import type { IntegrationEntity } from '../../types'; import { useProviders } from '../../useProviders'; import { When } from '../../../../components/utils/When'; +import { Conditions } from '../../../../components/conditions/Conditions'; interface ICreateProviderInstanceForm { name: string; environmentId: string; + conditions: { + isNegated?: boolean; + type?: BuilderFieldType; + value?: BuilderGroupValues; + children?: FilterParts[]; + }[]; } export function CreateProviderInstanceSidebar({ @@ -42,6 +58,7 @@ export function CreateProviderInstanceSidebar({ }) { const { environments, isLoading: areEnvironmentsLoading } = useFetchEnvironments(); const { isLoading: areIntegrationsLoading, providers: integrations } = useProviders(); + const [openConditions, setOpenConditions] = useState(false); const isLoading = areEnvironmentsLoading || areIntegrationsLoading; const queryClient = useQueryClient(); const segment = useSegment(); @@ -57,15 +74,17 @@ export function CreateProviderInstanceSidebar({ ICreateIntegrationBodyDto >(createIntegration); - const { handleSubmit, control, reset, watch } = useForm({ + const { handleSubmit, control, reset, watch, setValue, getValues } = useForm({ shouldUseNativeValidation: false, defaultValues: { name: '', environmentId: '', + conditions: [], }, }); const selectedEnvironmentId = watch('environmentId'); + const conditions = watch('conditions'); const showInAppErrorMessage = useMemo(() => { if (!provider || integrations.length === 0 || provider.id !== InAppProviderIdEnum.Novu) { @@ -86,7 +105,8 @@ export function CreateProviderInstanceSidebar({ } const { channel: selectedChannel } = provider; - const { environmentId } = data; + const { environmentId, conditions: cond } = data; + console.log('data', cond); const { _id: integrationId } = await createIntegrationApi({ providerId: provider.id, @@ -95,6 +115,7 @@ export function CreateProviderInstanceSidebar({ credentials: {}, active: provider.channel === ChannelTypeEnum.IN_APP ? true : false, check: false, + conditions: cond, _environmentId: environmentId, }); @@ -124,6 +145,21 @@ export function CreateProviderInstanceSidebar({ reset({ name: provider?.displayName ?? '', environmentId: environments.find((env) => env.name === 'Development')?._id || '', + conditions: [ + { + isNegated: false, + type: 'GROUP', + value: 'AND', + children: [ + { + on: FilterPartTypeEnum.TENANT, + field: 'identifier', + value: 'pawan', + operator: 'EQUAL', + }, + ], + }, + ], }); }, [environments, provider]); @@ -131,6 +167,21 @@ export function CreateProviderInstanceSidebar({ return null; } + console.log(conditions); + + if (openConditions) { + return ( + { + setValue('conditions', data.conditions); + }} + onClose={() => setOpenConditions(false)} + /> + ); + } + return ( + + You can only create one {provider.displayName} per environment. diff --git a/apps/web/src/pages/integrations/components/multi-provider/UpdateProviderSidebar.tsx b/apps/web/src/pages/integrations/components/multi-provider/UpdateProviderSidebar.tsx index 6580f2c836d..4b32a8089e5 100644 --- a/apps/web/src/pages/integrations/components/multi-provider/UpdateProviderSidebar.tsx +++ b/apps/web/src/pages/integrations/components/multi-provider/UpdateProviderSidebar.tsx @@ -5,9 +5,12 @@ import slugify from 'slugify'; import { Controller, FormProvider, useForm, useWatch } from 'react-hook-form'; import { useIntercom } from 'react-use-intercom'; import { + BuilderFieldType, + BuilderGroupValues, CHANNELS_WITH_PRIMARY, CredentialsKeyEnum, EmailProviderIdEnum, + FilterParts, IConfigCredentials, IConstructIntegrationDto, ICredentialsDto, @@ -36,12 +39,20 @@ import { NovuInAppSetupWarning } from '../NovuInAppSetupWarning'; import { NovuProviderSidebarContent } from './NovuProviderSidebarContent'; import { useSelectPrimaryIntegrationModal } from './useSelectPrimaryIntegrationModal'; import { ShareableUrl } from '../Modal/ConnectIntegrationForm'; +import { Conditions } from '../../../../components/conditions/Conditions'; +import { ConditionPlus } from '../../../../design-system/icons'; interface IProviderForm { name: string; credentials: ICredentialsDto; active: boolean; identifier: string; + conditions: { + isNegated?: boolean; + type?: BuilderFieldType; + value?: BuilderGroupValues; + children?: FilterParts[]; + }[]; } enum SidebarStateEnum { @@ -62,6 +73,7 @@ export function UpdateProviderSidebar({ const { isLoading: areEnvironmentsLoading } = useFetchEnvironments(); const [selectedProvider, setSelectedProvider] = useState(null); const [sidebarState, setSidebarState] = useState(SidebarStateEnum.NORMAL); + const [openConditions, setOpenConditions] = useState(false); const [framework, setFramework] = useState(null); const { providers, isLoading: areProvidersLoading } = useProviders(); const isNovuInAppProvider = selectedProvider?.providerId === InAppProviderIdEnum.Novu; @@ -79,6 +91,7 @@ export function UpdateProviderSidebar({ credentials: {}, active: false, identifier: '', + conditions: [], }, }); const { @@ -87,6 +100,7 @@ export function UpdateProviderSidebar({ reset, watch, setValue, + getValues, formState: { errors, isDirty, dirtyFields }, } = methods; @@ -138,6 +152,7 @@ export function UpdateProviderSidebar({ return prev; }, {} as any), + conditions: foundProvider.conditions, active: foundProvider.active, }); }, [integrationId, providers]); @@ -206,6 +221,19 @@ export function UpdateProviderSidebar({ name: `credentials.${CredentialsKeyEnum.Hmac}`, }); + if (openConditions) { + return ( + { + setValue('conditions', data.conditions); + }} + onClose={() => setOpenConditions(false)} + /> + ); + } + if ( SmsProviderIdEnum.Novu === selectedProvider?.providerId || EmailProviderIdEnum.Novu === selectedProvider?.providerId @@ -326,6 +354,9 @@ export function UpdateProviderSidebar({ + diff --git a/apps/web/src/pages/integrations/types.ts b/apps/web/src/pages/integrations/types.ts index 3439cc75579..8ee50d1fc59 100644 --- a/apps/web/src/pages/integrations/types.ts +++ b/apps/web/src/pages/integrations/types.ts @@ -1,5 +1,8 @@ import type { + BuilderFieldType, + BuilderGroupValues, ChannelTypeEnum, + FilterParts, IConfigCredentials, ICredentials, ILogoFileName, @@ -33,6 +36,12 @@ export interface IIntegratedProvider { comingSoon: boolean; active: boolean; connected: boolean; + conditions?: { + isNegated?: boolean; + type?: BuilderFieldType; + value?: BuilderGroupValues; + children?: FilterParts[]; + }[]; logoFileName: ILogoFileName; betaVersion: boolean; novu?: boolean; @@ -51,6 +60,12 @@ export interface IntegrationEntity { providerId: ProvidersIdEnum; channel: ChannelTypeEnum; credentials: ICredentials; + conditions?: { + isNegated?: boolean; + type?: BuilderFieldType; + value?: BuilderGroupValues; + children?: FilterParts[]; + }[]; active: boolean; deleted: boolean; order: number; diff --git a/apps/web/src/pages/integrations/useProviders.ts b/apps/web/src/pages/integrations/useProviders.ts index 923970872df..64ecbb4d9a4 100644 --- a/apps/web/src/pages/integrations/useProviders.ts +++ b/apps/web/src/pages/integrations/useProviders.ts @@ -2,6 +2,7 @@ import { useMemo } from 'react'; import * as cloneDeep from 'lodash.clonedeep'; import { ChannelTypeEnum, + FilterPartTypeEnum, IConfigCredentials, IProviderConfig, NOVU_SMS_EMAIL_PROVIDERS, @@ -116,6 +117,21 @@ function initializeProvidersByIntegration(integrations: IntegrationEntity[]): II name: integrationItem?.name, identifier: integrationItem?.identifier, primary: integrationItem?.primary ?? false, + conditions: integrationItem?.conditions ?? [ + { + isNegated: false, + type: 'GROUP', + value: 'AND', + children: [ + { + on: FilterPartTypeEnum.TENANT, + field: 'identifier', + value: 'pawan', + operator: 'EQUAL', + }, + ], + }, + ], }; }); } diff --git a/apps/web/src/pages/templates/editor/TemplateEditorPage.tsx b/apps/web/src/pages/templates/editor/TemplateEditorPage.tsx index 63555093811..4a62f010910 100644 --- a/apps/web/src/pages/templates/editor/TemplateEditorPage.tsx +++ b/apps/web/src/pages/templates/editor/TemplateEditorPage.tsx @@ -82,11 +82,11 @@ function BaseTemplateEditorPage() { - + {/**/} ); } diff --git a/apps/web/src/pages/templates/workflow/SideBar/Sidebar.tsx b/apps/web/src/pages/templates/workflow/SideBar/Sidebar.tsx index 8b4a57b6e31..d60a41667ca 100644 --- a/apps/web/src/pages/templates/workflow/SideBar/Sidebar.tsx +++ b/apps/web/src/pages/templates/workflow/SideBar/Sidebar.tsx @@ -40,5 +40,5 @@ const SideBarWrapper = styled.div<{ dark: boolean }>` background: transparent; height: 100%; right: 8px; - z-index: 9999; + z-index: 5; `; diff --git a/apps/web/src/pages/templates/workflow/SideBar/StepSettings.tsx b/apps/web/src/pages/templates/workflow/SideBar/StepSettings.tsx index aec4e688048..c27c95a7fb8 100644 --- a/apps/web/src/pages/templates/workflow/SideBar/StepSettings.tsx +++ b/apps/web/src/pages/templates/workflow/SideBar/StepSettings.tsx @@ -1,5 +1,5 @@ import { Group } from '@mantine/core'; -import { useFormContext } from 'react-hook-form'; +import { useFieldArray, useFormContext } from 'react-hook-form'; import { Button } from '../../../../design-system'; import type { IForm } from '../../components/formTypes'; @@ -7,7 +7,7 @@ import { StepActiveSwitch } from '../StepActiveSwitch'; import { useEnvController } from '../../../../hooks'; import { ShouldStopOnFailSwitch } from '../ShouldStopOnFailSwitch'; import { ReplyCallback, ReplyCallbackSwitch } from '../ReplyCallback'; -import { useParams } from 'react-router-dom'; +import { useParams, Outlet } from 'react-router-dom'; import { StepTypeEnum } from '@novu/shared'; import { When } from '../../../../components/utils/When'; import { FilterModal } from '../../filter/FilterModal'; @@ -15,18 +15,43 @@ import { useState } from 'react'; import { Filter } from '../../../../design-system/icons/actions/Filter'; import { FilterGradient } from '../../../../design-system/icons/gradient/FilterGradient'; import { FilterOutlined } from '../../../../design-system/icons/gradient/FilterOutlined'; +import { Conditions } from '../../../../components/conditions/Conditions'; export function StepSettings({ index }: { index: number }) { const { readonly } = useEnvController(); - const { control, watch, setValue } = useFormContext(); + const { control, watch, setValue, getValues } = useFormContext(); const [filterOpen, setFilterOpen] = useState(false); const { channel } = useParams<{ channel: StepTypeEnum; }>(); const [filterHover, setFilterHover] = useState(false); - + const { fields, replace, update, remove } = useFieldArray({ + control, + name: `steps.${index}.filters.0.children`, + }); const filters = watch(`steps.${index}.filters.0.children`); + console.log('fields', fields); + + /* + * if (filterOpen) { + * return ( + * { + * setFilterOpen(false); + * }} + * setConditions={(data) => { + * console.log(data); + * replace(data.conditions[0].children); + * setValue(`steps.${index}.filters.0.children`, fields); + * }} + * conditions={getValues(`steps.${index}.filters`)} + * /> + * ); + * } + */ + return ( <> @@ -79,18 +104,34 @@ export function StepSettings({ index }: { index: number }) { - { - setFilterOpen(false); - }} - confirm={() => { - setFilterOpen(false); - }} - control={control} - stepIndex={index} - setValue={setValue} - /> + {filterOpen && ( + <> + { + setFilterOpen(false); + }} + setConditions={(data) => { + console.log(data); + replace(data.conditions[0].children); + setValue(`steps.${index}.filters.0.children`, fields); + }} + conditions={getValues(`steps.${index}.filters`)} + /> + + )} + {/* {*/} + {/* setFilterOpen(false);*/} + {/* }}*/} + {/* confirm={() => {*/} + {/* setFilterOpen(false);*/} + {/* }}*/} + {/* control={control}*/} + {/* stepIndex={index}*/} + {/* setValue={setValue}*/} + {/*/>*/} ); } diff --git a/apps/web/src/pages/templates/workflow/WorkflowEditor.tsx b/apps/web/src/pages/templates/workflow/WorkflowEditor.tsx index bc055ab8fc4..1b5472f720c 100644 --- a/apps/web/src/pages/templates/workflow/WorkflowEditor.tsx +++ b/apps/web/src/pages/templates/workflow/WorkflowEditor.tsx @@ -61,6 +61,7 @@ const WorkflowEditor = () => { event.preventDefault(); if (node.type === 'channelNode') { + console.log('node.data.uuid', node.data.uuid); navigate(basePath + `/${node.data.channelType}/${node.data.uuid}`); } if (node.type === 'triggerNode') { diff --git a/libs/shared/src/consts/filters/filters.ts b/libs/shared/src/consts/filters/filters.ts index 9a7eac4db05..1bc00920ad3 100644 --- a/libs/shared/src/consts/filters/filters.ts +++ b/libs/shared/src/consts/filters/filters.ts @@ -2,6 +2,7 @@ import { FilterPartTypeEnum } from '../../types'; export const FILTER_TO_LABEL = { [FilterPartTypeEnum.PAYLOAD]: 'Payload', + [FilterPartTypeEnum.TENANT]: 'Tenant', [FilterPartTypeEnum.SUBSCRIBER]: 'Subscriber', [FilterPartTypeEnum.WEBHOOK]: 'Webhook', [FilterPartTypeEnum.IS_ONLINE]: 'Online right now', diff --git a/libs/shared/src/dto/integration/construct-integration.interface.ts b/libs/shared/src/dto/integration/construct-integration.interface.ts index 5754dd8dc37..8b3cc2c02ff 100644 --- a/libs/shared/src/dto/integration/construct-integration.interface.ts +++ b/libs/shared/src/dto/integration/construct-integration.interface.ts @@ -1,5 +1,6 @@ import { ICredentials } from '../../entities/integration'; import type { EnvironmentId } from '../../types'; +import { BuilderFieldType, BuilderGroupValues, FilterParts } from '../../types'; export type ICredentialsDto = ICredentials; @@ -10,4 +11,10 @@ export interface IConstructIntegrationDto { credentials?: ICredentialsDto; active?: boolean; check?: boolean; + conditions?: { + isNegated?: boolean; + type?: BuilderFieldType; + value?: BuilderGroupValues; + children?: FilterParts[]; + }[]; } From c259fbd412e837b89469a406a7209f3e66349d6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20S=C3=B6derberg?= Date: Mon, 4 Sep 2023 15:30:12 +0200 Subject: [PATCH 20/95] fix: after pr comments --- .../UpdateIntegrationSidebarHeader.tsx | 6 +----- .../SelectPrimaryIntegrationModal.tsx | 2 +- apps/web/src/pages/integrations/types.ts | 17 +++++++++++++++-- 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/apps/web/src/pages/integrations/components/UpdateIntegrationSidebarHeader.tsx b/apps/web/src/pages/integrations/components/UpdateIntegrationSidebarHeader.tsx index 4784d062a33..58de40fb612 100644 --- a/apps/web/src/pages/integrations/components/UpdateIntegrationSidebarHeader.tsx +++ b/apps/web/src/pages/integrations/components/UpdateIntegrationSidebarHeader.tsx @@ -64,11 +64,7 @@ export const UpdateIntegrationSidebarHeader = ({ environmentId: provider.environmentId, channelType: provider.channel, exclude: (el: IntegrationEntity) => { - if (el._id === provider.integrationId || (el.conditions && el.conditions.length > 0)) { - return true; - } - - return false; + return el._id === provider.integrationId; }, onClose: () => { deleteIntegration({ diff --git a/apps/web/src/pages/integrations/components/multi-provider/SelectPrimaryIntegrationModal.tsx b/apps/web/src/pages/integrations/components/multi-provider/SelectPrimaryIntegrationModal.tsx index acfd6cc2d38..4f40d12f44e 100644 --- a/apps/web/src/pages/integrations/components/multi-provider/SelectPrimaryIntegrationModal.tsx +++ b/apps/web/src/pages/integrations/components/multi-provider/SelectPrimaryIntegrationModal.tsx @@ -155,7 +155,7 @@ export const SelectPrimaryIntegrationModal = ({ return el.channel === channelType && isNotExcluded; }); - return filteredIntegrations.filter((el) => el.active).map((el) => mapToTableIntegration(el, environments)); + return filteredIntegrations.map((el) => mapToTableIntegration(el, environments)); }, [integrations, environments, channelType, environmentId, exclude]); const initialSelectedIndex = useMemo(() => { diff --git a/apps/web/src/pages/integrations/types.ts b/apps/web/src/pages/integrations/types.ts index 2dbe03c52c5..3bb25f6814a 100644 --- a/apps/web/src/pages/integrations/types.ts +++ b/apps/web/src/pages/integrations/types.ts @@ -1,5 +1,8 @@ import type { + BuilderFieldType, + BuilderGroupValues, ChannelTypeEnum, + FilterParts, IConfigCredentials, ICredentials, ILogoFileName, @@ -40,7 +43,12 @@ export interface IIntegratedProvider { name?: string; identifier?: string; primary: boolean; - conditions?: any[]; + conditions?: { + isNegated?: boolean; + type?: BuilderFieldType; + value?: BuilderGroupValues; + children?: FilterParts[]; + }[]; } export interface IntegrationEntity { @@ -58,5 +66,10 @@ export interface IntegrationEntity { primary: boolean; deletedAt: string; deletedBy: string; - conditions?: any[]; + conditions?: { + isNegated?: boolean; + type?: BuilderFieldType; + value?: BuilderGroupValues; + children?: FilterParts[]; + }[]; } From 1de9293045c62c6fbb2464dce6f453d0308aa7f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20S=C3=B6derberg?= Date: Mon, 4 Sep 2023 15:34:31 +0200 Subject: [PATCH 21/95] fix: after pr comment --- .../multi-provider/UpdateProviderSidebar.tsx | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/apps/web/src/pages/integrations/components/multi-provider/UpdateProviderSidebar.tsx b/apps/web/src/pages/integrations/components/multi-provider/UpdateProviderSidebar.tsx index 8bbe4b5f7b7..bfb59903fe6 100644 --- a/apps/web/src/pages/integrations/components/multi-provider/UpdateProviderSidebar.tsx +++ b/apps/web/src/pages/integrations/components/multi-provider/UpdateProviderSidebar.tsx @@ -173,16 +173,18 @@ export function UpdateProviderSidebar({ .filter((el) => !NOVU_PROVIDERS.includes(el.providerId) && el.integrationId !== selectedProvider.integrationId) .find((el) => el.active && el.channel === selectedChannel && el.environmentId === environmentId); const isChannelSupportPrimary = CHANNELS_WITH_PRIMARY.includes(selectedChannel); - const hasAddedCondition = primary && conditions && conditions.length > 0; // show modal - const hasNotAddedConditionOnActive = !(isActive && conditions && conditions.length > 0); // show modal - - if ( - isActiveFieldChanged && - isChannelSupportPrimary && - hasAddedCondition && - hasNotAddedConditionOnActive && - ((isActive && hasSameChannelActiveIntegration) || (!isActive && primary && hasSameChannelActiveIntegration)) - ) { + + const isChangedToActive = + isActiveFieldChanged && isChannelSupportPrimary && isActive && hasSameChannelActiveIntegration; + + const isChangedToInactiveAndIsPrimary = + isActiveFieldChanged && isChannelSupportPrimary && !isActive && primary && hasSameChannelActiveIntegration; + + const isPrimaryAndHasConditionsApplied = primary && conditions && conditions.length > 0; + + const hasNoConditions = !conditions || conditions.length === 0; + + if ((hasNoConditions && isChangedToActive) || isChangedToInactiveAndIsPrimary || isPrimaryAndHasConditionsApplied) { openSelectPrimaryIntegrationModal({ environmentId: selectedProvider?.environmentId, channelType: selectedProvider?.channel, From 7d8d0987e7110b4fd2df6cd06e18dd64d562822d Mon Sep 17 00:00:00 2001 From: ainouzgali Date: Mon, 4 Sep 2023 18:37:00 +0300 Subject: [PATCH 22/95] feat(wip): conditions component --- .../src/components/conditions/Conditions.tsx | 651 +++--------------- .../web/src/design-system/sidebar/Sidebar.tsx | 8 +- .../integrations/components/ConditionCell.tsx | 6 +- .../CreateProviderInstanceSidebar.tsx | 47 +- .../multi-provider/UpdateProviderSidebar.tsx | 15 +- apps/web/src/pages/integrations/types.ts | 23 +- .../src/pages/integrations/useProviders.ts | 17 +- apps/web/src/pages/integrations/utils.ts | 1 + .../templates/editor/TemplateEditorPage.tsx | 10 +- .../templates/workflow/SideBar/Sidebar.tsx | 2 +- .../workflow/SideBar/StepSettings.tsx | 81 +-- .../templates/workflow/WorkflowEditor.tsx | 1 - 12 files changed, 159 insertions(+), 703 deletions(-) diff --git a/apps/web/src/components/conditions/Conditions.tsx b/apps/web/src/components/conditions/Conditions.tsx index f83df40a908..fb9e52c7a7c 100644 --- a/apps/web/src/components/conditions/Conditions.tsx +++ b/apps/web/src/components/conditions/Conditions.tsx @@ -1,42 +1,47 @@ -import { Button, colors, Dropdown, Input, Select, Sidebar, Text, Title, Tooltip } from '../../design-system'; import { Grid, Group, ActionIcon, Center } from '@mantine/core'; +import styled from '@emotion/styled'; +import { Controller, useFieldArray, useForm } from 'react-hook-form'; + import { FILTER_TO_LABEL, FilterPartTypeEnum } from '@novu/shared'; + +import { Button, colors, Dropdown, Input, Select, Sidebar, Text, Title, Tooltip } from '../../design-system'; import { ConditionPlus, DotsHorizontal, Duplicate, Trash, Condition, ErrorIcon } from '../../design-system/icons'; -import { Controller, useFieldArray, useForm, useWatch } from 'react-hook-form'; -import styled from '@emotion/styled'; -import { useEffect } from 'react'; +import { When } from '../utils/When'; +import { IConditions } from '../../pages/integrations/types'; export function Conditions({ isOpened, conditions, onClose, setConditions, + name, }: { isOpened: boolean; onClose: () => void; - setConditions: (data: any) => void; - conditions: any; + setConditions: (data: IConditions[]) => void; + conditions?: IConditions[]; + name: string; }) { const { control, setValue, getValues, - handleSubmit: handleSubmit1, - watch, + trigger, + formState: { errors, isValid }, } = useForm({ defaultValues: { conditions }, + shouldUseNativeValidation: false, + mode: 'onChange', + reValidateMode: 'onChange', }); + const { fields, append, update, remove, insert } = useFieldArray({ control, name: `conditions.0.children`, }); - const watchConditions = watch(`conditions.0.children`); + const FilterPartTypeList = [{ value: FilterPartTypeEnum.TENANT, label: FILTER_TO_LABEL[FilterPartTypeEnum.TENANT] }]; - const FilterPartTypeList = [ - { value: FilterPartTypeEnum.TENANT, label: FILTER_TO_LABEL[FilterPartTypeEnum.TENANT] }, - { value: FilterPartTypeEnum.SUBSCRIBER, label: FILTER_TO_LABEL[FilterPartTypeEnum.SUBSCRIBER] }, - ]; function handleOnChildOnChange(index: number) { return (data) => { const newField = Object.assign({}, fields[index], { on: data }); @@ -44,14 +49,9 @@ export function Conditions({ }; } - useEffect(() => { - console.log(watchConditions); - }, [watchConditions]); - // console.log('conditions', conditions); - function updateConditions(data) { - console.log('data 1', data); - setConditions(data); + setConditions(data.conditions); + onClose(); } return ( @@ -59,22 +59,11 @@ export function Conditions({ isOpened={isOpened} onClose={onClose} isExpanded - // onSubmit={handleSubmit(updateConditions)} - - onSubmit={(e) => { - e.stopPropagation(); - e.preventDefault(); - console.log(e); - handleSubmit1(updateConditions)(e); - onClose(); - - // e.stopPropagation(); - }} customHeader={
- Condition for + Condition for {name} provider instance
} @@ -83,276 +72,31 @@ export function Conditions({ - -
- } - > - {fields.map((item, index) => { - const filterFieldOn = (fields[index] as any).on; - console.log('item', item); - - return ( -
- - - {index > 0 ? ( - - { - return ( - - ); + + 0} + label={!isValid ? 'Some conditions are missing values' : 'Add at least one condition'} + > +
+
- ); - })} - - - - - - ); -} -export function Conditions1({ - isOpened, - conditions, - onClose, - setConditions, -}: { - isOpened: boolean; - onClose: () => void; - setConditions: (data: any) => void; - conditions: any; -}) { - const { - control, - setValue, - getValues, - handleSubmit: handleSubmit1, - watch, - } = useForm({ - defaultValues: { conditions }, - }); - const { fields, append, update, remove, insert } = useFieldArray({ - control, - name: `conditions.0.children`, - }); - - const watchConditions = watch(`conditions.0.children`); - - const FilterPartTypeList = [ - { value: FilterPartTypeEnum.TENANT, label: FILTER_TO_LABEL[FilterPartTypeEnum.TENANT] }, - { value: FilterPartTypeEnum.SUBSCRIBER, label: FILTER_TO_LABEL[FilterPartTypeEnum.SUBSCRIBER] }, - ]; - function handleOnChildOnChange(index: number) { - return (data) => { - const newField = Object.assign({}, fields[index], { on: data }); - update(index, newField); - }; - } - - useEffect(() => { - console.log(watchConditions); - }, [watchConditions]); - // console.log('conditions', conditions); - - function updateConditions(data) { - console.log('data 1', data); - setConditions(data); - } - - return ( - { - e.preventDefault(); - console.log(e); - handleSubmit1(updateConditions)(e); - onClose(); - - // e.stopPropagation(); - }} - customHeader={ -
- - - Condition for - -
- } - customFooter={ - - - + Apply conditions + +
+ + } > {fields.map((item, index) => { - const filterFieldOn = (fields[index] as any).on; - console.log('item', item); - return (
@@ -388,7 +132,7 @@ export function Conditions1({ { return ( - ); - }} - /> - - - { return ( -
- -
- - } - required - disabled={field.value === 'IS_DEFINED'} - error={fieldState.error?.message} - placeholder="Value" - data-test-id="filter-value-input" - /> - ); - }} - /> -
- - - - - } - middlewares={{ flip: false, shift: false }} - position="bottom-end" - > - { - insert(index + 1, getValues(`conditions.0.children.${index}`)); - }} - icon={} - > - Duplicate - - { - remove(index); - }} - icon={} - > - Delete - - - -
-
- ); - })} - - - - - - ); -} -export function Conditions2({ - isOpened, - onClose, - control, - setValue, - getValues, -}: { - isOpened: boolean; - onClose: () => void; - control: any; - setValue: any; - getValues: any; -}) { - const { fields, append, update, remove, insert } = useFieldArray({ - control, - name: `conditions.0.children`, - }); - - const FilterPartTypeList = [ - { value: FilterPartTypeEnum.TENANT, label: FILTER_TO_LABEL[FilterPartTypeEnum.TENANT] }, - { value: FilterPartTypeEnum.SUBSCRIBER, label: FILTER_TO_LABEL[FilterPartTypeEnum.SUBSCRIBER] }, - ]; - console.log(28 / 4); - function handleOnChildOnChange(index: number) { - return (data) => { - const newField = Object.assign({}, fields[index], { on: data }); - update(index, newField); - }; - } - - return ( - - - - Condition for - - - } - customFooter={ - - - - - } - > - {fields.map((item, index) => { - const filterFieldOn = (fields[index] as any).on; - - return ( -
- - - {index > 0 ? ( - - { - return ( - - ); - }} - /> - - - { - return ( - ); }} @@ -678,22 +196,38 @@ export function Conditions2({ /> - { - return ( - - ); - }} - /> + {getValues(`conditions.0.children.${index}.operator`) !== 'IS_DEFINED' && ( + { + return ( + + + + + + + + + + } + required + disabled={getValues(`conditions.0.children.${index}.operator`) === 'IS_DEFINED'} + error={!!fieldState.error} + placeholder="Value" + data-test-id="filter-value-input" + /> + ); + }} + /> + )} { append({ operator: 'EQUAL', - on: 'tenant', + on: FilterPartTypeEnum.TENANT, + field: 'identifier', + value: '', }); }} icon={} @@ -748,10 +284,6 @@ export function Conditions2({ ); } -const ItemName = () => { - return
bla
; -}; - const Wrapper = styled.div` .mantine-Select-wrapper:not(:hover) { .mantine-Select-input { @@ -765,3 +297,24 @@ const Wrapper = styled.div` } } `; + +const TooltipContainer = styled.div` + & .mantine-Tooltip-tooltip { + color: ${colors.error}; + padding: 16px; + font-size: 14px; + font-weight: 400; + border-radius: 8px; + background: ${({ theme }) => + `linear-gradient(0deg, rgba(229, 69, 69, 0.2) 0%, rgba(229, 69, 69, 0.2) 100%), ${ + theme.colorScheme === 'dark' ? '#23232b' : colors.white + } !important`}; + } + + & .mantine-Tooltip-arrow { + background: ${({ theme }) => + `linear-gradient(0deg, rgba(229, 69, 69, 0.2) 0%, rgba(229, 69, 69, 0.2) 100%), ${ + theme.colorScheme === 'dark' ? '#23232b' : colors.white + } !important`}; + } +`; diff --git a/apps/web/src/design-system/sidebar/Sidebar.tsx b/apps/web/src/design-system/sidebar/Sidebar.tsx index 20f7f4485ad..274fc147e2c 100644 --- a/apps/web/src/design-system/sidebar/Sidebar.tsx +++ b/apps/web/src/design-system/sidebar/Sidebar.tsx @@ -1,5 +1,5 @@ import styled from '@emotion/styled'; -import { ActionIcon, Box, createStyles, Drawer, Loader, MantineTheme, Stack } from '@mantine/core'; +import { ActionIcon, createStyles, Drawer, Loader, MantineTheme, Stack } from '@mantine/core'; import { ReactNode } from 'react'; import { HEADER_HEIGHT } from '../../components/layout/constants'; @@ -46,11 +46,10 @@ const useDrawerStyles = createStyles((theme: MantineTheme) => { return { root: { position: 'absolute', - // zIndex: 1, + zIndex: 1, }, drawer: { position: 'fixed', - // zIndex: 9999, top: `${INTEGRATION_SETTING_TOP}px`, right: 0, bottom: 0, @@ -123,10 +122,9 @@ export const Sidebar = ({ closeOnEscape={false} withinPortal={false} trapFocus={false} - zIndex={999} data-expanded={isExpanded} > -
+ {isExpanded && onBack && ( diff --git a/apps/web/src/pages/integrations/components/ConditionCell.tsx b/apps/web/src/pages/integrations/components/ConditionCell.tsx index 3e07e6e308e..70c50525366 100644 --- a/apps/web/src/pages/integrations/components/ConditionCell.tsx +++ b/apps/web/src/pages/integrations/components/ConditionCell.tsx @@ -6,7 +6,7 @@ import type { ITableIntegration } from '../types'; const ConditionCellBase = ({ row: { original } }: IExtendedCellProps) => { const { colorScheme } = useMantineColorScheme(); - if (!original.conditions) { + if (!original.conditions || original.conditions.length < 1) { return (
-
{original.conditions.length}
+
+ {original.conditions?.[0]?.children?.length} +
); }; diff --git a/apps/web/src/pages/integrations/components/multi-provider/CreateProviderInstanceSidebar.tsx b/apps/web/src/pages/integrations/components/multi-provider/CreateProviderInstanceSidebar.tsx index ebd17b528a1..f6d667a753d 100644 --- a/apps/web/src/pages/integrations/components/multi-provider/CreateProviderInstanceSidebar.tsx +++ b/apps/web/src/pages/integrations/components/multi-provider/CreateProviderInstanceSidebar.tsx @@ -3,16 +3,7 @@ import { useEffect, useMemo, useState } from 'react'; import { useMutation, useQueryClient } from '@tanstack/react-query'; import { Controller, useForm } from 'react-hook-form'; import styled from '@emotion/styled'; -import { - BuilderFieldType, - BuilderGroupValues, - ChannelTypeEnum, - FilterParts, - FilterPartTypeEnum, - ICreateIntegrationBodyDto, - InAppProviderIdEnum, - providers, -} from '@novu/shared'; +import { ChannelTypeEnum, ICreateIntegrationBodyDto, InAppProviderIdEnum, providers } from '@novu/shared'; import { Button, colors, NameInput, Sidebar } from '../../../../design-system'; import { ArrowLeft, ConditionPlus } from '../../../../design-system/icons'; @@ -25,7 +16,7 @@ import { errorMessage, successMessage } from '../../../../utils/notifications'; import { QueryKeys } from '../../../../api/query.keys'; import { ProviderImage } from './SelectProviderSidebar'; import { CHANNEL_TYPE_TO_STRING } from '../../../../utils/channels'; -import type { IntegrationEntity } from '../../types'; +import type { IConditions, IntegrationEntity } from '../../types'; import { useProviders } from '../../useProviders'; import { When } from '../../../../components/utils/When'; import { Conditions } from '../../../../components/conditions/Conditions'; @@ -33,12 +24,7 @@ import { Conditions } from '../../../../components/conditions/Conditions'; interface ICreateProviderInstanceForm { name: string; environmentId: string; - conditions: { - isNegated?: boolean; - type?: BuilderFieldType; - value?: BuilderGroupValues; - children?: FilterParts[]; - }[]; + conditions: IConditions[]; } export function CreateProviderInstanceSidebar({ @@ -84,7 +70,6 @@ export function CreateProviderInstanceSidebar({ }); const selectedEnvironmentId = watch('environmentId'); - const conditions = watch('conditions'); const showInAppErrorMessage = useMemo(() => { if (!provider || integrations.length === 0 || provider.id !== InAppProviderIdEnum.Novu) { @@ -105,8 +90,7 @@ export function CreateProviderInstanceSidebar({ } const { channel: selectedChannel } = provider; - const { environmentId, conditions: cond } = data; - console.log('data', cond); + const { environmentId, conditions } = data; const { _id: integrationId } = await createIntegrationApi({ providerId: provider.id, @@ -115,7 +99,7 @@ export function CreateProviderInstanceSidebar({ credentials: {}, active: provider.channel === ChannelTypeEnum.IN_APP ? true : false, check: false, - conditions: cond, + conditions, _environmentId: environmentId, }); @@ -145,21 +129,7 @@ export function CreateProviderInstanceSidebar({ reset({ name: provider?.displayName ?? '', environmentId: environments.find((env) => env.name === 'Development')?._id || '', - conditions: [ - { - isNegated: false, - type: 'GROUP', - value: 'AND', - children: [ - { - on: FilterPartTypeEnum.TENANT, - field: 'identifier', - value: 'pawan', - operator: 'EQUAL', - }, - ], - }, - ], + conditions: [], }); }, [environments, provider]); @@ -167,15 +137,14 @@ export function CreateProviderInstanceSidebar({ return null; } - console.log(conditions); - if (openConditions) { return ( { - setValue('conditions', data.conditions); + setValue('conditions', data, { shouldDirty: true }); }} onClose={() => setOpenConditions(false)} /> diff --git a/apps/web/src/pages/integrations/components/multi-provider/UpdateProviderSidebar.tsx b/apps/web/src/pages/integrations/components/multi-provider/UpdateProviderSidebar.tsx index 4b32a8089e5..cbe036ca79e 100644 --- a/apps/web/src/pages/integrations/components/multi-provider/UpdateProviderSidebar.tsx +++ b/apps/web/src/pages/integrations/components/multi-provider/UpdateProviderSidebar.tsx @@ -5,12 +5,9 @@ import slugify from 'slugify'; import { Controller, FormProvider, useForm, useWatch } from 'react-hook-form'; import { useIntercom } from 'react-use-intercom'; import { - BuilderFieldType, - BuilderGroupValues, CHANNELS_WITH_PRIMARY, CredentialsKeyEnum, EmailProviderIdEnum, - FilterParts, IConfigCredentials, IConstructIntegrationDto, ICredentialsDto, @@ -21,7 +18,7 @@ import { import { Button, colors, Sidebar, Text } from '../../../../design-system'; import { useProviders } from '../../useProviders'; -import type { IIntegratedProvider } from '../../types'; +import type { IConditions, IIntegratedProvider } from '../../types'; import { IntegrationInput } from '../IntegrationInput'; import { useFetchEnvironments } from '../../../../hooks/useFetchEnvironments'; import { useUpdateIntegration } from '../../../../api/hooks/useUpdateIntegration'; @@ -47,12 +44,7 @@ interface IProviderForm { credentials: ICredentialsDto; active: boolean; identifier: string; - conditions: { - isNegated?: boolean; - type?: BuilderFieldType; - value?: BuilderGroupValues; - children?: FilterParts[]; - }[]; + conditions: IConditions[]; } enum SidebarStateEnum { @@ -225,9 +217,10 @@ export function UpdateProviderSidebar({ return ( { - setValue('conditions', data.conditions); + setValue('conditions', data, { shouldDirty: true }); }} onClose={() => setOpenConditions(false)} /> diff --git a/apps/web/src/pages/integrations/types.ts b/apps/web/src/pages/integrations/types.ts index 8ee50d1fc59..a4c11479f00 100644 --- a/apps/web/src/pages/integrations/types.ts +++ b/apps/web/src/pages/integrations/types.ts @@ -23,7 +23,7 @@ export interface ITableIntegration { environment: string; active: boolean; logoFileName: IProviderConfig['logoFileName']; - conditions?: any[]; + conditions?: IConditions[]; } export interface IIntegratedProvider { @@ -36,12 +36,7 @@ export interface IIntegratedProvider { comingSoon: boolean; active: boolean; connected: boolean; - conditions?: { - isNegated?: boolean; - type?: BuilderFieldType; - value?: BuilderGroupValues; - children?: FilterParts[]; - }[]; + conditions?: IConditions[]; logoFileName: ILogoFileName; betaVersion: boolean; novu?: boolean; @@ -60,12 +55,7 @@ export interface IntegrationEntity { providerId: ProvidersIdEnum; channel: ChannelTypeEnum; credentials: ICredentials; - conditions?: { - isNegated?: boolean; - type?: BuilderFieldType; - value?: BuilderGroupValues; - children?: FilterParts[]; - }[]; + conditions?: IConditions[]; active: boolean; deleted: boolean; order: number; @@ -73,3 +63,10 @@ export interface IntegrationEntity { deletedAt: string; deletedBy: string; } + +export interface IConditions { + isNegated?: boolean; + type?: BuilderFieldType; + value?: BuilderGroupValues; + children?: FilterParts[]; +} diff --git a/apps/web/src/pages/integrations/useProviders.ts b/apps/web/src/pages/integrations/useProviders.ts index 64ecbb4d9a4..d4b6e2faff6 100644 --- a/apps/web/src/pages/integrations/useProviders.ts +++ b/apps/web/src/pages/integrations/useProviders.ts @@ -2,7 +2,6 @@ import { useMemo } from 'react'; import * as cloneDeep from 'lodash.clonedeep'; import { ChannelTypeEnum, - FilterPartTypeEnum, IConfigCredentials, IProviderConfig, NOVU_SMS_EMAIL_PROVIDERS, @@ -117,21 +116,7 @@ function initializeProvidersByIntegration(integrations: IntegrationEntity[]): II name: integrationItem?.name, identifier: integrationItem?.identifier, primary: integrationItem?.primary ?? false, - conditions: integrationItem?.conditions ?? [ - { - isNegated: false, - type: 'GROUP', - value: 'AND', - children: [ - { - on: FilterPartTypeEnum.TENANT, - field: 'identifier', - value: 'pawan', - operator: 'EQUAL', - }, - ], - }, - ], + conditions: integrationItem?.conditions ?? [], }; }); } diff --git a/apps/web/src/pages/integrations/utils.ts b/apps/web/src/pages/integrations/utils.ts index d78a54b92ce..1104e3ac571 100644 --- a/apps/web/src/pages/integrations/utils.ts +++ b/apps/web/src/pages/integrations/utils.ts @@ -29,5 +29,6 @@ export const mapToTableIntegration = ( active: integration.active, logoFileName, providerId: integration.providerId, + conditions: integration.conditions, }; }; diff --git a/apps/web/src/pages/templates/editor/TemplateEditorPage.tsx b/apps/web/src/pages/templates/editor/TemplateEditorPage.tsx index 4a62f010910..63555093811 100644 --- a/apps/web/src/pages/templates/editor/TemplateEditorPage.tsx +++ b/apps/web/src/pages/templates/editor/TemplateEditorPage.tsx @@ -82,11 +82,11 @@ function BaseTemplateEditorPage() { - {/**/} + ); } diff --git a/apps/web/src/pages/templates/workflow/SideBar/Sidebar.tsx b/apps/web/src/pages/templates/workflow/SideBar/Sidebar.tsx index d60a41667ca..8b4a57b6e31 100644 --- a/apps/web/src/pages/templates/workflow/SideBar/Sidebar.tsx +++ b/apps/web/src/pages/templates/workflow/SideBar/Sidebar.tsx @@ -40,5 +40,5 @@ const SideBarWrapper = styled.div<{ dark: boolean }>` background: transparent; height: 100%; right: 8px; - z-index: 5; + z-index: 9999; `; diff --git a/apps/web/src/pages/templates/workflow/SideBar/StepSettings.tsx b/apps/web/src/pages/templates/workflow/SideBar/StepSettings.tsx index c27c95a7fb8..73953261279 100644 --- a/apps/web/src/pages/templates/workflow/SideBar/StepSettings.tsx +++ b/apps/web/src/pages/templates/workflow/SideBar/StepSettings.tsx @@ -1,5 +1,9 @@ import { Group } from '@mantine/core'; -import { useFieldArray, useFormContext } from 'react-hook-form'; +import { useState } from 'react'; +import { useFormContext } from 'react-hook-form'; +import { useParams } from 'react-router-dom'; + +import { StepTypeEnum } from '@novu/shared'; import { Button } from '../../../../design-system'; import type { IForm } from '../../components/formTypes'; @@ -7,50 +11,21 @@ import { StepActiveSwitch } from '../StepActiveSwitch'; import { useEnvController } from '../../../../hooks'; import { ShouldStopOnFailSwitch } from '../ShouldStopOnFailSwitch'; import { ReplyCallback, ReplyCallbackSwitch } from '../ReplyCallback'; -import { useParams, Outlet } from 'react-router-dom'; -import { StepTypeEnum } from '@novu/shared'; import { When } from '../../../../components/utils/When'; import { FilterModal } from '../../filter/FilterModal'; -import { useState } from 'react'; -import { Filter } from '../../../../design-system/icons/actions/Filter'; -import { FilterGradient } from '../../../../design-system/icons/gradient/FilterGradient'; +import { FilterGradient, Filter } from '../../../../design-system/icons'; import { FilterOutlined } from '../../../../design-system/icons/gradient/FilterOutlined'; -import { Conditions } from '../../../../components/conditions/Conditions'; export function StepSettings({ index }: { index: number }) { const { readonly } = useEnvController(); - const { control, watch, setValue, getValues } = useFormContext(); + const { control, watch, setValue } = useFormContext(); const [filterOpen, setFilterOpen] = useState(false); const { channel } = useParams<{ channel: StepTypeEnum; }>(); const [filterHover, setFilterHover] = useState(false); - const { fields, replace, update, remove } = useFieldArray({ - control, - name: `steps.${index}.filters.0.children`, - }); - const filters = watch(`steps.${index}.filters.0.children`); - console.log('fields', fields); - - /* - * if (filterOpen) { - * return ( - * { - * setFilterOpen(false); - * }} - * setConditions={(data) => { - * console.log(data); - * replace(data.conditions[0].children); - * setValue(`steps.${index}.filters.0.children`, fields); - * }} - * conditions={getValues(`steps.${index}.filters`)} - * /> - * ); - * } - */ + const filters = watch(`steps.${index}.filters.0.children`); return ( <> @@ -104,34 +79,18 @@ export function StepSettings({ index }: { index: number }) { - {filterOpen && ( - <> - { - setFilterOpen(false); - }} - setConditions={(data) => { - console.log(data); - replace(data.conditions[0].children); - setValue(`steps.${index}.filters.0.children`, fields); - }} - conditions={getValues(`steps.${index}.filters`)} - /> - - )} - {/* {*/} - {/* setFilterOpen(false);*/} - {/* }}*/} - {/* confirm={() => {*/} - {/* setFilterOpen(false);*/} - {/* }}*/} - {/* control={control}*/} - {/* stepIndex={index}*/} - {/* setValue={setValue}*/} - {/*/>*/} + { + setFilterOpen(false); + }} + confirm={() => { + setFilterOpen(false); + }} + control={control} + stepIndex={index} + setValue={setValue} + /> ); } diff --git a/apps/web/src/pages/templates/workflow/WorkflowEditor.tsx b/apps/web/src/pages/templates/workflow/WorkflowEditor.tsx index 1b5472f720c..bc055ab8fc4 100644 --- a/apps/web/src/pages/templates/workflow/WorkflowEditor.tsx +++ b/apps/web/src/pages/templates/workflow/WorkflowEditor.tsx @@ -61,7 +61,6 @@ const WorkflowEditor = () => { event.preventDefault(); if (node.type === 'channelNode') { - console.log('node.data.uuid', node.data.uuid); navigate(basePath + `/${node.data.channelType}/${node.data.uuid}`); } if (node.type === 'triggerNode') { From b8fea49b22d36d052bdd0e157ee6f77d0c4b357e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20S=C3=B6derberg?= Date: Tue, 5 Sep 2023 06:27:46 +0200 Subject: [PATCH 23/95] fix: after pr comment --- .../components/multi-provider/UpdateProviderSidebar.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/web/src/pages/integrations/components/multi-provider/UpdateProviderSidebar.tsx b/apps/web/src/pages/integrations/components/multi-provider/UpdateProviderSidebar.tsx index bfb59903fe6..99c437ee150 100644 --- a/apps/web/src/pages/integrations/components/multi-provider/UpdateProviderSidebar.tsx +++ b/apps/web/src/pages/integrations/components/multi-provider/UpdateProviderSidebar.tsx @@ -180,7 +180,8 @@ export function UpdateProviderSidebar({ const isChangedToInactiveAndIsPrimary = isActiveFieldChanged && isChannelSupportPrimary && !isActive && primary && hasSameChannelActiveIntegration; - const isPrimaryAndHasConditionsApplied = primary && conditions && conditions.length > 0; + const isPrimaryAndHasConditionsApplied = + primary && conditions && conditions.length > 0 && hasSameChannelActiveIntegration; const hasNoConditions = !conditions || conditions.length === 0; From 7cc4c4eae71b5848c757608f3b9df7f83eb4c977 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20S=C3=B6derberg?= Date: Mon, 4 Sep 2023 12:33:36 +0200 Subject: [PATCH 24/95] feat: add condition and primary icon buttons --- .../icons/general/AddCondition.tsx | 13 ++ .../icons/general/RemoveCondition.tsx | 12 ++ .../design-system/icons/general/StarEmpty.tsx | 2 +- .../design-system/icons/general/Warning.tsx | 12 ++ apps/web/src/design-system/icons/index.ts | 3 + .../components/ConditionIconButton.tsx | 66 +++++++++ .../components/PrimaryIconButton.tsx | 133 ++++++++++++++++++ .../UpdateIntegrationSidebarHeader.tsx | 12 +- .../CreateProviderInstanceSidebar.tsx | 6 +- apps/web/src/pages/integrations/types.ts | 12 -- 10 files changed, 256 insertions(+), 15 deletions(-) create mode 100644 apps/web/src/design-system/icons/general/AddCondition.tsx create mode 100644 apps/web/src/design-system/icons/general/RemoveCondition.tsx create mode 100644 apps/web/src/design-system/icons/general/Warning.tsx create mode 100644 apps/web/src/pages/integrations/components/ConditionIconButton.tsx create mode 100644 apps/web/src/pages/integrations/components/PrimaryIconButton.tsx diff --git a/apps/web/src/design-system/icons/general/AddCondition.tsx b/apps/web/src/design-system/icons/general/AddCondition.tsx new file mode 100644 index 00000000000..7641faae6e9 --- /dev/null +++ b/apps/web/src/design-system/icons/general/AddCondition.tsx @@ -0,0 +1,13 @@ +import React from 'react'; +/* eslint-disable */ +export function AddCondition(props: React.ComponentPropsWithoutRef<'svg'>) { + return ( + + + + + ); +} diff --git a/apps/web/src/design-system/icons/general/RemoveCondition.tsx b/apps/web/src/design-system/icons/general/RemoveCondition.tsx new file mode 100644 index 00000000000..8c61a4abe27 --- /dev/null +++ b/apps/web/src/design-system/icons/general/RemoveCondition.tsx @@ -0,0 +1,12 @@ +import React from 'react'; +/* eslint-disable */ +export function RemoveCondition(props: React.ComponentPropsWithoutRef<'svg'>) { + return ( + + + + ); +} diff --git a/apps/web/src/design-system/icons/general/StarEmpty.tsx b/apps/web/src/design-system/icons/general/StarEmpty.tsx index c0a3d3b062f..b64ae38ae9d 100644 --- a/apps/web/src/design-system/icons/general/StarEmpty.tsx +++ b/apps/web/src/design-system/icons/general/StarEmpty.tsx @@ -4,7 +4,7 @@ export const StarEmpty = (props: React.ComponentPropsWithoutRef<'svg'>) => { return ( diff --git a/apps/web/src/design-system/icons/general/Warning.tsx b/apps/web/src/design-system/icons/general/Warning.tsx new file mode 100644 index 00000000000..5fa4fa4c0a4 --- /dev/null +++ b/apps/web/src/design-system/icons/general/Warning.tsx @@ -0,0 +1,12 @@ +import React from 'react'; +/* eslint-disable */ +export function Warning(props: React.ComponentPropsWithoutRef<'svg'>) { + return ( + + + + ); +} diff --git a/apps/web/src/design-system/icons/index.ts b/apps/web/src/design-system/icons/index.ts index a209a84f806..29a00931e40 100644 --- a/apps/web/src/design-system/icons/index.ts +++ b/apps/web/src/design-system/icons/index.ts @@ -82,6 +82,9 @@ export { UserAccess } from './general/UserAccess'; export { SSO } from './general/SSO'; export { Cloud } from './general/Cloud'; export { Condition } from './general/Condition'; +export { AddCondition } from './general/AddCondition'; +export { RemoveCondition } from './general/RemoveCondition'; +export { Warning } from './general/Warning'; export { Copy } from './actions/Copy'; export { Close } from './actions/Close'; diff --git a/apps/web/src/pages/integrations/components/ConditionIconButton.tsx b/apps/web/src/pages/integrations/components/ConditionIconButton.tsx new file mode 100644 index 00000000000..c50dc22935e --- /dev/null +++ b/apps/web/src/pages/integrations/components/ConditionIconButton.tsx @@ -0,0 +1,66 @@ +import styled from '@emotion/styled'; +import { Group, UnstyledButton, Text } from '@mantine/core'; +import { colors } from '@novu/notification-center'; +import { When } from '../../../components/utils/When'; +import { Tooltip } from '../../../design-system'; +import { AddCondition, Condition } from '../../../design-system/icons'; + +const Button = styled(Group)` + text-align: center; + border-radius: 8px; + width: 32px; + height: 32px; + color: ${({ theme }) => (theme.colorScheme === 'dark' ? colors.B60 : colors.B30)}; + + &:hover { + background: ${({ theme }) => (theme.colorScheme === 'dark' ? colors.B30 : colors.B85)}; + color: ${({ theme }) => (theme.colorScheme === 'dark' ? colors.white : colors.B30)}; + } +`; + +const RemovesPrimary = () => { + return ( + + This action replaces +
the primary provider +
+ Learn more...{' '} +
+ ); +}; + +export const ConditionIconButton = ({ + conditions, + primary = false, + onClick, +}: { + conditions?: any[]; + primary?: boolean; + onClick: () => void; +}) => { + return ( + + Add Conditions + + + + + } + position="bottom" + > + + + + + ); +}; diff --git a/apps/web/src/pages/integrations/components/PrimaryIconButton.tsx b/apps/web/src/pages/integrations/components/PrimaryIconButton.tsx new file mode 100644 index 00000000000..b123f304dea --- /dev/null +++ b/apps/web/src/pages/integrations/components/PrimaryIconButton.tsx @@ -0,0 +1,133 @@ +import styled from '@emotion/styled'; +import { Group, UnstyledButton, Text, Title, useMantineTheme, Modal } from '@mantine/core'; +import { colors } from '@novu/notification-center'; +import { useState } from 'react'; +import { When } from '../../../components/utils/When'; +import { shadows, Tooltip, Button } from '../../../design-system'; +import { RemoveCondition, StarEmpty, Warning } from '../../../design-system/icons'; + +const IconButton = styled(Group)` + text-align: center; + border-radius: 8px; + width: 32px; + height: 32px; + color: ${({ theme }) => (theme.colorScheme === 'dark' ? colors.B60 : colors.B30)}; + + &:hover { + background: ${({ theme }) => (theme.colorScheme === 'dark' ? colors.B30 : colors.B85)}; + color: ${({ theme }) => (theme.colorScheme === 'dark' ? colors.white : colors.B30)}; + } +`; + +const RemovesCondition = () => { + return ( + + This action remove +
applied conditions +
+ Learn more... +
+ ); +}; + +export const PrimaryIconButton = ({ + conditions, + primary = false, + onClick, +}: { + conditions?: any[]; + primary?: boolean; + onClick: () => void; +}) => { + const [modalOpen, setModalOpen] = useState(false); + const theme = useMantineTheme(); + + if (primary) { + return null; + } + + return ( + <> + + Mark as Primary + 0}> + + + + } + position="bottom" + > + { + if (conditions && conditions.length > 0) { + setModalOpen(true); + + return; + } + onClick(); + }} + > + + + + + + + + Conditions will be removed + + } + sx={{ backdropFilter: 'blur(10px)' }} + shadow={theme.colorScheme === 'dark' ? shadows.dark : shadows.medium} + radius="md" + size="lg" + onClose={() => { + setModalOpen(false); + }} + centered + overflow="inside" + > + + Marking this instance as primary will remove all conditions since primary instances cannot have any + conditions. + + + + + + + + ); +}; diff --git a/apps/web/src/pages/integrations/components/UpdateIntegrationSidebarHeader.tsx b/apps/web/src/pages/integrations/components/UpdateIntegrationSidebarHeader.tsx index 58de40fb612..3f8b48cce00 100644 --- a/apps/web/src/pages/integrations/components/UpdateIntegrationSidebarHeader.tsx +++ b/apps/web/src/pages/integrations/components/UpdateIntegrationSidebarHeader.tsx @@ -14,6 +14,8 @@ import { DotsHorizontal, StarEmpty, Trash } from '../../../design-system/icons'; import { ProviderInfo } from './multi-provider/ProviderInfo'; import { useSelectPrimaryIntegrationModal } from './multi-provider/useSelectPrimaryIntegrationModal'; import { useMakePrimaryIntegration } from '../../../api/hooks/useMakePrimaryIntegration'; +import { ConditionIconButton } from './ConditionIconButton'; +import { PrimaryIconButton } from './PrimaryIconButton'; export const UpdateIntegrationSidebarHeader = ({ provider, @@ -107,6 +109,14 @@ export const UpdateIntegrationSidebarHeader = ({ /> {children} + { + makePrimaryIntegration({ id: provider.integrationId }); + }} + conditions={provider.conditions} + /> + {}} conditions={provider.conditions} />
{ makePrimaryIntegration({ id: provider.integrationId }); }} - icon={} + icon={} disabled={isLoading || isMarkingPrimary} > Mark as primary diff --git a/apps/web/src/pages/integrations/components/multi-provider/CreateProviderInstanceSidebar.tsx b/apps/web/src/pages/integrations/components/multi-provider/CreateProviderInstanceSidebar.tsx index f6d667a753d..b95080de512 100644 --- a/apps/web/src/pages/integrations/components/multi-provider/CreateProviderInstanceSidebar.tsx +++ b/apps/web/src/pages/integrations/components/multi-provider/CreateProviderInstanceSidebar.tsx @@ -20,6 +20,7 @@ import type { IConditions, IntegrationEntity } from '../../types'; import { useProviders } from '../../useProviders'; import { When } from '../../../../components/utils/When'; import { Conditions } from '../../../../components/conditions/Conditions'; +import { ConditionIconButton } from '../ConditionIconButton'; interface ICreateProviderInstanceForm { name: string; @@ -161,7 +162,7 @@ export function CreateProviderInstanceSidebar({ }} onClose={onClose} customHeader={ - + @@ -182,6 +183,9 @@ export function CreateProviderInstanceSidebar({ ); }} /> + + {}} /> + } customFooter={ diff --git a/apps/web/src/pages/integrations/types.ts b/apps/web/src/pages/integrations/types.ts index bfa3461a15d..a4c11479f00 100644 --- a/apps/web/src/pages/integrations/types.ts +++ b/apps/web/src/pages/integrations/types.ts @@ -44,12 +44,6 @@ export interface IIntegratedProvider { name?: string; identifier?: string; primary: boolean; - conditions?: { - isNegated?: boolean; - type?: BuilderFieldType; - value?: BuilderGroupValues; - children?: FilterParts[]; - }[]; } export interface IntegrationEntity { @@ -68,12 +62,6 @@ export interface IntegrationEntity { primary: boolean; deletedAt: string; deletedBy: string; - conditions?: { - isNegated?: boolean; - type?: BuilderFieldType; - value?: BuilderGroupValues; - children?: FilterParts[]; - }[]; } export interface IConditions { From 121601ab3013c29aa8c93eaae7037212320b5de0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20S=C3=B6derberg?= Date: Mon, 4 Sep 2023 15:22:38 +0200 Subject: [PATCH 25/95] feat: Add conditions button for create integration --- .../design-system/icons/general/Condition.tsx | 12 ++++++++++-- .../components/ConditionIconButton.tsx | 2 +- .../CreateProviderInstanceSidebar.tsx | 16 +++++++++++++++- 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/apps/web/src/design-system/icons/general/Condition.tsx b/apps/web/src/design-system/icons/general/Condition.tsx index 1593e16d602..cfa890652db 100644 --- a/apps/web/src/design-system/icons/general/Condition.tsx +++ b/apps/web/src/design-system/icons/general/Condition.tsx @@ -2,7 +2,15 @@ import React from 'react'; export function Condition(props: React.ComponentPropsWithoutRef<'svg'>) { return ( - + @@ -12,7 +20,7 @@ export function Condition(props: React.ComponentPropsWithoutRef<'svg'>) { diff --git a/apps/web/src/pages/integrations/components/ConditionIconButton.tsx b/apps/web/src/pages/integrations/components/ConditionIconButton.tsx index c50dc22935e..b1fa58be328 100644 --- a/apps/web/src/pages/integrations/components/ConditionIconButton.tsx +++ b/apps/web/src/pages/integrations/components/ConditionIconButton.tsx @@ -42,7 +42,7 @@ export const ConditionIconButton = ({ - Add Conditions + {conditions && conditions.length > 0 ? 'Edit' : 'Add'} Conditions diff --git a/apps/web/src/pages/integrations/components/multi-provider/CreateProviderInstanceSidebar.tsx b/apps/web/src/pages/integrations/components/multi-provider/CreateProviderInstanceSidebar.tsx index b95080de512..d860c95b019 100644 --- a/apps/web/src/pages/integrations/components/multi-provider/CreateProviderInstanceSidebar.tsx +++ b/apps/web/src/pages/integrations/components/multi-provider/CreateProviderInstanceSidebar.tsx @@ -6,7 +6,7 @@ import styled from '@emotion/styled'; import { ChannelTypeEnum, ICreateIntegrationBodyDto, InAppProviderIdEnum, providers } from '@novu/shared'; import { Button, colors, NameInput, Sidebar } from '../../../../design-system'; -import { ArrowLeft, ConditionPlus } from '../../../../design-system/icons'; +import { AddCondition, ConditionPlus, ArrowLeft, Condition } from '../../../../design-system/icons'; import { inputStyles } from '../../../../design-system/config/inputs.styles'; import { useFetchEnvironments } from '../../../../hooks/useFetchEnvironments'; import { useSegment } from '../../../../components/providers/SegmentProvider'; @@ -264,6 +264,20 @@ export function CreateProviderInstanceSidebar({ You can only create one {provider.displayName} per environment. + + + ); } From 1a11fb0bd1f52392fc53690fd7682cbe7f8688e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20S=C3=B6derberg?= Date: Tue, 5 Sep 2023 09:34:38 +0200 Subject: [PATCH 26/95] feat: add some nodes missing provider modal --- .../SelectPrimaryIntegrationModal.tsx | 19 +++--- .../multi-provider/UpdateProviderSidebar.tsx | 63 ++++++++++++++++++- .../useSelectPrimaryIntegrationModal.tsx | 11 ++-- 3 files changed, 78 insertions(+), 15 deletions(-) diff --git a/apps/web/src/pages/integrations/components/multi-provider/SelectPrimaryIntegrationModal.tsx b/apps/web/src/pages/integrations/components/multi-provider/SelectPrimaryIntegrationModal.tsx index 4f40d12f44e..d6a97bd9d49 100644 --- a/apps/web/src/pages/integrations/components/multi-provider/SelectPrimaryIntegrationModal.tsx +++ b/apps/web/src/pages/integrations/components/multi-provider/SelectPrimaryIntegrationModal.tsx @@ -115,7 +115,7 @@ export interface ISelectPrimaryIntegrationModalProps { environmentId?: string; channelType?: ChannelTypeEnum; exclude?: (integration: IntegrationEntity) => boolean; - onClose: () => void; + onClose: (cancel?: boolean) => void; } export const SelectPrimaryIntegrationModal = ({ @@ -132,14 +132,17 @@ export const SelectPrimaryIntegrationModal = ({ const { environments, isLoading: areEnvironmentsLoading } = useFetchEnvironments(); const environmentName = environments?.find((el) => el._id === environmentId)?.name ?? ''; - const onCloseCallback = useCallback(() => { - setSelectedState(initialState); - onClose(); - }, [onClose]); + const onCloseCallback = useCallback( + (cancel?: boolean) => { + setSelectedState(initialState); + onClose(cancel); + }, + [onClose] + ); const { integrations, loading: areIntegrationsLoading } = useIntegrations(); const { makePrimaryIntegration, isLoading: isMarkingPrimaryIntegration } = useMakePrimaryIntegration({ - onSuccess: onCloseCallback, + onSuccess: () => onCloseCallback(), }); const integrationsByEnvAndChannel = useMemo(() => { const filteredIntegrations = (integrations ?? []).filter((el) => { @@ -209,7 +212,7 @@ export const SelectPrimaryIntegrationModal = ({ shadow={theme.colorScheme === 'dark' ? shadows.dark : shadows.medium} radius="md" size="lg" - onClose={onCloseCallback} + onClose={() => onCloseCallback()} > @@ -249,7 +252,7 @@ export const SelectPrimaryIntegrationModal = ({ The selected provider instance will be activated as the primary provider cannot be disabled. )} - + + + Some nodes missing provider + + } + sx={{ backdropFilter: 'blur(10px)' }} + shadow={theme.colorScheme === 'dark' ? shadows.dark : shadows.medium} + radius="md" + size="lg" + onClose={() => { + setModalOpen(false); + }} + centered + overflow="inside" + > + + Conditions applied to all instances designate them to specific nodes, while nodes lacking conditions remain + provider-less. Add a provider instance without conditions to cover all nodes. + + + + + + ); } diff --git a/apps/web/src/pages/integrations/components/multi-provider/useSelectPrimaryIntegrationModal.tsx b/apps/web/src/pages/integrations/components/multi-provider/useSelectPrimaryIntegrationModal.tsx index d8f655a7664..8a83027030d 100644 --- a/apps/web/src/pages/integrations/components/multi-provider/useSelectPrimaryIntegrationModal.tsx +++ b/apps/web/src/pages/integrations/components/multi-provider/useSelectPrimaryIntegrationModal.tsx @@ -12,10 +12,13 @@ export const useSelectPrimaryIntegrationModal = () => { const isMultiProviderConfigurationEnabled = useIsMultiProviderConfigurationEnabled(); const isOpened = opened && isMultiProviderConfigurationEnabled; - const onCloseCallback = useCallback(() => { - close(); - onClose?.(); - }, [close, onClose]); + const onCloseCallback = useCallback( + (cancel?: boolean) => { + close(); + onClose?.(cancel); + }, + [close, onClose] + ); const Component = useInlineComponent(SelectPrimaryIntegrationModal, { isOpened, From 33d70ce9d8832bee85757c5c1ac321410e053673 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20S=C3=B6derberg?= Date: Tue, 5 Sep 2023 10:24:09 +0200 Subject: [PATCH 27/95] fix: after merging feature branch to this branch --- .../icons/general/AddCondition.tsx | 13 ----- apps/web/src/design-system/icons/index.ts | 1 - .../components/ConditionIconButton.tsx | 4 +- .../UpdateIntegrationSidebarHeader.tsx | 4 +- .../CreateProviderInstanceSidebar.tsx | 58 ++++++++++++------- .../multi-provider/UpdateProviderSidebar.tsx | 35 ++++++----- 6 files changed, 63 insertions(+), 52 deletions(-) delete mode 100644 apps/web/src/design-system/icons/general/AddCondition.tsx diff --git a/apps/web/src/design-system/icons/general/AddCondition.tsx b/apps/web/src/design-system/icons/general/AddCondition.tsx deleted file mode 100644 index 7641faae6e9..00000000000 --- a/apps/web/src/design-system/icons/general/AddCondition.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import React from 'react'; -/* eslint-disable */ -export function AddCondition(props: React.ComponentPropsWithoutRef<'svg'>) { - return ( - - - - - ); -} diff --git a/apps/web/src/design-system/icons/index.ts b/apps/web/src/design-system/icons/index.ts index 29a00931e40..beaf524bec2 100644 --- a/apps/web/src/design-system/icons/index.ts +++ b/apps/web/src/design-system/icons/index.ts @@ -82,7 +82,6 @@ export { UserAccess } from './general/UserAccess'; export { SSO } from './general/SSO'; export { Cloud } from './general/Cloud'; export { Condition } from './general/Condition'; -export { AddCondition } from './general/AddCondition'; export { RemoveCondition } from './general/RemoveCondition'; export { Warning } from './general/Warning'; diff --git a/apps/web/src/pages/integrations/components/ConditionIconButton.tsx b/apps/web/src/pages/integrations/components/ConditionIconButton.tsx index b1fa58be328..d897abc4829 100644 --- a/apps/web/src/pages/integrations/components/ConditionIconButton.tsx +++ b/apps/web/src/pages/integrations/components/ConditionIconButton.tsx @@ -3,7 +3,7 @@ import { Group, UnstyledButton, Text } from '@mantine/core'; import { colors } from '@novu/notification-center'; import { When } from '../../../components/utils/When'; import { Tooltip } from '../../../design-system'; -import { AddCondition, Condition } from '../../../design-system/icons'; +import { Condition, ConditionPlus } from '../../../design-system/icons'; const Button = styled(Group)` text-align: center; @@ -53,7 +53,7 @@ export const ConditionIconButton = ({ + + + Conditions + + (optional) + + + + } + description="Add a condition if you want to apply the provider instance to a specific tenant, subscriber, workflow, etc." + styles={inputStyles} + > + + + + You can only create one {provider.displayName} per environment. - - - ); } diff --git a/apps/web/src/pages/integrations/components/multi-provider/UpdateProviderSidebar.tsx b/apps/web/src/pages/integrations/components/multi-provider/UpdateProviderSidebar.tsx index 627775df018..847cde76d14 100644 --- a/apps/web/src/pages/integrations/components/multi-provider/UpdateProviderSidebar.tsx +++ b/apps/web/src/pages/integrations/components/multi-provider/UpdateProviderSidebar.tsx @@ -37,8 +37,9 @@ import { NovuProviderSidebarContent } from './NovuProviderSidebarContent'; import { useSelectPrimaryIntegrationModal } from './useSelectPrimaryIntegrationModal'; import { ShareableUrl } from '../Modal/ConnectIntegrationForm'; import { Conditions } from '../../../../components/conditions/Conditions'; -import { ConditionPlus } from '../../../../design-system/icons'; +import { useNavigate } from 'react-router-dom'; import { Warning } from '../../../../design-system/icons'; +import { ROUTES } from '../../../../constants/routes.enum'; interface IProviderForm { name: string; @@ -62,7 +63,7 @@ export function UpdateProviderSidebar({ integrationId?: string; onClose: () => void; }) { - const [modalOpen, setModalOpen] = useState(false); + const [missingProviderModalOpen, setMissingProviderModalOpen] = useState(true); const theme = useMantineTheme(); const { update } = useIntercom(); const { isLoading: areEnvironmentsLoading } = useFetchEnvironments(); @@ -72,7 +73,7 @@ export function UpdateProviderSidebar({ const [framework, setFramework] = useState(null); const { providers, isLoading: areProvidersLoading } = useProviders(); const isNovuInAppProvider = selectedProvider?.providerId === InAppProviderIdEnum.Novu; - + const navigate = useNavigate(); const { openModal: openSelectPrimaryIntegrationModal, SelectPrimaryIntegrationModal } = useSelectPrimaryIntegrationModal(); @@ -201,8 +202,8 @@ export function UpdateProviderSidebar({ channelType: selectedProvider?.channel, exclude: !isActive ? (el) => el._id === selectedProvider.integrationId : undefined, onClose: (cancel?: boolean) => { - if (cancel === true) { - setModalOpen(true); + if (cancel === true && primary && conditions && conditions?.length > 0) { + setMissingProviderModalOpen(true); } updateIntegration(data); }, @@ -252,7 +253,11 @@ export function UpdateProviderSidebar({ onClose={onSidebarClose} onSubmit={onSubmit} customHeader={ - + setOpenConditions(true)} + provider={selectedProvider} + onSuccessDelete={onSidebarClose} + > Test Provider } @@ -288,7 +293,11 @@ export function UpdateProviderSidebar({ onBack={onBack} customHeader={ sidebarState === SidebarStateEnum.NORMAL ? ( - + setOpenConditions(true)} + provider={selectedProvider} + onSuccessDelete={onSidebarClose} + /> ) : ( <> @@ -360,13 +369,10 @@ export function UpdateProviderSidebar({ - { - setModalOpen(false); + setMissingProviderModalOpen(false); }} centered overflow="inside" @@ -402,14 +408,15 @@ export function UpdateProviderSidebar({ - + ); }; diff --git a/apps/web/src/pages/integrations/components/PrimaryIconButton.tsx b/apps/web/src/pages/integrations/components/PrimaryIconButton.tsx index b123f304dea..ff2635cf54c 100644 --- a/apps/web/src/pages/integrations/components/PrimaryIconButton.tsx +++ b/apps/web/src/pages/integrations/components/PrimaryIconButton.tsx @@ -1,9 +1,8 @@ import styled from '@emotion/styled'; -import { Group, UnstyledButton, Text, Title, useMantineTheme, Modal } from '@mantine/core'; -import { colors } from '@novu/notification-center'; +import { Group, ActionIcon, Text, Title } from '@mantine/core'; import { useState } from 'react'; import { When } from '../../../components/utils/When'; -import { shadows, Tooltip, Button } from '../../../design-system'; +import { Tooltip, Button, colors, Modal } from '../../../design-system'; import { RemoveCondition, StarEmpty, Warning } from '../../../design-system/icons'; const IconButton = styled(Group)` @@ -24,8 +23,6 @@ const RemovesCondition = () => { This action remove
applied conditions -
- Learn more...
); }; @@ -39,8 +36,7 @@ export const PrimaryIconButton = ({ primary?: boolean; onClick: () => void; }) => { - const [modalOpen, setModalOpen] = useState(false); - const theme = useMantineTheme(); + const [modalOpen, setModalOpen] = useState(true); if (primary) { return null; @@ -59,7 +55,7 @@ export const PrimaryIconButton = ({ } position="bottom" > - { if (conditions && conditions.length > 0) { setModalOpen(true); @@ -68,40 +64,25 @@ export const PrimaryIconButton = ({ } onClick(); }} + variant="transparent" > - + Conditions will be removed } - sx={{ backdropFilter: 'blur(10px)' }} - shadow={theme.colorScheme === 'dark' ? shadows.dark : shadows.medium} - radius="md" size="lg" onClose={() => { setModalOpen(false); }} - centered - overflow="inside" > Marking this instance as primary will remove all conditions since primary instances cannot have any diff --git a/apps/web/src/pages/integrations/components/UpdateIntegrationSidebarHeader.tsx b/apps/web/src/pages/integrations/components/UpdateIntegrationSidebarHeader.tsx index 47f697e20fe..7f172ee9dec 100644 --- a/apps/web/src/pages/integrations/components/UpdateIntegrationSidebarHeader.tsx +++ b/apps/web/src/pages/integrations/components/UpdateIntegrationSidebarHeader.tsx @@ -1,5 +1,5 @@ import { ReactNode, useMemo, useState } from 'react'; -import { Group } from '@mantine/core'; +import { Group, useMantineTheme } from '@mantine/core'; import { Controller, useFormContext } from 'react-hook-form'; import { CHANNELS_WITH_PRIMARY, NOVU_PROVIDERS } from '@novu/shared'; @@ -31,6 +31,7 @@ export const UpdateIntegrationSidebarHeader = ({ const [isModalOpened, setModalIsOpened] = useState(false); const { control } = useFormContext(); const { environments } = useFetchEnvironments(); + const { colorScheme } = useMantineTheme(); const { providers, isLoading } = useProviders(); const canMarkAsPrimary = provider && !provider.primary && CHANNELS_WITH_PRIMARY.includes(provider.channel); const { openModal, SelectPrimaryIntegrationModal } = useSelectPrimaryIntegrationModal(); @@ -136,7 +137,7 @@ export const UpdateIntegrationSidebarHeader = ({ onClick={() => { makePrimaryIntegration({ id: provider.integrationId }); }} - icon={} + icon={} disabled={isLoading || isMarkingPrimary} > Mark as primary diff --git a/apps/web/src/pages/integrations/components/multi-provider/CreateProviderInstanceSidebar.tsx b/apps/web/src/pages/integrations/components/multi-provider/CreateProviderInstanceSidebar.tsx index 7a8fc7285ab..0d659bafb32 100644 --- a/apps/web/src/pages/integrations/components/multi-provider/CreateProviderInstanceSidebar.tsx +++ b/apps/web/src/pages/integrations/components/multi-provider/CreateProviderInstanceSidebar.tsx @@ -1,4 +1,4 @@ -import { ActionIcon, Group, Radio, Text, Input } from '@mantine/core'; +import { ActionIcon, Group, Radio, Text, Input, useMantineTheme } from '@mantine/core'; import { useEffect, useMemo, useState } from 'react'; import { useMutation, useQueryClient } from '@tanstack/react-query'; import { Controller, useForm } from 'react-hook-form'; @@ -42,6 +42,7 @@ export function CreateProviderInstanceSidebar({ onGoBack: () => void; onIntegrationCreated: (id: string) => void; }) { + const { colorScheme } = useMantineTheme(); const { environments, isLoading: areEnvironmentsLoading } = useFetchEnvironments(); const { isLoading: areIntegrationsLoading, providers: integrations } = useProviders(); const [openConditions, setOpenConditions] = useState(false); @@ -70,6 +71,13 @@ export function CreateProviderInstanceSidebar({ }); const watchedConditions = watch('conditions'); + const numOfConditions: number = useMemo(() => { + if (watchedConditions && watchedConditions[0] && watchedConditions[0].children) { + return watchedConditions[0].children.length; + } + + return 0; + }, [watchedConditions]); const selectedEnvironmentId = watch('environmentId'); const showInAppErrorMessage = useMemo(() => { @@ -267,25 +275,30 @@ export function CreateProviderInstanceSidebar({ } - description="Add a condition if you want to apply the provider instance to a specific tenant, subscriber, workflow, etc." + description="Add a condition if you want to apply the provider instance to a specific tenant." styles={inputStyles} > - diff --git a/apps/web/src/pages/integrations/components/multi-provider/UpdateProviderSidebar.tsx b/apps/web/src/pages/integrations/components/multi-provider/UpdateProviderSidebar.tsx index 847cde76d14..83b39f404a0 100644 --- a/apps/web/src/pages/integrations/components/multi-provider/UpdateProviderSidebar.tsx +++ b/apps/web/src/pages/integrations/components/multi-provider/UpdateProviderSidebar.tsx @@ -63,7 +63,7 @@ export function UpdateProviderSidebar({ integrationId?: string; onClose: () => void; }) { - const [missingProviderModalOpen, setMissingProviderModalOpen] = useState(true); + const [missingProviderModalOpen, setMissingProviderModalOpen] = useState(false); const theme = useMantineTheme(); const { update } = useIntercom(); const { isLoading: areEnvironmentsLoading } = useFetchEnvironments(); @@ -416,7 +416,7 @@ export function UpdateProviderSidebar({ - - + <> + + {numOfConditions > 0 ? 'Edit' : 'Add'} Conditions + + + + + } + position="bottom" + > + { + if (primary) { + setModalOpen(true); + + return; + } + onClick(); + }} + variant="transparent" + > + + + + + 0}> + +
{numOfConditions}
+
+
+
+
+ + + Primary will be removed + + } + size="lg" + onClose={() => { + setModalOpen(false); + }} + > + + Adding conditions to this instance will remove it as primary since primary instances cannot have any + conditions. + + + + + + + ); }; diff --git a/apps/web/src/pages/integrations/components/PrimaryIconButton.tsx b/apps/web/src/pages/integrations/components/PrimaryIconButton.tsx index ff2635cf54c..b11030aa934 100644 --- a/apps/web/src/pages/integrations/components/PrimaryIconButton.tsx +++ b/apps/web/src/pages/integrations/components/PrimaryIconButton.tsx @@ -36,7 +36,7 @@ export const PrimaryIconButton = ({ primary?: boolean; onClick: () => void; }) => { - const [modalOpen, setModalOpen] = useState(true); + const [modalOpen, setModalOpen] = useState(false); if (primary) { return null; diff --git a/apps/web/src/pages/integrations/components/multi-provider/SelectPrimaryIntegrationModal.tsx b/apps/web/src/pages/integrations/components/multi-provider/SelectPrimaryIntegrationModal.tsx index d6a97bd9d49..a99dd5e0020 100644 --- a/apps/web/src/pages/integrations/components/multi-provider/SelectPrimaryIntegrationModal.tsx +++ b/apps/web/src/pages/integrations/components/multi-provider/SelectPrimaryIntegrationModal.tsx @@ -115,7 +115,7 @@ export interface ISelectPrimaryIntegrationModalProps { environmentId?: string; channelType?: ChannelTypeEnum; exclude?: (integration: IntegrationEntity) => boolean; - onClose: (cancel?: boolean) => void; + onClose: () => void; } export const SelectPrimaryIntegrationModal = ({ @@ -132,13 +132,10 @@ export const SelectPrimaryIntegrationModal = ({ const { environments, isLoading: areEnvironmentsLoading } = useFetchEnvironments(); const environmentName = environments?.find((el) => el._id === environmentId)?.name ?? ''; - const onCloseCallback = useCallback( - (cancel?: boolean) => { - setSelectedState(initialState); - onClose(cancel); - }, - [onClose] - ); + const onCloseCallback = useCallback(() => { + setSelectedState(initialState); + onClose(); + }, [onClose]); const { integrations, loading: areIntegrationsLoading } = useIntegrations(); const { makePrimaryIntegration, isLoading: isMarkingPrimaryIntegration } = useMakePrimaryIntegration({ @@ -252,7 +249,7 @@ export const SelectPrimaryIntegrationModal = ({ The selected provider instance will be activated as the primary provider cannot be disabled.
)} - void; }) { - const [missingProviderModalOpen, setMissingProviderModalOpen] = useState(false); - const theme = useMantineTheme(); const { update } = useIntercom(); const { isLoading: areEnvironmentsLoading } = useFetchEnvironments(); const [selectedProvider, setSelectedProvider] = useState(null); @@ -73,7 +68,6 @@ export function UpdateProviderSidebar({ const [framework, setFramework] = useState(null); const { providers, isLoading: areProvidersLoading } = useProviders(); const isNovuInAppProvider = selectedProvider?.providerId === InAppProviderIdEnum.Novu; - const navigate = useNavigate(); const { openModal: openSelectPrimaryIntegrationModal, SelectPrimaryIntegrationModal } = useSelectPrimaryIntegrationModal(); @@ -201,10 +195,7 @@ export function UpdateProviderSidebar({ environmentId: selectedProvider?.environmentId, channelType: selectedProvider?.channel, exclude: !isActive ? (el) => el._id === selectedProvider.integrationId : undefined, - onClose: (cancel?: boolean) => { - if (cancel === true && primary && conditions && conditions?.length > 0) { - setMissingProviderModalOpen(true); - } + onClose: () => { updateIntegration(data); }, }); @@ -371,58 +362,6 @@ export function UpdateProviderSidebar({ - - - Some nodes missing provider - - } - sx={{ backdropFilter: 'blur(10px)' }} - shadow={theme.colorScheme === 'dark' ? shadows.dark : shadows.medium} - radius="md" - size="lg" - onClose={() => { - setMissingProviderModalOpen(false); - }} - centered - overflow="inside" - > - - Conditions applied to all instances designate them to specific nodes, while nodes lacking conditions remain - provider-less. Add a provider instance without conditions to cover all nodes. - - - - - - ); } diff --git a/apps/web/src/pages/integrations/components/multi-provider/useSelectPrimaryIntegrationModal.tsx b/apps/web/src/pages/integrations/components/multi-provider/useSelectPrimaryIntegrationModal.tsx index 8a83027030d..d8f655a7664 100644 --- a/apps/web/src/pages/integrations/components/multi-provider/useSelectPrimaryIntegrationModal.tsx +++ b/apps/web/src/pages/integrations/components/multi-provider/useSelectPrimaryIntegrationModal.tsx @@ -12,13 +12,10 @@ export const useSelectPrimaryIntegrationModal = () => { const isMultiProviderConfigurationEnabled = useIsMultiProviderConfigurationEnabled(); const isOpened = opened && isMultiProviderConfigurationEnabled; - const onCloseCallback = useCallback( - (cancel?: boolean) => { - close(); - onClose?.(cancel); - }, - [close, onClose] - ); + const onCloseCallback = useCallback(() => { + close(); + onClose?.(); + }, [close, onClose]); const Component = useInlineComponent(SelectPrimaryIntegrationModal, { isOpened, From 00199f3aa641e52c0efd956e1942228c8c4f27d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20S=C3=B6derberg?= Date: Tue, 5 Sep 2023 12:37:27 +0200 Subject: [PATCH 30/95] fix: so modal are not always shown --- .../src/pages/integrations/components/ConditionIconButton.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/src/pages/integrations/components/ConditionIconButton.tsx b/apps/web/src/pages/integrations/components/ConditionIconButton.tsx index c2d909a4071..c4bd088fff7 100644 --- a/apps/web/src/pages/integrations/components/ConditionIconButton.tsx +++ b/apps/web/src/pages/integrations/components/ConditionIconButton.tsx @@ -37,7 +37,7 @@ export const ConditionIconButton = ({ primary?: boolean; onClick: () => void; }) => { - const [modalOpen, setModalOpen] = useState(true); + const [modalOpen, setModalOpen] = useState(false); const numOfConditions: number = useMemo(() => { if (conditions && conditions[0] && conditions[0].children) { return conditions[0].children.length; From 51b0507c07420a3c943fa61cc3e9b14bfdda5441 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20S=C3=B6derberg?= Date: Tue, 5 Sep 2023 13:11:05 +0200 Subject: [PATCH 31/95] fix: after pr comments --- .../pages/integrations/components/ConditionIconButton.tsx | 8 +++++--- .../pages/integrations/components/PrimaryIconButton.tsx | 2 +- .../multi-provider/CreateProviderInstanceSidebar.tsx | 6 +++--- .../multi-provider/SelectPrimaryIntegrationModal.tsx | 2 +- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/apps/web/src/pages/integrations/components/ConditionIconButton.tsx b/apps/web/src/pages/integrations/components/ConditionIconButton.tsx index c4bd088fff7..a11e175190e 100644 --- a/apps/web/src/pages/integrations/components/ConditionIconButton.tsx +++ b/apps/web/src/pages/integrations/components/ConditionIconButton.tsx @@ -75,8 +75,10 @@ export const ConditionIconButton = ({ 0}> - -
{numOfConditions}
+ + +
{numOfConditions}
+
@@ -95,7 +97,7 @@ export const ConditionIconButton = ({ }} > - Adding conditions to this instance will remove it as primary since primary instances cannot have any + Adding conditions to this instance will remove it as primary since primary instances can not have any conditions. diff --git a/apps/web/src/pages/integrations/components/PrimaryIconButton.tsx b/apps/web/src/pages/integrations/components/PrimaryIconButton.tsx index b11030aa934..0f0d2daa6a0 100644 --- a/apps/web/src/pages/integrations/components/PrimaryIconButton.tsx +++ b/apps/web/src/pages/integrations/components/PrimaryIconButton.tsx @@ -85,7 +85,7 @@ export const PrimaryIconButton = ({ }} > - Marking this instance as primary will remove all conditions since primary instances cannot have any + Marking this instance as primary will remove all conditions since primary instances can not have any conditions. diff --git a/apps/web/src/pages/integrations/components/multi-provider/CreateProviderInstanceSidebar.tsx b/apps/web/src/pages/integrations/components/multi-provider/CreateProviderInstanceSidebar.tsx index 0d659bafb32..db62d530cb8 100644 --- a/apps/web/src/pages/integrations/components/multi-provider/CreateProviderInstanceSidebar.tsx +++ b/apps/web/src/pages/integrations/components/multi-provider/CreateProviderInstanceSidebar.tsx @@ -286,12 +286,12 @@ export function CreateProviderInstanceSidebar({ <> - Add conditions + 0}> - - + + {numOfConditions} diff --git a/apps/web/src/pages/integrations/components/multi-provider/SelectPrimaryIntegrationModal.tsx b/apps/web/src/pages/integrations/components/multi-provider/SelectPrimaryIntegrationModal.tsx index a99dd5e0020..53ebc5dd8d4 100644 --- a/apps/web/src/pages/integrations/components/multi-provider/SelectPrimaryIntegrationModal.tsx +++ b/apps/web/src/pages/integrations/components/multi-provider/SelectPrimaryIntegrationModal.tsx @@ -246,7 +246,7 @@ export const SelectPrimaryIntegrationModal = ({ {!isActive && !isInitialProviderSelected && ( - The selected provider instance will be activated as the primary provider cannot be disabled. + The selected provider instance will be activated as the primary provider can not be disabled. )} - - 0} - label={!isValid ? 'Some conditions are missing values' : 'Add at least one condition'} - > -
- -
-
-
+ 0} + label={!isValid ? 'Some conditions are missing values' : 'Add at least one condition'} + > +
+ +
+
} > @@ -110,13 +132,12 @@ export function Conditions({ render={({ field }) => { return ( - ); - }} - /> - - - { - return ( - - - - - - - - - - } - required - disabled={getValues(`conditions.0.children.${index}.operator`) === 'IS_DEFINED'} - error={!!fieldState.error} - placeholder="Value" - data-test-id="filter-value-input" - /> - ); - }} - /> - )} - + - { - insert(index + 1, getValues(`conditions.0.children.${index}`)); - }} - icon={} - > + handleDuplicate(index)} icon={}> Duplicate - { - remove(index); - }} - icon={} - > + handleDelete(index)} icon={}> Delete @@ -284,6 +213,93 @@ export function Conditions({ ); } +function EqualityForm({ control, index }: { control: Control; index: number }) { + const operator = useWatch({ + control, + name: `conditions.0.children.${index}.operator`, + }); + + return ( + <> + + { + return ( + + ); + }} + /> + + + + {operator !== 'IS_DEFINED' && ( + { + return ( + + + + + + + + } + required + error={!!fieldState.error} + placeholder="Value" + data-test-id="conditions-form-value-input" + /> + ); + }} + /> + )} + + + ); +} + const Wrapper = styled.div` .mantine-Select-wrapper:not(:hover) { .mantine-Select-input { @@ -297,24 +313,3 @@ const Wrapper = styled.div` } } `; - -const TooltipContainer = styled.div` - & .mantine-Tooltip-tooltip { - color: ${colors.error}; - padding: 16px; - font-size: 14px; - font-weight: 400; - border-radius: 8px; - background: ${({ theme }) => - `linear-gradient(0deg, rgba(229, 69, 69, 0.2) 0%, rgba(229, 69, 69, 0.2) 100%), ${ - theme.colorScheme === 'dark' ? '#23232b' : colors.white - } !important`}; - } - - & .mantine-Tooltip-arrow { - background: ${({ theme }) => - `linear-gradient(0deg, rgba(229, 69, 69, 0.2) 0%, rgba(229, 69, 69, 0.2) 100%), ${ - theme.colorScheme === 'dark' ? '#23232b' : colors.white - } !important`}; - } -`; diff --git a/apps/web/src/components/conditions/index.ts b/apps/web/src/components/conditions/index.ts new file mode 100644 index 00000000000..a4dfe570c58 --- /dev/null +++ b/apps/web/src/components/conditions/index.ts @@ -0,0 +1,2 @@ +export * from './Conditions'; +export * from './types'; diff --git a/apps/web/src/components/conditions/types.ts b/apps/web/src/components/conditions/types.ts new file mode 100644 index 00000000000..fc8556e2ed5 --- /dev/null +++ b/apps/web/src/components/conditions/types.ts @@ -0,0 +1,19 @@ +import { BuilderFieldType, BuilderGroupValues, FilterParts, FilterPartTypeEnum } from '@novu/shared'; + +export interface IConditions { + isNegated?: boolean; + type?: BuilderFieldType; + value?: BuilderGroupValues; + children?: FilterParts[]; +} + +export enum ConditionsContextEnum { + INTEGRATIONS = 'INTEGRATIONS', +} + +export const ConditionsContextFields = { + [ConditionsContextEnum.INTEGRATIONS]: { + label: 'provider instance', + filterPartsList: [FilterPartTypeEnum.TENANT], + }, +}; diff --git a/apps/web/src/design-system/tooltip/Tooltip.styles.ts b/apps/web/src/design-system/tooltip/Tooltip.styles.ts index aab5ac085da..ed0bd07b040 100644 --- a/apps/web/src/design-system/tooltip/Tooltip.styles.ts +++ b/apps/web/src/design-system/tooltip/Tooltip.styles.ts @@ -1,20 +1,27 @@ import { createStyles, MantineTheme } from '@mantine/core'; import { colors, shadows } from '../config'; +import { getGradient } from '../config/helper'; -export default createStyles((theme: MantineTheme) => { +export default createStyles((theme: MantineTheme, { error }: { error: boolean }) => { const dark = theme.colorScheme === 'dark'; + const opacityErrorColor = theme.fn.rgba(colors.error, 0.2); + const errorGradient = getGradient(opacityErrorColor); + const backgroundErrorColor = dark ? colors.B17 : colors.white; + const backgroundColor = dark ? colors.B20 : colors.white; + const background = error ? `${errorGradient}, ${backgroundErrorColor}` : backgroundColor; + const color = error ? colors.error : colors.B60; return { tooltip: { - backgroundColor: dark ? colors.B20 : theme.white, - color: colors.B60, + background, + color, boxShadow: dark ? shadows.dark : shadows.medium, padding: '12px 15px', fontSize: '14px', fontWeight: 400, }, arrow: { - backgroundColor: dark ? colors.B20 : theme.white, + background, }, }; }); diff --git a/apps/web/src/design-system/tooltip/Tooltip.tsx b/apps/web/src/design-system/tooltip/Tooltip.tsx index bd268add1d6..53d854a1c26 100644 --- a/apps/web/src/design-system/tooltip/Tooltip.tsx +++ b/apps/web/src/design-system/tooltip/Tooltip.tsx @@ -2,30 +2,29 @@ import { Tooltip as MantineTooltip, TooltipProps } from '@mantine/core'; import useStyles from './Tooltip.styles'; +interface ITooltipProps + extends Pick< + TooltipProps, + | 'multiline' + | 'width' + | 'label' + | 'opened' + | 'position' + | 'disabled' + | 'children' + | 'sx' + | 'withinPortal' + | 'offset' + | 'classNames' + > { + error?: boolean; +} /** * Tooltip component * */ -export function Tooltip({ - children, - label, - opened = undefined, - ...props -}: Pick< - TooltipProps, - | 'multiline' - | 'width' - | 'label' - | 'opened' - | 'position' - | 'disabled' - | 'children' - | 'sx' - | 'withinPortal' - | 'offset' - | 'classNames' ->) { - const { classes } = useStyles(); +export function Tooltip({ children, label, opened = undefined, error = false, ...props }: ITooltipProps) { + const { classes } = useStyles({ error }); return ( 0}> - +
{numOfConditions}
- +
diff --git a/apps/web/src/pages/integrations/components/multi-provider/CreateProviderInstanceSidebar.tsx b/apps/web/src/pages/integrations/components/multi-provider/CreateProviderInstanceSidebar.tsx index db62d530cb8..61bd0f96827 100644 --- a/apps/web/src/pages/integrations/components/multi-provider/CreateProviderInstanceSidebar.tsx +++ b/apps/web/src/pages/integrations/components/multi-provider/CreateProviderInstanceSidebar.tsx @@ -1,8 +1,9 @@ import { ActionIcon, Group, Radio, Text, Input, useMantineTheme } from '@mantine/core'; -import { useEffect, useMemo, useState } from 'react'; +import { useEffect, useMemo } from 'react'; import { useMutation, useQueryClient } from '@tanstack/react-query'; import { Controller, useForm } from 'react-hook-form'; import styled from '@emotion/styled'; +import { useDisclosure } from '@mantine/hooks'; import { ChannelTypeEnum, ICreateIntegrationBodyDto, InAppProviderIdEnum, providers } from '@novu/shared'; import { Button, colors, NameInput, Sidebar } from '../../../../design-system'; import { ConditionPlus, ArrowLeft, Condition } from '../../../../design-system/icons'; @@ -15,10 +16,10 @@ import { errorMessage, successMessage } from '../../../../utils/notifications'; import { QueryKeys } from '../../../../api/query.keys'; import { ProviderImage } from './SelectProviderSidebar'; import { CHANNEL_TYPE_TO_STRING } from '../../../../utils/channels'; -import type { IConditions, IntegrationEntity } from '../../types'; +import type { IntegrationEntity } from '../../types'; import { useProviders } from '../../useProviders'; import { When } from '../../../../components/utils/When'; -import { Conditions } from '../../../../components/conditions/Conditions'; +import { Conditions, IConditions } from '../../../../components/conditions'; import { ConditionIconButton } from '../ConditionIconButton'; interface ICreateProviderInstanceForm { @@ -45,10 +46,10 @@ export function CreateProviderInstanceSidebar({ const { colorScheme } = useMantineTheme(); const { environments, isLoading: areEnvironmentsLoading } = useFetchEnvironments(); const { isLoading: areIntegrationsLoading, providers: integrations } = useProviders(); - const [openConditions, setOpenConditions] = useState(false); const isLoading = areEnvironmentsLoading || areIntegrationsLoading; const queryClient = useQueryClient(); const segment = useSegment(); + const [conditionsFormOpened, { close: closeConditionsForm, open: openConditionsForm }] = useDisclosure(false); const provider = useMemo( () => providers.find((el) => el.channel === channel && el.id === providerId), @@ -145,17 +146,20 @@ export function CreateProviderInstanceSidebar({ if (!provider) { return null; } + const updateConditions = (conditions: IConditions[]) => { + setValue('conditions', conditions, { shouldDirty: true }); + }; + + if (conditionsFormOpened) { + const [conditions, name] = getValues(['conditions', 'name']); - if (openConditions) { return ( { - setValue('conditions', data, { shouldDirty: true }); - }} - onClose={() => setOpenConditions(false)} + conditions={conditions} + name={name} + isOpened={conditionsFormOpened} + setConditions={updateConditions} + onClose={closeConditionsForm} /> ); } @@ -192,7 +196,7 @@ export function CreateProviderInstanceSidebar({ }} /> - setOpenConditions(true)} /> +
} @@ -281,7 +285,7 @@ export function CreateProviderInstanceSidebar({ - 0} - label={!isValid ? 'Some conditions are missing values' : 'Add at least one condition'} - > +
-
@@ -160,7 +150,6 @@ export function Conditions({ placeholder="On" data={FilterPartTypeList} {...field} - onChange={handleOnChildOnChange(index)} data-test-id="conditions-form-on-dropdown" /> ); @@ -286,7 +275,6 @@ function EqualityForm({ control, index }: { control: Control; i
} - required error={!!fieldState.error} placeholder="Value" data-test-id="conditions-form-value-input" diff --git a/apps/web/src/design-system/typography/title/Title.tsx b/apps/web/src/design-system/typography/title/Title.tsx index 23383af3a2e..284ac551161 100644 --- a/apps/web/src/design-system/typography/title/Title.tsx +++ b/apps/web/src/design-system/typography/title/Title.tsx @@ -1,23 +1,31 @@ -import { Title as MantineTitle } from '@mantine/core'; +import { MantineColor, Title as MantineTitle, useMantineTheme } from '@mantine/core'; import { colors } from '../../config'; import { SpacingProps } from '../../shared/spacing.props'; interface ITitleProps extends JSX.ElementChildrenAttribute, SpacingProps { size?: 1 | 2; + color?: MantineColor; } /** * Use Title to create headers. * */ -export function Title({ size = 1, children, ...rest }: ITitleProps) { +export function Title({ size = 1, children, ...props }: ITitleProps) { + const { colorScheme } = useMantineTheme(); + + let textColor = props.color; + if (!textColor) { + textColor = colorScheme === 'dark' ? colors.white : colors.B40; + } + return ( ({ + sx={{ fontWeight: size === 1 ? 800 : 700, - color: theme.colorScheme === 'dark' ? colors.white : colors.B40, - })} + }} order={size} - {...rest} + color={textColor} + {...props} > {children} diff --git a/apps/web/src/pages/integrations/components/ConditionIconButton.tsx b/apps/web/src/pages/integrations/components/ConditionIconButton.tsx index 5f4208ff537..830791b39c2 100644 --- a/apps/web/src/pages/integrations/components/ConditionIconButton.tsx +++ b/apps/web/src/pages/integrations/components/ConditionIconButton.tsx @@ -1,10 +1,9 @@ -import { useMemo, useState } from 'react'; +import { useState } from 'react'; import styled from '@emotion/styled'; -import { Group, ActionIcon, Title, Center } from '@mantine/core'; +import { Group, ActionIcon, Center } from '@mantine/core'; import { When } from '../../../components/utils/When'; -import { colors, Tooltip, Text, Modal, Button } from '../../../design-system'; +import { colors, Tooltip, Text, Modal, Button, Title } from '../../../design-system'; import { Condition, ConditionPlus, Warning } from '../../../design-system/icons'; -import { IConditions } from '../../../components/conditions'; const IconButton = styled(Group)` text-align: center; @@ -29,29 +28,22 @@ const RemovesPrimary = () => { }; export const ConditionIconButton = ({ - conditions, + conditions = 0, primary = false, onClick, }: { - conditions?: IConditions[]; + conditions?: number; primary?: boolean; onClick: () => void; }) => { const [modalOpen, setModalOpen] = useState(false); - const numOfConditions: number = useMemo(() => { - if (conditions && conditions[0] && conditions[0].children) { - return conditions[0].children.length; - } - - return 0; - }, [conditions]); return ( <> - {numOfConditions > 0 ? 'Edit' : 'Add'} Conditions + {conditions > 0 ? 'Edit' : 'Add'} Conditions @@ -71,13 +63,13 @@ export const ConditionIconButton = ({ variant="transparent" > - + - 0}> + 0}>
-
{numOfConditions}
+
{conditions}
@@ -88,7 +80,9 @@ export const ConditionIconButton = ({ title={ - Primary will be removed + + Primary will be removed + } size="lg" diff --git a/apps/web/src/pages/integrations/components/PrimaryIconButton.tsx b/apps/web/src/pages/integrations/components/PrimaryIconButton.tsx index 0f0d2daa6a0..e81a4ab0640 100644 --- a/apps/web/src/pages/integrations/components/PrimaryIconButton.tsx +++ b/apps/web/src/pages/integrations/components/PrimaryIconButton.tsx @@ -1,8 +1,8 @@ import styled from '@emotion/styled'; -import { Group, ActionIcon, Text, Title } from '@mantine/core'; +import { Group, ActionIcon, Text } from '@mantine/core'; import { useState } from 'react'; import { When } from '../../../components/utils/When'; -import { Tooltip, Button, colors, Modal } from '../../../design-system'; +import { Tooltip, Button, colors, Modal, Title } from '../../../design-system'; import { RemoveCondition, StarEmpty, Warning } from '../../../design-system/icons'; const IconButton = styled(Group)` @@ -28,11 +28,11 @@ const RemovesCondition = () => { }; export const PrimaryIconButton = ({ - conditions, + conditions = 0, primary = false, onClick, }: { - conditions?: any[]; + conditions?: number; primary?: boolean; onClick: () => void; }) => { @@ -48,7 +48,7 @@ export const PrimaryIconButton = ({ label={ <> Mark as Primary - 0}> + 0}> @@ -57,7 +57,7 @@ export const PrimaryIconButton = ({ > { - if (conditions && conditions.length > 0) { + if (conditions > 0) { setModalOpen(true); return; diff --git a/apps/web/src/pages/integrations/components/UpdateIntegrationSidebarHeader.tsx b/apps/web/src/pages/integrations/components/UpdateIntegrationSidebarHeader.tsx index 1b223899d60..bf50ae76243 100644 --- a/apps/web/src/pages/integrations/components/UpdateIntegrationSidebarHeader.tsx +++ b/apps/web/src/pages/integrations/components/UpdateIntegrationSidebarHeader.tsx @@ -1,6 +1,6 @@ import { ReactNode, useMemo, useState } from 'react'; import { Group, useMantineTheme } from '@mantine/core'; -import { Controller, useFormContext } from 'react-hook-form'; +import { Controller, useFormContext, useWatch } from 'react-hook-form'; import { CHANNELS_WITH_PRIMARY } from '@novu/shared'; import { Button, colors, Dropdown, Modal, NameInput, Text, Title } from '../../../design-system'; @@ -36,6 +36,15 @@ export const UpdateIntegrationSidebarHeader = ({ const canMarkAsPrimary = provider && !provider.primary && CHANNELS_WITH_PRIMARY.includes(provider.channel); const { openModal, SelectPrimaryIntegrationModal } = useSelectPrimaryIntegrationModal(); + const watchedConditions = useWatch({ control, name: 'conditions' }); + const numOfConditions: number = useMemo(() => { + if (watchedConditions && watchedConditions[0] && watchedConditions[0].children) { + return watchedConditions[0].children.length; + } + + return 0; + }, [watchedConditions]); + const shouldSetNewPrimary = useMemo(() => { if (!provider) return false; @@ -117,9 +126,9 @@ export const UpdateIntegrationSidebarHeader = ({ onClick={() => { makePrimaryIntegration({ id: provider.integrationId }); }} - conditions={provider.conditions} + conditions={numOfConditions} /> - +
Date: Thu, 7 Sep 2023 10:34:16 +0200 Subject: [PATCH 46/95] fix: so remove primary on condition modal is not shown as often as before --- .../integrations/components/ConditionIconButton.tsx | 10 +++++----- .../multi-provider/UpdateProviderSidebar.tsx | 13 +++++++++++-- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/apps/web/src/pages/integrations/components/ConditionIconButton.tsx b/apps/web/src/pages/integrations/components/ConditionIconButton.tsx index 830791b39c2..c39ce861b73 100644 --- a/apps/web/src/pages/integrations/components/ConditionIconButton.tsx +++ b/apps/web/src/pages/integrations/components/ConditionIconButton.tsx @@ -53,7 +53,7 @@ export const ConditionIconButton = ({ > { - if (primary) { + if (primary && conditions === 0) { setModalOpen(true); return; @@ -81,7 +81,7 @@ export const ConditionIconButton = ({ - Primary will be removed + Primary flag will be removed } @@ -91,8 +91,8 @@ export const ConditionIconButton = ({ }} > - Adding conditions to this instance will remove it as primary since primary instances can not have any - conditions. + Adding conditions to the primary provider instance removes its primary status on update, potentially causing + notification failures for the steps that were using the primary provider. diff --git a/apps/web/src/pages/integrations/components/multi-provider/UpdateProviderSidebar.tsx b/apps/web/src/pages/integrations/components/multi-provider/UpdateProviderSidebar.tsx index 7c298816478..f4c706a2cb6 100644 --- a/apps/web/src/pages/integrations/components/multi-provider/UpdateProviderSidebar.tsx +++ b/apps/web/src/pages/integrations/components/multi-provider/UpdateProviderSidebar.tsx @@ -191,11 +191,20 @@ export function UpdateProviderSidebar({ const hasNoConditions = !conditions || conditions.length === 0; - if ((hasNoConditions && isChangedToActive) || isChangedToInactiveAndIsPrimary || isPrimaryAndHasConditionsApplied) { + const hasUpdatedConditions = data.conditions && data.conditions.length > 0; + + const hasConditionsAndIsPrimary = hasUpdatedConditions && primary && dirtyFields.conditions; + + if ( + (hasNoConditions && isChangedToActive) || + isChangedToInactiveAndIsPrimary || + isPrimaryAndHasConditionsApplied || + hasConditionsAndIsPrimary + ) { openSelectPrimaryIntegrationModal({ environmentId: selectedProvider?.environmentId, channelType: selectedProvider?.channel, - exclude: !isActive ? (el) => el._id === selectedProvider.integrationId : undefined, + exclude: !isActive || hasConditionsAndIsPrimary ? (el) => el._id === selectedProvider.integrationId : undefined, onClose: () => { updateIntegration(data); }, From 043f78b47563b97bb20fe7e8ef4968f1ee88795c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20S=C3=B6derberg?= Date: Thu, 7 Sep 2023 13:51:58 +0200 Subject: [PATCH 47/95] fix: copy for primary flag will be removed modal --- .../integrations/components/ConditionIconButton.tsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/apps/web/src/pages/integrations/components/ConditionIconButton.tsx b/apps/web/src/pages/integrations/components/ConditionIconButton.tsx index c39ce861b73..b926b5ecc2b 100644 --- a/apps/web/src/pages/integrations/components/ConditionIconButton.tsx +++ b/apps/web/src/pages/integrations/components/ConditionIconButton.tsx @@ -21,8 +21,9 @@ const IconButton = styled(Group)` const RemovesPrimary = () => { return ( - This action replaces -
the primary provider + This action replaces the +
+ primary provider flag
); }; @@ -91,8 +92,9 @@ export const ConditionIconButton = ({ }} > - Adding conditions to the primary provider instance removes its primary status on update, potentially causing - notification failures for the steps that were using the primary provider. + Adding conditions to the primary provider instance removes its primary status when a user applies changes by + clicking the Update button. This can potentially cause notification failures for the steps that were using the + primary provider. @@ -224,7 +228,7 @@ function EqualityForm({ control, index }: { control: Control; i { value: 'identifier', label: 'Identifier' }, ]} {...field} - data-test-id="conditions-form-field-dropdown" + data-test-id="conditions-form-key" /> ); }} @@ -247,7 +251,7 @@ function EqualityForm({ control, index }: { control: Control; i { value: 'IS_DEFINED', label: 'Is defined' }, ]} {...field} - data-test-id="conditions-form-operator-dropdown" + data-test-id="conditions-form-operator" /> ); }} @@ -268,16 +272,23 @@ function EqualityForm({ control, index }: { control: Control; i value={field.value as string} rightSection={ - + - + } error={!!fieldState.error} placeholder="Value" - data-test-id="conditions-form-value-input" + data-test-id="conditions-form-value" /> ); }} diff --git a/apps/web/src/components/execution-detail/ExecutionDetailsConditionItem.cy.tsx b/apps/web/src/components/execution-detail/ExecutionDetailsConditionItem.cy.tsx index c47f61b64c2..5a6e0009e9f 100644 --- a/apps/web/src/components/execution-detail/ExecutionDetailsConditionItem.cy.tsx +++ b/apps/web/src/components/execution-detail/ExecutionDetailsConditionItem.cy.tsx @@ -12,7 +12,7 @@ const condition: ICondition = { }; describe('Execution Details Condition Component', function () { - it('should render ExecutionDetailsCondtions properly', function () { + it('should render ExecutionDetailsConditions properly', function () { cy.mount( diff --git a/apps/web/src/components/execution-detail/ExecutionDetailsConditions.cy.tsx b/apps/web/src/components/execution-detail/ExecutionDetailsConditions.cy.tsx index d6cdc2c55b6..dab60f4c9d5 100644 --- a/apps/web/src/components/execution-detail/ExecutionDetailsConditions.cy.tsx +++ b/apps/web/src/components/execution-detail/ExecutionDetailsConditions.cy.tsx @@ -39,7 +39,7 @@ const conditions: ICondition[] = [ ]; describe('Execution Details Condition Component', function () { - it('should render ExecutionDetailsCondtions properly', function () { + it('should render ExecutionDetailsConditions properly', function () { cy.mount( diff --git a/apps/web/src/pages/integrations/components/ConditionCell.tsx b/apps/web/src/pages/integrations/components/ConditionCell.tsx index 70c50525366..eabd3fc969f 100644 --- a/apps/web/src/pages/integrations/components/ConditionCell.tsx +++ b/apps/web/src/pages/integrations/components/ConditionCell.tsx @@ -9,6 +9,7 @@ const ConditionCellBase = ({ row: { original } }: IExtendedCellProps { if (primary && conditions === 0) { setModalOpen(true); @@ -78,6 +79,7 @@ export const ConditionIconButton = ({ diff --git a/apps/web/src/pages/integrations/components/IntegrationNameCell.tsx b/apps/web/src/pages/integrations/components/IntegrationNameCell.tsx index e0b65da5227..14dafb26ede 100644 --- a/apps/web/src/pages/integrations/components/IntegrationNameCell.tsx +++ b/apps/web/src/pages/integrations/components/IntegrationNameCell.tsx @@ -93,7 +93,7 @@ export const IntegrationNameCell = ({ row: { original }, isLoading }: IExtendedC target={ setPopoverOpened(true)} onMouseLeave={() => setPopoverOpened(false)}> {original.name} - {original.primary && } + {original.primary && } } /> diff --git a/apps/web/src/pages/integrations/components/PrimaryIconButton.tsx b/apps/web/src/pages/integrations/components/PrimaryIconButton.tsx index e81a4ab0640..250807840a7 100644 --- a/apps/web/src/pages/integrations/components/PrimaryIconButton.tsx +++ b/apps/web/src/pages/integrations/components/PrimaryIconButton.tsx @@ -56,6 +56,7 @@ export const PrimaryIconButton = ({ position="bottom" > { if (conditions > 0) { setModalOpen(true); @@ -72,6 +73,7 @@ export const PrimaryIconButton = ({ diff --git a/apps/web/src/pages/integrations/components/multi-provider/CreateProviderInstanceSidebar.tsx b/apps/web/src/pages/integrations/components/multi-provider/CreateProviderInstanceSidebar.tsx index 61bd0f96827..79cede5bee3 100644 --- a/apps/web/src/pages/integrations/components/multi-provider/CreateProviderInstanceSidebar.tsx +++ b/apps/web/src/pages/integrations/components/multi-provider/CreateProviderInstanceSidebar.tsx @@ -196,7 +196,7 @@ export function CreateProviderInstanceSidebar({ }} /> - + } @@ -285,6 +285,7 @@ export function CreateProviderInstanceSidebar({