Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 95815a417b | |||
| bc5c5f3fb9 | |||
| 7b7dab66ed | |||
| d3ffbaba30 | |||
| 59b7f1ba68 | |||
| fd3e80aaa1 | |||
| 212d3dc59d | |||
| 18a13d2175 | |||
| 726bb56bb6 | |||
| de46f5440a | |||
| c266ce4859 | |||
| 2e8fd6cd99 | |||
| 9f55ce1889 | |||
| c94b79a8a7 |
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "twenty-emails",
|
||||
"version": "0.34.0-canary",
|
||||
"version": "0.33.4",
|
||||
"description": "",
|
||||
"author": "",
|
||||
"private": true,
|
||||
|
||||
@@ -22,6 +22,10 @@ const grayScale = {
|
||||
gray0: '#ffffff',
|
||||
};
|
||||
|
||||
const colors = {
|
||||
blue40: '#5e90f2',
|
||||
};
|
||||
|
||||
export const emailTheme = {
|
||||
font: {
|
||||
colors: {
|
||||
@@ -29,6 +33,7 @@ export const emailTheme = {
|
||||
primary: grayScale.gray50,
|
||||
tertiary: grayScale.gray35,
|
||||
inverted: grayScale.gray0,
|
||||
blue: colors.blue40,
|
||||
},
|
||||
family: 'Trebuchet MS', // Google Inter not working, we need to use a web safe font, see https://templates.mailchimp.com/design/typography/
|
||||
weight: {
|
||||
|
||||
@@ -1,25 +1,15 @@
|
||||
import { Column, Container, Row } from '@react-email/components';
|
||||
import React, { PropsWithChildren } from 'react';
|
||||
import { Container } from '@react-email/components';
|
||||
|
||||
import { emailTheme } from 'src/common-style';
|
||||
|
||||
type HighlightedContainerProps = PropsWithChildren;
|
||||
|
||||
const highlightedContainerStyle = {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
background: emailTheme.background.colors.highlight,
|
||||
border: `1px solid ${emailTheme.border.color.highlighted}`,
|
||||
borderRadius: emailTheme.border.radius.md,
|
||||
padding: '24px 48px',
|
||||
gap: '24px',
|
||||
};
|
||||
|
||||
const divStyle = {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
} as React.CSSProperties;
|
||||
|
||||
export const HighlightedContainer = ({
|
||||
@@ -27,7 +17,11 @@ export const HighlightedContainer = ({
|
||||
}: HighlightedContainerProps) => {
|
||||
return (
|
||||
<Container style={highlightedContainerStyle}>
|
||||
<div style={divStyle}>{children}</div>
|
||||
{React.Children.map(children, (child) => (
|
||||
<Row>
|
||||
<Column align="center">{child}</Column>
|
||||
</Row>
|
||||
))}
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { ReactNode } from 'react';
|
||||
import { Text } from '@react-email/text';
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
import { emailTheme } from 'src/common-style';
|
||||
|
||||
@@ -12,19 +12,11 @@ const highlightedStyle = {
|
||||
color: emailTheme.font.colors.highlighted,
|
||||
};
|
||||
|
||||
const divStyle = {
|
||||
display: 'flex',
|
||||
};
|
||||
|
||||
type HighlightedTextProps = {
|
||||
value: ReactNode;
|
||||
centered?: boolean;
|
||||
};
|
||||
|
||||
export const HighlightedText = ({ value }: HighlightedTextProps) => {
|
||||
return (
|
||||
<div style={divStyle}>
|
||||
<Text style={highlightedStyle}>{value}</Text>
|
||||
</div>
|
||||
);
|
||||
return <Text style={highlightedStyle}>{value}</Text>;
|
||||
};
|
||||
|
||||
@@ -1,21 +1,27 @@
|
||||
import { ReactNode } from 'react';
|
||||
import { Link as EmailLink } from '@react-email/components';
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
import { emailTheme } from 'src/common-style';
|
||||
|
||||
const linkStyle = {
|
||||
color: emailTheme.font.colors.tertiary,
|
||||
textDecoration: 'underline',
|
||||
};
|
||||
|
||||
type LinkProps = {
|
||||
value: ReactNode;
|
||||
href: string;
|
||||
color?: string;
|
||||
};
|
||||
|
||||
export const Link = ({ value, href }: LinkProps) => {
|
||||
export const Link = ({ value, href, color }: LinkProps) => {
|
||||
return (
|
||||
<EmailLink href={href} style={linkStyle}>
|
||||
<EmailLink
|
||||
href={href}
|
||||
style={{
|
||||
...linkStyle,
|
||||
color: color ?? emailTheme.font.colors.tertiary,
|
||||
}}
|
||||
>
|
||||
{value}
|
||||
</EmailLink>
|
||||
);
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
import { Column, Row } from '@react-email/components';
|
||||
|
||||
import { Link } from 'src/components/Link';
|
||||
import { MainText } from 'src/components/MainText';
|
||||
import { ShadowText } from 'src/components/ShadowText';
|
||||
import { SubTitle } from 'src/components/SubTitle';
|
||||
|
||||
export const WhatIsTwenty = () => {
|
||||
return (
|
||||
<>
|
||||
<SubTitle value="What is Twenty?" />
|
||||
<MainText>
|
||||
A software to help businesses manage their customer data and
|
||||
It's a CRM, a software to help businesses manage their customer data and
|
||||
relationships efficiently.
|
||||
</MainText>
|
||||
<Row>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Img } from '@react-email/components';
|
||||
import { emailTheme } from 'src/common-style';
|
||||
|
||||
import { BaseEmail } from 'src/components/BaseEmail';
|
||||
import { CallToAction } from 'src/components/CallToAction';
|
||||
@@ -33,8 +34,12 @@ export const SendInviteLinkEmail = ({
|
||||
<Title value="Join your team on Twenty" />
|
||||
<MainText>
|
||||
{capitalize(sender.firstName)} (
|
||||
<Link href={sender.email} value={sender.email} />) has invited you to
|
||||
join a workspace called <b>{workspace.name}</b>
|
||||
<Link
|
||||
href={sender.email}
|
||||
value={sender.email}
|
||||
color={emailTheme.font.colors.blue}
|
||||
/>
|
||||
) has invited you to join a workspace called <b>{workspace.name}</b>
|
||||
<br />
|
||||
</MainText>
|
||||
<HighlightedContainer>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "twenty-front",
|
||||
"version": "0.34.0-canary",
|
||||
"version": "0.33.4",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
+5
-1
@@ -55,7 +55,11 @@ export const ObjectFilterDropdownRecordSelect = ({
|
||||
const selectedFilter = useRecoilValue(selectedFilterState);
|
||||
|
||||
const objectNameSingular =
|
||||
filterDefinitionUsedInDropdown?.relationObjectMetadataNameSingular ?? '';
|
||||
filterDefinitionUsedInDropdown?.relationObjectMetadataNameSingular;
|
||||
|
||||
if (!isDefined(objectNameSingular)) {
|
||||
throw new Error('objectNameSingular is not defined');
|
||||
}
|
||||
|
||||
const { loading, filteredSelectedRecords, recordsToSelect, selectedRecords } =
|
||||
useRecordsForSelect({
|
||||
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
import { filterDefinitionUsedInDropdownComponentState } from '../states/filterDefinitionUsedInDropdownComponentState';
|
||||
import { FilterDefinition } from '../types/FilterDefinition';
|
||||
import { extractComponentState } from '@/ui/utilities/state/component-state/utils/extractComponentState';
|
||||
|
||||
export const useSetFilterDefinitionUsedInDropdownInScope = () => {
|
||||
const setFilterDefinitionUsedInDropdownInScope = useRecoilCallback(
|
||||
({ set }) =>
|
||||
(scopeId: string, filterDefinition: FilterDefinition | null) => {
|
||||
const filterDefinitionUsedInDropdownState = extractComponentState(
|
||||
filterDefinitionUsedInDropdownComponentState,
|
||||
scopeId,
|
||||
);
|
||||
set(filterDefinitionUsedInDropdownState, filterDefinition);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
return {
|
||||
setFilterDefinitionUsedInDropdownInScope,
|
||||
};
|
||||
};
|
||||
+9
@@ -177,6 +177,15 @@ export const isRecordMatchingFilter = ({
|
||||
value: record[filterKey],
|
||||
});
|
||||
}
|
||||
case FieldMetadataType.RichText: {
|
||||
// TODO: Implement a better rich text filter once it becomes a composite field
|
||||
// See this issue for more context: https://github.com/twentyhq/twenty/issues/7613#issuecomment-2408944585
|
||||
// This should be tackled in Q4'24
|
||||
return isMatchingStringFilter({
|
||||
stringFilter: filterValue as StringFilter,
|
||||
value: record[filterKey],
|
||||
});
|
||||
}
|
||||
case FieldMetadataType.Select:
|
||||
return isMatchingSelectFilter({
|
||||
selectFilter: filterValue as SelectFilter,
|
||||
|
||||
+70
-31
@@ -3,12 +3,16 @@ import { v4 } from 'uuid';
|
||||
|
||||
import { useColumnDefinitionsFromFieldMetadata } from '@/object-metadata/hooks/useColumnDefinitionsFromFieldMetadata';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions';
|
||||
import { useSetFilterDefinitionUsedInDropdownInScope } from '@/object-record/object-filter-dropdown/hooks/useSetFilterDefinitionUsedInDropdownInScope';
|
||||
import { Filter } from '@/object-record/object-filter-dropdown/types/Filter';
|
||||
import { FilterDefinition } from '@/object-record/object-filter-dropdown/types/FilterDefinition';
|
||||
import { getOperandsForFilterDefinition } from '@/object-record/object-filter-dropdown/utils/getOperandsForFilterType';
|
||||
import { useDropdownV2 } from '@/ui/layout/dropdown/hooks/useDropdownV2';
|
||||
import { isDropdownOpenComponentState } from '@/ui/layout/dropdown/states/isDropdownOpenComponentState';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { extractComponentState } from '@/ui/utilities/state/component-state/utils/extractComponentState';
|
||||
import { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
|
||||
import { useUpsertCombinedViewFilters } from '@/views/hooks/useUpsertCombinedViewFilters';
|
||||
import { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
type UseHandleToggleColumnFilterProps = {
|
||||
@@ -17,8 +21,8 @@ type UseHandleToggleColumnFilterProps = {
|
||||
};
|
||||
|
||||
export const useHandleToggleColumnFilter = ({
|
||||
viewBarId,
|
||||
objectNameSingular,
|
||||
viewBarId,
|
||||
}: UseHandleToggleColumnFilterProps) => {
|
||||
const { objectMetadataItem } = useObjectMetadataItem({
|
||||
objectNameSingular,
|
||||
@@ -28,10 +32,29 @@ export const useHandleToggleColumnFilter = ({
|
||||
useColumnDefinitionsFromFieldMetadata(objectMetadataItem);
|
||||
|
||||
const { upsertCombinedViewFilter } = useUpsertCombinedViewFilters(viewBarId);
|
||||
const { openDropdown } = useDropdownV2();
|
||||
|
||||
const openDropdown = useRecoilCallback(({ set }) => {
|
||||
return (dropdownId: string) => {
|
||||
const dropdownOpenState = extractComponentState(
|
||||
isDropdownOpenComponentState,
|
||||
dropdownId,
|
||||
);
|
||||
|
||||
set(dropdownOpenState, true);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const availableFilterDefinitions = useRecoilComponentValueV2(
|
||||
availableFilterDefinitionsComponentState,
|
||||
);
|
||||
|
||||
const { currentViewWithCombinedFiltersAndSorts } = useGetCurrentView();
|
||||
|
||||
const { setFilterDefinitionUsedInDropdownInScope } =
|
||||
useSetFilterDefinitionUsedInDropdownInScope();
|
||||
|
||||
const handleToggleColumnFilter = useCallback(
|
||||
(fieldMetadataId: string) => {
|
||||
async (fieldMetadataId: string) => {
|
||||
const correspondingColumnDefinition = columnDefinitions.find(
|
||||
(columnDefinition) =>
|
||||
columnDefinition.fieldMetadataId === fieldMetadataId,
|
||||
@@ -39,38 +62,54 @@ export const useHandleToggleColumnFilter = ({
|
||||
|
||||
if (!isDefined(correspondingColumnDefinition)) return;
|
||||
|
||||
const filterType = getFilterTypeFromFieldType(
|
||||
correspondingColumnDefinition?.type,
|
||||
);
|
||||
const newFilterId = v4();
|
||||
|
||||
const filterDefinition = {
|
||||
label: correspondingColumnDefinition.label,
|
||||
iconName: correspondingColumnDefinition.iconName,
|
||||
fieldMetadataId,
|
||||
type: filterType,
|
||||
} satisfies FilterDefinition;
|
||||
const existingViewFilter =
|
||||
currentViewWithCombinedFiltersAndSorts?.viewFilters.find(
|
||||
(viewFilter) => viewFilter.fieldMetadataId === fieldMetadataId,
|
||||
);
|
||||
|
||||
const availableOperandsForFilter =
|
||||
getOperandsForFilterDefinition(filterDefinition);
|
||||
if (!existingViewFilter) {
|
||||
const filterDefinition = availableFilterDefinitions.find(
|
||||
(fd) => fd.fieldMetadataId === fieldMetadataId,
|
||||
);
|
||||
|
||||
const defaultOperand = availableOperandsForFilter[0];
|
||||
if (!isDefined(filterDefinition)) {
|
||||
throw new Error('Filter definition not found');
|
||||
}
|
||||
|
||||
const newFilter: Filter = {
|
||||
id: v4(),
|
||||
fieldMetadataId,
|
||||
operand: defaultOperand,
|
||||
displayValue: '',
|
||||
definition: filterDefinition,
|
||||
value: '',
|
||||
};
|
||||
const availableOperandsForFilter =
|
||||
getOperandsForFilterDefinition(filterDefinition);
|
||||
|
||||
upsertCombinedViewFilter(newFilter);
|
||||
const defaultOperand = availableOperandsForFilter[0];
|
||||
|
||||
openDropdown(newFilter.id, {
|
||||
scope: newFilter.id,
|
||||
});
|
||||
const newFilter: Filter = {
|
||||
id: newFilterId,
|
||||
fieldMetadataId,
|
||||
operand: defaultOperand,
|
||||
displayValue: '',
|
||||
definition: filterDefinition,
|
||||
value: '',
|
||||
};
|
||||
|
||||
await upsertCombinedViewFilter(newFilter);
|
||||
|
||||
setFilterDefinitionUsedInDropdownInScope(
|
||||
newFilter.id,
|
||||
filterDefinition,
|
||||
);
|
||||
}
|
||||
|
||||
openDropdown(existingViewFilter?.id ?? newFilterId);
|
||||
},
|
||||
[columnDefinitions, upsertCombinedViewFilter, openDropdown],
|
||||
[
|
||||
openDropdown,
|
||||
columnDefinitions,
|
||||
upsertCombinedViewFilter,
|
||||
setFilterDefinitionUsedInDropdownInScope,
|
||||
currentViewWithCombinedFiltersAndSorts,
|
||||
availableFilterDefinitions,
|
||||
],
|
||||
);
|
||||
|
||||
return handleToggleColumnFilter;
|
||||
|
||||
+4
-1
@@ -152,7 +152,10 @@ export const SettingsDataModelObjectAboutForm = ({
|
||||
<IconPicker
|
||||
disabled={disableEdition}
|
||||
selectedIconKey={value}
|
||||
onChange={({ iconKey }) => onChange(iconKey)}
|
||||
onChange={({ iconKey }) => {
|
||||
onChange(iconKey);
|
||||
onBlur?.();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import react from '@vitejs/plugin-react-swc';
|
||||
import wyw from '@wyw-in-js/vite';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { defineConfig, loadEnv, searchForWorkspaceRoot } from 'vite';
|
||||
import checker from 'vite-plugin-checker';
|
||||
import svgr from 'vite-plugin-svgr';
|
||||
@@ -133,7 +133,7 @@ export default defineConfig(({ command, mode }) => {
|
||||
],
|
||||
|
||||
optimizeDeps: {
|
||||
exclude: ['node_modules/.vite', 'node_modules/.cache'],
|
||||
exclude: ['../../node_modules/.vite', '../../node_modules/.cache'],
|
||||
},
|
||||
|
||||
build: {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "twenty-server",
|
||||
"version": "0.34.0-canary",
|
||||
"version": "0.33.4",
|
||||
"description": "",
|
||||
"author": "",
|
||||
"private": true,
|
||||
|
||||
@@ -6,6 +6,7 @@ import { CommandRunner, Option } from 'nest-commander';
|
||||
export type BaseCommandOptions = {
|
||||
workspaceId?: string;
|
||||
dryRun?: boolean;
|
||||
verbose?: boolean;
|
||||
};
|
||||
|
||||
export abstract class BaseCommandRunner extends CommandRunner {
|
||||
@@ -25,6 +26,14 @@ export abstract class BaseCommandRunner extends CommandRunner {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Option({
|
||||
flags: '--verbose',
|
||||
description: 'Verbose output',
|
||||
})
|
||||
parseVerbose() {
|
||||
return true;
|
||||
}
|
||||
|
||||
override async run(
|
||||
passedParams: string[],
|
||||
options: BaseCommandOptions,
|
||||
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import chalk from 'chalk';
|
||||
import { Command } from 'nest-commander';
|
||||
import { IsNull, Repository } from 'typeorm';
|
||||
|
||||
import {
|
||||
ActiveWorkspacesCommandOptions,
|
||||
ActiveWorkspacesCommandRunner,
|
||||
} from 'src/database/commands/active-workspaces.command';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
|
||||
interface DeleteViewFieldsWithoutViewsCommandOptions
|
||||
extends ActiveWorkspacesCommandOptions {}
|
||||
|
||||
@Command({
|
||||
name: 'upgrade-0.33:delete-view-fields-without-views',
|
||||
description: 'Delete ViewFields that do not have a View',
|
||||
})
|
||||
export class DeleteViewFieldsWithoutViewsCommand extends ActiveWorkspacesCommandRunner {
|
||||
constructor(
|
||||
@InjectRepository(Workspace, 'core')
|
||||
protected readonly workspaceRepository: Repository<Workspace>,
|
||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
) {
|
||||
super(workspaceRepository);
|
||||
}
|
||||
|
||||
async executeActiveWorkspacesCommand(
|
||||
_passedParam: string[],
|
||||
options: DeleteViewFieldsWithoutViewsCommandOptions,
|
||||
workspaceIds: string[],
|
||||
): Promise<void> {
|
||||
this.logger.log(
|
||||
'Running command to delete ViewFields that do not have a View',
|
||||
);
|
||||
|
||||
for (const workspaceId of workspaceIds) {
|
||||
this.logger.log(`Running command for workspace ${workspaceId}`);
|
||||
|
||||
try {
|
||||
await this.deleteViewFieldsWithoutViewsForWorkspace(
|
||||
workspaceId,
|
||||
options,
|
||||
);
|
||||
} catch (error) {
|
||||
this.logger.log(
|
||||
chalk.red(
|
||||
`Running command on workspace ${workspaceId} failed with error: ${error}, ${error.stack}`,
|
||||
),
|
||||
);
|
||||
continue;
|
||||
} finally {
|
||||
this.logger.log(
|
||||
chalk.green(`Finished running command for workspace ${workspaceId}.`),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
this.logger.log(chalk.green(`Command completed!`));
|
||||
}
|
||||
|
||||
private async deleteViewFieldsWithoutViewsForWorkspace(
|
||||
workspaceId: string,
|
||||
options: DeleteViewFieldsWithoutViewsCommandOptions,
|
||||
): Promise<void> {
|
||||
const viewFieldRepository =
|
||||
await this.twentyORMGlobalManager.getRepositoryForWorkspace(
|
||||
workspaceId,
|
||||
'viewField',
|
||||
false,
|
||||
);
|
||||
|
||||
const viewFieldsWithoutViews = await viewFieldRepository.find({
|
||||
where: {
|
||||
viewId: IsNull(),
|
||||
},
|
||||
});
|
||||
|
||||
const viewFieldIds = viewFieldsWithoutViews.map((vf) => vf.id);
|
||||
|
||||
if (!options.dryRun && viewFieldIds.length > 0) {
|
||||
await viewFieldRepository.delete(viewFieldIds);
|
||||
}
|
||||
|
||||
if (options.verbose) {
|
||||
this.logger.log(
|
||||
chalk.yellow(
|
||||
`Deleted ${viewFieldsWithoutViews.length} ViewFields that do not have a View`,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
+18
-13
@@ -17,7 +17,6 @@ interface EnforceUniqueConstraintsCommandOptions
|
||||
company?: boolean;
|
||||
viewField?: boolean;
|
||||
viewSort?: boolean;
|
||||
verbose?: boolean;
|
||||
}
|
||||
|
||||
@Command({
|
||||
@@ -42,14 +41,6 @@ export class EnforceUniqueConstraintsCommand extends ActiveWorkspacesCommandRunn
|
||||
return true;
|
||||
}
|
||||
|
||||
@Option({
|
||||
flags: '--verbose',
|
||||
description: 'Verbose output',
|
||||
})
|
||||
parseVerbose() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Option({
|
||||
flags: '--company',
|
||||
description: 'Enforce unique constraints on company domainName',
|
||||
@@ -250,9 +241,16 @@ export class EnforceUniqueConstraintsCommand extends ActiveWorkspacesCommandRunn
|
||||
.getRawMany();
|
||||
|
||||
for (const duplicate of duplicates) {
|
||||
const { fieldMetadataId, viewId } = duplicate;
|
||||
const {
|
||||
viewField_fieldMetadataId: fieldMetadataId,
|
||||
viewField_viewId: viewId,
|
||||
} = duplicate;
|
||||
const viewFields = await viewFieldRepository.find({
|
||||
where: { fieldMetadataId, viewId, deletedAt: IsNull() },
|
||||
where: {
|
||||
fieldMetadataId,
|
||||
viewId,
|
||||
deletedAt: IsNull(),
|
||||
},
|
||||
order: { createdAt: 'DESC' },
|
||||
});
|
||||
|
||||
@@ -292,9 +290,16 @@ export class EnforceUniqueConstraintsCommand extends ActiveWorkspacesCommandRunn
|
||||
.getRawMany();
|
||||
|
||||
for (const duplicate of duplicates) {
|
||||
const { fieldMetadataId, viewId } = duplicate;
|
||||
const {
|
||||
viewSort_fieldMetadataId: fieldMetadataId,
|
||||
viewSort_viewId: viewId,
|
||||
} = duplicate;
|
||||
const viewSorts = await viewSortRepository.find({
|
||||
where: { fieldMetadataId, viewId, deletedAt: IsNull() },
|
||||
where: {
|
||||
fieldMetadataId,
|
||||
viewId,
|
||||
deletedAt: IsNull(),
|
||||
},
|
||||
order: { createdAt: 'DESC' },
|
||||
});
|
||||
|
||||
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import chalk from 'chalk';
|
||||
import { Command } from 'nest-commander';
|
||||
import { IsNull, Repository } from 'typeorm';
|
||||
|
||||
import {
|
||||
ActiveWorkspacesCommandOptions,
|
||||
ActiveWorkspacesCommandRunner,
|
||||
} from 'src/database/commands/active-workspaces.command';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
import { CUSTOM_OBJECT_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids';
|
||||
|
||||
interface SetMissingLabelIdentifierToCustomObjectsCommandOptions
|
||||
extends ActiveWorkspacesCommandOptions {}
|
||||
|
||||
@Command({
|
||||
name: 'upgrade-0.33:set-missing-label-identifier-to-custom-objects',
|
||||
description: 'Set missing labelIdentifier to custom objects',
|
||||
})
|
||||
export class SetMissingLabelIdentifierToCustomObjectsCommand extends ActiveWorkspacesCommandRunner {
|
||||
constructor(
|
||||
@InjectRepository(Workspace, 'core')
|
||||
protected readonly workspaceRepository: Repository<Workspace>,
|
||||
@InjectRepository(FieldMetadataEntity, 'metadata')
|
||||
private readonly fieldMetadataRepository: Repository<FieldMetadataEntity>,
|
||||
@InjectRepository(ObjectMetadataEntity, 'metadata')
|
||||
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
) {
|
||||
super(workspaceRepository);
|
||||
}
|
||||
|
||||
async executeActiveWorkspacesCommand(
|
||||
_passedParam: string[],
|
||||
options: SetMissingLabelIdentifierToCustomObjectsCommandOptions,
|
||||
workspaceIds: string[],
|
||||
): Promise<void> {
|
||||
this.logger.log(
|
||||
'Running command to set missing labelIdentifier to custom objects',
|
||||
);
|
||||
|
||||
for (const workspaceId of workspaceIds) {
|
||||
this.logger.log(`Running command for workspace ${workspaceId}`);
|
||||
|
||||
try {
|
||||
await this.setMissingLabelIdentifierToCustomObjectsForWorkspace(
|
||||
workspaceId,
|
||||
options,
|
||||
);
|
||||
} catch (error) {
|
||||
this.logger.log(
|
||||
chalk.red(
|
||||
`Running command on workspace ${workspaceId} failed with error: ${error}, ${error.stack}`,
|
||||
),
|
||||
);
|
||||
continue;
|
||||
} finally {
|
||||
this.logger.log(
|
||||
chalk.green(`Finished running command for workspace ${workspaceId}.`),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
this.logger.log(chalk.green(`Command completed!`));
|
||||
}
|
||||
|
||||
private async setMissingLabelIdentifierToCustomObjectsForWorkspace(
|
||||
workspaceId: string,
|
||||
options: SetMissingLabelIdentifierToCustomObjectsCommandOptions,
|
||||
): Promise<void> {
|
||||
const customObjects = await this.objectMetadataRepository.find({
|
||||
where: {
|
||||
workspaceId,
|
||||
labelIdentifierFieldMetadataId: IsNull(),
|
||||
isCustom: true,
|
||||
},
|
||||
});
|
||||
|
||||
for (const customObject of customObjects) {
|
||||
const labelIdentifierFieldMetadata =
|
||||
await this.fieldMetadataRepository.findOne({
|
||||
where: {
|
||||
workspaceId,
|
||||
objectMetadataId: customObject.id,
|
||||
standardId: CUSTOM_OBJECT_STANDARD_FIELD_IDS.name,
|
||||
},
|
||||
});
|
||||
|
||||
if (labelIdentifierFieldMetadata && !options.dryRun) {
|
||||
await this.objectMetadataRepository.update(customObject.id, {
|
||||
labelIdentifierFieldMetadataId: labelIdentifierFieldMetadata.id,
|
||||
});
|
||||
}
|
||||
|
||||
if (options.verbose) {
|
||||
this.logger.log(
|
||||
chalk.yellow(
|
||||
`Set labelIdentifierFieldMetadataId for custom object ${customObject.nameSingular}`,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+14
@@ -4,7 +4,9 @@ import { Command } from 'nest-commander';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { ActiveWorkspacesCommandRunner } from 'src/database/commands/active-workspaces.command';
|
||||
import { DeleteViewFieldsWithoutViewsCommand } from 'src/database/commands/upgrade-version/0-33/0-33-delete-view-fields-without-views.command';
|
||||
import { EnforceUniqueConstraintsCommand } from 'src/database/commands/upgrade-version/0-33/0-33-enforce-unique-constraints.command';
|
||||
import { SetMissingLabelIdentifierToCustomObjectsCommand } from 'src/database/commands/upgrade-version/0-33/0-33-set-missing-label-identifier-to-custom-objects.command';
|
||||
import { UpdateRichTextSearchVectorCommand } from 'src/database/commands/upgrade-version/0-33/0-33-update-rich-text-search-vector-expression';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { SyncWorkspaceMetadataCommand } from 'src/engine/workspace-manager/workspace-sync-metadata/commands/sync-workspace-metadata.command';
|
||||
@@ -23,7 +25,9 @@ export class UpgradeTo0_33Command extends ActiveWorkspacesCommandRunner {
|
||||
protected readonly workspaceRepository: Repository<Workspace>,
|
||||
private readonly updateRichTextSearchVectorCommand: UpdateRichTextSearchVectorCommand,
|
||||
private readonly enforceUniqueConstraintsCommand: EnforceUniqueConstraintsCommand,
|
||||
private readonly deleteViewFieldsWithoutViewsCommand: DeleteViewFieldsWithoutViewsCommand,
|
||||
private readonly syncWorkspaceMetadataCommand: SyncWorkspaceMetadataCommand,
|
||||
private readonly setMissingLabelIdentifierToCustomObjectsCommand: SetMissingLabelIdentifierToCustomObjectsCommand,
|
||||
) {
|
||||
super(workspaceRepository);
|
||||
}
|
||||
@@ -33,6 +37,11 @@ export class UpgradeTo0_33Command extends ActiveWorkspacesCommandRunner {
|
||||
options: UpdateTo0_33CommandOptions,
|
||||
workspaceIds: string[],
|
||||
): Promise<void> {
|
||||
await this.deleteViewFieldsWithoutViewsCommand.executeActiveWorkspacesCommand(
|
||||
passedParam,
|
||||
options,
|
||||
workspaceIds,
|
||||
);
|
||||
await this.enforceUniqueConstraintsCommand.executeActiveWorkspacesCommand(
|
||||
passedParam,
|
||||
{
|
||||
@@ -57,5 +66,10 @@ export class UpgradeTo0_33Command extends ActiveWorkspacesCommandRunner {
|
||||
options,
|
||||
workspaceIds,
|
||||
);
|
||||
await this.setMissingLabelIdentifierToCustomObjectsCommand.executeActiveWorkspacesCommand(
|
||||
passedParam,
|
||||
options,
|
||||
workspaceIds,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+4
@@ -1,7 +1,9 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { DeleteViewFieldsWithoutViewsCommand } from 'src/database/commands/upgrade-version/0-33/0-33-delete-view-fields-without-views.command';
|
||||
import { EnforceUniqueConstraintsCommand } from 'src/database/commands/upgrade-version/0-33/0-33-enforce-unique-constraints.command';
|
||||
import { SetMissingLabelIdentifierToCustomObjectsCommand } from 'src/database/commands/upgrade-version/0-33/0-33-set-missing-label-identifier-to-custom-objects.command';
|
||||
import { UpdateRichTextSearchVectorCommand } from 'src/database/commands/upgrade-version/0-33/0-33-update-rich-text-search-vector-expression';
|
||||
import { UpgradeTo0_33Command } from 'src/database/commands/upgrade-version/0-33/0-33-upgrade-version.command';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
@@ -26,6 +28,8 @@ import { WorkspaceSyncMetadataCommandsModule } from 'src/engine/workspace-manage
|
||||
UpgradeTo0_33Command,
|
||||
UpdateRichTextSearchVectorCommand,
|
||||
EnforceUniqueConstraintsCommand,
|
||||
DeleteViewFieldsWithoutViewsCommand,
|
||||
SetMissingLabelIdentifierToCustomObjectsCommand,
|
||||
],
|
||||
})
|
||||
export class UpgradeTo0_33CommandModule {}
|
||||
|
||||
+7
-1
@@ -14,6 +14,7 @@ import { ProcessNestedRelationsHelper } from 'src/engine/api/graphql/graphql-que
|
||||
import { assertIsValidUuid } from 'src/engine/api/graphql/workspace-query-runner/utils/assert-is-valid-uuid.util';
|
||||
import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util';
|
||||
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
||||
import { computeTableName } from 'src/engine/utils/compute-table-name.util';
|
||||
|
||||
@Injectable()
|
||||
export class GraphqlQueryDeleteManyResolverService extends GraphqlQueryBaseResolverService<
|
||||
@@ -30,9 +31,14 @@ export class GraphqlQueryDeleteManyResolverService extends GraphqlQueryBaseResol
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
);
|
||||
|
||||
const tableName = computeTableName(
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
objectMetadataItemWithFieldMaps.isCustom,
|
||||
);
|
||||
|
||||
executionArgs.graphqlQueryParser.applyFilterToBuilder(
|
||||
queryBuilder,
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
tableName,
|
||||
executionArgs.args.filter,
|
||||
);
|
||||
|
||||
|
||||
+1
-1
@@ -35,8 +35,8 @@ export class GraphqlQueryDeleteOneResolverService extends GraphqlQueryBaseResolv
|
||||
);
|
||||
|
||||
const nonFormattedDeletedObjectRecords = await queryBuilder
|
||||
.where({ id: executionArgs.args.id })
|
||||
.softDelete()
|
||||
.where({ id: executionArgs.args.id })
|
||||
.returning('*')
|
||||
.execute();
|
||||
|
||||
|
||||
+7
-1
@@ -12,6 +12,7 @@ import { QUERY_MAX_RECORDS } from 'src/engine/api/graphql/graphql-query-runner/c
|
||||
import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper';
|
||||
import { ProcessNestedRelationsHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/process-nested-relations.helper';
|
||||
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
||||
import { computeTableName } from 'src/engine/utils/compute-table-name.util';
|
||||
|
||||
@Injectable()
|
||||
export class GraphqlQueryDestroyManyResolverService extends GraphqlQueryBaseResolverService<
|
||||
@@ -28,9 +29,14 @@ export class GraphqlQueryDestroyManyResolverService extends GraphqlQueryBaseReso
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
);
|
||||
|
||||
const tableName = computeTableName(
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
objectMetadataItemWithFieldMaps.isCustom,
|
||||
);
|
||||
|
||||
executionArgs.graphqlQueryParser.applyFilterToBuilder(
|
||||
queryBuilder,
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
tableName,
|
||||
executionArgs.args.filter,
|
||||
);
|
||||
|
||||
|
||||
+1
-10
@@ -16,7 +16,6 @@ import {
|
||||
import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper';
|
||||
import { ProcessNestedRelationsHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/process-nested-relations.helper';
|
||||
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
||||
import { computeTableName } from 'src/engine/utils/compute-table-name.util';
|
||||
|
||||
@Injectable()
|
||||
export class GraphqlQueryDestroyOneResolverService extends GraphqlQueryBaseResolverService<
|
||||
@@ -33,17 +32,9 @@ export class GraphqlQueryDestroyOneResolverService extends GraphqlQueryBaseResol
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
);
|
||||
|
||||
const tableName = computeTableName(
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
objectMetadataItemWithFieldMaps.isCustom,
|
||||
);
|
||||
|
||||
const nonFormattedDeletedObjectRecords = await queryBuilder
|
||||
.where(`"${tableName}".id = :id`, {
|
||||
id: executionArgs.args.id,
|
||||
})
|
||||
.take(1)
|
||||
.delete()
|
||||
.where({ id: executionArgs.args.id })
|
||||
.returning('*')
|
||||
.execute();
|
||||
|
||||
|
||||
+7
-1
@@ -14,6 +14,7 @@ import { ProcessNestedRelationsHelper } from 'src/engine/api/graphql/graphql-que
|
||||
import { assertIsValidUuid } from 'src/engine/api/graphql/workspace-query-runner/utils/assert-is-valid-uuid.util';
|
||||
import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util';
|
||||
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
||||
import { computeTableName } from 'src/engine/utils/compute-table-name.util';
|
||||
|
||||
@Injectable()
|
||||
export class GraphqlQueryRestoreManyResolverService extends GraphqlQueryBaseResolverService<
|
||||
@@ -30,9 +31,14 @@ export class GraphqlQueryRestoreManyResolverService extends GraphqlQueryBaseReso
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
);
|
||||
|
||||
const tableName = computeTableName(
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
objectMetadataItemWithFieldMaps.isCustom,
|
||||
);
|
||||
|
||||
executionArgs.graphqlQueryParser.applyFilterToBuilder(
|
||||
queryBuilder,
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
tableName,
|
||||
executionArgs.args.filter,
|
||||
);
|
||||
|
||||
|
||||
+1
-1
@@ -35,8 +35,8 @@ export class GraphqlQueryRestoreOneResolverService extends GraphqlQueryBaseResol
|
||||
);
|
||||
|
||||
const nonFormattedRestoredObjectRecords = await queryBuilder
|
||||
.where({ id: executionArgs.args.id })
|
||||
.restore()
|
||||
.where({ id: executionArgs.args.id })
|
||||
.returning('*')
|
||||
.execute();
|
||||
|
||||
|
||||
+15
-3
@@ -15,6 +15,7 @@ import { assertIsValidUuid } from 'src/engine/api/graphql/workspace-query-runner
|
||||
import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util';
|
||||
import { formatData } from 'src/engine/twenty-orm/utils/format-data.util';
|
||||
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
||||
import { computeTableName } from 'src/engine/utils/compute-table-name.util';
|
||||
|
||||
@Injectable()
|
||||
export class GraphqlQueryUpdateManyResolverService extends GraphqlQueryBaseResolverService<
|
||||
@@ -31,14 +32,14 @@ export class GraphqlQueryUpdateManyResolverService extends GraphqlQueryBaseResol
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
);
|
||||
|
||||
const existingRecordsBuilder = queryBuilder.clone();
|
||||
|
||||
executionArgs.graphqlQueryParser.applyFilterToBuilder(
|
||||
queryBuilder,
|
||||
existingRecordsBuilder,
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
executionArgs.args.filter,
|
||||
);
|
||||
|
||||
const existingRecordsBuilder = queryBuilder.clone();
|
||||
|
||||
const existingRecords = await existingRecordsBuilder.getMany();
|
||||
|
||||
const formattedExistingRecords = formatResult<ObjectRecord[]>(
|
||||
@@ -47,6 +48,17 @@ export class GraphqlQueryUpdateManyResolverService extends GraphqlQueryBaseResol
|
||||
objectMetadataMaps,
|
||||
);
|
||||
|
||||
const tableName = computeTableName(
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
objectMetadataItemWithFieldMaps.isCustom,
|
||||
);
|
||||
|
||||
executionArgs.graphqlQueryParser.applyFilterToBuilder(
|
||||
queryBuilder,
|
||||
tableName,
|
||||
executionArgs.args.filter,
|
||||
);
|
||||
|
||||
const data = formatData(
|
||||
executionArgs.args.data,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
|
||||
+1
@@ -12,4 +12,5 @@ export enum ObjectMetadataExceptionCode {
|
||||
INVALID_OBJECT_INPUT = 'INVALID_OBJECT_INPUT',
|
||||
OBJECT_MUTATION_NOT_ALLOWED = 'OBJECT_MUTATION_NOT_ALLOWED',
|
||||
OBJECT_ALREADY_EXISTS = 'OBJECT_ALREADY_EXISTS',
|
||||
MISSING_CUSTOM_OBJECT_DEFAULT_LABEL_IDENTIFIER_FIELD = 'MISSING_CUSTOM_OBJECT_DEFAULT_LABEL_IDENTIFIER_FIELD',
|
||||
}
|
||||
|
||||
+16
@@ -31,6 +31,7 @@ import { SearchService } from 'src/engine/metadata-modules/search/search.service
|
||||
import { WorkspaceMetadataVersionService } from 'src/engine/metadata-modules/workspace-metadata-version/services/workspace-metadata-version.service';
|
||||
import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util';
|
||||
import { WorkspaceMigrationRunnerService } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.service';
|
||||
import { CUSTOM_OBJECT_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids';
|
||||
import { isSearchableFieldType } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/is-searchable-field.util';
|
||||
|
||||
import { ObjectMetadataEntity } from './object-metadata.entity';
|
||||
@@ -118,6 +119,21 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
||||
: buildDefaultFieldsForCustomObject(objectMetadataInput.workspaceId),
|
||||
});
|
||||
|
||||
const labelIdentifierFieldMetadata = createdObjectMetadata.fields.find(
|
||||
(field) => field.standardId === CUSTOM_OBJECT_STANDARD_FIELD_IDS.name,
|
||||
);
|
||||
|
||||
if (!labelIdentifierFieldMetadata) {
|
||||
throw new ObjectMetadataException(
|
||||
'Label identifier field metadata not created properly',
|
||||
ObjectMetadataExceptionCode.MISSING_CUSTOM_OBJECT_DEFAULT_LABEL_IDENTIFIER_FIELD,
|
||||
);
|
||||
}
|
||||
|
||||
await this.objectMetadataRepository.update(createdObjectMetadata.id, {
|
||||
labelIdentifierFieldMetadataId: labelIdentifierFieldMetadata.id,
|
||||
});
|
||||
|
||||
if (objectMetadataInput.isRemote) {
|
||||
await this.remoteTableRelationsService.createForeignKeysMetadataAndMigrations(
|
||||
objectMetadataInput.workspaceId,
|
||||
|
||||
+1
-2
@@ -165,12 +165,11 @@ export class ObjectMetadataMigrationService {
|
||||
});
|
||||
|
||||
if (relatedObject) {
|
||||
// 1. Update to and from relation fieldMetadata)
|
||||
// 1. Update to and from relation fieldMetadata
|
||||
const toFieldRelationFieldMetadataId =
|
||||
await this.fieldMetadataRepository
|
||||
.findOneByOrFail({
|
||||
name: existingObjectMetadata.nameSingular,
|
||||
label: existingObjectMetadata.labelSingular,
|
||||
objectMetadataId: relatedObject.id,
|
||||
workspaceId: workspaceId,
|
||||
})
|
||||
|
||||
+5
-1
@@ -2,7 +2,10 @@ import { Relation } from 'src/engine/workspace-manager/workspace-sync-metadata/i
|
||||
|
||||
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { RelationMetadataType } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity';
|
||||
import {
|
||||
RelationMetadataType,
|
||||
RelationOnDeleteAction,
|
||||
} from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity';
|
||||
import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity';
|
||||
import { WorkspaceEntity } from 'src/engine/twenty-orm/decorators/workspace-entity.decorator';
|
||||
import { WorkspaceField } from 'src/engine/twenty-orm/decorators/workspace-field.decorator';
|
||||
@@ -53,6 +56,7 @@ export class FavoriteFolderWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
icon: 'IconHeart',
|
||||
inverseSideFieldKey: 'favoriteFolder',
|
||||
inverseSideTarget: () => FavoriteWorkspaceEntity,
|
||||
onDelete: RelationOnDeleteAction.SET_NULL,
|
||||
})
|
||||
favorites: Relation<FavoriteWorkspaceEntity[]>;
|
||||
}
|
||||
|
||||
+4
-4
@@ -20,11 +20,11 @@ export class GmailHandleErrorService {
|
||||
) {
|
||||
throw parseGaxiosError(error);
|
||||
}
|
||||
if (error.response?.status !== 410) {
|
||||
if (error.code != 410) {
|
||||
const gmailError = {
|
||||
code: error.response?.status,
|
||||
reason: `${error.response?.data?.error?.errors?.[0].reason || error.response?.data?.error || ''}`,
|
||||
message: `${error.response?.data?.error?.errors?.[0].message || error.response?.data?.error_description || ''}${messageExternalId ? ` for message with externalId: ${messageExternalId}` : ''}`,
|
||||
code: error.code,
|
||||
reason: `${error?.errors?.[0].reason || error.response?.data?.error || ''}`,
|
||||
message: `${error?.errors?.[0].message || error.response?.data?.error_description || ''}${messageExternalId ? ` for message with externalId: ${messageExternalId}` : ''}`,
|
||||
};
|
||||
|
||||
throw parseGmailError(gmailError);
|
||||
|
||||
+4
-2
@@ -1,9 +1,9 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
|
||||
import { CALENDAR_THROTTLE_MAX_ATTEMPTS } from 'src/modules/calendar/calendar-event-import-manager/constants/calendar-throttle-max-attempts';
|
||||
import { MessageChannelSyncStatusService } from 'src/modules/messaging/common/services/message-channel-sync-status.service';
|
||||
import { MessageChannelWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
import { MESSAGING_THROTTLE_MAX_ATTEMPTS } from 'src/modules/messaging/message-import-manager/constants/messaging-throttle-max-attempts';
|
||||
import {
|
||||
MessageImportDriverException,
|
||||
MessageImportDriverExceptionCode,
|
||||
@@ -77,7 +77,9 @@ export class MessageImportExceptionHandlerService {
|
||||
>,
|
||||
workspaceId: string,
|
||||
): Promise<void> {
|
||||
if (messageChannel.throttleFailureCount >= CALENDAR_THROTTLE_MAX_ATTEMPTS) {
|
||||
if (
|
||||
messageChannel.throttleFailureCount >= MESSAGING_THROTTLE_MAX_ATTEMPTS
|
||||
) {
|
||||
await this.messageChannelSyncStatusService.markAsFailedUnknownAndFlushMessagesToImport(
|
||||
[messageChannel.id],
|
||||
workspaceId,
|
||||
|
||||
+3
-3
@@ -85,8 +85,8 @@ export class MessagingMessagesImportService {
|
||||
);
|
||||
} catch (error) {
|
||||
switch (error.code) {
|
||||
case (RefreshAccessTokenExceptionCode.REFRESH_ACCESS_TOKEN_FAILED,
|
||||
RefreshAccessTokenExceptionCode.REFRESH_TOKEN_NOT_FOUND):
|
||||
case RefreshAccessTokenExceptionCode.REFRESH_ACCESS_TOKEN_FAILED:
|
||||
case RefreshAccessTokenExceptionCode.REFRESH_TOKEN_NOT_FOUND:
|
||||
await this.messagingTelemetryService.track({
|
||||
eventName: `refresh_token.error.insufficient_permissions`,
|
||||
workspaceId,
|
||||
@@ -191,7 +191,7 @@ export class MessagingMessagesImportService {
|
||||
|
||||
await this.messageImportErrorHandlerService.handleDriverException(
|
||||
error,
|
||||
MessageImportSyncStep.PARTIAL_MESSAGE_LIST_FETCH,
|
||||
MessageImportSyncStep.MESSAGES_IMPORT,
|
||||
messageChannel,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
+4
-4
@@ -1,3 +1,5 @@
|
||||
import { Relation } from 'typeorm';
|
||||
|
||||
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { RelationMetadataType } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity';
|
||||
import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity';
|
||||
@@ -5,7 +7,6 @@ import { WorkspaceEntity } from 'src/engine/twenty-orm/decorators/workspace-enti
|
||||
import { WorkspaceField } from 'src/engine/twenty-orm/decorators/workspace-field.decorator';
|
||||
import { WorkspaceIndex } from 'src/engine/twenty-orm/decorators/workspace-index.decorator';
|
||||
import { WorkspaceIsNotAuditLogged } from 'src/engine/twenty-orm/decorators/workspace-is-not-audit-logged.decorator';
|
||||
import { WorkspaceIsNullable } from 'src/engine/twenty-orm/decorators/workspace-is-nullable.decorator';
|
||||
import { WorkspaceIsSystem } from 'src/engine/twenty-orm/decorators/workspace-is-system.decorator';
|
||||
import { WorkspaceJoinColumn } from 'src/engine/twenty-orm/decorators/workspace-join-column.decorator';
|
||||
import { WorkspaceRelation } from 'src/engine/twenty-orm/decorators/workspace-relation.decorator';
|
||||
@@ -77,9 +78,8 @@ export class ViewFieldWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
inverseSideTarget: () => ViewWorkspaceEntity,
|
||||
inverseSideFieldKey: 'viewFields',
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
view?: ViewWorkspaceEntity | null;
|
||||
view: Relation<ViewWorkspaceEntity>;
|
||||
|
||||
@WorkspaceJoinColumn('view')
|
||||
viewId: string | null;
|
||||
viewId: string;
|
||||
}
|
||||
|
||||
@@ -111,7 +111,7 @@ export class ViewWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
description: 'View Fields',
|
||||
icon: 'IconTag',
|
||||
inverseSideTarget: () => ViewFieldWorkspaceEntity,
|
||||
onDelete: RelationOnDeleteAction.SET_NULL,
|
||||
onDelete: RelationOnDeleteAction.CASCADE,
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
viewFields: Relation<ViewFieldWorkspaceEntity[]>;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "twenty-ui",
|
||||
"version": "0.34.0-canary",
|
||||
"version": "0.33.4",
|
||||
"type": "module",
|
||||
"main": "./src/index.ts",
|
||||
"exports": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "twenty-website",
|
||||
"version": "0.34.0-canary",
|
||||
"version": "0.33.4",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"nx": "NX_DEFAULT_PROJECT=twenty-website node ../../node_modules/nx/bin/nx.js",
|
||||
|
||||
Reference in New Issue
Block a user