diff --git a/api/constants.ts b/api/constants.ts index 5473ee5f..07083336 100644 --- a/api/constants.ts +++ b/api/constants.ts @@ -15,4 +15,5 @@ export { RETURN_META_COLUMNS, RETURN_FAVORITES_COLUMNS, RETURN_NAVIGATION_COLUMNS, + INTER_TENANT_GET_ENTRIES_SCHEMA, } from '../src/const'; diff --git a/api/types.ts b/api/types.ts index 478059ef..414911be 100644 --- a/api/types.ts +++ b/api/types.ts @@ -24,4 +24,5 @@ export * as ST from '../src/types/services.types'; export {EmbeddingToken} from '../src/types/embedding'; +export {InterTenantGetEntriesArgs} from '../src/db/models/navigation/utils'; export {ZitadelUserRole} from '../src/types/zitadel'; diff --git a/api/utils.ts b/api/utils.ts index 100e6641..2f8ca1ae 100644 --- a/api/utils.ts +++ b/api/utils.ts @@ -12,3 +12,5 @@ export {normalizedEnv} from '../src/utils/normalized-env'; export {default as axiosInstance} from '../src/utils/axios'; export {objectKeys} from '../src/utils/utility-types'; + +export {whereBuilderInterTenantGetEntries} from '../src/db/models/navigation/utils'; diff --git a/src/const/common.ts b/src/const/common.ts index 7e0c5f93..a7ed7266 100644 --- a/src/const/common.ts +++ b/src/const/common.ts @@ -210,3 +210,29 @@ export enum AppEnv { export const COPY_START = '(COPY'; export const COPY_END = ')'; + +export const INTER_TENANT_GET_ENTRIES_SCHEMA = { + scope: { + type: 'string', + enum: ['dataset', 'connection', 'config', 'widget', 'dash'], + }, + ids: { + type: ['string', 'array'], + }, + type: { + type: 'string', + }, + createdBy: { + oneOf: [ + {type: 'string'}, + { + type: 'array', + items: {type: 'string'}, + }, + ], + }, + meta: { + type: 'object', + patternProperties: AJV_PATTERN_KEYS_NOT_OBJECT, + }, +}; diff --git a/src/db/models/navigation/index.ts b/src/db/models/navigation/index.ts index 02bde47d..64bbf8e7 100644 --- a/src/db/models/navigation/index.ts +++ b/src/db/models/navigation/index.ts @@ -5,7 +5,7 @@ import Revision from '../revision'; import Favorite from '../favorite'; import {AppError} from '@gravity-ui/nodekit'; import * as MT from '../../../types/models'; -import {RETURN_NAVIGATION_COLUMNS, COMPARISON_OPERATORS, US_ERRORS} from '../../../const'; +import {RETURN_NAVIGATION_COLUMNS, US_ERRORS} from '../../../const'; import {validateGetEntries, validateInterTenantGetEntries} from './scheme'; import {registry} from '../../../registry'; @@ -14,6 +14,7 @@ import {getEntryPermissionsByWorkbook} from '../../../services/new/workbook/util import {getWorkbooksListByIds} from '../../../services/new/workbook/get-workbooks-list-by-ids'; import {WorkbookInstance} from '../../../registry/common/entities/workbook/types'; +import {whereBuilderInterTenantGetEntries} from './utils'; interface Navigation extends MT.EntryColumns { isLocked?: boolean; @@ -348,7 +349,7 @@ class Navigation extends Model { ids, scope, type, - metaFilters, + meta, creationTimeFilters, createdBy, orderBy = 'desc', @@ -363,7 +364,7 @@ class Navigation extends Model { scope, type, createdBy, - metaFilters, + meta, creationTimeFilters, orderBy, page, @@ -376,7 +377,7 @@ class Navigation extends Model { scope, type, createdBy, - metaFilters, + meta, creationTimeFilters, orderBy, page, @@ -393,47 +394,16 @@ class Navigation extends Model { const entries = await Navigation.query(this.replica) .select([...RETURN_NAVIGATION_COLUMNS, 'tenantId']) .join('revisions', 'entries.savedId', 'revisions.revId') - .where({ - isDeleted: false, - scope, - }) - .where((builder) => { - if (ids) { - if (Array.isArray(ids)) { - builder.where('entries.entryId', 'in', ids); - } else { - builder.where('entries.entryId', ids); - } - } - if (type) { - builder.where('type', type); - } - if (createdBy) { - builder.whereIn( - 'entries.createdBy', - Array.isArray(createdBy) ? createdBy : [createdBy], - ); - } - if (metaFilters) { - Object.entries(metaFilters).map(([metaField, value]) => { - return builder.whereRaw('meta->>?::text = ?::text', [metaField, value]); - }); - } - if (creationTimeFilters) { - Object.entries(creationTimeFilters).forEach(([comparisonOperator, date]) => { - const sqlComparisonOperator = COMPARISON_OPERATORS[comparisonOperator]; - - if (sqlComparisonOperator) { - return builder.whereRaw('entries.created_at ? ?', [ - raw(sqlComparisonOperator), - date, - ]); - } - - return; - }); - } - }) + .where( + whereBuilderInterTenantGetEntries({ + ids, + type, + createdBy, + meta, + creationTimeFilters, + scope, + }), + ) .page(page, pageSize) .orderBy('revisions.updatedAt', orderBy) .timeout(Model.DEFAULT_QUERY_TIMEOUT); diff --git a/src/db/models/navigation/scheme.ts b/src/db/models/navigation/scheme.ts index 353cd620..c8b2b89f 100644 --- a/src/db/models/navigation/scheme.ts +++ b/src/db/models/navigation/scheme.ts @@ -1,5 +1,9 @@ import compileSchema from '../../../components/validation-schema-compiler'; -import {AJV_PATTERN_KEYS_NOT_OBJECT, COMPARISON_OPERATORS} from '../../../const'; +import { + AJV_PATTERN_KEYS_NOT_OBJECT, + COMPARISON_OPERATORS, + INTER_TENANT_GET_ENTRIES_SCHEMA, +} from '../../../const'; export const validateGetEntries = compileSchema({ type: 'object', @@ -76,36 +80,7 @@ export const validateInterTenantGetEntries = compileSchema({ type: 'object', required: ['scope'], properties: { - scope: { - type: 'string', - enum: ['dataset', 'connection', 'config', 'widget', 'dash'], - }, - ids: { - type: ['string', 'array'], - }, - type: { - type: 'string', - }, - createdBy: { - oneOf: [ - {type: 'string'}, - { - type: 'array', - items: {type: 'string'}, - }, - ], - }, - metaFilters: { - type: 'object', - patternProperties: AJV_PATTERN_KEYS_NOT_OBJECT, - }, - creationTimeFilters: { - type: 'object', - patternProperties: AJV_PATTERN_KEYS_NOT_OBJECT, - propertyNames: { - enum: [...Object.keys(COMPARISON_OPERATORS)], - }, - }, + ...INTER_TENANT_GET_ENTRIES_SCHEMA, orderBy: { type: 'string', enum: ['asc', 'desc'], @@ -119,6 +94,13 @@ export const validateInterTenantGetEntries = compileSchema({ minimum: 1, maximum: 200, }, + creationTimeFilters: { + type: 'object', + patternProperties: AJV_PATTERN_KEYS_NOT_OBJECT, + propertyNames: { + enum: [...Object.keys(COMPARISON_OPERATORS)], + }, + }, }, }); diff --git a/src/db/models/navigation/utils.ts b/src/db/models/navigation/utils.ts new file mode 100644 index 00000000..7d6b599f --- /dev/null +++ b/src/db/models/navigation/utils.ts @@ -0,0 +1,64 @@ +import {raw} from 'objection'; +import type {Knex} from 'knex'; +import {COMPARISON_OPERATORS} from '../../../const'; +import {InterTenantGetEntriesConfig} from '../../../types/models'; + +export type InterTenantGetEntriesArgs = Omit< + InterTenantGetEntriesConfig, + 'page' | 'pageSize' | 'orderBy' | 'requestedBy' +>; + +export const whereBuilderInterTenantGetEntries = ({ + ids, + type, + createdBy, + meta, + creationTimeFilters, + scope, + createdAtFrom, +}: InterTenantGetEntriesArgs & {createdAtFrom?: number}) => { + return (builder: Knex.QueryBuilder) => { + builder.where({ + isDeleted: false, + scope, + }); + + if (ids) { + if (Array.isArray(ids)) { + builder.where('entries.entryId', 'in', ids); + } else { + builder.where('entries.entryId', ids); + } + } + if (type) { + builder.where('type', type); + } + if (createdBy) { + builder.whereIn( + 'entries.createdBy', + Array.isArray(createdBy) ? createdBy : [createdBy], + ); + } + if (meta) { + Object.entries(meta).map(([metaField, value]) => { + return builder.whereRaw('meta->>?::text = ?::text', [metaField, value]); + }); + } + if (creationTimeFilters) { + Object.entries(creationTimeFilters).forEach(([comparisonOperator, date]) => { + const sqlComparisonOperator = COMPARISON_OPERATORS[comparisonOperator]; + + if (sqlComparisonOperator) { + return builder.whereRaw('entries.created_at ? ?', [ + raw(sqlComparisonOperator), + date, + ]); + } + + return; + }); + } else if (createdAtFrom) { + builder.andWhere('entries.created_at', '>', raw('to_timestamp(?)', [createdAtFrom])); + } + }; +}; diff --git a/src/db/presentations/joined-entry-revision/index.ts b/src/db/presentations/joined-entry-revision/index.ts index d2ff4c35..d32dc55c 100644 --- a/src/db/presentations/joined-entry-revision/index.ts +++ b/src/db/presentations/joined-entry-revision/index.ts @@ -5,6 +5,7 @@ import {Entry} from '../../models/new/entry'; import {RevisionModel} from '../../models/new/revision'; import {selectedEntryColumns} from '../constants'; +import {EntriesOrderByFilter} from '../../../types/models'; const selectedRevisionColumns = [ 'data', @@ -60,18 +61,31 @@ export class JoinedEntryRevision extends Model { where, joinRevisionArgs = {}, trx, + limit, + orderBy, }: { where: Record | ((builder: Knex.QueryBuilder) => void); joinRevisionArgs?: JoinRevisionArgs; trx: TransactionOrKnex; + limit?: number; + orderBy?: EntriesOrderByFilter; }) { - return JoinedEntryRevision.query(trx) + const joinedEntryRevision = JoinedEntryRevision.query(trx) .select(selectedColumns) .join(RevisionModel.tableName, joinRevision(joinRevisionArgs)) - .where(where) - .timeout(JoinedEntryRevision.DEFAULT_QUERY_TIMEOUT) as unknown as Promise< - JoinedEntryRevisionColumns[] - >; + .where(where); + + if (orderBy) { + joinedEntryRevision.orderBy(orderBy.field, orderBy.direction); + } + + if (limit) { + joinedEntryRevision.limit(limit); + } + + return joinedEntryRevision.timeout( + JoinedEntryRevision.DEFAULT_QUERY_TIMEOUT, + ) as unknown as Promise; } static findOne({ diff --git a/src/services/navigation.service.ts b/src/services/navigation.service.ts index 82ecc359..8daaf5f9 100644 --- a/src/services/navigation.service.ts +++ b/src/services/navigation.service.ts @@ -68,7 +68,7 @@ export default class NavigationService { type, createdBy, orderBy, - metaFilters: meta, + meta, creationTimeFilters: creationTime, page, pageSize, diff --git a/src/types/models/navigation.ts b/src/types/models/navigation.ts index d7a01242..8c56b377 100644 --- a/src/types/models/navigation.ts +++ b/src/types/models/navigation.ts @@ -32,7 +32,7 @@ export interface InterTenantGetEntriesConfig { type?: string; orderBy?: OrderByDirection; createdBy?: string | string[]; - metaFilters?: object; + meta?: object; creationTimeFilters?: object; page?: number; pageSize?: number; diff --git a/src/utils/utils.ts b/src/utils/utils.ts index ebec9bdc..243fef43 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -9,6 +9,8 @@ import {ID_VARIABLES, CODING_BASE, TRUE_FLAGS, COPY_START, COPY_END} from '../co import {EntryScope as EntryScopeEnum, EntryType} from '../db/models/new/entry/types'; +const MAX_PAGE_LIMIT = 10000; + const PROFILES: { [key: string]: any; } = {}; @@ -454,4 +456,8 @@ export class Utils { static getTimestampInSeconds = () => { return Math.floor(new Date().getTime() / 1000); }; + + static getCorrectedPageLimit = (limit?: number, maxPageLimit = MAX_PAGE_LIMIT) => { + return limit ? Math.min(Math.abs(limit), maxPageLimit) : maxPageLimit; + }; }