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 const DOMAIN_CALENDAR = 'calendar' as Domain
export const DOMAIN_EVENT = 'event' as Domain
@Model(calendar.class.Calendar, core.class.Doc, DOMAIN_CALENDAR)
@UX(calendar.string.Calendar, calendar.icon.Calendar)
@ -85,7 +86,7 @@ export class TExternalCalendar extends TCalendar implements ExternalCalendar {
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)
export class TEvent extends TAttachedDoc implements Event {
declare space: Ref<SystemSpace>

View File

@ -24,7 +24,7 @@ import {
type MigrationUpgradeClient
} from '@hcengineering/model'
import { DOMAIN_SPACE } from '@hcengineering/model-core'
import { DOMAIN_CALENDAR } from '.'
import { DOMAIN_CALENDAR, DOMAIN_EVENT } from '.'
import calendar from './plugin'
async function migrateCalendars (client: MigrationClient): Promise<void> {
@ -136,6 +136,16 @@ export const calendarOperation: MigrateOperation = {
{
state: 'migrate_calendars',
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.
-->
<script lang="ts">
import calendar from '@hcengineering/calendar'
import calendar, { Calendar } from '@hcengineering/calendar'
import type { Contact, PersonAccount, Organization, Person } from '@hcengineering/contact'
import contact from '@hcengineering/contact'
import core, {
@ -41,7 +41,6 @@
import recruit from '../../plugin'
import IconCompany from '../icons/Company.svelte'
import { Analytics } from '@hcengineering/analytics'
import { getCandidateIdentifier } from '../../utils'
// export let space: Ref<Project>
export let candidate: Ref<Person>
@ -86,7 +85,8 @@
title,
participants: [currentUser.person],
eventId: '',
dueDate: 0
dueDate: 0,
calendar: '' as Ref<Calendar>
}
const dispatch = createEventDispatcher()
@ -139,7 +139,7 @@
access: 'reader',
allDay: false,
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 {
domainSchemas[translateDomain(domain)] = schema
}
@ -166,6 +223,9 @@ export function translateDomain (domain: string): string {
export const domainSchemas: Record<string, Schema> = {
[DOMAIN_SPACE]: spaceSchema,
[DOMAIN_TX]: txSchema,
[translateDomain('time')]: timeSchema,
[translateDomain('calendar')]: calendarSchema,
[translateDomain('event')]: eventSchema,
[translateDomain(DOMAIN_DOC_INDEX_STATE)]: docIndexStateSchema,
notification: notificationSchema,
[translateDomain('notification-dnc')]: dncSchema,

View File

@ -210,7 +210,7 @@ abstract class PostgresAdapterBase implements DbAdapter {
await close(cursorName)
return null
}
return result.map((p) => parseDoc(p as any))
return result.map((p) => parseDoc(p as any, _domain))
}
await init()
@ -254,7 +254,7 @@ abstract class PostgresAdapterBase implements DbAdapter {
}
const finalSql: string = [select, ...sqlChunks].join(' ')
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 {
@ -307,7 +307,7 @@ abstract class PostgresAdapterBase implements DbAdapter {
const res = await client.unsafe(
`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) {
if (doc === undefined) continue
const prevAttachedTo = (doc as any).attachedTo
@ -460,11 +460,11 @@ abstract class PostgresAdapterBase implements DbAdapter {
const result = await connection.unsafe(finalSql)
if (options?.lookup === undefined && options?.domainLookup === undefined) {
return toFindResult(
result.map((p) => parseDocWithProjection(p as any, options?.projection)),
result.map((p) => parseDocWithProjection(p as any, domain, options?.projection)),
total
)
} else {
const res = this.parseLookup<T>(result, joins, options?.projection)
const res = this.parseLookup<T>(result, joins, options?.projection, domain)
return toFindResult(res, total)
}
} catch (err) {
@ -493,7 +493,8 @@ abstract class PostgresAdapterBase implements DbAdapter {
private parseLookup<T extends Doc>(
rows: any[],
joins: JoinProps[],
projection: Projection<T> | undefined
projection: Projection<T> | undefined,
domain: string
): WithLookup<T>[] {
const map = new Map<Ref<T>, WithLookup<T>>()
const modelJoins: JoinProps[] = []
@ -526,7 +527,7 @@ abstract class PostgresAdapterBase implements DbAdapter {
if (res === undefined) continue
const { obj, key } = res
const parsed = row[column].map(parseDoc)
const parsed = row[column].map((p: any) => parseDoc(p, domain))
obj[key] = parsed
}
} else if (column.startsWith('lookup_')) {
@ -982,7 +983,11 @@ abstract class PostgresAdapterBase implements DbAdapter {
const val = value[operator]
switch (operator) {
case '$ne':
res.push(`${tkey} != '${val}'`)
if (val === null) {
res.push(`${tkey} IS NOT NULL`)
} else {
res.push(`${tkey} != '${val}'`)
}
break
case '$gt':
res.push(`${tkey} > '${val}'`)
@ -1131,7 +1136,7 @@ abstract class PostgresAdapterBase implements DbAdapter {
if (result.length === 0) {
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> => {
@ -1221,7 +1226,7 @@ abstract class PostgresAdapterBase implements DbAdapter {
const connection = (await this.getConnection(ctx)) ?? this.client
const res =
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 {
const res =
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]))
for (const [_id, ops] of operations) {
const doc = map.get(_id)
@ -1581,7 +1586,8 @@ class PostgresAdapter extends PostgresAdapterBase {
forUpdate ? client` FOR UPDATE` : client``
}`
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
.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
const systemTx: Tx[] = []
const userTx: Tx[] = []

View File

@ -326,8 +326,8 @@ export function parseUpdate<T extends Doc> (
const remainingData: Partial<T> = {}
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) {
if (getDocFieldsByDomains(domain).includes(k)) {
;(extractedFields as any)[k] = val[key]
@ -337,9 +337,9 @@ export function parseUpdate<T extends Doc> (
}
} else {
if (getDocFieldsByDomains(domain).includes(key)) {
;(extractedFields as any)[key] = (ops as any)[key]
;(extractedFields as any)[key] = val
} 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 schema = getSchema(domain)
for (const key in rest) {
if ((rest as any)[key] === 'NULL' || (rest as any)[key] === null) {
if (key === 'attachedTo') {
@ -407,7 +412,7 @@ export function parseDocWithProjection<T extends Doc> (doc: DBDoc, projection?:
;(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])
}
}
@ -427,7 +432,8 @@ export function parseDocWithProjection<T extends Doc> (doc: DBDoc, projection?:
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
for (const key in rest) {
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
}
}
if (key === 'modifiedOn' || key === 'createdOn') {
if (schema[key] !== undefined && schema[key].type === 'bigint') {
;(rest as any)[key] = Number.parseInt((rest as any)[key])
}
}