Improve calendar schema (#7156)

Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
Denis Bykhov 2024-11-12 22:40:15 +05:00 committed by GitHub
parent e0ca033405
commit 76bcd3c52c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 151 additions and 27 deletions

View File

@ -68,6 +68,7 @@ export { calendarId } from '@hcengineering/calendar'
export { calendarOperation } from './migration' export { calendarOperation } from './migration'
export const DOMAIN_CALENDAR = 'calendar' as Domain export const DOMAIN_CALENDAR = 'calendar' as Domain
export const DOMAIN_EVENT = 'event' as Domain
@Model(calendar.class.Calendar, core.class.Doc, DOMAIN_CALENDAR) @Model(calendar.class.Calendar, core.class.Doc, DOMAIN_CALENDAR)
@UX(calendar.string.Calendar, calendar.icon.Calendar) @UX(calendar.string.Calendar, calendar.icon.Calendar)
@ -85,7 +86,7 @@ export class TExternalCalendar extends TCalendar implements ExternalCalendar {
externalUser!: string externalUser!: string
} }
@Model(calendar.class.Event, core.class.AttachedDoc, DOMAIN_CALENDAR) @Model(calendar.class.Event, core.class.AttachedDoc, DOMAIN_EVENT)
@UX(calendar.string.Event, calendar.icon.Calendar) @UX(calendar.string.Event, calendar.icon.Calendar)
export class TEvent extends TAttachedDoc implements Event { export class TEvent extends TAttachedDoc implements Event {
declare space: Ref<SystemSpace> declare space: Ref<SystemSpace>

View File

@ -24,7 +24,7 @@ import {
type MigrationUpgradeClient type MigrationUpgradeClient
} from '@hcengineering/model' } from '@hcengineering/model'
import { DOMAIN_SPACE } from '@hcengineering/model-core' import { DOMAIN_SPACE } from '@hcengineering/model-core'
import { DOMAIN_CALENDAR } from '.' import { DOMAIN_CALENDAR, DOMAIN_EVENT } from '.'
import calendar from './plugin' import calendar from './plugin'
async function migrateCalendars (client: MigrationClient): Promise<void> { async function migrateCalendars (client: MigrationClient): Promise<void> {
@ -136,6 +136,16 @@ export const calendarOperation: MigrateOperation = {
{ {
state: 'migrate_calendars', state: 'migrate_calendars',
func: migrateCalendars func: migrateCalendars
},
{
state: 'move-events',
func: async (client) => {
await client.move(
DOMAIN_CALENDAR,
{ _class: { $in: client.hierarchy.getDescendants(calendar.class.Event) } },
DOMAIN_EVENT
)
}
} }
]) ])
}, },

View File

@ -13,7 +13,7 @@
// limitations under the License. // limitations under the License.
--> -->
<script lang="ts"> <script lang="ts">
import calendar from '@hcengineering/calendar' import calendar, { Calendar } from '@hcengineering/calendar'
import type { Contact, PersonAccount, Organization, Person } from '@hcengineering/contact' import type { Contact, PersonAccount, Organization, Person } from '@hcengineering/contact'
import contact from '@hcengineering/contact' import contact from '@hcengineering/contact'
import core, { import core, {
@ -41,7 +41,6 @@
import recruit from '../../plugin' import recruit from '../../plugin'
import IconCompany from '../icons/Company.svelte' import IconCompany from '../icons/Company.svelte'
import { Analytics } from '@hcengineering/analytics' import { Analytics } from '@hcengineering/analytics'
import { getCandidateIdentifier } from '../../utils'
// export let space: Ref<Project> // export let space: Ref<Project>
export let candidate: Ref<Person> export let candidate: Ref<Person>
@ -86,7 +85,8 @@
title, title,
participants: [currentUser.person], participants: [currentUser.person],
eventId: '', eventId: '',
dueDate: 0 dueDate: 0,
calendar: '' as Ref<Calendar>
} }
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
@ -139,7 +139,7 @@
access: 'reader', access: 'reader',
allDay: false, allDay: false,
eventId: '', eventId: '',
calendar: undefined calendar: '' as Ref<Calendar>
} }
) )

View File

@ -0,0 +1,5 @@
ALTER TABLE calendar
ADD "hidden" bool;
UPDATE calendar
SET "hidden" = (data->>'hidden')::boolean;

View File

@ -0,0 +1,19 @@
ALTER TABLE event
ADD "date" bigint,
ADD "dueDate" bigint,
add calendar text,
ADD "participants" text[];
UPDATE event
SET "date" = (data->>'date')::bigint;
UPDATE event
SET "dueDate" = (data->>'dueDate')::bigint;
UPDATE calendar
SET "calendar" = (data->>'calendar');
UPDATE event
SET "participants" = array(
SELECT jsonb_array_elements_text(data->'participants')
);

