Fix various bugs before 0.2.2 (#3118)

* Fix various bugs before 0.2.2 release

* Additional fixes

* More fixes

* Fixes
This commit is contained in:
Charles Bochet 2023-12-21 23:48:52 +01:00 committed by GitHub
parent 69ffa0d229
commit 756b30815e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 183 additions and 245 deletions

View File

@ -28,7 +28,6 @@ export const ActivityBodyEditor = ({
const [body, setBody] = useState<string | null>(null);
const { updateOneRecord } = useUpdateOneRecord({
objectNameSingular: 'activity',
refetchFindManyQuery: true,
});
useEffect(() => {

View File

@ -82,7 +82,6 @@ export const ActivityEditor = ({
const containerRef = useRef<HTMLDivElement>(null);
const { updateOneRecord: updateOneActivity } = useUpdateOneRecord<Activity>({
objectNameSingular: 'activity',
refetchFindManyQuery: true,
});
const { FieldContextProvider: DueAtFieldContextProvider } = useFieldContext({

View File

@ -24,7 +24,6 @@ export const useOpenCreateActivityDrawer = () => {
});
const { createOneRecord: createOneActivity } = useCreateOneRecord<Activity>({
objectNameSingular: 'activity',
refetchFindManyQuery: true,
});
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
const setHotkeyScope = useSetHotkeyScope();

View File

@ -10,7 +10,6 @@ import {
IconCheckbox,
IconSearch,
IconSettings,
IconTargetArrow,
} from '@/ui/display/icon';
import { NavigationDrawerItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItem';
import { NavigationDrawerSection } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSection';
@ -70,12 +69,6 @@ export const MainNavigationDrawerItems = () => {
<NavigationDrawerSection>
<NavigationDrawerSectionTitle label="Workspace" />
<ObjectMetadataNavItems />
<NavigationDrawerItem
label="Opportunities"
to="/objects/opportunities"
active={location.pathname === '/objects/opportunities'}
Icon={IconTargetArrow}
/>
</NavigationDrawerSection>
</>
);

View File

@ -12,20 +12,45 @@ export const ObjectMetadataNavItems = () => {
return (
<>
{activeObjectMetadataItems.map((objectMetadataItem) =>
objectMetadataItem.nameSingular === 'opportunity' ? null : (
<NavigationDrawerItem
key={objectMetadataItem.id}
label={objectMetadataItem.labelPlural}
to={`/objects/${objectMetadataItem.namePlural}`}
active={currentPath == `/objects/${objectMetadataItem.namePlural}`}
Icon={getIcon(objectMetadataItem.icon)}
onClick={() => {
navigate(`/objects/${objectMetadataItem.namePlural}`);
}}
/>
),
)}
{[
...activeObjectMetadataItems
.filter((item) =>
['person', 'company', 'opportunity'].includes(item.nameSingular),
)
.sort((objectMetadataItemA, _) => {
if (objectMetadataItemA.nameSingular === 'person') {
return -1;
}
if (objectMetadataItemA.nameSingular === 'opportunity') {
return 1;
}
return 0;
}),
...activeObjectMetadataItems
.filter(
(item) =>
!['person', 'company', 'opportunity'].includes(item.nameSingular),
)
.sort((objectMetadataItemA, objectMetadataItemB) => {
return new Date(objectMetadataItemA.createdAt) <
new Date(objectMetadataItemB.createdAt)
? 1
: -1;
}),
].map((objectMetadataItem) => (
<NavigationDrawerItem
key={objectMetadataItem.id}
label={objectMetadataItem.labelPlural}
to={`/objects/${objectMetadataItem.namePlural}`}
active={currentPath == `/objects/${objectMetadataItem.namePlural}`}
Icon={getIcon(objectMetadataItem.icon)}
onClick={() => {
navigate(`/objects/${objectMetadataItem.namePlural}`);
}}
/>
))}
</>
);
};

View File

@ -38,7 +38,7 @@ const initializeValue = (
return {
amount: convertCurrencyMicrosToCurrency(fieldValue.amountMicros),
currencyCode: fieldValue.currencyCode,
currencyCode: CurrencyCode.USD,
};
};

View File

@ -8,7 +8,6 @@ import { capitalize } from '~/utils/string/capitalize';
type useCreateOneRecordProps = {
objectNameSingular: string;
refetchFindManyQuery?: boolean;
};
export const useCreateOneRecord = <T>({
@ -36,6 +35,7 @@ export const useCreateOneRecord = <T>({
const generatedEmptyRecord = generateEmptyRecord<Record<string, unknown>>({
id: recordId,
createdAt: new Date().toISOString(),
...input,
});
@ -53,7 +53,7 @@ export const useCreateOneRecord = <T>({
},
optimisticResponse: {
[`create${capitalize(objectMetadataItem.nameSingular)}`]:
generateEmptyRecord({ id: recordId, ...input }),
generatedEmptyRecord,
},
});

View File

@ -1,4 +1,5 @@
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { generateEmptyFieldValue } from '@/object-record/utils/generateEmptyFieldValue';
export const useGenerateEmptyRecord = ({
objectMetadataItem,
@ -10,157 +11,14 @@ export const useGenerateEmptyRecord = ({
// Todo replace this by runtime typing
const validatedInput = input as { id: string } & { [key: string]: any };
if (objectMetadataItem.nameSingular === 'company') {
return {
id: validatedInput.id,
domainName: '',
accountOwnerId: null,
createdAt: new Date().toISOString(),
address: '',
people: [
{
edges: [],
__typename: 'PersonConnection',
},
],
xLink: {
label: '',
url: '',
__typename: 'Link',
},
attachments: {
edges: [],
__typename: 'AttachmentConnection',
},
activityTargets: {
edges: [],
__typename: 'ActivityTargetConnection',
},
idealCustomerProfile: null,
annualRecurringRevenue: {
amountMicros: null,
currencyCode: null,
__typename: 'Currency',
},
updatedAt: new Date().toISOString(),
employees: null,
accountOwner: null,
name: '',
linkedinLink: {
label: '',
url: '',
__typename: 'Link',
},
favorites: {
edges: [],
__typename: 'FavoriteConnection',
},
opportunities: {
edges: [],
__typename: 'OpportunityConnection',
},
__typename: 'Company',
} as T;
}
const emptyRecord = {} as Record<string, any>;
if (objectMetadataItem.nameSingular === 'person') {
return {
id: validatedInput.id,
activityTargets: {
edges: [],
__typename: 'ActivityTargetConnection',
},
opportunities: {
edges: [],
__typename: 'OpportunityConnection',
},
companyId: null,
favorites: {
edges: [],
__typename: 'FavoriteConnection',
},
phone: '',
company: null,
xLink: {
label: '',
url: '',
__typename: 'Link',
},
jobTitle: '',
pointOfContactForOpportunities: {
edges: [],
__typename: 'OpportunityConnection',
},
email: '',
attachments: {
edges: [],
__typename: 'AttachmentConnection',
},
name: {
firstName: '',
lastName: '',
__typename: 'FullName',
},
avatarUrl: '',
updatedAt: new Date().toISOString(),
createdAt: new Date().toISOString(),
city: '',
linkedinLink: {
label: '',
url: '',
__typename: 'Link',
},
__typename: 'Person',
} as T;
}
if (objectMetadataItem.nameSingular === 'opportunity') {
return {
id: validatedInput.id,
pipelineStepId: validatedInput.pipelineStepId,
closeDate: null,
updatedAt: new Date().toISOString(),
pipelineStep: null,
probability: '0',
pointOfContactId: null,
personId: null,
amount: {
amountMicros: null,
currencyCode: null,
__typename: 'Currency',
},
createdAt: new Date().toISOString(),
pointOfContact: null,
person: null,
company: null,
companyId: validatedInput.companyId,
__typename: 'Opportunity',
} as T;
}
if (objectMetadataItem.nameSingular === 'opportunity') {
return {
id: validatedInput.id,
pipelineStepId: validatedInput.pipelineStepId,
closeDate: null,
updatedAt: new Date().toISOString(),
pipelineStep: null,
probability: '0',
pointOfContactId: null,
personId: null,
amount: {
amountMicros: null,
currencyCode: null,
__typename: 'Currency',
},
createdAt: new Date().toISOString(),
pointOfContact: null,
person: null,
company: null,
companyId: validatedInput.companyId,
__typename: 'Opportunity',
} as T;
for (const fieldMetadataItem of objectMetadataItem.fields) {
emptyRecord[fieldMetadataItem.name] =
validatedInput[fieldMetadataItem.name] ??
generateEmptyFieldValue(fieldMetadataItem);
}
return emptyRecord as T;
};
return {

View File

@ -7,7 +7,6 @@ import { capitalize } from '~/utils/string/capitalize';
type useUpdateOneRecordProps = {
objectNameSingular: string;
refetchFindManyQuery?: boolean;
};
export const useUpdateOneRecord = <T>({

View File

@ -43,13 +43,11 @@ export const ColumnHead = ({ column }: ColumnHeadProps) => {
const Icon = getIcon(column.iconName);
return (
<>
<StyledTitle>
<StyledIcon>
<Icon size={theme.icon.size.md} />
</StyledIcon>
<StyledText>{column.label}</StyledText>
</StyledTitle>
</>
<StyledTitle>
<StyledIcon>
<Icon size={theme.icon.size.md} />
</StyledIcon>
<StyledText>{column.label}</StyledText>
</StyledTitle>
);
};

View File

@ -1,3 +1,5 @@
import styled from '@emotion/styled';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { DropdownScope } from '@/ui/layout/dropdown/scopes/DropdownScope';
@ -14,6 +16,11 @@ type ColumnHeadWithDropdownProps = {
primaryColumnKey: string;
};
const StyledDropdown = styled(Dropdown)`
display: flex;
flex: 1;
`;
export const ColumnHeadWithDropdown = ({
column,
isFirstColumn,
@ -22,7 +29,7 @@ export const ColumnHeadWithDropdown = ({
}: ColumnHeadWithDropdownProps) => {
return (
<DropdownScope dropdownScopeId={column.fieldMetadataId + '-header'}>
<Dropdown
<StyledDropdown
clickableComponent={<ColumnHead column={column} />}
dropdownComponents={
<RecordTableColumnDropdownMenu

View File

@ -10,15 +10,17 @@ export const RecordTableBody = () => {
const tableRowIds = useRecoilValue(tableRowIdsState);
return (
<tbody>
{tableRowIds.map((rowId, rowIndex) => (
<RowIdContext.Provider value={rowId} key={rowId}>
<RowIndexContext.Provider value={rowIndex}>
<RecordTableRow key={rowId} rowId={rowId} />
</RowIndexContext.Provider>
</RowIdContext.Provider>
))}
<>
<tbody>
{tableRowIds.map((rowId, rowIndex) => (
<RowIdContext.Provider value={rowId} key={rowId}>
<RowIndexContext.Provider value={rowIndex}>
<RecordTableRow key={rowId} rowId={rowId} />
</RowIndexContext.Provider>
</RowIdContext.Provider>
))}
</tbody>
<RecordTableBodyFetchMoreLoader />
</tbody>
</>
);
};

View File

@ -30,11 +30,15 @@ export const RecordTableBodyFetchMoreLoader = () => {
onChange: onLastRowVisible,
});
return isFetchingMoreObjects ? (
<StyledRow ref={tbodyRef} selected={false}>
<td style={{ height: 50 }} colSpan={1000}>
Loading more...
</td>
</StyledRow>
) : null;
return (
<tbody ref={tbodyRef}>
{isFetchingMoreObjects ? (
<StyledRow selected={false}>
<td style={{ height: 50 }} colSpan={1000}>
Loading more...
</td>
</StyledRow>
) : null}
</tbody>
);
};

View File

@ -0,0 +1,57 @@
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { FieldMetadataType } from '~/generated/graphql';
export const generateEmptyFieldValue = (
fieldMetadataItem: FieldMetadataItem,
) => {
switch (fieldMetadataItem.type) {
case FieldMetadataType.Email:
case FieldMetadataType.Phone:
case FieldMetadataType.Text: {
return '';
}
case FieldMetadataType.Link: {
return {
label: '',
url: '',
__typename: 'Link',
};
}
case FieldMetadataType.FullName: {
return {
firstName: '',
lastName: '',
__typename: 'FullName',
};
}
case FieldMetadataType.DateTime: {
return null;
}
case FieldMetadataType.Number:
case FieldMetadataType.Rating:
case FieldMetadataType.Numeric: {
return null;
}
case FieldMetadataType.Uuid: {
return '';
}
case FieldMetadataType.Boolean: {
return true;
}
case FieldMetadataType.Relation: {
return null;
}
case FieldMetadataType.Currency: {
return {
amountMicros: null,
currencyCode: null,
__typename: 'Currency',
};
}
case FieldMetadataType.MultiSelect:
case FieldMetadataType.Select: {
throw new Error('Not implemented yet');
}
}
};

View File

@ -5,7 +5,9 @@ export const isFieldMetadataItemAvailable = (
fieldMetadataItem: FieldMetadataItem,
) =>
fieldMetadataItem.type !== 'UUID' &&
(fieldMetadataItem.type !== 'RELATION' ||
parseFieldRelationType(fieldMetadataItem) === 'TO_ONE_OBJECT') &&
!(
fieldMetadataItem.type === 'RELATION' &&
parseFieldRelationType(fieldMetadataItem) !== 'TO_ONE_OBJECT'
) &&
!fieldMetadataItem.isSystem &&
!!fieldMetadataItem.isActive;

View File

@ -111,9 +111,9 @@ export const SettingsObjectFieldPreview = ({
{objectMetadataItem?.labelPlural}
</StyledObjectName>
{objectMetadataItem?.isCustom ? (
<Tag color="orange" text="Custom" />
<Tag color="orange" text="Custom" weight="medium" />
) : (
<Tag color="blue" text="Standard" />
<Tag color="blue" text="Standard" weight="medium" />
)}
</StyledObjectSummary>
<SettingsObjectFieldPreviewValueEffect

View File

@ -79,9 +79,9 @@ export const SettingsAboutSection = ({
{name}
</StyledName>
{isCustom ? (
<StyledTag color="orange" text="Custom" />
<StyledTag color="orange" text="Custom" weight="medium" />
) : (
<StyledTag color="blue" text="Standard" />
<StyledTag color="blue" text="Standard" weight="medium" />
)}
<DropdownScope dropdownScopeId={dropdownScopeId}>
<Dropdown

View File

@ -51,9 +51,9 @@ export const SettingsObjectItemTableRow = ({
</StyledNameTableCell>
<TableCell>
{objectItem.isCustom ? (
<Tag color="orange" text="Custom" />
<Tag color="orange" text="Custom" weight="medium" />
) : (
<Tag color="blue" text="Standard" />
<Tag color="blue" text="Standard" weight="medium" />
)}
</TableCell>
<TableCell align="right">

View File

@ -5,6 +5,7 @@ import { themeColorSchema } from '@/ui/theme/utils/themeColorSchema';
const StyledTag = styled.h3<{
color: ThemeColor;
weight: TagWeight;
}>`
align-items: center;
background: ${({ color, theme }) => theme.tag.background[color]};
@ -13,7 +14,10 @@ const StyledTag = styled.h3<{
display: inline-flex;
font-size: ${({ theme }) => theme.font.size.md};
font-style: normal;
font-weight: ${({ theme }) => theme.font.weight.regular};
font-weight: ${({ theme, weight }) =>
weight === 'regular'
? theme.font.weight.regular
: theme.font.weight.medium};
height: ${({ theme }) => theme.spacing(5)};
margin: 0;
overflow: hidden;
@ -26,18 +30,28 @@ const StyledContent = styled.span`
white-space: nowrap;
`;
type TagWeight = 'regular' | 'medium';
type TagProps = {
className?: string;
color: ThemeColor;
text: string;
onClick?: () => void;
weight?: TagWeight;
};
export const Tag = ({ className, color, text, onClick }: TagProps) => (
export const Tag = ({
className,
color,
text,
onClick,
weight = 'regular',
}: TagProps) => (
<StyledTag
className={className}
color={themeColorSchema.catch('gray').parse(color)}
onClick={onClick}
weight={weight}
>
<StyledContent>{text}</StyledContent>
</StyledTag>

View File

@ -102,7 +102,11 @@ export const Dropdown = ({
return (
<div ref={containerRef} className={className}>
{clickableComponent && (
<div ref={refs.setReference} onClick={toggleDropdown}>
<div
ref={refs.setReference}
onClick={toggleDropdown}
className={className}
>
{clickableComponent}
</div>
)}

View File

@ -18,7 +18,9 @@ const StyledMainContainer = styled.div`
flex-direction: row;
gap: ${({ theme }) => theme.spacing(2)};
min-height: 0;
padding: ${({ theme }) => theme.spacing(0, 3)};
padding-bottom: ${({ theme }) => theme.spacing(3)};
padding-left: 0;
padding-right: ${({ theme }) => theme.spacing(3)};
width: 100%;
@media (max-width: ${MOBILE_VIEWPORT}px) {

View File

@ -94,7 +94,6 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
{
name: objectMetadata.targetTableName,
action: 'alter',
schemaName: objectMetadata.dataSource.schema,
columns: this.workspaceMigrationFactory.createColumnActions(
WorkspaceMigrationColumnActionType.CREATE,
createdFieldMetadata,
@ -205,7 +204,6 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
{
name: objectMetadata.targetTableName,
action: 'alter',
schemaName: objectMetadata.dataSource.schema,
columns: this.workspaceMigrationFactory.createColumnActions(
WorkspaceMigrationColumnActionType.ALTER,
existingFieldMetadata,

View File

@ -230,13 +230,11 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
{
name: createdObjectMetadata.targetTableName,
action: 'create',
schemaName: createdObjectMetadata.dataSource.schema,
} satisfies WorkspaceMigrationTableAction,
// Add activity target relation
{
name: activityTargetObjectMetadata.targetTableName,
action: 'alter',
schemaName: activityTargetObjectMetadata.dataSource.schema,
columns: [
{
action: WorkspaceMigrationColumnActionType.CREATE,
@ -249,7 +247,6 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
{
name: activityTargetObjectMetadata.targetTableName,
action: 'alter',
schemaName: activityTargetObjectMetadata.dataSource.schema,
columns: [
{
action: WorkspaceMigrationColumnActionType.RELATION,
@ -263,7 +260,6 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
{
name: createdObjectMetadata.targetTableName,
action: 'alter',
schemaName: createdObjectMetadata.dataSource.schema,
columns: [
{
action: WorkspaceMigrationColumnActionType.CREATE,

View File

@ -200,9 +200,6 @@ export class RelationMetadataService extends TypeOrmQueryService<RelationMetadat
name: objectMetadataMap[relationMetadataInput.toObjectMetadataId]
.targetTableName,
action: 'alter',
schemaName:
objectMetadataMap[relationMetadataInput.toObjectMetadataId]
.dataSource.schema,
columns: [
{
action: WorkspaceMigrationColumnActionType.CREATE,
@ -217,9 +214,6 @@ export class RelationMetadataService extends TypeOrmQueryService<RelationMetadat
name: objectMetadataMap[relationMetadataInput.toObjectMetadataId]
.targetTableName,
action: 'alter',
schemaName:
objectMetadataMap[relationMetadataInput.toObjectMetadataId]
.dataSource.schema,
columns: [
{
action: WorkspaceMigrationColumnActionType.RELATION,

View File

@ -39,7 +39,6 @@ export type WorkspaceMigrationColumnRelation = {
columnName: string;
referencedTableName: string;
referencedTableColumnName: string;
referencedSchema?: string;
isUnique?: boolean;
};
@ -60,7 +59,6 @@ export type WorkspaceMigrationColumnAction = {
export type WorkspaceMigrationTableAction = {
name: string;
action: 'create' | 'alter';
schemaName: string;
columns?: WorkspaceMigrationColumnAction[];
};

View File

@ -63,11 +63,13 @@ export class WorkspaceMigrationRunnerService {
}, []);
const queryRunner = workspaceDataSource?.createQueryRunner();
const schemaName =
this.workspaceDataSourceService.getSchemaName(workspaceId);
// Loop over each migration and create or update the table
// TODO: Should be done in a transaction
for (const migration of flattenedPendingMigrations) {
await this.handleTableChanges(queryRunner, migration);
await this.handleTableChanges(queryRunner, schemaName, migration);
}
// Update appliedAt date for each migration
@ -96,20 +98,17 @@ export class WorkspaceMigrationRunnerService {
*/
private async handleTableChanges(
queryRunner: QueryRunner,
schemaName: string,
tableMigration: WorkspaceMigrationTableAction,
) {
switch (tableMigration.action) {
case 'create':
await this.createTable(
queryRunner,
tableMigration.schemaName,
tableMigration.name,
);
await this.createTable(queryRunner, schemaName, tableMigration.name);
break;
case 'alter':
await this.handleColumnChanges(
queryRunner,
tableMigration.schemaName,
schemaName,
tableMigration.name,
tableMigration?.columns,
);
@ -293,7 +292,6 @@ export class WorkspaceMigrationRunnerService {
new TableForeignKey({
columnNames: [migrationColumn.columnName],
referencedColumnNames: [migrationColumn.referencedTableColumnName],
referencedSchema: migrationColumn.referencedSchema,
referencedTableName: migrationColumn.referencedTableName,
onDelete: 'CASCADE',
}),

View File

@ -92,8 +92,8 @@ export class CompanyObjectMetadata extends BaseObjectMetadata {
description:
'Ideal Customer Profile: Indicates whether the company is the most suitable and valuable customer for you',
icon: 'IconTarget',
defaultValue: { value: false },
})
@IsNullable()
idealCustomerProfile: boolean;
// Relations

View File

@ -393,7 +393,6 @@ export class WorkspaceSyncMetadataService {
{
name: object.targetTableName,
action: 'create',
schemaName: object.dataSource.schema,
} satisfies WorkspaceMigrationTableAction,
...Object.values(object.fields)
.filter((field) => field.type !== FieldMetadataType.RELATION)
@ -402,7 +401,6 @@ export class WorkspaceSyncMetadataService {
({
name: object.targetTableName,
action: 'alter',
schemaName: object.dataSource.schema,
columns: this.workspaceMigrationFactory.createColumnActions(
WorkspaceMigrationColumnActionType.CREATE,
field,
@ -434,8 +432,6 @@ export class WorkspaceSyncMetadataService {
{
name: objectsInDbById[field.objectMetadataId].targetTableName,
action: 'alter',
schemaName:
objectsInDbById[field.objectMetadataId].dataSource.schema,
columns: this.workspaceMigrationFactory.createColumnActions(
WorkspaceMigrationColumnActionType.CREATE,
field,
@ -457,8 +453,6 @@ export class WorkspaceSyncMetadataService {
{
name: objectsInDbById[field.objectMetadataId].targetTableName,
action: 'alter',
schemaName:
objectsInDbById[field.objectMetadataId].dataSource.schema,
columns: [
{
action: WorkspaceMigrationColumnActionType.DROP,
@ -524,13 +518,11 @@ export class WorkspaceSyncMetadataService {
{
name: toObjectMetadata.targetTableName,
action: 'alter',
schemaName: toObjectMetadata.dataSource.schema,
columns: [
{
action: WorkspaceMigrationColumnActionType.RELATION,
columnName: `${camelCase(toFieldMetadata.name)}Id`,
referencedTableName: fromObjectMetadata.targetTableName,
referencedSchema: fromObjectMetadata.dataSource.schema,
referencedTableColumnName: 'id',
isUnique:
relation.relationType === RelationMetadataType.ONE_TO_ONE,