Skip to content

Commit 99f9d0d

Browse files
committed
Merge branch 'main' into upsert-endpoint
2 parents a54be3e + 3b7901b commit 99f9d0d

File tree

234 files changed

+3798
-2100
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

234 files changed

+3798
-2100
lines changed

packages/twenty-chrome-extension/src/generated/graphql.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -1640,7 +1640,7 @@ export type Captcha = {
16401640
};
16411641

16421642
export enum CaptchaDriverType {
1643-
GoogleRecatpcha = 'GoogleRecatpcha',
1643+
GoogleRecaptcha = 'GoogleRecaptcha',
16441644
Turnstile = 'Turnstile'
16451645
}
16461646

packages/twenty-front/jest.config.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ const jestConfig: JestConfigWithTsJest = {
2525
coverageThreshold: {
2626
global: {
2727
statements: 65,
28-
lines: 65,
28+
lines: 64,
2929
functions: 55,
3030
},
3131
},

packages/twenty-front/src/generated-metadata/graphql.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ export type Captcha = {
136136
};
137137

138138
export enum CaptchaDriverType {
139-
GoogleRecatpcha = 'GoogleRecatpcha',
139+
GoogleRecaptcha = 'GoogleRecaptcha',
140140
Turnstile = 'Turnstile'
141141
}
142142

packages/twenty-front/src/generated/graphql.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ export type Captcha = {
130130
};
131131

132132
export enum CaptchaDriverType {
133-
GoogleRecatpcha = 'GoogleRecatpcha',
133+
GoogleRecaptcha = 'GoogleRecaptcha',
134134
Turnstile = 'Turnstile'
135135
}
136136

packages/twenty-front/src/modules/captcha/components/CaptchaProviderScriptLoaderEffect.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ export const CaptchaProviderScriptLoaderEffect = () => {
3232
scriptElement = document.createElement('script');
3333
scriptElement.src = scriptUrl;
3434
scriptElement.onload = () => {
35-
if (captchaProvider.provider === CaptchaDriverType.GoogleRecatpcha) {
35+
if (captchaProvider.provider === CaptchaDriverType.GoogleRecaptcha) {
3636
window.grecaptcha?.ready(() => {
3737
setIsCaptchaScriptLoaded(true);
3838
});

packages/twenty-front/src/modules/captcha/hooks/useRequestFreshCaptchaToken.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export const useRequestFreshCaptchaToken = () => {
3535
let captchaWidget: any;
3636

3737
switch (captchaProvider.provider) {
38-
case CaptchaDriverType.GoogleRecatpcha:
38+
case CaptchaDriverType.GoogleRecaptcha:
3939
window.grecaptcha
4040
.execute(captchaProvider.siteKey, {
4141
action: 'submit',
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { expect } from '@storybook/test';
2+
3+
import { CaptchaDriverType } from '~/generated/graphql';
4+
5+
import { getCaptchaUrlByProvider } from '../getCaptchaUrlByProvider';
6+
7+
describe('getCaptchaUrlByProvider', () => {
8+
it('handles GoogleRecaptcha', async () => {
9+
const captchaUrl = getCaptchaUrlByProvider(
10+
CaptchaDriverType.GoogleRecaptcha,
11+
'siteKey',
12+
);
13+
14+
expect(captchaUrl).toEqual(
15+
'https://www.google.com/recaptcha/api.js?render=siteKey',
16+
);
17+
18+
expect(() =>
19+
getCaptchaUrlByProvider(CaptchaDriverType.GoogleRecaptcha, ''),
20+
).toThrow(
21+
'SiteKey must be provided while generating url for GoogleRecaptcha provider',
22+
);
23+
});
24+
25+
it('handles Turnstile', async () => {
26+
const captchaUrl = getCaptchaUrlByProvider(CaptchaDriverType.Turnstile, '');
27+
28+
expect(captchaUrl).toEqual(
29+
'https://challenges.cloudflare.com/turnstile/v0/api.js',
30+
);
31+
});
32+
33+
it('handles unknown provider', async () => {
34+
expect(() =>
35+
getCaptchaUrlByProvider('Unknown' as CaptchaDriverType, ''),
36+
).toThrow('Unknown captcha provider');
37+
});
38+
});
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,22 @@
1-
import { CaptchaDriverType } from '~/generated-metadata/graphql';
1+
import { isNonEmptyString } from '@sniptt/guards';
22

3-
export const getCaptchaUrlByProvider = (name: string, siteKey: string) => {
4-
if (!name) {
5-
return '';
6-
}
3+
import { CaptchaDriverType } from '~/generated-metadata/graphql';
74

5+
export const getCaptchaUrlByProvider = (
6+
name: CaptchaDriverType,
7+
siteKey: string,
8+
) => {
89
switch (name) {
9-
case CaptchaDriverType.GoogleRecatpcha:
10+
case CaptchaDriverType.GoogleRecaptcha:
11+
if (!isNonEmptyString(siteKey)) {
12+
throw new Error(
13+
'SiteKey must be provided while generating url for GoogleRecaptcha provider',
14+
);
15+
}
1016
return `https://www.google.com/recaptcha/api.js?render=${siteKey}`;
1117
case CaptchaDriverType.Turnstile:
1218
return 'https://challenges.cloudflare.com/turnstile/v0/api.js';
1319
default:
14-
return '';
20+
throw new Error('Unknown captcha provider');
1521
}
1622
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { getForeignDataWrapperType } from '../getForeignDataWrapperType';
2+
3+
describe('getForeignDataWrapperType', () => {
4+
it('should handle postgres', () => {
5+
expect(getForeignDataWrapperType('postgresql')).toBe('postgres_fdw');
6+
});
7+
8+
it('should handle stripe', () => {
9+
expect(getForeignDataWrapperType('stripe')).toBe('stripe_fdw');
10+
});
11+
12+
it('should return null for unknown', () => {
13+
expect(getForeignDataWrapperType('unknown')).toBeNull();
14+
});
15+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { AppPath } from '@/types/AppPath';
2+
3+
import indexAppPath from '../indexAppPath';
4+
5+
describe('getIndexAppPath', () => {
6+
it('returns the index app path', () => {
7+
const { getIndexAppPath } = indexAppPath;
8+
expect(getIndexAppPath()).toEqual(AppPath.Index);
9+
});
10+
});

packages/twenty-front/src/modules/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions.ts

-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ export const formatFieldMetadataItemsAsFilterDefinitions = ({
2222
FieldMetadataType.Address,
2323
FieldMetadataType.Relation,
2424
FieldMetadataType.Select,
25-
FieldMetadataType.MultiSelect,
2625
FieldMetadataType.Currency,
2726
].includes(field.type)
2827
) {

packages/twenty-front/src/modules/object-record/graphql/types/RecordGqlOperationFilter.ts

+5
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ export type UUIDFilter = {
99
is?: IsFilter;
1010
};
1111

12+
export type RelationFilter = {
13+
is?: IsFilter;
14+
in?: UUIDFilterValue[];
15+
};
16+
1217
export type BooleanFilter = {
1318
eq?: boolean;
1419
is?: IsFilter;

packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/MultipleFiltersDropdownContent.tsx

+8
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { useRecoilValue } from 'recoil';
33
import { ObjectFilterDropdownSearchInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownSearchInput';
44
import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown';
55
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
6+
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
67

78
import { MultipleFiltersDropdownFilterOnFilterChangedEffect } from './MultipleFiltersDropdownFilterOnFilterChangedEffect';
89
import { ObjectFilterDropdownDateInput } from './ObjectFilterDropdownDateInput';
@@ -36,13 +37,20 @@ export const MultipleFiltersDropdownContent = ({
3637
const selectedOperandInDropdown = useRecoilValue(
3738
selectedOperandInDropdownState,
3839
);
40+
const isEmptyOperand =
41+
selectedOperandInDropdown &&
42+
[ViewFilterOperand.IsEmpty, ViewFilterOperand.IsNotEmpty].includes(
43+
selectedOperandInDropdown,
44+
);
3945

4046
return (
4147
<>
4248
{!filterDefinitionUsedInDropdown ? (
4349
<ObjectFilterDropdownFilterSelect />
4450
) : isObjectFilterDropdownOperandSelectUnfolded ? (
4551
<ObjectFilterDropdownOperandSelect />
52+
) : isEmptyOperand ? (
53+
<ObjectFilterDropdownOperandButton />
4654
) : (
4755
selectedOperandInDropdown && (
4856
<>

packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownOperandSelect.tsx

+20-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { useRecoilValue } from 'recoil';
22
import { v4 } from 'uuid';
33

44
import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown';
5+
import { FilterDefinition } from '@/object-record/object-filter-dropdown/types/FilterDefinition';
56
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
67
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
78
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
@@ -34,10 +35,27 @@ export const ObjectFilterDropdownOperandSelect = () => {
3435
filterDefinitionUsedInDropdown?.type,
3536
);
3637

37-
const handleOperangeChange = (newOperand: ViewFilterOperand) => {
38+
const handleOperandChange = (newOperand: ViewFilterOperand) => {
39+
const isEmptyOperand = [
40+
ViewFilterOperand.IsEmpty,
41+
ViewFilterOperand.IsNotEmpty,
42+
].includes(newOperand);
43+
3844
setSelectedOperandInDropdown(newOperand);
3945
setIsObjectFilterDropdownOperandSelectUnfolded(false);
4046

47+
if (isEmptyOperand) {
48+
selectFilter?.({
49+
id: v4(),
50+
fieldMetadataId: filterDefinitionUsedInDropdown?.fieldMetadataId ?? '',
51+
displayValue: '',
52+
operand: newOperand,
53+
value: '',
54+
definition: filterDefinitionUsedInDropdown as FilterDefinition,
55+
});
56+
return;
57+
}
58+
4159
if (
4260
isDefined(filterDefinitionUsedInDropdown) &&
4361
isDefined(selectedFilter)
@@ -63,7 +81,7 @@ export const ObjectFilterDropdownOperandSelect = () => {
6381
<MenuItem
6482
key={`select-filter-operand-${index}`}
6583
onClick={() => {
66-
handleOperangeChange(filterOperand);
84+
handleOperandChange(filterOperand);
6785
}}
6886
text={getOperandLabel(filterOperand)}
6987
/>

packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/__tests__/getOperandsForFilterType.test.tsx

+27-13
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,34 @@ import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
44
import { getOperandsForFilterType } from '../getOperandsForFilterType';
55

66
describe('getOperandsForFilterType', () => {
7+
const emptyOperands = [
8+
ViewFilterOperand.IsEmpty,
9+
ViewFilterOperand.IsNotEmpty,
10+
];
11+
12+
const containsOperands = [
13+
ViewFilterOperand.Contains,
14+
ViewFilterOperand.DoesNotContain,
15+
];
16+
17+
const numberOperands = [
18+
ViewFilterOperand.GreaterThan,
19+
ViewFilterOperand.LessThan,
20+
];
21+
22+
const relationOperand = [ViewFilterOperand.Is, ViewFilterOperand.IsNot];
23+
724
const testCases = [
8-
['TEXT', [ViewFilterOperand.Contains, ViewFilterOperand.DoesNotContain]],
9-
['EMAIL', [ViewFilterOperand.Contains, ViewFilterOperand.DoesNotContain]],
10-
[
11-
'FULL_NAME',
12-
[ViewFilterOperand.Contains, ViewFilterOperand.DoesNotContain],
13-
],
14-
['ADDRESS', [ViewFilterOperand.Contains, ViewFilterOperand.DoesNotContain]],
15-
['LINK', [ViewFilterOperand.Contains, ViewFilterOperand.DoesNotContain]],
16-
['LINKS', [ViewFilterOperand.Contains, ViewFilterOperand.DoesNotContain]],
17-
['CURRENCY', [ViewFilterOperand.GreaterThan, ViewFilterOperand.LessThan]],
18-
['NUMBER', [ViewFilterOperand.GreaterThan, ViewFilterOperand.LessThan]],
19-
['DATE_TIME', [ViewFilterOperand.GreaterThan, ViewFilterOperand.LessThan]],
20-
['RELATION', [ViewFilterOperand.Is, ViewFilterOperand.IsNot]],
25+
['TEXT', [...containsOperands, ...emptyOperands]],
26+
['EMAIL', [...containsOperands, ...emptyOperands]],
27+
['FULL_NAME', [...containsOperands, ...emptyOperands]],
28+
['ADDRESS', [...containsOperands, ...emptyOperands]],
29+
['LINK', [...containsOperands, ...emptyOperands]],
30+
['LINKS', [...containsOperands, ...emptyOperands]],
31+
['CURRENCY', [...numberOperands, ...emptyOperands]],
32+
['NUMBER', [...numberOperands, ...emptyOperands]],
33+
['DATE_TIME', [...numberOperands, ...emptyOperands]],
34+
['RELATION', [...relationOperand, ...emptyOperands]],
2135
[undefined, []],
2236
[null, []],
2337
['UNKNOWN_TYPE', []],

packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getOperandLabel.ts

+8
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ export const getOperandLabel = (
1818
return 'Is not';
1919
case ViewFilterOperand.IsNotNull:
2020
return 'Is not null';
21+
case ViewFilterOperand.IsEmpty:
22+
return 'Is empty';
23+
case ViewFilterOperand.IsNotEmpty:
24+
return 'Is not empty';
2125
default:
2226
return '';
2327
}
@@ -35,6 +39,10 @@ export const getOperandLabelShort = (
3539
return ': Not';
3640
case ViewFilterOperand.IsNotNull:
3741
return ': NotNull';
42+
case ViewFilterOperand.IsNotEmpty:
43+
return ': NotEmpty';
44+
case ViewFilterOperand.IsEmpty:
45+
return ': Empty';
3846
case ViewFilterOperand.GreaterThan:
3947
return '\u00A0> ';
4048
case ViewFilterOperand.LessThan:

packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getOperandsForFilterType.ts

+19-4
Original file line numberDiff line numberDiff line change
@@ -5,24 +5,39 @@ import { FilterType } from '../types/FilterType';
55
export const getOperandsForFilterType = (
66
filterType: FilterType | null | undefined,
77
): ViewFilterOperand[] => {
8+
const emptyOperands = [
9+
ViewFilterOperand.IsEmpty,
10+
ViewFilterOperand.IsNotEmpty,
11+
];
12+
13+
const relationOperands = [ViewFilterOperand.Is, ViewFilterOperand.IsNot];
14+
815
switch (filterType) {
916
case 'TEXT':
1017
case 'EMAIL':
1118
case 'FULL_NAME':
1219
case 'ADDRESS':
1320
case 'PHONE':
1421
case 'LINK':
15-
return [ViewFilterOperand.Contains, ViewFilterOperand.DoesNotContain];
1622
case 'LINKS':
17-
return [ViewFilterOperand.Contains, ViewFilterOperand.DoesNotContain];
23+
return [
24+
ViewFilterOperand.Contains,
25+
ViewFilterOperand.DoesNotContain,
26+
...emptyOperands,
27+
];
1828
case 'CURRENCY':
1929
case 'NUMBER':
2030
case 'DATE_TIME':
2131
case 'DATE':
22-
return [ViewFilterOperand.GreaterThan, ViewFilterOperand.LessThan];
32+
return [
33+
ViewFilterOperand.GreaterThan,
34+
ViewFilterOperand.LessThan,
35+
...emptyOperands,
36+
];
2337
case 'RELATION':
38+
return [...relationOperands, ...emptyOperands];
2439
case 'SELECT':
25-
return [ViewFilterOperand.Is, ViewFilterOperand.IsNot];
40+
return [...relationOperands];
2641
default:
2742
return [];
2843
}

packages/twenty-front/src/modules/object-record/record-board/record-board-card/components/RecordBoardCard.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,7 @@ export const RecordBoardCard = () => {
262262
recoilScopeId: recordId + fieldDefinition.fieldMetadataId,
263263
isLabelIdentifier: false,
264264
fieldDefinition: {
265-
disableTooltip: true,
265+
disableTooltip: false,
266266
fieldMetadataId: fieldDefinition.fieldMetadataId,
267267
label: fieldDefinition.label,
268268
iconName: fieldDefinition.iconName,

packages/twenty-front/src/modules/object-record/record-field/types/CurrencyCode.ts

+2
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,6 @@ export enum CurrencyCode {
1515
QAR = 'QAR',
1616
AED = 'AED',
1717
KRW = 'KRW',
18+
BRL = 'BRL',
19+
AUD = 'AUD',
1820
}

0 commit comments

Comments
 (0)