Skip to content

Commit

Permalink
Merge branch 'main' into alex/WEB-2467
Browse files Browse the repository at this point in the history
  • Loading branch information
AlexandruPopovici committed Jan 15, 2025
2 parents 36b02e6 + ef0c800 commit 8baa91f
Show file tree
Hide file tree
Showing 44 changed files with 885 additions and 345 deletions.
14 changes: 8 additions & 6 deletions packages/frontend-2/components/viewer/gendo/Dialog.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<template>
<LayoutDialog v-model:open="isOpen" max-width="xl" is-transparent>
<LayoutDialog v-model:open="isOpen" max-width="xl" is-transparent hide-closer>
<div class="relative flex flex-col items-center justify-center gap-2 w-full h-full">
<!-- Fullscreen button behind image to handle background clicks -->
<!-- that are still inside dialog and show loading state -->
Expand All @@ -9,11 +9,13 @@
>
<CommonLoadingIcon />
</button>
<NuxtImg
:src="renderUrl"
:alt="renderPrompt"
class="relative z-10 w-full h-full max-h-[70vh] max-w-[80vw] object-contain"
/>
<div class="w-full h-full min-h-96">
<NuxtImg
:src="renderUrl"
:alt="renderPrompt"
class="relative z-10 w-full h-full max-h-[70vh] max-w-[80vw] object-contain"
/>
</div>
<div class="relative z-10 flex gap-2">
<FormButton
:to="renderUrl"
Expand Down
13 changes: 8 additions & 5 deletions packages/frontend-2/components/viewer/gendo/Item.vue
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
<template>
<div v-if="detailedRender" class="mb-2">
<div v-if="detailedRender">
<div class="relative">
<div v-if="detailedRender.status === 'COMPLETED' && renderUrl" class="group">
<button class="relative flex cursor-zoom-in" @click="openPreview">
<button
class="relative flex cursor-zoom-in w-full aspect-video"
@click="openPreview"
>
<div
class="absolute inset-0 bg-highlight-3 flex items-center justify-center rounded-lg"
>
Expand All @@ -15,15 +18,15 @@
/>
</button>
<div class="hidden group-hover:flex absolute top-2 left-2 gap-1 z-10">
<div v-tippy="`Set view`">
<div v-tippy="`Reset view`">
<FormButton
:icon-left="VideoCameraIcon"
hide-text
color="outline"
size="sm"
@click="setView()"
>
Set View
Reset View
</FormButton>
</div>
<div v-tippy="`Copy prompt`">
Expand Down Expand Up @@ -66,7 +69,7 @@
<ExclamationCircleIcon v-else class="w-6 text-danger" />
</div>
<div
class="absolute bottom-2 left-0 gap-x-2 px-2 flex items-center min-w-0 max-w-full overflow-hidden z-10"
class="absolute bottom-0 left-0 gap-x-2 p-2 flex items-center min-w-0 max-w-full overflow-hidden z-10"
>
<div
class="bg-foundation p-0.5 flex items-center gap-x-1 min-w-0 max-w-full rounded-md"
Expand Down
4 changes: 3 additions & 1 deletion packages/frontend-2/components/viewer/gendo/List.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<template>
<div
class="flex flex-col gap-y-2 max-h-64 2xl:max-h-96 overflow-y-auto overflow-x-hidden simple-scrollbar"
v-if="renders.length"
class="flex flex-col gap-y-2 max-h-[calc(100dvh-22rem)] overflow-y-auto overflow-x-hidden simple-scrollbar mb-3"
>
<ViewerGendoItem
v-for="render in renders"
Expand All @@ -9,6 +10,7 @@
@reuse-prompt="$emit('reuse-prompt', $event)"
/>
</div>
<div v-else />
</template>
<script setup lang="ts">
import { useQuery, useSubscription } from '@vue/apollo-composable'
Expand Down
11 changes: 10 additions & 1 deletion packages/frontend-2/components/viewer/gendo/Panel.vue
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@
<CommonAlert v-if="!limits" color="danger" size="xs">
<template #title>No credits available</template>
</CommonAlert>
<CommonAlert v-else-if="isOutOfCredits" color="neutral" size="xs">
<template #title>Credits reset on {{ formattedResetDate }}</template>
</CommonAlert>
<div class="flex flex-col gap-y-3">
<FormTextArea
v-model="prompt"
Expand All @@ -38,7 +41,7 @@
color="outline"
size="sm"
external
to="https://www.gendo.ai/terms-of-service?utm=speckle"
to="https://speckle.community/t/say-hello-to-ai-renders-in-speckle/15913"
target="_blank"
>
<div class="flex items-center gap-1 text-foreground-2 font-normal">
Expand Down Expand Up @@ -105,6 +108,7 @@ import { useInjectedViewerState } from '~~/lib/viewer/composables/setup'
import { useMixpanel } from '~/lib/core/composables/mp'
import { CommonAlert, CommonBadge } from '@speckle/ui-components'
import { ArrowTopRightOnSquareIcon } from '@heroicons/vue/24/outline'
import dayjs from 'dayjs'
const {
projectId,
Expand Down Expand Up @@ -147,6 +151,11 @@ const isOutOfCredits = computed(() => {
return (limits.value?.used || 0) >= (limits.value?.limit || 0)
})
const formattedResetDate = computed(() => {
if (!limits.value?.resetDate) return ''
return dayjs(limits.value.resetDate).format('Do MMMM YYYY')
})
const enqueMagic = async () => {
isLoading.value = true
const [depthData, width, height] = await viewerInstance
Expand Down
11 changes: 11 additions & 0 deletions packages/frontend-2/lib/common/generated/gql/graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4388,6 +4388,7 @@ export type WorkspaceMutations = {
join: Workspace;
leave: Scalars['Boolean']['output'];
projects: WorkspaceProjectMutations;
requestToJoin: Scalars['Boolean']['output'];
/** Set the default region where project data will be stored. Only available to admins. */
setDefaultRegion: Workspace;
update: Workspace;
Expand Down Expand Up @@ -4436,6 +4437,11 @@ export type WorkspaceMutationsLeaveArgs = {
};


export type WorkspaceMutationsRequestToJoinArgs = {
input: WorkspaceRequestToJoinInput;
};


export type WorkspaceMutationsSetDefaultRegionArgs = {
regionKey: Scalars['String']['input'];
workspaceId: Scalars['String']['input'];
Expand Down Expand Up @@ -4545,6 +4551,10 @@ export enum WorkspaceProjectsUpdatedMessageType {
Removed = 'REMOVED'
}

export type WorkspaceRequestToJoinInput = {
workspaceId: Scalars['ID']['input'];
};

export enum WorkspaceRole {
Admin = 'ADMIN',
Guest = 'GUEST',
Expand Down Expand Up @@ -8133,6 +8143,7 @@ export type WorkspaceMutationsFieldArgs = {
join: WorkspaceMutationsJoinArgs,
leave: WorkspaceMutationsLeaveArgs,
projects: {},
requestToJoin: WorkspaceMutationsRequestToJoinArgs,
setDefaultRegion: WorkspaceMutationsSetDefaultRegionArgs,
update: WorkspaceMutationsUpdateArgs,
updateCreationState: WorkspaceMutationsUpdateCreationStateArgs,
Expand Down
9 changes: 8 additions & 1 deletion packages/server/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ import {
enableMixpanel,
getPort,
getBindAddress,
shutdownTimeoutSeconds
shutdownTimeoutSeconds,
asyncRequestContextEnabled
} from '@/modules/shared/helpers/envHelper'
import * as ModulesSetup from '@/modules'
import { GraphQLContext, Optional } from '@/modules/shared/helpers/typeHelper'
Expand Down Expand Up @@ -78,6 +79,7 @@ import type { ReadinessHandler } from '@/healthchecks/types'
import type ws from 'ws'
import type { Server as MockWsServer } from 'mock-socket'
import { SetOptional } from 'type-fest'
import { initiateRequestContextMiddleware } from '@/logging/requestContext'

const GRAPHQL_PATH = '/graphql'

Expand Down Expand Up @@ -405,9 +407,14 @@ export async function init() {

app.use(cookieParser())
app.use(DetermineRequestIdMiddleware)
app.use(initiateRequestContextMiddleware)
app.use(determineClientIpAddressMiddleware)
app.use(LoggingExpressMiddleware)

if (asyncRequestContextEnabled()) {
startupLogger.info('Async request context tracking enabled 👀')
}

if (process.env.COMPRESSION) {
app.use(compression())
}
Expand Down
34 changes: 34 additions & 0 deletions packages/server/assets/workspacesCore/typedefs/workspaces.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,17 @@ type Workspace {
Info about the workspace creation state
"""
creationState: WorkspaceCreationState
"""
Get all join requests for all the workspaces the user is an admin of
"""
adminWorkspacesJoinRequests(
filter: AdminWorkspaceJoinRequestFilter
cursor: String
limit: Int! = 25
): WorkspaceJoinRequestCollection!
@hasServerRole(role: SERVER_USER)
@hasScope(scope: "workspace:read")
@hasWorkspaceRole(role: ADMIN)
}

type WorkspaceCreationState {
Expand Down Expand Up @@ -450,6 +461,29 @@ type WorkspaceCollection {
items: [Workspace!]!
}

type WorkspaceJoinRequestCollection {
totalCount: Int!
cursor: String
items: [WorkspaceJoinRequest!]!
}

type WorkspaceJoinRequest {
workspace: Workspace!
user: LimitedUser!
status: WorkspaceJoinRequestStatus!
createdAt: DateTime!
}

enum WorkspaceJoinRequestStatus {
pending
accepted
denied
}

input AdminWorkspaceJoinRequestFilter {
status: WorkspaceJoinRequestStatus
}

extend type User {
"""
Get discoverable workspaces with verified domains that match the active user's
Expand Down
1 change: 1 addition & 0 deletions packages/server/codegen.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ generates:
WorkspaceBillingMutations: '@/modules/gatekeeper/helpers/graphTypes#WorkspaceBillingMutationsGraphQLReturn'
PendingWorkspaceCollaborator: '@/modules/workspacesCore/helpers/graphTypes#PendingWorkspaceCollaboratorGraphQLReturn'
WorkspaceCollaborator: '@/modules/workspacesCore/helpers/graphTypes#WorkspaceCollaboratorGraphQLReturn'
WorkspaceJoinRequest: '@/modules/workspacesCore/helpers/graphTypes#WorkspaceJoinRequestGraphQLReturn'
Webhook: '@/modules/webhooks/helpers/graphTypes#WebhookGraphQLReturn'
SmartTextEditorValue: '@/modules/core/services/richTextEditorService#SmartTextEditorValueGraphQLReturn'
BlobMetadata: '@/modules/blobstorage/domain/types#BlobStorageItem'
Expand Down
39 changes: 5 additions & 34 deletions packages/server/healthchecks/health.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,35 +88,9 @@ export const handleLivenessFactory =

export const handleReadinessFactory = (deps: {
isRedisAlive: RedisCheck
areAllPostgresAlive: MultiDBCheck
getFreeConnectionsCalculators: () => FreeConnectionsCalculators
}): ReadinessHandler => {
return async () => {
const allPostgresResults = await deps.areAllPostgresAlive()
const deadPostgresKeys = Object.entries(allPostgresResults)
.filter((result) => !result[1].isAlive)
.map((result) => result[0])

if (deadPostgresKeys.length) {
throw new ReadinessError(
`Readiness health check failed. Postgres for ${join(
deadPostgresKeys,
', '
)} is not available.`,
{
cause: new MultiError(
Object.entries(allPostgresResults).map((kv) =>
ensureErrorOrWrapAsCause(
//HACK: kv[1] is not typed correctly as the filter does not narrow the type
(kv[1] as { isAlive: false; err: unknown }).err,
'Unknown Postgres error.'
)
)
)
}
)
}

const redisClient = getGenericRedis()
const redisCheck = await deps.isRedisAlive({ client: redisClient })
if (!redisCheck.isAlive) {
Expand Down Expand Up @@ -149,14 +123,11 @@ export const handleReadinessFactory = (deps: {

return {
details: {
postgres: merge(
allPostgresResults,
Object.fromEntries(
Object.entries(percentageFreeConnections).map(([k, v]) => [
k,
{ percentageFreeConnections: v.toFixed(0) }
])
)
postgres: Object.fromEntries(
Object.entries(percentageFreeConnections).map(([k, v]) => [
k,
{ percentageFreeConnections: v.toFixed(0) }
])
),
redis: true
}
Expand Down
3 changes: 1 addition & 2 deletions packages/server/healthchecks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export const updateFreeDbConnectionSamplers = async () => {
knexFreeDbConnectionSamplerFactory({
db: dbClient.client,
collectionPeriod: highFrequencyMetricsCollectionPeriodMs(),
sampledDuration: 20_000 //number of ms over which to average the database connections, before declaring unready. 20 seconds.
sampledDuration: 4_000 //number of ms over which to average the database connections, before declaring unready. 4 seconds.
})
knexFreeDbConnectionSamplerReadiness[dbClient.regionKey].start()
}
Expand All @@ -71,7 +71,6 @@ export const initFactory: () => (

const readinessHandler = handleReadinessFactory({
isRedisAlive,
areAllPostgresAlive,
getFreeConnectionsCalculators: getKnexFreeDbConnectionSamplerReadiness
})

Expand Down
2 changes: 1 addition & 1 deletion packages/server/logging/expressLogging.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { ensureError, type Optional } from '@speckle/shared'
import { getRequestParameters, getRequestPath } from '@/modules/core/helpers/server'
import { get } from 'lodash'

const REQUEST_ID_HEADER = 'x-request-id'
export const REQUEST_ID_HEADER = 'x-request-id'

const GenerateRequestId: GenReqId = (req: IncomingMessage) => DetermineRequestId(req)

Expand Down
28 changes: 26 additions & 2 deletions packages/server/logging/knexMonitoring.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { type Knex } from 'knex'
import { Logger } from 'pino'
import { toNDecimalPlaces } from '@/modules/core/utils/formatting'
import { omit } from 'lodash'
import { getRequestContext } from '@/logging/requestContext'

let metricQueryDuration: prometheusClient.Summary<string>
let metricQueryErrors: prometheusClient.Counter<string>
Expand Down Expand Up @@ -214,6 +215,15 @@ const initKnexPrometheusMetricsForRegionEvents = async (params: {
.observe(durationSec)

const trace = (new Error().stack || '').split('\n').slice(1).join('\n').trim()
const reqCtx = getRequestContext()

// Update reqCtx with DB query metrics
if (reqCtx) {
reqCtx.dbMetrics.totalCount++
reqCtx.dbMetrics.totalDuration += durationMs || 0
reqCtx.dbMetrics.queries.push(data.sql)
}

params.logger.info(
{
region,
Expand All @@ -222,7 +232,8 @@ const initKnexPrometheusMetricsForRegionEvents = async (params: {
sqlQueryId: queryId,
sqlQueryDurationMs: toNDecimalPlaces(durationMs, 0),
sqlNumberBindings: data.bindings?.length || -1,
trace
trace,
...(reqCtx ? { req: { id: reqCtx.requestId } } : {})
},
'DB query successfully completed after {sqlQueryDurationMs} ms'
)
Expand All @@ -243,6 +254,17 @@ const initKnexPrometheusMetricsForRegionEvents = async (params: {
})
.observe(durationSec)
metricQueryErrors.inc()

const trace = (new Error().stack || '').split('\n').slice(1).join('\n').trim()
const reqCtx = getRequestContext()

// Update reqCtx with DB query metrics
if (reqCtx) {
reqCtx.dbMetrics.totalCount++
reqCtx.dbMetrics.totalDuration += durationMs || 0
reqCtx.dbMetrics.queries.push(data.sql)
}

params.logger.warn(
{
err: typeof err === 'object' ? omit(err, 'detail') : err,
Expand All @@ -251,7 +273,9 @@ const initKnexPrometheusMetricsForRegionEvents = async (params: {
sqlMethod: normalizeSqlMethod(data.method),
sqlQueryId: queryId,
sqlQueryDurationMs: toNDecimalPlaces(durationMs, 0),
sqlNumberBindings: data.bindings?.length || -1
sqlNumberBindings: data.bindings?.length || -1,
trace,
...(reqCtx ? { req: { id: reqCtx.requestId } } : {})
},
'DB query errored for {sqlMethod} after {sqlQueryDurationMs}ms'
)
Expand Down
Loading

0 comments on commit 8baa91f

Please sign in to comment.