View File

@ -0,0 +1,17 @@
ALTER TABLE time
ADD "workslots" bigint,
ADD "doneOn" bigint,
add rank text,
ADD "user" text;
UPDATE time
SET "workslots" = (data->>'workslots')::bigint;
UPDATE time
SET "doneOn" = (data->>'doneOn')::bigint;
UPDATE time
SET "rank" = (data->>'rank');
UPDATE time
SET "user" = (data->>'user');

View File

@ -155,6 +155,63 @@ const docIndexStateSchema: Schema = {
} }
} }
const timeSchema: Schema = {
...baseSchema,
workslots: {
type: 'bigint',
notNull: false,
index: true
},
doneOn: {
type: 'bigint',
notNull: false,
index: true
},
user: {
type: 'text',
notNull: true,
index: true
},
rank: {
type: 'text',
notNull: true,
index: false
}
}
const calendarSchema: Schema = {
...baseSchema,
hidden: {
type: 'bool',
notNull: true,
index: true
}
}
const eventSchema: Schema = {
...defaultSchema,
calendar: {
type: 'text',
notNull: true,
index: true
},
date: {
type: 'bigint',
notNull: true,
index: true
},
dueDate: {
type: 'bigint',
notNull: true,
index: true
},
participants: {
type: 'text[]',
notNull: true,
index: true
}
}
export function addSchema (domain: string, schema: Schema): void { export function addSchema (domain: string, schema: Schema): void {
domainSchemas[translateDomain(domain)] = schema domainSchemas[translateDomain(domain)] = schema
} }
@ -166,6 +223,9 @@ export function translateDomain (domain: string): string {
export const domainSchemas: Record<string, Schema> = { export const domainSchemas: Record<string, Schema> = {
[DOMAIN_SPACE]: spaceSchema, [DOMAIN_SPACE]: spaceSchema,
[DOMAIN_TX]: txSchema, [DOMAIN_TX]: txSchema,
[translateDomain('time')]: timeSchema,
[translateDomain('calendar')]: calendarSchema,
[translateDomain('event')]: eventSchema,
[translateDomain(DOMAIN_DOC_INDEX_STATE)]: docIndexStateSchema, [translateDomain(DOMAIN_DOC_INDEX_STATE)]: docIndexStateSchema,
notification: notificationSchema, notification: notificationSchema,
[translateDomain('notification-dnc')]: dncSchema, [translateDomain('notification-dnc')]: dncSchema,

View File

@ -210,7 +210,7 @@ abstract class PostgresAdapterBase implements DbAdapter {
await close(cursorName) await close(cursorName)
return null return null
} }
return result.map((p) => parseDoc(p as any)) return result.map((p) => parseDoc(p as any, _domain))
} }
await init() await init()
@ -254,7 +254,7 @@ abstract class PostgresAdapterBase implements DbAdapter {
} }
const finalSql: string = [select, ...sqlChunks].join(' ') const finalSql: string = [select, ...sqlChunks].join(' ')
const result = await this.client.unsafe(finalSql) const result = await this.client.unsafe(finalSql)
return result.map((p) => parseDocWithProjection(p as any, options?.projection)) return result.map((p) => parseDocWithProjection(p as any, domain, options?.projection))
} }
buildRawOrder<T extends Doc>(domain: string, sort: SortingQuery<T>): string { buildRawOrder<T extends Doc>(domain: string, sort: SortingQuery<T>): string {
@ -307,7 +307,7 @@ abstract class PostgresAdapterBase implements DbAdapter {
const res = await client.unsafe( const res = await client.unsafe(
`SELECT * FROM ${translateDomain(domain)} WHERE ${translatedQuery} FOR UPDATE` `SELECT * FROM ${translateDomain(domain)} WHERE ${translatedQuery} FOR UPDATE`
) )
const docs = res.map((p) => parseDoc(p as any)) const docs = res.map((p) => parseDoc(p as any, domain))
for (const doc of docs) { for (const doc of docs) {
if (doc === undefined) continue if (doc === undefined) continue
const prevAttachedTo = (doc as any).attachedTo const prevAttachedTo = (doc as any).attachedTo
@ -460,11 +460,11 @@ abstract class PostgresAdapterBase implements DbAdapter {
const result = await connection.unsafe(finalSql) const result = await connection.unsafe(finalSql)
if (options?.lookup === undefined && options?.domainLookup === undefined) { if (options?.lookup === undefined && options?.domainLookup === undefined) {
return toFindResult( return toFindResult(
result.map((p) => parseDocWithProjection(p as any, options?.projection)), result.map((p) => parseDocWithProjection(p as any, domain, options?.projection)),
total total
) )
} else { } else {
const res = this.parseLookup<T>(result, joins, options?.projection) const res = this.parseLookup<T>(result, joins, options?.projection, domain)
return toFindResult(res, total) return toFindResult(res, total)
} }
} catch (err) { } catch (err) {
@ -493,7 +493,8 @@ abstract class PostgresAdapterBase implements DbAdapter {
private parseLookup<T extends Doc>( private parseLookup<T extends Doc>(
rows: any[], rows: any[],
joins: JoinProps[], joins: JoinProps[],
projection: Projection<T> | undefined projection: Projection<T> | undefined,
domain: string
): WithLookup<T>[] { ): WithLookup<T>[] {
const map = new Map<Ref<T>, WithLookup<T>>() const map = new Map<Ref<T>, WithLookup<T>>()
const modelJoins: JoinProps[] = [] const modelJoins: JoinProps[] = []
@ -526,7 +527,7 @@ abstract class PostgresAdapterBase implements DbAdapter {
if (res === undefined) continue if (res === undefined) continue
const { obj, key } = res const { obj, key } = res
const parsed = row[column].map(parseDoc) const parsed = row[column].map((p: any) => parseDoc(p, domain))
obj[key] = parsed obj[key] = parsed
} }
} else if (column.startsWith('lookup_')) { } else if (column.startsWith('lookup_')) {
@ -982,7 +983,11 @@ abstract class PostgresAdapterBase implements DbAdapter {
const val = value[operator] const val = value[operator]
switch (operator) { switch (operator) {
case '$ne': case '$ne':
res.push(`${tkey} != '${val}'`) if (val === null) {
res.push(`${tkey} IS NOT NULL`)
} else {
res.push(`${tkey} != '${val}'`)
}
break break
case '$gt': case '$gt':
res.push(`${tkey} > '${val}'`) res.push(`${tkey} > '${val}'`)
@ -1131,7 +1136,7 @@ abstract class PostgresAdapterBase implements DbAdapter {
if (result.length === 0) { if (result.length === 0) {
return [] return []
} }
return result.filter((it) => it != null).map((it) => parseDoc(it as any)) return result.filter((it) => it != null).map((it) => parseDoc(it as any, domain))
} }
const flush = async (flush = false): Promise<void> => { const flush = async (flush = false): Promise<void> => {
@ -1221,7 +1226,7 @@ abstract class PostgresAdapterBase implements DbAdapter {
const connection = (await this.getConnection(ctx)) ?? this.client const connection = (await this.getConnection(ctx)) ?? this.client
const res = const res =
await connection`SELECT * FROM ${connection(translateDomain(domain))} WHERE _id = ANY(${docs}) AND "workspaceId" = ${this.workspaceId.name}` await connection`SELECT * FROM ${connection(translateDomain(domain))} WHERE _id = ANY(${docs}) AND "workspaceId" = ${this.workspaceId.name}`
return res.map((p) => parseDocWithProjection(p as any)) return res.map((p) => parseDocWithProjection(p as any, domain))
}) })
} }
@ -1306,7 +1311,7 @@ abstract class PostgresAdapterBase implements DbAdapter {
try { try {
const res = const res =
await client`SELECT * FROM ${client(translateDomain(domain))} WHERE _id = ANY(${ids}) AND "workspaceId" = ${this.workspaceId.name} FOR UPDATE` await client`SELECT * FROM ${client(translateDomain(domain))} WHERE _id = ANY(${ids}) AND "workspaceId" = ${this.workspaceId.name} FOR UPDATE`
const docs = res.map((p) => parseDoc(p as any)) const docs = res.map((p) => parseDoc(p as any, domain))
const map = new Map(docs.map((d) => [d._id, d])) const map = new Map(docs.map((d) => [d._id, d]))
for (const [_id, ops] of operations) { for (const [_id, ops] of operations) {
const doc = map.get(_id) const doc = map.get(_id)
@ -1581,7 +1586,8 @@ class PostgresAdapter extends PostgresAdapterBase {
forUpdate ? client` FOR UPDATE` : client`` forUpdate ? client` FOR UPDATE` : client``
}` }`
const dbDoc = res[0] const dbDoc = res[0]
return dbDoc !== undefined ? parseDoc(dbDoc as any) : undefined const domain = this.hierarchy.getDomain(_class)
return dbDoc !== undefined ? parseDoc(dbDoc as any, domain) : undefined
}) })
} }
@ -1621,7 +1627,7 @@ class PostgresTxAdapter extends PostgresAdapterBase implements TxAdapter {
const res = await this const res = await this
.client`SELECT * FROM ${this.client(translateDomain(DOMAIN_TX))} WHERE "workspaceId" = ${this.workspaceId.name} AND "objectSpace" = ${core.space.Model} ORDER BY _id ASC, "modifiedOn" ASC` .client`SELECT * FROM ${this.client(translateDomain(DOMAIN_TX))} WHERE "workspaceId" = ${this.workspaceId.name} AND "objectSpace" = ${core.space.Model} ORDER BY _id ASC, "modifiedOn" ASC`
const model = res.map((p) => parseDoc<Tx>(p as any)) const model = res.map((p) => parseDoc<Tx>(p as any, DOMAIN_TX))
// We need to put all core.account.System transactions first // We need to put all core.account.System transactions first
const systemTx: Tx[] = [] const systemTx: Tx[] = []
const userTx: Tx[] = [] const userTx: Tx[] = []

View File

@ -326,8 +326,8 @@ export function parseUpdate<T extends Doc> (
const remainingData: Partial<T> = {} const remainingData: Partial<T> = {}
for (const key in ops) { for (const key in ops) {
if (key === '$push' || key === '$pull') { const val = (ops as any)[key]
const val = (ops as any)[key] if (key.startsWith('$')) {
for (const k in val) { for (const k in val) {
if (getDocFieldsByDomains(domain).includes(k)) { if (getDocFieldsByDomains(domain).includes(k)) {
;(extractedFields as any)[k] = val[key] ;(extractedFields as any)[k] = val[key]
@ -337,9 +337,9 @@ export function parseUpdate<T extends Doc> (
} }
} else { } else {
if (getDocFieldsByDomains(domain).includes(key)) { if (getDocFieldsByDomains(domain).includes(key)) {
;(extractedFields as any)[key] = (ops as any)[key] ;(extractedFields as any)[key] = val
} else { } else {
;(remainingData as any)[key] = (ops as any)[key] ;(remainingData as any)[key] = val
} }
} }
} }
@ -396,8 +396,13 @@ export class DBCollectionHelper implements DomainHelperOperations {
} }
} }
export function parseDocWithProjection<T extends Doc> (doc: DBDoc, projection?: Projection<T> | undefined): T { export function parseDocWithProjection<T extends Doc> (
doc: DBDoc,
domain: string,
projection?: Projection<T> | undefined
): T {
const { workspaceId, data, ...rest } = doc const { workspaceId, data, ...rest } = doc
const schema = getSchema(domain)
for (const key in rest) { for (const key in rest) {
if ((rest as any)[key] === 'NULL' || (rest as any)[key] === null) { if ((rest as any)[key] === 'NULL' || (rest as any)[key] === null) {
if (key === 'attachedTo') { if (key === 'attachedTo') {
@ -407,7 +412,7 @@ export function parseDocWithProjection<T extends Doc> (doc: DBDoc, projection?:
;(rest as any)[key] = null ;(rest as any)[key] = null
} }
} }
if (key === 'modifiedOn' || key === 'createdOn') { if (schema[key] !== undefined && schema[key].type === 'bigint') {
;(rest as any)[key] = Number.parseInt((rest as any)[key]) ;(rest as any)[key] = Number.parseInt((rest as any)[key])
} }
} }
@ -427,7 +432,8 @@ export function parseDocWithProjection<T extends Doc> (doc: DBDoc, projection?:
return res return res
} }
export function parseDoc<T extends Doc> (doc: DBDoc): T { export function parseDoc<T extends Doc> (doc: DBDoc, domain: string): T {
const schema = getSchema(domain)
const { workspaceId, data, ...rest } = doc const { workspaceId, data, ...rest } = doc
for (const key in rest) { for (const key in rest) {
if ((rest as any)[key] === 'NULL' || (rest as any)[key] === null) { if ((rest as any)[key] === 'NULL' || (rest as any)[key] === null) {
@ -438,7 +444,7 @@ export function parseDoc<T extends Doc> (doc: DBDoc): T {
;(rest as any)[key] = null ;(rest as any)[key] = null
} }
} }
if (key === 'modifiedOn' || key === 'createdOn') { if (schema[key] !== undefined && schema[key].type === 'bigint') {
;(rest as any)[key] = Number.parseInt((rest as any)[key]) ;(rest as any)[key] = Number.parseInt((rest as any)[key])
} }
} }