diff --git a/front/src/modules/companies/table/components/CompanyTable.tsx b/front/src/modules/companies/table/components/CompanyTable.tsx index ccc70cc9c..7dccb26f7 100644 --- a/front/src/modules/companies/table/components/CompanyTable.tsx +++ b/front/src/modules/companies/table/components/CompanyTable.tsx @@ -10,7 +10,6 @@ import { TableContext } from '@/ui/data/data-table/contexts/TableContext'; import { useUpsertDataTableItem } from '@/ui/data/data-table/hooks/useUpsertDataTableItem'; import { TableOptionsDropdown } from '@/ui/data/data-table/options/components/TableOptionsDropdown'; import { ViewBar } from '@/views/components/ViewBar'; -import { ViewBarEffect } from '@/views/components/ViewBarEffect'; import { useViewFields } from '@/views/hooks/internal/useViewFields'; import { useView } from '@/views/hooks/useView'; import { ViewScope } from '@/views/scopes/ViewScope'; @@ -78,12 +77,7 @@ export const CompanyTable = () => { `; return ( - {}} - onViewSortsChange={() => {}} - onViewFiltersChange={() => {}} - > + { }, }} > - - } optionsDropdownScopeId="table-dropdown-option" /> - ; +// TODO: merge in a single effect component export const ObjectDataTableEffect = ({ objectNamePlural, }: ObjectDataTableEffectProps) => { @@ -32,32 +27,5 @@ export const ObjectDataTableEffect = ({ } }, [objects, setDataTableData, loading]); - const [searchParams] = useSearchParams(); - const tableRecoilScopeId = useRecoilScopeId(TableRecoilScopeContext); - const handleViewSelect = useRecoilCallback( - ({ set, snapshot }) => - (viewId: string) => { - const currentView = snapshot - .getLoadable( - currentViewIdScopedState({ scopeId: tableRecoilScopeId }), - ) - .getValue(); - - if (currentView === viewId) { - return; - } - - set(currentViewIdScopedState({ scopeId: tableRecoilScopeId }), viewId); - }, - [tableRecoilScopeId], - ); - - useEffect(() => { - const viewId = searchParams.get('view'); - if (viewId) { - handleViewSelect(viewId); - } - }, [handleViewSelect, searchParams, objectNamePlural]); - return <>; }; diff --git a/front/src/modules/metadata/components/ObjectTable.tsx b/front/src/modules/metadata/components/ObjectTable.tsx index 481b09754..01ff66d4e 100644 --- a/front/src/modules/metadata/components/ObjectTable.tsx +++ b/front/src/modules/metadata/components/ObjectTable.tsx @@ -1,10 +1,29 @@ +import styled from '@emotion/styled'; +import { useRecoilCallback } from 'recoil'; + import { DataTable } from '@/ui/data/data-table/components/DataTable'; import { TableContext } from '@/ui/data/data-table/contexts/TableContext'; +import { TableOptionsDropdown } from '@/ui/data/data-table/options/components/TableOptionsDropdown'; +import { tableColumnsScopedState } from '@/ui/data/data-table/states/tableColumnsScopedState'; +import { ColumnDefinition } from '@/ui/data/data-table/types/ColumnDefinition'; +import { FieldMetadata } from '@/ui/data/field/types/FieldMetadata'; +import { ViewBar } from '@/views/components/ViewBar'; +import { useViewFields } from '@/views/hooks/internal/useViewFields'; +import { useView } from '@/views/hooks/useView'; +import { ViewScope } from '@/views/scopes/ViewScope'; import { useUpdateOneObject } from '../hooks/useUpdateOneObject'; import { MetadataObjectIdentifier } from '../types/MetadataObjectIdentifier'; import { ObjectDataTableEffect } from './ObjectDataTableEffect'; +import { ObjectTableEffect } from './ObjectTableEffect'; + +const StyledContainer = styled.div` + display: flex; + flex-direction: column; + height: 100%; + overflow: auto; +`; export type ObjectTableProps = Pick< MetadataObjectIdentifier, @@ -16,6 +35,11 @@ export const ObjectTable = ({ objectNamePlural }: ObjectTableProps) => { objectNamePlural, }); + const viewScopeId = objectNamePlural ?? ''; + + const { persistViewFields } = useViewFields(viewScopeId); + const { setCurrentViewFields } = useView({ viewScopeId: viewScopeId }); + const updateEntity = ({ variables, }: { @@ -32,17 +56,38 @@ export const ObjectTable = ({ objectNamePlural }: ObjectTableProps) => { }); }; + const updateTableColumns = useRecoilCallback( + ({ set, snapshot }) => + (viewFields: ColumnDefinition[]) => { + set(tableColumnsScopedState(viewScopeId), viewFields); + }, + ); + return ( - { - // - }, + { + // updateTableColumns(viewFields); }} > - - - - + + { + // setCurrentViewFields?.(columns); + // persistViewFields(columns); + }, + }} + > + } + optionsDropdownScopeId="table-dropdown-option" + /> + + + + + + ); }; diff --git a/front/src/modules/metadata/components/ObjectTableEffect.tsx b/front/src/modules/metadata/components/ObjectTableEffect.tsx new file mode 100644 index 000000000..2936025cb --- /dev/null +++ b/front/src/modules/metadata/components/ObjectTableEffect.tsx @@ -0,0 +1,86 @@ +import { useEffect } from 'react'; +import { useSetRecoilState } from 'recoil'; + +import { availableTableColumnsScopedState } from '@/ui/data/data-table/states/availableTableColumnsScopedState'; +import { useView } from '@/views/hooks/useView'; +import { ViewType } from '~/generated/graphql'; + +import { useMetadataObjectInContext } from '../hooks/useMetadataObjectInContext'; + +export const ObjectTableEffect = () => { + console.log('ObjectTableEffect'); + + const { + setAvailableSorts, + setAvailableFilters, + setAvailableFields, + setViewType, + setViewObjectId, + } = useView(); + + // const [, setTableColumns] = useRecoilScopedState( + // tableColumnsScopedState, + // TableRecoilScopeContext, + // ); + + // const [, setTableSorts] = useRecoilScopedState( + // tableSortsScopedState, + // TableRecoilScopeContext, + // ); + + // const [, setTableFilters] = useRecoilScopedState( + // tableFiltersScopedState, + // TableRecoilScopeContext, + // ); + + const { columnDefinitions, objectNamePlural } = useMetadataObjectInContext(); + + const setAvailableTableColumns = useSetRecoilState( + availableTableColumnsScopedState(objectNamePlural ?? ''), + ); + + useEffect(() => { + setAvailableSorts?.([]); // TODO: extract from metadata fields + setAvailableFilters?.([]); // TODO: extract from metadata fields + setAvailableFields?.(columnDefinitions); + setViewObjectId?.(objectNamePlural); + setViewType?.(ViewType.Table); + + setAvailableTableColumns(columnDefinitions); + }, [ + setAvailableFields, + setAvailableFilters, + setAvailableSorts, + setAvailableTableColumns, + setViewObjectId, + setViewType, + columnDefinitions, + objectNamePlural, + ]); + + // useEffect(() => { + // if (currentViewFields) { + // setTableColumns([...currentViewFields].sort((a, b) => a.index - b.index)); + // } + // }, [currentViewFields, setTableColumns]); + + // useEffect(() => { + // if (currentViewSorts) { + // setTableSorts(currentViewSorts); + // } + // }, [currentViewFields, currentViewSorts, setTableColumns, setTableSorts]); + + // useEffect(() => { + // if (currentViewFilters) { + // setTableFilters(currentViewFilters); + // } + // }, [ + // currentViewFields, + // currentViewFilters, + // setTableColumns, + // setTableFilters, + // setTableSorts, + // ]); + + return <>; +}; diff --git a/front/src/modules/metadata/hooks/useFindManyMetadataObjects.ts b/front/src/modules/metadata/hooks/useFindManyMetadataObjects.ts index c2e16467f..c2c9c5ee5 100644 --- a/front/src/modules/metadata/hooks/useFindManyMetadataObjects.ts +++ b/front/src/modules/metadata/hooks/useFindManyMetadataObjects.ts @@ -38,6 +38,10 @@ export const useFindManyMetadataObjects = () => { }, ); }, + onCompleted: (data) => { + // eslint-disable-next-line no-console + //console.log('useFindManyMetadataObjects data : ', data); + }, }, ); diff --git a/front/src/modules/metadata/hooks/useFindManyObjects.ts b/front/src/modules/metadata/hooks/useFindManyObjects.ts index 4780c1ef5..e777cb643 100644 --- a/front/src/modules/metadata/hooks/useFindManyObjects.ts +++ b/front/src/modules/metadata/hooks/useFindManyObjects.ts @@ -15,7 +15,14 @@ export const useFindManyObjects = < ObjectType extends { id: string } & Record, >({ objectNamePlural, -}: Pick) => { + filter, + orderBy, + onCompleted, +}: Pick & { + filter?: any; + orderBy?: any; + onCompleted?: (data: any) => void; +}) => { const { foundMetadataObject, objectNotFoundInMetadata, findManyQuery } = useFindOneMetadataObject({ objectNamePlural, @@ -27,6 +34,12 @@ export const useFindManyObjects = < findManyQuery, { skip: !foundMetadataObject, + variables: { + filter: filter ?? {}, + orderBy: orderBy ?? {}, + }, + onCompleted: (data) => + objectNamePlural && onCompleted?.(data[objectNamePlural]), onError: (error) => { // eslint-disable-next-line no-console console.error( diff --git a/front/src/modules/metadata/types/PaginatedObjectType.ts b/front/src/modules/metadata/types/PaginatedObjectType.ts index 19b28fc12..6fcf1c85a 100644 --- a/front/src/modules/metadata/types/PaginatedObjectType.ts +++ b/front/src/modules/metadata/types/PaginatedObjectType.ts @@ -1,8 +1,5 @@ +import { PaginatedObjectTypeResults } from './PaginatedObjectTypeResults'; + export type PaginatedObjectType = { - [objectNamePlural: string]: { - edges: { - node: ObjectType; - cursor: string; - }[]; - }; + [objectNamePlural: string]: PaginatedObjectTypeResults; }; diff --git a/front/src/modules/metadata/types/PaginatedObjectTypeResults.ts b/front/src/modules/metadata/types/PaginatedObjectTypeResults.ts new file mode 100644 index 000000000..d4427780a --- /dev/null +++ b/front/src/modules/metadata/types/PaginatedObjectTypeResults.ts @@ -0,0 +1,6 @@ +export type PaginatedObjectTypeResults = { + edges: { + node: ObjectType; + cursor: string; + }[]; +}; diff --git a/front/src/modules/metadata/utils/generateFindManyCustomObjectsQuery.ts b/front/src/modules/metadata/utils/generateFindManyCustomObjectsQuery.ts index 506ce7c31..4ed4e0650 100644 --- a/front/src/modules/metadata/utils/generateFindManyCustomObjectsQuery.ts +++ b/front/src/modules/metadata/utils/generateFindManyCustomObjectsQuery.ts @@ -1,5 +1,7 @@ import { gql } from '@apollo/client'; +import { capitalize } from '~/utils/string/capitalize'; + import { MetadataObject } from '../types/MetadataObject'; import { mapFieldMetadataToGraphQLQuery } from './mapFieldMetadataToGraphQLQuery'; @@ -12,8 +14,10 @@ export const generateFindManyCustomObjectsQuery = ({ _fromCursor?: string; }) => { return gql` - query FindMany${metadataObject.namePlural} { - ${metadataObject.namePlural}{ + query FindMany${metadataObject.namePlural}($filter: ${capitalize( + metadataObject.nameSingular, + )}FilterInput, $orderBy: ${capitalize(metadataObject.nameSingular)}OrderBy) { + ${metadataObject.namePlural}(filter: $filter, orderBy: $orderBy){ edges { node { id diff --git a/front/src/modules/views/components/ViewBar.tsx b/front/src/modules/views/components/ViewBar.tsx index c1408a9fc..c4b3af26d 100644 --- a/front/src/modules/views/components/ViewBar.tsx +++ b/front/src/modules/views/components/ViewBar.tsx @@ -14,6 +14,7 @@ import { ViewsHotkeyScope } from '../types/ViewsHotkeyScope'; import { UpdateViewButtonGroup } from './UpdateViewButtonGroup'; import { ViewBarDetails } from './ViewBarDetails'; +import { ViewBarEffect } from './ViewBarEffect'; import { ViewsDropdownButton } from './ViewsDropdownButton'; export type ViewBarProps = { @@ -43,6 +44,7 @@ export const ViewBar = ({ availableSorts={availableSorts} onSortAdd={upsertViewSort} > + { const { @@ -45,80 +46,78 @@ export const ViewBarEffect = () => { const { viewType, viewObjectId } = useViewInternalStates(viewScopeId); - useGetViewFieldsQuery({ - skip: !currentViewId, - variables: { - orderBy: { index: SortOrder.Asc }, - where: { - viewId: { equals: currentViewId }, - }, - }, - onCompleted: useRecoilCallback(({ snapshot }) => async (data) => { - const availableFields = snapshot - .getLoadable(availableFieldsScopedState({ scopeId: viewScopeId })) - .getValue(); + useFindManyObjects({ + objectNamePlural: 'viewFieldsV2', + filter: { viewId: { eq: currentViewId } }, + onCompleted: useRecoilCallback( + ({ snapshot }) => + async (data: PaginatedObjectTypeResults) => { + const availableFields = snapshot + .getLoadable(availableFieldsScopedState({ scopeId: viewScopeId })) + .getValue(); - if (!availableFields || !currentViewId) { - return; - } + if (!availableFields || !currentViewId) { + return; + } - const savedViewFields = snapshot - .getLoadable( - savedViewFieldsScopedFamilyState({ - scopeId: viewScopeId, - familyKey: currentViewId, - }), - ) - .getValue(); + const savedViewFields = snapshot + .getLoadable( + savedViewFieldsScopedFamilyState({ + scopeId: viewScopeId, + familyKey: currentViewId, + }), + ) + .getValue(); - const queriedViewFields = data.viewFields - .map | null>((viewField) => { - const columnDefinition = availableFields.find( - ({ key }) => viewField.key === key, - ); + const queriedViewFields = data.edges + .map | null>((viewField) => { + const columnDefinition = availableFields.find( + ({ key }) => viewField.node.fieldId === key, + ); - return columnDefinition - ? { - ...columnDefinition, - key: viewField.key, - name: viewField.name, - index: viewField.index, - size: viewField.size ?? columnDefinition.size, - isVisible: viewField.isVisible, - } - : null; - }) - .filter>(assertNotNull); + return columnDefinition + ? { + ...columnDefinition, + key: viewField.node.fieldId, + name: viewField.node.fieldId, + index: viewField.node.position, + size: viewField.node.size ?? columnDefinition.size, + isVisible: viewField.node.isVisible, + } + : null; + }) + .filter>(assertNotNull); - if (!isDeeplyEqual(savedViewFields, queriedViewFields)) { - setCurrentViewFields?.(queriedViewFields); - setSavedViewFields?.(queriedViewFields); - } - }), + if (!isDeeplyEqual(savedViewFields, queriedViewFields)) { + setCurrentViewFields?.(queriedViewFields); + setSavedViewFields?.(queriedViewFields); + } + }, + ), }); - useGetViewsQuery({ - variables: { - where: { - objectId: { equals: viewObjectId }, - type: { equals: viewType }, - }, - }, - onCompleted: useRecoilCallback(({ snapshot }) => async (data) => { - const nextViews = data.views.map((view) => ({ - id: view.id, - name: view.name, - })); - const views = snapshot - .getLoadable(viewsScopedState({ scopeId: viewScopeId })) - .getValue(); + useFindManyObjects({ + objectNamePlural: 'viewsV2', + filter: { type: { eq: viewType }, objectId: { eq: viewObjectId } }, + onCompleted: useRecoilCallback( + ({ snapshot }) => + async (data: PaginatedObjectTypeResults) => { + const nextViews = data.edges.map((view) => ({ + id: view.node.id, + name: view.node.name, + objectId: view.node.objectId, + })); + const views = snapshot + .getLoadable(viewsScopedState({ scopeId: viewScopeId })) + .getValue(); - if (!isDeeplyEqual(views, nextViews)) setViews(nextViews); + if (!isDeeplyEqual(views, nextViews)) setViews(nextViews); - if (!nextViews.length) return; + if (!nextViews.length) return; - if (!currentViewId) return changeView(nextViews[0].id); - }), + if (!currentViewId) return setCurrentViewId(nextViews[0].id); + }, + ), }); useGetViewSortsQuery({ diff --git a/front/src/modules/views/hooks/internal/useViews.ts b/front/src/modules/views/hooks/internal/useViews.ts index 7f0380f27..68517ea21 100644 --- a/front/src/modules/views/hooks/internal/useViews.ts +++ b/front/src/modules/views/hooks/internal/useViews.ts @@ -17,29 +17,32 @@ export const useViews = (scopeId: string) => { const [updateViewMutation] = useUpdateViewMutation(); const [deleteViewMutation] = useDeleteViewMutation(); - const createView = useRecoilCallback(({ snapshot }) => async (view: View) => { - const viewObjectId = await snapshot - .getLoadable(viewObjectIdScopeState({ scopeId })) - .getValue(); + const createView = useRecoilCallback( + ({ snapshot }) => + async (view: Pick) => { + const viewObjectId = await snapshot + .getLoadable(viewObjectIdScopeState({ scopeId })) + .getValue(); - const viewType = await snapshot - .getLoadable(viewTypeScopedState({ scopeId })) - .getValue(); + const viewType = await snapshot + .getLoadable(viewTypeScopedState({ scopeId })) + .getValue(); - if (!viewObjectId || !viewType) { - return; - } - await createViewMutation({ - variables: { - data: { - ...view, - objectId: viewObjectId, - type: viewType, - }, + if (!viewObjectId || !viewType) { + return; + } + await createViewMutation({ + variables: { + data: { + ...view, + objectId: viewObjectId, + type: viewType, + }, + }, + refetchQueries: [getOperationName(GET_VIEWS) ?? ''], + }); }, - refetchQueries: [getOperationName(GET_VIEWS) ?? ''], - }); - }); + ); const updateView = async (view: View) => { await updateViewMutation({ diff --git a/front/src/modules/views/types/View.ts b/front/src/modules/views/types/View.ts index 5990433a2..484b49db7 100644 --- a/front/src/modules/views/types/View.ts +++ b/front/src/modules/views/types/View.ts @@ -1 +1 @@ -export type View = { id: string; name: string }; +export type View = { id: string; name: string; objectId: string }; diff --git a/front/src/modules/views/types/ViewField.ts b/front/src/modules/views/types/ViewField.ts new file mode 100644 index 000000000..78382dcbc --- /dev/null +++ b/front/src/modules/views/types/ViewField.ts @@ -0,0 +1,9 @@ +export type ViewField = { + id: string; + objectId: string; + fieldId: string; + viewId: string; + position: number; + isVisible: boolean; + size: number; +}; diff --git a/server/package.json b/server/package.json index f21b6bd88..fbb35581b 100644 --- a/server/package.json +++ b/server/package.json @@ -24,17 +24,16 @@ "prisma:generate-gql-select": "node scripts/generate-model-select-map.js", "prisma:generate-nest-graphql": "npx prisma generate --generator nestgraphql", "prisma:generate": "yarn prisma:generate-client && yarn prisma:generate-gql-select && yarn prisma:generate-nest-graphql", - "prisma:reset": "npx prisma migrate reset && yarn prisma:generate", "prisma:seed": "npx prisma db seed", "prisma:migrate": "npx prisma migrate deploy", "typeorm": "ts-node -r tsconfig-paths/register ./node_modules/typeorm/cli.js", "typeorm:migrate": "yarn typeorm migration:run -d ./src/metadata/metadata.datasource.ts", "database:init": "yarn database:setup && yarn database:seed", - "database:setup": "npx ts-node ./scripts/setup-db.ts && yarn database:migrate", + "database:setup": "npx ts-node ./scripts/setup-db.ts && yarn database:migrate && yarn database:generate", "database:truncate": "npx ts-node ./scripts/truncate-db.ts", "database:migrate": "yarn typeorm:migrate && yarn prisma:migrate", "database:generate": "yarn prisma:generate", - "database:seed": "yarn prisma:seed", + "database:seed": "yarn prisma:seed && yarn command tenant:sync-metadata -w twenty-7ed9d212-1c25-4d02-bf25-6aeccf7ea419 && yarn command tenant:migrate -w twenty-7ed9d212-1c25-4d02-bf25-6aeccf7ea419 && yarn command tenant:data-seed -w twenty-7ed9d212-1c25-4d02-bf25-6aeccf7ea419", "database:reset": "yarn database:truncate && yarn database:init", "command": "node dist/src/command" }, diff --git a/server/src/database/seeds/metadata.ts b/server/src/database/seeds/metadata.ts index 8964d8e0a..575ace3b0 100644 --- a/server/src/database/seeds/metadata.ts +++ b/server/src/database/seeds/metadata.ts @@ -12,51 +12,4 @@ export const seedMetadata = async (prisma: PrismaClient) => { 'b37b2163-7f63-47a9-b1b3-6c7290ca9fb1', 'workspace_twenty_7icsva0r6s00mpcp6cwg4w4rd', 'postgres', 'twenty-7ed9d212-1c25-4d02-bf25-6aeccf7ea419' ) ON CONFLICT DO NOTHING`, ); - - await prisma.$queryRawUnsafe(`CREATE TABLE IF NOT EXISTS - workspace_twenty_7icsva0r6s00mpcp6cwg4w4rd.company( - "id" TEXT PRIMARY KEY, - "name" TEXT NOT NULL, - "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), - "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), - "deletedAt" TIMESTAMP WITH TIME ZONE, - "domainName" TEXT NOT NULL, - "address" TEXT NOT NULL, - "employees" INTEGER NOT NULL - ); - `); - await prisma.$queryRawUnsafe(`INSERT INTO workspace_twenty_7icsva0r6s00mpcp6cwg4w4rd.company( - "id", "name", "domainName", "address", "employees" - ) - VALUES ( - '89bb825c-171e-4bcc-9cf7-43448d6fb278', 'Airbnb', 'airbnb.com', 'San Francisco', 5000 - ), ( - '04b2e9f5-0713-40a5-8216-82802401d33e', 'Qonto', 'qonto.com', 'San Francisco', 800 - ), ( - '118995f3-5d81-46d6-bf83-f7fd33ea6102', 'Facebook', 'facebook.com', 'San Francisco', 8000 - ), ( - '460b6fb1-ed89-413a-b31a-962986e67bb4', 'Microsoft', 'microsoft.com', 'San Francisco', 800 - ), ( - 'fe256b39-3ec3-4fe3-8997-b76aa0bfa408', 'Linkedin', 'linkedin.com', 'San Francisco', 400 - ) ON CONFLICT DO NOTHING`); - - await prisma.$queryRawUnsafe(`INSERT INTO metadata.object_metadata( - id, name_singular, name_plural, label_singular, label_plural, description, icon, target_table_name, is_custom, is_active, workspace_id, data_source_id - ) - VALUES ( - 'ba391617-ee08-432f-9438-2e17df5ac279', 'companyV2', 'companiesV2', 'Company', 'Companies', 'Companies', 'IconBuilding', 'company', false, true, 'twenty-7ed9d212-1c25-4d02-bf25-6aeccf7ea419', 'b37b2163-7f63-47a9-b1b3-6c7290ca9fb1' - ) ON CONFLICT DO NOTHING`); - - await prisma.$queryRawUnsafe(`INSERT INTO metadata.field_metadata( - id, object_id, type, name, label, target_column_map, description, icon, enums, is_custom, is_active, is_nullable, workspace_id - ) - VALUES ( - '22f5906d-153f-448c-b254-28adce721dcd', 'ba391617-ee08-432f-9438-2e17df5ac279', 'text', 'name', 'Name', '{"value": "name"}', 'Name', 'IconUser', NULL, false, true, false, 'twenty-7ed9d212-1c25-4d02-bf25-6aeccf7ea419' - ), ( - '19bfab29-1cbb-4ce2-9117-8540ac45a0f1', 'ba391617-ee08-432f-9438-2e17df5ac279', 'text', 'domainName', 'Domain Name', '{"value": "domainName"}', 'Domain Name', 'IconExternalLink', NULL, false, true, true, 'twenty-7ed9d212-1c25-4d02-bf25-6aeccf7ea419' - ), ( - '70130f27-9497-4b44-a04c-1a0fb9a4829c', 'ba391617-ee08-432f-9438-2e17df5ac279', 'text', 'address', 'Address', '{"value": "address"}', 'Address', 'IconMap', NULL, false, true, true, 'twenty-7ed9d212-1c25-4d02-bf25-6aeccf7ea419' - ), ( - '2a63c30e-8e80-475b-b5d7-9dda17adc537', 'ba391617-ee08-432f-9438-2e17df5ac279', 'number', 'employees', 'Employees', '{"value": "employees"}', 'Employees', 'IconUsers', NULL, false, true, true, 'twenty-7ed9d212-1c25-4d02-bf25-6aeccf7ea419' - ) ON CONFLICT DO NOTHING`); }; diff --git a/server/src/metadata/commands/data-seed-tenant.command.ts b/server/src/metadata/commands/data-seed-tenant.command.ts new file mode 100644 index 000000000..75e99a1e2 --- /dev/null +++ b/server/src/metadata/commands/data-seed-tenant.command.ts @@ -0,0 +1,47 @@ +import { Command, CommandRunner, Option } from 'nest-commander'; + +import { TenantInitialisationService } from '../tenant-initialisation/tenant-initialisation.service'; +import { DataSourceMetadataService } from '../data-source-metadata/data-source-metadata.service'; + +// TODO: implement dry-run +interface DataSeedTenantOptions { + workspaceId: string; +} + +@Command({ + name: 'tenant:data-seed', + description: 'Seed tenant with initial data', +}) +export class DataSeedTenantCommand extends CommandRunner { + constructor( + private readonly dataSourceMetadataService: DataSourceMetadataService, + private readonly tenantInitialisationService: TenantInitialisationService, + ) { + super(); + } + + async run( + _passedParam: string[], + options: DataSeedTenantOptions, + ): Promise { + const dataSourceMetadata = + await this.dataSourceMetadataService.getLastDataSourceMetadataFromWorkspaceIdOrFail( + options.workspaceId, + ); + // TODO: run in a dedicated job + run queries in a transaction. + await this.tenantInitialisationService.prefillWorkspaceWithStandardObjects( + dataSourceMetadata, + options.workspaceId, + ); + } + + // TODO: workspaceId should be optional and we should run migrations for all workspaces + @Option({ + flags: '-w, --workspace-id [workspace_id]', + description: 'workspace id', + required: true, + }) + parseWorkspaceId(value: string): string { + return value; + } +} diff --git a/server/src/metadata/commands/metadata-command.module.ts b/server/src/metadata/commands/metadata-command.module.ts index 423b236b0..0ae736111 100644 --- a/server/src/metadata/commands/metadata-command.module.ts +++ b/server/src/metadata/commands/metadata-command.module.ts @@ -9,6 +9,7 @@ import { DataSourceMetadataModule } from 'src/metadata/data-source-metadata/data import { SyncTenantMetadataCommand } from './sync-tenant-metadata.command'; import { RunTenantMigrationsCommand } from './run-tenant-migrations.command'; +import { DataSeedTenantCommand } from './data-seed-tenant.command'; @Module({ imports: [ @@ -19,6 +20,10 @@ import { RunTenantMigrationsCommand } from './run-tenant-migrations.command'; DataSourceMetadataModule, TenantInitialisationModule, ], - providers: [RunTenantMigrationsCommand, SyncTenantMetadataCommand], + providers: [ + RunTenantMigrationsCommand, + SyncTenantMetadataCommand, + DataSeedTenantCommand, + ], }) export class MetadataCommandModule {} diff --git a/server/src/metadata/commands/sync-tenant-metadata.command.ts b/server/src/metadata/commands/sync-tenant-metadata.command.ts index 6d3470dbe..28b6a86c4 100644 --- a/server/src/metadata/commands/sync-tenant-metadata.command.ts +++ b/server/src/metadata/commands/sync-tenant-metadata.command.ts @@ -1,7 +1,6 @@ import { Command, CommandRunner, Option } from 'nest-commander'; import { ObjectMetadataService } from 'src/metadata/object-metadata/services/object-metadata.service'; -import { FieldMetadataService } from 'src/metadata/field-metadata/services/field-metadata.service'; import { TenantInitialisationService } from 'src/metadata/tenant-initialisation/tenant-initialisation.service'; import { DataSourceMetadataService } from 'src/metadata/data-source-metadata/data-source-metadata.service'; @@ -17,7 +16,6 @@ interface RunTenantMigrationsOptions { export class SyncTenantMetadataCommand extends CommandRunner { constructor( private readonly objectMetadataService: ObjectMetadataService, - private readonly fieldMetadataService: FieldMetadataService, private readonly dataSourceMetadataService: DataSourceMetadataService, private readonly tenantInitialisationService: TenantInitialisationService, ) { diff --git a/server/src/metadata/tenant-initialisation/standard-objects/companies/companies.metadata.json b/server/src/metadata/tenant-initialisation/standard-objects/companies/companies.metadata.json index 3838af5e3..2678b6d7d 100644 --- a/server/src/metadata/tenant-initialisation/standard-objects/companies/companies.metadata.json +++ b/server/src/metadata/tenant-initialisation/standard-objects/companies/companies.metadata.json @@ -1,4 +1,5 @@ { + "id": "1a8487a0-480c-434e-b4c7-e22408b97047", "nameSingular": "companyV2", "namePlural": "companiesV2", "labelSingular": "Company", diff --git a/server/src/metadata/tenant-initialisation/standard-objects/standard-object-metadata.ts b/server/src/metadata/tenant-initialisation/standard-objects/standard-object-metadata.ts index 2c27b0560..06d6ec446 100644 --- a/server/src/metadata/tenant-initialisation/standard-objects/standard-object-metadata.ts +++ b/server/src/metadata/tenant-initialisation/standard-objects/standard-object-metadata.ts @@ -1,7 +1,11 @@ import companyObject from './companies/companies.metadata.json'; import personObject from './people/people.metadata.json'; +import viewObject from './views/views.metadata.json'; +import viewFieldObject from './view-fields/view-fields.metadata.json'; export const standardObjectsMetadata = { companyV2: companyObject, personV2: personObject, + viewV2: viewObject, + viewFieldV2: viewFieldObject, }; diff --git a/server/src/metadata/tenant-initialisation/standard-objects/standard-object-seeds.ts b/server/src/metadata/tenant-initialisation/standard-objects/standard-object-seeds.ts index 6b8d6ab0b..92db850d3 100644 --- a/server/src/metadata/tenant-initialisation/standard-objects/standard-object-seeds.ts +++ b/server/src/metadata/tenant-initialisation/standard-objects/standard-object-seeds.ts @@ -1,5 +1,9 @@ import companySeeds from './companies/companies.seeds.json'; +import viewSeeds from './views/views.seeds.json'; +import viewFieldSeeds from './view-fields/view-fields.seeds.json'; export const standardObjectsSeeds = { companyV2: companySeeds, + viewV2: viewSeeds, + viewFieldV2: viewFieldSeeds, }; diff --git a/server/src/metadata/tenant-initialisation/standard-objects/view-fields/view-fields.metadata.json b/server/src/metadata/tenant-initialisation/standard-objects/view-fields/view-fields.metadata.json new file mode 100644 index 000000000..afe052a62 --- /dev/null +++ b/server/src/metadata/tenant-initialisation/standard-objects/view-fields/view-fields.metadata.json @@ -0,0 +1,77 @@ +{ + "nameSingular": "viewFieldV2", + "namePlural": "viewFieldsV2", + "labelSingular": "View Field", + "labelPlural": "View Fields", + "targetTableName": "viewField", + "description": "(System) View Fields", + "icon": "arrows-sort", + "fields": [ + { + "type": "text", + "name": "objectId", + "label": "Object Id", + "targetColumnMap": { + "value": "objectId" + }, + "description": "View Field target object", + "icon": null, + "isNullable": false + }, + { + "type": "text", + "name": "fieldId", + "label": "Field Id", + "targetColumnMap": { + "value": "fieldId" + }, + "description": "View Field target field", + "icon": null, + "isNullable": false + }, + { + "type": "text", + "name": "viewId", + "label": "View Id", + "targetColumnMap": { + "value": "viewId" + }, + "description": "View Field related view", + "icon": null, + "isNullable": false + }, + { + "type": "boolean", + "name": "isVisible", + "label": "Visible", + "targetColumnMap": { + "value": "isVisible" + }, + "description": "View Field visibility", + "icon": null, + "isNullable": false + }, + { + "type": "number", + "name": "size", + "label": "Size", + "targetColumnMap": { + "value": "size" + }, + "description": "View Field size", + "icon": null, + "isNullable": false + }, + { + "type": "number", + "name": "position", + "label": "Position", + "targetColumnMap": { + "value": "position" + }, + "description": "View Field position", + "icon": null, + "isNullable": false + } + ] +} \ No newline at end of file diff --git a/server/src/metadata/tenant-initialisation/standard-objects/view-fields/view-fields.seeds.json b/server/src/metadata/tenant-initialisation/standard-objects/view-fields/view-fields.seeds.json new file mode 100644 index 000000000..099b38b58 --- /dev/null +++ b/server/src/metadata/tenant-initialisation/standard-objects/view-fields/view-fields.seeds.json @@ -0,0 +1,43 @@ +[ + { + "objectId": "1a8487a0-480c-434e-b4c7-e22408b97047", + "fieldId": "name", + "viewId": "10bec73c-0aea-4cc4-a3b2-8c2186f29b43", + "position": 0, + "isVisible": true, + "size": 180 + }, + + { + "objectId": "company", + "fieldId": "name", + "viewId": "37a8a866-eb17-4e76-9382-03143a2f6a80", + "position": 0, + "isVisible": true, + "size": 180 + }, + { + "objectId": "company", + "fieldId": "domainName", + "viewId": "37a8a866-eb17-4e76-9382-03143a2f6a80", + "position": 1, + "isVisible": true, + "size": 100 + }, + { + "objectId": "company", + "fieldId": "accountOwner", + "viewId": "37a8a866-eb17-4e76-9382-03143a2f6a80", + "position": 2, + "isVisible": true, + "size": 150 + }, + { + "objectId": "company", + "fieldId": "createdAt", + "viewId": "37a8a866-eb17-4e76-9382-03143a2f6a80", + "position": 3, + "isVisible": true, + "size": 150 + } +] diff --git a/server/src/metadata/tenant-initialisation/standard-objects/views/views.metadata.json b/server/src/metadata/tenant-initialisation/standard-objects/views/views.metadata.json new file mode 100644 index 000000000..57c9773be --- /dev/null +++ b/server/src/metadata/tenant-initialisation/standard-objects/views/views.metadata.json @@ -0,0 +1,44 @@ +{ + "nameSingular": "viewV2", + "namePlural": "viewsV2", + "labelSingular": "View", + "labelPlural": "Views", + "targetTableName": "view", + "description": "(System) Views", + "icon": "layout-collage", + "fields": [ + { + "type": "text", + "name": "name", + "label": "Name", + "targetColumnMap": { + "value": "name" + }, + "description": "View name", + "icon": null, + "isNullable": false + }, + { + "type": "text", + "name": "objectId", + "label": "Object Id", + "targetColumnMap": { + "value": "objectId" + }, + "description": "View target object", + "icon": null, + "isNullable": false + }, + { + "type": "text", + "name": "type", + "label": "Type", + "targetColumnMap": { + "value": "type" + }, + "description": "View type", + "icon": null, + "isNullable": false + } + ] +} \ No newline at end of file diff --git a/server/src/metadata/tenant-initialisation/standard-objects/views/views.seeds.json b/server/src/metadata/tenant-initialisation/standard-objects/views/views.seeds.json new file mode 100644 index 000000000..4cfb91fbf --- /dev/null +++ b/server/src/metadata/tenant-initialisation/standard-objects/views/views.seeds.json @@ -0,0 +1,26 @@ +[ + { + "id": "37a8a866-eb17-4e76-9382-03143a2f6a80", + "name": "All companies", + "objectId": "company", + "type": "Table" + }, + { + "id": "6095799e-b48f-4e00-b071-10818083593a", + "name": "All companies", + "objectId": "person", + "type": "Table" + }, + { + "id": "e26f66b7-f890-4a5c-b4d2-ec09987b5308", + "name": "All Opportunities", + "objectId": "company", + "type": "Pipeline" + }, + { + "id": "10bec73c-0aea-4cc4-a3b2-8c2186f29b43", + "name": "All Companies (V2)", + "objectId": "1a8487a0-480c-434e-b4c7-e22408b97047", + "type": "Table" + } +] diff --git a/server/src/metadata/tenant-initialisation/tenant-initialisation.service.ts b/server/src/metadata/tenant-initialisation/tenant-initialisation.service.ts index 53393b427..e8f47543a 100644 --- a/server/src/metadata/tenant-initialisation/tenant-initialisation.service.ts +++ b/server/src/metadata/tenant-initialisation/tenant-initialisation.service.ts @@ -98,7 +98,7 @@ export class TenantInitialisationService { ); } - private async prefillWorkspaceWithStandardObjects( + public async prefillWorkspaceWithStandardObjects( dataSourceMetadata: DataSourceMetadata, workspaceId: string, ) { @@ -117,11 +117,7 @@ export class TenantInitialisationService { continue; } - const fields = standardObjectsMetadata[object.nameSingular].fields; - - const columns = fields.map((field: FieldMetadata) => - Object.values(field.targetColumnMap), - ); + const columns = Object.keys(seedData[0]); await workspaceDataSource ?.createQueryBuilder() diff --git a/server/src/metadata/tenant-migration/migrations/1697618011-addViewTable.ts b/server/src/metadata/tenant-migration/migrations/1697618011-addViewTable.ts new file mode 100644 index 000000000..aa27a9a87 --- /dev/null +++ b/server/src/metadata/tenant-migration/migrations/1697618011-addViewTable.ts @@ -0,0 +1,29 @@ +import { TenantMigrationTableAction } from 'src/metadata/tenant-migration/tenant-migration.entity'; + +export const addViewTable: TenantMigrationTableAction[] = [ + { + name: 'view', + action: 'create', + }, + { + name: 'view', + action: 'alter', + columns: [ + { + name: 'name', + type: 'varchar', + action: 'create', + }, + { + name: 'objectId', + type: 'varchar', + action: 'create', + }, + { + name: 'type', + type: 'varchar', + action: 'create', + }, + ], + }, +]; diff --git a/server/src/metadata/tenant-migration/migrations/1697618012-addViewFieldTable.ts b/server/src/metadata/tenant-migration/migrations/1697618012-addViewFieldTable.ts new file mode 100644 index 000000000..1dc792942 --- /dev/null +++ b/server/src/metadata/tenant-migration/migrations/1697618012-addViewFieldTable.ts @@ -0,0 +1,44 @@ +import { TenantMigrationTableAction } from 'src/metadata/tenant-migration/tenant-migration.entity'; + +export const addViewFieldTable: TenantMigrationTableAction[] = [ + { + name: 'viewField', + action: 'create', + }, + { + name: 'viewField', + action: 'alter', + columns: [ + { + name: 'objectId', + type: 'varchar', + action: 'create', + }, + { + name: 'fieldId', + type: 'varchar', + action: 'create', + }, + { + name: 'viewId', + type: 'varchar', + action: 'create', + }, + { + name: 'position', + type: 'integer', + action: 'create', + }, + { + name: 'isVisible', + type: 'boolean', + action: 'create', + }, + { + name: 'size', + type: 'integer', + action: 'create', + }, + ], + }, +]; diff --git a/server/src/metadata/tenant-migration/standard-migrations.ts b/server/src/metadata/tenant-migration/standard-migrations.ts index c89a58924..a96c7aca8 100644 --- a/server/src/metadata/tenant-migration/standard-migrations.ts +++ b/server/src/metadata/tenant-migration/standard-migrations.ts @@ -1,8 +1,12 @@ import { addCompanyTable } from './migrations/1697618009-addCompanyTable'; import { addPeopleTable } from './migrations/1697618010-addPeopleTable'; +import { addViewTable } from './migrations/1697618011-addViewTable'; +import { addViewFieldTable } from './migrations/1697618012-addViewFieldTable'; // TODO: read the folder and return all migrations export const standardMigrations = { '1697618009-addCompanyTable': addCompanyTable, '1697618010-addPeopleTable': addPeopleTable, + '1697618011-addViewTable': addViewTable, + '1697618012-addViewFieldTable': addViewFieldTable, };