From 44bfa6d2c86de4e799ea62446aebac5feb3d199e Mon Sep 17 00:00:00 2001 From: andrewwallacespeckle <139135120+andrewwallacespeckle@users.noreply.github.com> Date: Fri, 8 Sep 2023 12:55:03 +0100 Subject: [PATCH] Fe2 server management bugfixes (#1787) * fix(server): inviteList pagination * Fixes from call with fabians * more BE bufxies * reducing server invite precision * Infinite Scroll fixes. Slight design change to "update available" * fixed tests --------- Co-authored-by: Kristaps Fabians Geikins --- .../components/server-management/CardRow.vue | 10 ++- .../lib/common/generated/gql/graphql.ts | 6 ++ .../lib/server-management/helpers/types.ts | 12 +-- .../pages/server-management/active-users.vue | 6 +- .../pages/server-management/index.vue | 6 +- .../server-management/pending-invitations.vue | 6 +- .../pages/server-management/projects.vue | 6 +- .../modules/core/tests/usersAdminList.spec.ts | 83 ++++++++++--------- ...e_invites_to_lower_precision_timestamps.ts | 31 +++++++ .../serverinvites/repositories/index.js | 15 ++-- 10 files changed, 119 insertions(+), 62 deletions(-) create mode 100644 packages/server/modules/serverinvites/migrations/20230907131636_migrate_invites_to_lower_precision_timestamps.ts diff --git a/packages/frontend-2/components/server-management/CardRow.vue b/packages/frontend-2/components/server-management/CardRow.vue index 88cd69512..e6aa96180 100644 --- a/packages/frontend-2/components/server-management/CardRow.vue +++ b/packages/frontend-2/components/server-management/CardRow.vue @@ -21,12 +21,18 @@ {{ cta.label }} + diff --git a/packages/frontend-2/lib/common/generated/gql/graphql.ts b/packages/frontend-2/lib/common/generated/gql/graphql.ts index 046dfe4ca..bfca57235 100644 --- a/packages/frontend-2/lib/common/generated/gql/graphql.ts +++ b/packages/frontend-2/lib/common/generated/gql/graphql.ts @@ -1305,6 +1305,7 @@ export type Project = { /** Return metadata about resources being requested in the viewer */ viewerResources: Array; visibility: ProjectVisibility; + webhooks?: Maybe; }; @@ -1355,6 +1356,11 @@ export type ProjectViewerResourcesArgs = { resourceIdString: Scalars['String']; }; + +export type ProjectWebhooksArgs = { + id?: InputMaybe; +}; + export type ProjectCollaborator = { __typename?: 'ProjectCollaborator'; role: Scalars['String']; diff --git a/packages/frontend-2/lib/server-management/helpers/types.ts b/packages/frontend-2/lib/server-management/helpers/types.ts index b00f9bede..979fc40cf 100644 --- a/packages/frontend-2/lib/server-management/helpers/types.ts +++ b/packages/frontend-2/lib/server-management/helpers/types.ts @@ -20,9 +20,9 @@ export type InviteItem = NonNullable< > export interface CTA { - type: 'button' | 'link' + type: 'button' | 'link' | 'text' label: string - action: () => MaybeAsync + action?: () => MaybeAsync } export interface Button { @@ -35,11 +35,5 @@ export interface CardInfo { title: string value: string icon: ConcreteComponent - cta?: - | { - type: 'button' | 'link' - label: string - action: () => MaybeAsync - } - | undefined + cta?: CTA } diff --git a/packages/frontend-2/pages/server-management/active-users.vue b/packages/frontend-2/pages/server-management/active-users.vue index 9b93e5213..a2698adf9 100644 --- a/packages/frontend-2/pages/server-management/active-users.vue +++ b/packages/frontend-2/pages/server-management/active-users.vue @@ -91,7 +91,10 @@ + + userToModify.value?.role as Optional) diff --git a/packages/frontend-2/pages/server-management/index.vue b/packages/frontend-2/pages/server-management/index.vue index ee3d7548d..914fc063e 100644 --- a/packages/frontend-2/pages/server-management/index.vue +++ b/packages/frontend-2/pages/server-management/index.vue @@ -66,6 +66,7 @@ const isLatestVersion = computed(() => { !latestVersion.value || currentVersion.value === latestVersion.value || currentVersion.value === 'dev' || + currentVersion.value?.includes('alpha') || currentVersion.value === 'N/A' ) }) @@ -92,7 +93,10 @@ const serverData = computed((): CardInfo[] => [ label: 'Update is available', action: openGithubReleasePage } - : undefined + : { + type: 'text', + label: 'Up-to-date' + } } ]) const userData = computed((): CardInfo[] => [ diff --git a/packages/frontend-2/pages/server-management/pending-invitations.vue b/packages/frontend-2/pages/server-management/pending-invitations.vue index f8d2da232..c68b06a87 100644 --- a/packages/frontend-2/pages/server-management/pending-invitations.vue +++ b/packages/frontend-2/pages/server-management/pending-invitations.vue @@ -85,7 +85,10 @@ :result-variables="resultVariables" /> + + ({ limit: 50, query: searchString.value diff --git a/packages/frontend-2/pages/server-management/projects.vue b/packages/frontend-2/pages/server-management/projects.vue index df2a05d54..36658550a 100644 --- a/packages/frontend-2/pages/server-management/projects.vue +++ b/packages/frontend-2/pages/server-management/projects.vue @@ -89,7 +89,10 @@ + + ({ limit: 50, query: searchString.value diff --git a/packages/server/modules/core/tests/usersAdminList.spec.ts b/packages/server/modules/core/tests/usersAdminList.spec.ts index 3642a3475..c0a975f91 100644 --- a/packages/server/modules/core/tests/usersAdminList.spec.ts +++ b/packages/server/modules/core/tests/usersAdminList.spec.ts @@ -12,6 +12,10 @@ import { expect } from 'chai' import { ApolloServer } from 'apollo-server-express' import { ServerInviteRecord } from '@/modules/serverinvites/helpers/types' import { Optional } from '@/modules/shared/helpers/typeHelper' +import { wait } from '@speckle/shared' + +// To ensure that the invites are created in the correct order, we need to wait a bit between each creation +const WAIT_TIMEOUT = 5 function randomEl(array: T[]): T { return array[Math.floor(Math.random() * array.length)] @@ -90,18 +94,18 @@ describe('[Admin users list]', () => { let remainingSearchQueryInviteCount = SEARCH_QUERY_RESULT_COUNT // Create Users - await Promise.all( - // count - 1, cause `me` also exists - times(USER_COUNT - 1, (i) => - createUser({ - name: `User #${i} - ${ - remainingSearchQueryUserCount-- >= 1 ? SEARCH_QUERY : '' - }`, - email: `speckleuser${i}@gmail.com`, - password: 'sn3aky-1337-b1m' - }).then((id) => userIds.push(id)) - ) - ) + // count - 1, cause `me` also exists + for (let i = 0; i < USER_COUNT - 1; i++) { + const id = await createUser({ + name: `User #${i} - ${ + remainingSearchQueryUserCount-- >= 1 ? SEARCH_QUERY : '' + }`, + email: `speckleuser${i}@gmail.com`, + password: 'sn3aky-1337-b1m' + }) + userIds.push(id) + await wait(WAIT_TIMEOUT) + } // Create streams const streamData: { id: string; ownerId: string }[] = [] @@ -117,34 +121,35 @@ describe('[Admin users list]', () => { ) // Create invites - await Promise.all([ - // Server invites - ...times(SERVER_INVITE_COUNT, (i) => - createInviteDirectly( - { - email: `randominvitee${i}.${ - remainingSearchQueryInviteCount-- >= 1 ? SEARCH_QUERY : '' - }@gmail.com` - }, - randomEl(userIds) - ) - ), - // Stream invites - ...times(STREAM_INVITE_COUNT, (i) => { - const { id: streamId, ownerId } = randomEl(streamData) - const email = `streamrandominvitee${i}.${ - remainingSearchQueryInviteCount-- >= 1 ? SEARCH_QUERY : '' - }@gmail.com` + // Server invites + for (let i = 0; i < SERVER_INVITE_COUNT; i++) { + await createInviteDirectly( + { + email: `randominvitee${i}.${ + remainingSearchQueryInviteCount-- >= 1 ? SEARCH_QUERY : '' + }@gmail.com` + }, + randomEl(userIds) + ) + await wait(WAIT_TIMEOUT) + } - return createInviteDirectly( - { - streamId, - email - }, - ownerId - ) - }) - ]) + // Stream invites + for (let i = 0; i < STREAM_INVITE_COUNT; i++) { + const { id: streamId, ownerId } = randomEl(streamData) + const email = `streamrandominvitee${i}.${ + remainingSearchQueryInviteCount-- >= 1 ? SEARCH_QUERY : '' + }@gmail.com` + + await createInviteDirectly( + { + streamId, + email + }, + ownerId + ) + await wait(WAIT_TIMEOUT) + } // Create a few more stream invites to registered users, which should not appear in // the users list diff --git a/packages/server/modules/serverinvites/migrations/20230907131636_migrate_invites_to_lower_precision_timestamps.ts b/packages/server/modules/serverinvites/migrations/20230907131636_migrate_invites_to_lower_precision_timestamps.ts new file mode 100644 index 000000000..74ea3cf91 --- /dev/null +++ b/packages/server/modules/serverinvites/migrations/20230907131636_migrate_invites_to_lower_precision_timestamps.ts @@ -0,0 +1,31 @@ +import { Knex } from 'knex' + +/** + * MIGRATING STREAMS TIMESTAMP FIELDS TO A LOWER PRECISION, CAUSE JS CANT HANDLE + * IT BEING THAT HIGH AND THIS GENERATES BUGS + */ + +const TABLE_NAME = 'server_invites' +const TIMESTAMP_COLUMNS = ['createdAt'] + +export async function up(knex: Knex): Promise { + await knex.schema.alterTable(TABLE_NAME, (table) => { + TIMESTAMP_COLUMNS.forEach((col) => { + table + .timestamp(col, { precision: 3, useTz: true }) + .defaultTo(knex.fn.now()) + .alter() + }) + }) +} + +export async function down(knex: Knex): Promise { + await knex.schema.alterTable(TABLE_NAME, (table) => { + TIMESTAMP_COLUMNS.forEach((col) => { + table + .timestamp(col, { precision: 6, useTz: true }) + .defaultTo(knex.fn.now()) + .alter() + }) + }) +} diff --git a/packages/server/modules/serverinvites/repositories/index.js b/packages/server/modules/serverinvites/repositories/index.js index 2c3d48463..d1fbf4b85 100644 --- a/packages/server/modules/serverinvites/repositories/index.js +++ b/packages/server/modules/serverinvites/repositories/index.js @@ -13,7 +13,7 @@ const { getStream } = require('@/modules/core/repositories/streams') /** * Use this wherever you're retrieving invites, not necessarily where you're writing to them */ -const getInvitesBaseQuery = () => { +const getInvitesBaseQuery = (sort = 'asc') => { const q = ServerInvites.knex().select(ServerInvites.cols) // join just to ensure we don't retrieve invalid invites @@ -25,7 +25,7 @@ const getInvitesBaseQuery = () => { w1.whereNull(ServerInvites.col.resourceId).orWhereNotNull(Streams.col.id) }) - q.orderBy(ServerInvites.col.createdAt) + q.orderBy(ServerInvites.col.createdAt, sort) return q } @@ -225,8 +225,8 @@ async function deleteStreamInvite(inviteId) { .delete() } -function findServerInvitesBaseQuery(searchQuery) { - const q = getInvitesBaseQuery() +function findServerInvitesBaseQuery(searchQuery, sort) { + const q = getInvitesBaseQuery(sort) if (searchQuery) { // TODO: Is this safe from SQL injection? @@ -272,10 +272,9 @@ async function findServerInvites(searchQuery, limit, offset) { * @returns {Promise} */ async function queryServerInvites(searchQuery, limit, cursor) { - const q = findServerInvitesBaseQuery(searchQuery) - .limit(limit) - .orderBy(ServerInvites.col.createdAt, 'desc') - if (cursor) q.where(ServerInvites.col.createdAt, '<', cursor) + const q = findServerInvitesBaseQuery(searchQuery, 'desc').limit(limit) + + if (cursor) q.where(ServerInvites.col.createdAt, '<', cursor.toISOString()) return await q }