Merge branch 'main' into andrew/web-3627-implement-frontend-of-preventing-workspace-creations
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
import http from 'node:http'
|
||||
import { Logger } from 'pino'
|
||||
|
||||
export const startHealthCheckServer = (params: { logger: Logger }) => {
|
||||
const { logger } = params
|
||||
const server = http.createServer((req, res) => {
|
||||
if (req.url === '/healthz' && req.method === 'GET') {
|
||||
res.writeHead(200, { 'Content-Type': 'text/plain' })
|
||||
res.end('OK')
|
||||
} else {
|
||||
res.writeHead(404, { 'Content-Type': 'text/plain' })
|
||||
res.end('Not Found')
|
||||
}
|
||||
})
|
||||
|
||||
server.listen(9080, 'localhost', () => {
|
||||
logger.info('Server running at http://localhost with endpoint /healthz')
|
||||
})
|
||||
|
||||
return server
|
||||
}
|
||||
@@ -10,10 +10,12 @@ import { logger } from '@/observability/logging.js'
|
||||
import { Logger } from 'pino'
|
||||
import { ensureError, TIME_MS } from '@speckle/shared'
|
||||
import { jobProcessor } from './jobProcessor.js'
|
||||
import { startHealthCheckServer } from './healthcheck.js'
|
||||
|
||||
let jobQueue: Bull.Queue<JobPayload> | undefined = undefined
|
||||
let appState: AppState = AppState.STARTING
|
||||
let currentJob: { logger: Logger; done: Bull.DoneCallback } | undefined = undefined
|
||||
let healthCheckServer: ReturnType<typeof startHealthCheckServer> | undefined
|
||||
|
||||
export const main = async () => {
|
||||
logger.info('Starting FileUploads Service (nextGen 🚀)...')
|
||||
@@ -37,6 +39,9 @@ export const main = async () => {
|
||||
process.exit(1)
|
||||
}
|
||||
appState = AppState.RUNNING
|
||||
|
||||
healthCheckServer = startHealthCheckServer({ logger })
|
||||
|
||||
logger.debug(`Starting processing of "${QUEUE_NAME}" message queue`)
|
||||
|
||||
await jobQueue.process(async (payload, done) => {
|
||||
@@ -143,6 +148,13 @@ const beforeShutdown = async () => {
|
||||
currentJob.done(new Error('Job cancelled due to fileimport-service shutdown'))
|
||||
}
|
||||
// no need to close the job queue and redis client, when the process exits they will be closed automatically
|
||||
|
||||
if (healthCheckServer) {
|
||||
logger.info('Stopping health check server')
|
||||
healthCheckServer.close(() => {
|
||||
logger.info('Health check server stopped')
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const onShutdown = () => {
|
||||
|
||||
@@ -277,7 +277,7 @@ const onModelCreate = (params: { model: ProjectPageLatestItemsModelItemFragment
|
||||
if (!isFileUploadUploadable.value) return
|
||||
|
||||
uploadSelected({
|
||||
modelName: params.model.name
|
||||
model: params.model
|
||||
})
|
||||
}
|
||||
|
||||
@@ -296,11 +296,18 @@ watch(showNewModelDialog, (newVal, oldVal) => {
|
||||
|
||||
watch(isUploading, (newVal, oldVal) => {
|
||||
// fileUpload is always gonna be non-null when isUploading changes
|
||||
emit('uploading', { isUploading: newVal, upload: fileUpload.value! })
|
||||
emit('uploading', {
|
||||
isUploading: newVal,
|
||||
upload: fileUpload.value!,
|
||||
error: errorMessage.value
|
||||
})
|
||||
|
||||
if (!newVal && oldVal) {
|
||||
// Reset file upload state when upload finishes
|
||||
resetSelected()
|
||||
// but only if it was successful! otherwise we wanna show the error
|
||||
if (!errorMessage.value) {
|
||||
resetSelected()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -238,7 +238,7 @@ const onCardClick = (event: KeyboardEvent | MouseEvent) => {
|
||||
}
|
||||
|
||||
const onVersionUploading = (payload: FileAreaUploadingPayload) => {
|
||||
isVersionUploading.value = payload.isUploading
|
||||
isVersionUploading.value = !!(payload.isUploading || payload.error)
|
||||
}
|
||||
|
||||
const triggerVersionUpload = () => {
|
||||
|
||||
@@ -204,7 +204,7 @@ const calculateLoaderId = () => {
|
||||
}
|
||||
|
||||
const onModelUploading = (payload: FileAreaUploadingPayload) => {
|
||||
isModelUploading.value = payload.isUploading
|
||||
isModelUploading.value = !!(payload.isUploading || payload.error)
|
||||
}
|
||||
|
||||
watch(areQueriesLoading, (newVal) => {
|
||||
|
||||
@@ -216,7 +216,7 @@ const calculateLoaderId = () => {
|
||||
}
|
||||
|
||||
const onModelUploading = (payload: FileAreaUploadingPayload) => {
|
||||
isModelUploading.value = payload.isUploading
|
||||
isModelUploading.value = !!(payload.isUploading || payload.error)
|
||||
}
|
||||
|
||||
watch(areQueriesLoading, (newVal) => {
|
||||
|
||||
@@ -436,7 +436,7 @@ const triggerVersionUpload = () => {
|
||||
}
|
||||
|
||||
const onVersionUploading = (payload: FileAreaUploadingPayload) => {
|
||||
isVersionUploading.value = payload.isUploading
|
||||
isVersionUploading.value = !!(payload.isUploading || payload.error)
|
||||
}
|
||||
|
||||
const onVersionsClick = () => {
|
||||
|
||||
@@ -202,6 +202,6 @@ const gridClasses = computed(() => [
|
||||
])
|
||||
|
||||
const onModelUploading = (payload: FileAreaUploadingPayload) => {
|
||||
isModelUploading.value = payload.isUploading
|
||||
isModelUploading.value = !!(payload.isUploading || payload.error)
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -212,6 +212,8 @@ type Documents = {
|
||||
"\n query ServerInfoBlobSizeLimit {\n serverInfo {\n configuration {\n blobSizeLimitBytes\n }\n }\n }\n": typeof types.ServerInfoBlobSizeLimitDocument,
|
||||
"\n query ServerInfoAllScopes {\n serverInfo {\n scopes {\n name\n description\n }\n }\n }\n": typeof types.ServerInfoAllScopesDocument,
|
||||
"\n query ProjectModelsSelectorValues($projectId: String!, $cursor: String) {\n project(id: $projectId) {\n id\n models(limit: 100, cursor: $cursor) {\n cursor\n totalCount\n items {\n ...CommonModelSelectorModel\n }\n }\n }\n }\n": typeof types.ProjectModelsSelectorValuesDocument,
|
||||
"\n mutation GenerateUploadUrl($input: GenerateFileUploadUrlInput!) {\n fileUploadMutations {\n generateUploadUrl(input: $input) {\n url\n fileId\n }\n }\n }\n": typeof types.GenerateUploadUrlDocument,
|
||||
"\n mutation StartFileImport($input: StartFileImportInput!) {\n fileUploadMutations {\n startFileImport(input: $input) {\n id\n }\n }\n }\n": typeof types.StartFileImportDocument,
|
||||
"\n fragment UseFileImport_Project on Project {\n id\n }\n": typeof types.UseFileImport_ProjectFragmentDoc,
|
||||
"\n fragment UseFileImport_Model on Model {\n id\n name\n }\n": typeof types.UseFileImport_ModelFragmentDoc,
|
||||
"\n query MainServerInfoData {\n serverInfo {\n adminContact\n canonicalUrl\n company\n description\n guestModeEnabled\n inviteOnly\n name\n termsOfService\n version\n automateUrl\n configuration {\n isEmailEnabled\n }\n }\n }\n": typeof types.MainServerInfoDataDocument,
|
||||
@@ -666,6 +668,8 @@ const documents: Documents = {
|
||||
"\n query ServerInfoBlobSizeLimit {\n serverInfo {\n configuration {\n blobSizeLimitBytes\n }\n }\n }\n": types.ServerInfoBlobSizeLimitDocument,
|
||||
"\n query ServerInfoAllScopes {\n serverInfo {\n scopes {\n name\n description\n }\n }\n }\n": types.ServerInfoAllScopesDocument,
|
||||
"\n query ProjectModelsSelectorValues($projectId: String!, $cursor: String) {\n project(id: $projectId) {\n id\n models(limit: 100, cursor: $cursor) {\n cursor\n totalCount\n items {\n ...CommonModelSelectorModel\n }\n }\n }\n }\n": types.ProjectModelsSelectorValuesDocument,
|
||||
"\n mutation GenerateUploadUrl($input: GenerateFileUploadUrlInput!) {\n fileUploadMutations {\n generateUploadUrl(input: $input) {\n url\n fileId\n }\n }\n }\n": types.GenerateUploadUrlDocument,
|
||||
"\n mutation StartFileImport($input: StartFileImportInput!) {\n fileUploadMutations {\n startFileImport(input: $input) {\n id\n }\n }\n }\n": types.StartFileImportDocument,
|
||||
"\n fragment UseFileImport_Project on Project {\n id\n }\n": types.UseFileImport_ProjectFragmentDoc,
|
||||
"\n fragment UseFileImport_Model on Model {\n id\n name\n }\n": types.UseFileImport_ModelFragmentDoc,
|
||||
"\n query MainServerInfoData {\n serverInfo {\n adminContact\n canonicalUrl\n company\n description\n guestModeEnabled\n inviteOnly\n name\n termsOfService\n version\n automateUrl\n configuration {\n isEmailEnabled\n }\n }\n }\n": types.MainServerInfoDataDocument,
|
||||
@@ -1728,6 +1732,14 @@ export function graphql(source: "\n query ServerInfoAllScopes {\n serverInfo
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
export function graphql(source: "\n query ProjectModelsSelectorValues($projectId: String!, $cursor: String) {\n project(id: $projectId) {\n id\n models(limit: 100, cursor: $cursor) {\n cursor\n totalCount\n items {\n ...CommonModelSelectorModel\n }\n }\n }\n }\n"): (typeof documents)["\n query ProjectModelsSelectorValues($projectId: String!, $cursor: String) {\n project(id: $projectId) {\n id\n models(limit: 100, cursor: $cursor) {\n cursor\n totalCount\n items {\n ...CommonModelSelectorModel\n }\n }\n }\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
export function graphql(source: "\n mutation GenerateUploadUrl($input: GenerateFileUploadUrlInput!) {\n fileUploadMutations {\n generateUploadUrl(input: $input) {\n url\n fileId\n }\n }\n }\n"): (typeof documents)["\n mutation GenerateUploadUrl($input: GenerateFileUploadUrlInput!) {\n fileUploadMutations {\n generateUploadUrl(input: $input) {\n url\n fileId\n }\n }\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
export function graphql(source: "\n mutation StartFileImport($input: StartFileImportInput!) {\n fileUploadMutations {\n startFileImport(input: $input) {\n id\n }\n }\n }\n"): (typeof documents)["\n mutation StartFileImport($input: StartFileImportInput!) {\n fileUploadMutations {\n startFileImport(input: $input) {\n id\n }\n }\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
|
||||
@@ -5803,6 +5803,20 @@ export type ProjectModelsSelectorValuesQueryVariables = Exact<{
|
||||
|
||||
export type ProjectModelsSelectorValuesQuery = { __typename?: 'Query', project: { __typename?: 'Project', id: string, models: { __typename?: 'ModelCollection', cursor?: string | null, totalCount: number, items: Array<{ __typename?: 'Model', id: string, name: string }> } } };
|
||||
|
||||
export type GenerateUploadUrlMutationVariables = Exact<{
|
||||
input: GenerateFileUploadUrlInput;
|
||||
}>;
|
||||
|
||||
|
||||
export type GenerateUploadUrlMutation = { __typename?: 'Mutation', fileUploadMutations: { __typename?: 'FileUploadMutations', generateUploadUrl: { __typename?: 'GenerateFileUploadUrlOutput', url: string, fileId: string } } };
|
||||
|
||||
export type StartFileImportMutationVariables = Exact<{
|
||||
input: StartFileImportInput;
|
||||
}>;
|
||||
|
||||
|
||||
export type StartFileImportMutation = { __typename?: 'Mutation', fileUploadMutations: { __typename?: 'FileUploadMutations', startFileImport: { __typename?: 'FileUpload', id: string } } };
|
||||
|
||||
export type UseFileImport_ProjectFragment = { __typename?: 'Project', id: string };
|
||||
|
||||
export type UseFileImport_ModelFragment = { __typename?: 'Model', id: string, name: string };
|
||||
@@ -7623,6 +7637,8 @@ export const MentionsUserSearchDocument = {"kind":"Document","definitions":[{"ki
|
||||
export const ServerInfoBlobSizeLimitDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ServerInfoBlobSizeLimit"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"serverInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"configuration"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"blobSizeLimitBytes"}}]}}]}}]}}]} as unknown as DocumentNode<ServerInfoBlobSizeLimitQuery, ServerInfoBlobSizeLimitQueryVariables>;
|
||||
export const ServerInfoAllScopesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ServerInfoAllScopes"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"serverInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"scopes"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}}]}}]}}]}}]} as unknown as DocumentNode<ServerInfoAllScopesQuery, ServerInfoAllScopesQueryVariables>;
|
||||
export const ProjectModelsSelectorValuesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ProjectModelsSelectorValues"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"projectId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"cursor"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"project"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"projectId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"models"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"100"}},{"kind":"Argument","name":{"kind":"Name","value":"cursor"},"value":{"kind":"Variable","name":{"kind":"Name","value":"cursor"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"cursor"}},{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"CommonModelSelectorModel"}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"CommonModelSelectorModel"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Model"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]} as unknown as DocumentNode<ProjectModelsSelectorValuesQuery, ProjectModelsSelectorValuesQueryVariables>;
|
||||
export const GenerateUploadUrlDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"GenerateUploadUrl"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"GenerateFileUploadUrlInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"fileUploadMutations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"generateUploadUrl"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"url"}},{"kind":"Field","name":{"kind":"Name","value":"fileId"}}]}}]}}]}}]} as unknown as DocumentNode<GenerateUploadUrlMutation, GenerateUploadUrlMutationVariables>;
|
||||
export const StartFileImportDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"StartFileImport"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"StartFileImportInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"fileUploadMutations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"startFileImport"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]}}]} as unknown as DocumentNode<StartFileImportMutation, StartFileImportMutationVariables>;
|
||||
export const MainServerInfoDataDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"MainServerInfoData"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"serverInfo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"adminContact"}},{"kind":"Field","name":{"kind":"Name","value":"canonicalUrl"}},{"kind":"Field","name":{"kind":"Name","value":"company"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"guestModeEnabled"}},{"kind":"Field","name":{"kind":"Name","value":"inviteOnly"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"termsOfService"}},{"kind":"Field","name":{"kind":"Name","value":"version"}},{"kind":"Field","name":{"kind":"Name","value":"automateUrl"}},{"kind":"Field","name":{"kind":"Name","value":"configuration"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"isEmailEnabled"}}]}}]}}]}}]} as unknown as DocumentNode<MainServerInfoDataQuery, MainServerInfoDataQueryVariables>;
|
||||
export const DeleteAccessTokenDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"DeleteAccessToken"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"token"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"apiTokenRevoke"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"token"},"value":{"kind":"Variable","name":{"kind":"Name","value":"token"}}}]}]}}]} as unknown as DocumentNode<DeleteAccessTokenMutation, DeleteAccessTokenMutationVariables>;
|
||||
export const CreateAccessTokenDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateAccessToken"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"token"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ApiTokenCreateInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"apiTokenCreate"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"token"},"value":{"kind":"Variable","name":{"kind":"Name","value":"token"}}}]}]}}]} as unknown as DocumentNode<CreateAccessTokenMutation, CreateAccessTokenMutationVariables>;
|
||||
|
||||
@@ -8,18 +8,25 @@ export enum FileUploadConvertedStatus {
|
||||
Error = 3
|
||||
}
|
||||
|
||||
export function importFile(
|
||||
export type ImportFile = (
|
||||
params: {
|
||||
file: File
|
||||
projectId: string
|
||||
apiOrigin: string
|
||||
authToken: string
|
||||
modelName?: string
|
||||
modelName: string
|
||||
modelId: string
|
||||
},
|
||||
callbacks?: Partial<{
|
||||
onProgress: (percentage: number) => void
|
||||
}>
|
||||
) {
|
||||
) => Promise<BlobPostResultItem>
|
||||
|
||||
/**
|
||||
* Old upload mechanism that streams uploads through the server
|
||||
* @deprecated Use useFileImportApi() instead
|
||||
*/
|
||||
export const importFileLegacy: ImportFile = (params, callbacks) => {
|
||||
const { file, projectId, modelName, apiOrigin, authToken } = params
|
||||
const { onProgress } = callbacks || {}
|
||||
|
||||
|
||||
@@ -1,20 +1,149 @@
|
||||
import type { MaybeRef } from '@vueuse/core'
|
||||
import { ensureError } from '@speckle/shared'
|
||||
import { buildManualPromise, ensureError } from '@speckle/shared'
|
||||
import type { MaybeNullOrUndefined, Nullable, Optional } from '@speckle/shared'
|
||||
import { useServerFileUploadLimit } from '~~/lib/common/composables/serverInfo'
|
||||
import type {
|
||||
UploadableFileItem,
|
||||
UploadFileItem
|
||||
} from '~~/lib/form/composables/fileUpload'
|
||||
import { importFile } from '~~/lib/core/api/fileImport'
|
||||
import { importFileLegacy, type ImportFile } from '~~/lib/core/api/fileImport'
|
||||
import { useAuthCookie } from '~~/lib/auth/composables/auth'
|
||||
import { BlobUploadStatus } from '~~/lib/core/api/blobStorage'
|
||||
import { BlobUploadStatus, type BlobPostResultItem } from '~~/lib/core/api/blobStorage'
|
||||
import { useMixpanel } from '~~/lib/core/composables/mp'
|
||||
import { graphql } from '~/lib/common/generated/gql'
|
||||
import type {
|
||||
UseFileImport_ModelFragment,
|
||||
UseFileImport_ProjectFragment
|
||||
} from '~/lib/common/generated/gql/graphql'
|
||||
import { useApolloClient } from '@vue/apollo-composable'
|
||||
|
||||
const generateUploadUrlMutation = graphql(`
|
||||
mutation GenerateUploadUrl($input: GenerateFileUploadUrlInput!) {
|
||||
fileUploadMutations {
|
||||
generateUploadUrl(input: $input) {
|
||||
url
|
||||
fileId
|
||||
}
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
const startFileImportMutation = graphql(`
|
||||
mutation StartFileImport($input: StartFileImportInput!) {
|
||||
fileUploadMutations {
|
||||
startFileImport(input: $input) {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
`)
|
||||
|
||||
export const useFileImportApi = () => {
|
||||
const {
|
||||
public: { FF_LARGE_FILE_IMPORTS_ENABLED }
|
||||
} = useRuntimeConfig()
|
||||
const apollo = useApolloClient().client
|
||||
|
||||
const importFileV2: ImportFile = async (params, callbacks) => {
|
||||
const { file, projectId, modelId } = params
|
||||
const { onProgress } = callbacks || {}
|
||||
|
||||
// Generate upload URL
|
||||
const generateUploadUrlResponse = await apollo.mutate({
|
||||
mutation: generateUploadUrlMutation,
|
||||
variables: {
|
||||
input: {
|
||||
projectId,
|
||||
fileName: file.name
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const generateUploadUrl =
|
||||
generateUploadUrlResponse.data?.fileUploadMutations.generateUploadUrl
|
||||
if (!generateUploadUrl) {
|
||||
const errMsg = getFirstGqlErrorMessage(
|
||||
generateUploadUrlResponse.errors,
|
||||
"Couldn't generate upload URL"
|
||||
)
|
||||
throw new Error(errMsg)
|
||||
}
|
||||
|
||||
const { url: uploadUrl, fileId } = generateUploadUrl
|
||||
|
||||
// Upload to S3 compatible endpoint
|
||||
const request = new XMLHttpRequest()
|
||||
const uploadPromise = buildManualPromise<{ etag: string }>()
|
||||
request.open('PUT', uploadUrl)
|
||||
request.setRequestHeader('Content-Type', file.type)
|
||||
|
||||
request.upload.addEventListener('progress', (e) => {
|
||||
const percentage = (e.loaded / e.total) * 100
|
||||
onProgress?.(percentage)
|
||||
})
|
||||
|
||||
const handleResponse = () => {
|
||||
const statusCode = request.status
|
||||
if (statusCode >= 200 && statusCode < 300) {
|
||||
// Collect etag
|
||||
const etag = request.getResponseHeader('ETag')
|
||||
if (!etag) {
|
||||
return uploadPromise.reject(new Error('No ETag in upload response'))
|
||||
}
|
||||
return uploadPromise.resolve({ etag })
|
||||
} else {
|
||||
// Try to resolve error message from XML response w/ regex (dont want to parse XML)
|
||||
const errorMessage = request.responseText.match(
|
||||
/<Message>(.*?)<\/Message>/
|
||||
)?.[1]
|
||||
return uploadPromise.reject(
|
||||
new Error(errorMessage || `Upload failed with status ${statusCode}`)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
request.addEventListener('load', () => handleResponse())
|
||||
request.addEventListener('error', () => handleResponse())
|
||||
request.send(file)
|
||||
const { etag } = await uploadPromise.promise
|
||||
|
||||
// Now lets start the file import
|
||||
const startFileImportResponse = await apollo.mutate({
|
||||
mutation: startFileImportMutation,
|
||||
variables: {
|
||||
input: {
|
||||
projectId,
|
||||
fileId,
|
||||
etag,
|
||||
modelId
|
||||
}
|
||||
}
|
||||
})
|
||||
const fileImportStarted =
|
||||
startFileImportResponse.data?.fileUploadMutations.startFileImport.id
|
||||
if (!fileImportStarted) {
|
||||
const errMsg = getFirstGqlErrorMessage(
|
||||
startFileImportResponse.errors,
|
||||
"Couldn't start file import"
|
||||
)
|
||||
throw new Error(errMsg)
|
||||
}
|
||||
|
||||
const res: BlobPostResultItem = {
|
||||
fileName: file.name,
|
||||
fileSize: file.size,
|
||||
formKey: 'file',
|
||||
uploadStatus: BlobUploadStatus.Completed,
|
||||
uploadError: ''
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
return {
|
||||
importFile: FF_LARGE_FILE_IMPORTS_ENABLED ? importFileV2 : importFileLegacy
|
||||
}
|
||||
}
|
||||
|
||||
graphql(`
|
||||
fragment UseFileImport_Project on Project {
|
||||
@@ -31,12 +160,11 @@ graphql(`
|
||||
|
||||
export function useFileImport(params: {
|
||||
project: MaybeRef<UseFileImport_ProjectFragment>
|
||||
model?: MaybeRef<MaybeNullOrUndefined<UseFileImport_ModelFragment>>
|
||||
/**
|
||||
* Sometimes we don't have a model, but we still want to specify a target model name (e.g. for
|
||||
* model list view uploads, where list items don't necessarily represent real models)
|
||||
* Model should exist if upload is automatically triggered. Otherwise you must still feed it in, but
|
||||
* at the point when you call uploadSelected().
|
||||
*/
|
||||
modelName?: MaybeRef<MaybeNullOrUndefined<string>>
|
||||
model?: MaybeRef<MaybeNullOrUndefined<UseFileImport_ModelFragment>>
|
||||
/**
|
||||
* If true, the upload will be prepared and validated, but for it to start you must invoke uploadSelected() manually
|
||||
*/
|
||||
@@ -58,18 +186,19 @@ export function useFileImport(params: {
|
||||
fileSelectedCallback
|
||||
} = params
|
||||
|
||||
const { importFile } = useFileImportApi()
|
||||
const { maxSizeInBytes } = useServerFileUploadLimit()
|
||||
const authToken = useAuthCookie()
|
||||
const apiOrigin = useApiOrigin()
|
||||
|
||||
const accept = ref('.ifc,.stl,.obj')
|
||||
const upload = ref(null as Nullable<UploadFileItem & { modelName: Optional<string> }>)
|
||||
const upload = ref(null as Nullable<UploadFileItem>)
|
||||
const isUploading = ref(false)
|
||||
|
||||
const modelName = computed(() => unref(params.modelName) || unref(model)?.name)
|
||||
const isUploadable = computed(() => {
|
||||
if (!upload.value) return false
|
||||
if (upload.value.error) return false
|
||||
if (upload.value.result) return false
|
||||
if (isUploading.value) return false
|
||||
if (!authToken.value) return false
|
||||
if (!upload.value.file) return false
|
||||
@@ -80,20 +209,32 @@ export function useFileImport(params: {
|
||||
|
||||
const uploadSelected = async (params?: {
|
||||
/**
|
||||
* Optionally override model name to target for the upload
|
||||
* Optionally override model target for the upload
|
||||
*/
|
||||
modelName?: string
|
||||
model: UseFileImport_ModelFragment
|
||||
}) => {
|
||||
if (!isUploadable.value || !upload.value || !authToken.value) return
|
||||
const finalModelName = params?.modelName || upload.value.modelName
|
||||
|
||||
const baseModel = unref(model)
|
||||
const overridenModel = params?.model
|
||||
|
||||
isUploading.value = true
|
||||
try {
|
||||
let finalModel: UseFileImport_ModelFragment
|
||||
if (overridenModel) {
|
||||
finalModel = overridenModel
|
||||
} else if (baseModel) {
|
||||
finalModel = baseModel
|
||||
} else {
|
||||
throw new Error('No model provided for file import')
|
||||
}
|
||||
|
||||
const res = await importFile(
|
||||
{
|
||||
file: upload.value.file,
|
||||
projectId: unref(project).id,
|
||||
modelName: finalModelName,
|
||||
modelName: finalModel.name,
|
||||
modelId: finalModel.id,
|
||||
authToken: authToken.value,
|
||||
apiOrigin
|
||||
},
|
||||
@@ -104,13 +245,11 @@ export function useFileImport(params: {
|
||||
}
|
||||
)
|
||||
upload.value.result = res
|
||||
// TODO: add file extension
|
||||
// const extension = res.fileName?.split('.').reverse()[0]
|
||||
|
||||
mp.track('Upload Action', {
|
||||
type: 'action',
|
||||
name: 'create',
|
||||
source: finalModelName ? 'model card' : 'empty card'
|
||||
// extension
|
||||
source: 'model card'
|
||||
})
|
||||
|
||||
fileUploadedCallback?.(upload.value)
|
||||
@@ -131,13 +270,7 @@ export function useFileImport(params: {
|
||||
upload.value = null
|
||||
}
|
||||
|
||||
const onFilesSelected = async (params: {
|
||||
files: UploadableFileItem[]
|
||||
/**
|
||||
* Optionally override model name to target for the upload
|
||||
*/
|
||||
modelName?: string
|
||||
}) => {
|
||||
const onFilesSelected = async (params: { files: UploadableFileItem[] }) => {
|
||||
if (isUploading.value || !authToken.value) return
|
||||
|
||||
const file = params.files[0]
|
||||
@@ -146,8 +279,7 @@ export function useFileImport(params: {
|
||||
upload.value = {
|
||||
...file,
|
||||
result: undefined,
|
||||
progress: 0,
|
||||
modelName: params.modelName || modelName.value || undefined
|
||||
progress: 0
|
||||
}
|
||||
|
||||
if (file.error) {
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
import type { UploadFileItem } from '@speckle/ui-components'
|
||||
|
||||
export type FileAreaUploadingPayload = { isUploading: boolean; upload: UploadFileItem }
|
||||
export type FileAreaUploadingPayload = {
|
||||
isUploading: boolean
|
||||
upload: UploadFileItem
|
||||
error: string | null
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import type { FileTypeSpecifier } from '~~/src/helpers/form/file'
|
||||
import { computed, unref } from 'vue'
|
||||
import type { CSSProperties } from 'vue'
|
||||
import { BaseError } from '~~/src/lib'
|
||||
import type { BlobUploadStatus } from '@speckle/shared/blobs'
|
||||
|
||||
/**
|
||||
* A file, as emitted out from FileUploadZone
|
||||
@@ -31,7 +32,7 @@ export type BlobPostResultItem = {
|
||||
/**
|
||||
* Success = 1, Failure = 2
|
||||
*/
|
||||
uploadStatus: number
|
||||
uploadStatus: BlobUploadStatus
|
||||
uploadError: string
|
||||
}
|
||||
|
||||
|
||||
@@ -560,6 +560,9 @@ const getStream = () => {
|
||||
|
||||
// Instances with far away transform
|
||||
// 'https://app.speckle.systems/projects/9d0ce16ba8/models/3c079572ea'
|
||||
|
||||
// Duplicate display values
|
||||
// 'https://app.speckle.systems/projects/1466fe31c6/models/2eaf0f0571'
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import { NodeMap } from '../../tree/NodeMap.js'
|
||||
import { SpeckleType, type SpeckleObject } from '../../../index.js'
|
||||
import Logger from '../../utils/Logger.js'
|
||||
import { ObjectLoader2 } from '@speckle/objectloader2'
|
||||
import { SpeckleTypeAllRenderables } from '../GeometryConverter.js'
|
||||
|
||||
export type ConverterResultDelegate = (count: number) => void
|
||||
export type SpeckleConverterNodeDelegate =
|
||||
@@ -29,6 +30,7 @@ export default class SpeckleConverter {
|
||||
protected renderMaterialMap: { [id: string]: SpeckleObject } = {}
|
||||
protected colorMap: { [id: string]: SpeckleObject } = {}
|
||||
protected instanceCounter = 0
|
||||
protected duplicateCounter = 0
|
||||
private traverseCount = 0
|
||||
|
||||
protected readonly NodeConverterMapping: {
|
||||
@@ -140,9 +142,9 @@ export default class SpeckleConverter {
|
||||
children: []
|
||||
})
|
||||
this.tree.addSubtree(this.subtree)
|
||||
this.tree.addNode(childNode, this.subtree)
|
||||
this.addNode(childNode, this.subtree)
|
||||
} else {
|
||||
this.tree.addNode(childNode, node)
|
||||
this.addNode(childNode, node)
|
||||
}
|
||||
|
||||
// If we can convert it, we should invoke the respective conversion routine.
|
||||
@@ -183,7 +185,7 @@ export default class SpeckleConverter {
|
||||
atomic: false,
|
||||
children: []
|
||||
})
|
||||
this.tree.addNode(nestedNode, childNode)
|
||||
this.addNode(nestedNode, childNode)
|
||||
await this.convertToNode(displayValue, nestedNode)
|
||||
} catch (e) {
|
||||
Logger.warn(
|
||||
@@ -202,7 +204,7 @@ export default class SpeckleConverter {
|
||||
atomic: false,
|
||||
children: []
|
||||
})
|
||||
this.tree.addNode(nestedNode, childNode)
|
||||
this.addNode(nestedNode, childNode)
|
||||
await this.convertToNode(val, nestedNode)
|
||||
}
|
||||
}
|
||||
@@ -269,9 +271,17 @@ export default class SpeckleConverter {
|
||||
|
||||
private getNodeId(obj: SpeckleObject): string {
|
||||
if (this.spoofIDs) return MathUtils.generateUUID()
|
||||
|
||||
return obj.id
|
||||
}
|
||||
|
||||
private addNode(node: TreeNode, parent: TreeNode) {
|
||||
if (this.tree.hasNodeId(node.model.id, parent.model.subtreeId)) {
|
||||
node.model.id = this.getDuplicateId(node.model.id, ++this.duplicateCounter)
|
||||
}
|
||||
this.tree.addNode(node, parent)
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes an array composed of chunked references and dechunks it.
|
||||
* @param {[type]} arr [description]
|
||||
@@ -410,6 +420,14 @@ export default class SpeckleConverter {
|
||||
return baseId.substring(0, index) + NodeMap.COMPOUND_ID_CHAR + counter
|
||||
}
|
||||
|
||||
private getDuplicateId(baseId: string, counter: number) {
|
||||
const index = baseId.indexOf(NodeMap.DUPLICATE_ID_CHAR)
|
||||
if (index === -1) {
|
||||
return baseId + NodeMap.DUPLICATE_ID_CHAR + counter
|
||||
}
|
||||
return baseId.substring(0, index) + NodeMap.DUPLICATE_ID_CHAR + counter
|
||||
}
|
||||
|
||||
private getEmptyTransformData(id: string) {
|
||||
// eslint-disable-next-line camelcase
|
||||
return { id, speckle_type: 'Transform', units: 'm', matrix: new Array(16) }
|
||||
@@ -456,7 +474,7 @@ export default class SpeckleConverter {
|
||||
instanced
|
||||
})
|
||||
|
||||
this.tree.addNode(valueNode, node)
|
||||
this.addNode(valueNode, node)
|
||||
await this.displayableLookup(value, valueNode, instanced)
|
||||
}
|
||||
}
|
||||
@@ -485,7 +503,7 @@ export default class SpeckleConverter {
|
||||
atomic: false,
|
||||
children: []
|
||||
})
|
||||
this.tree.addNode(transformNode, instanceNode)
|
||||
this.addNode(transformNode, instanceNode)
|
||||
|
||||
const childNode: TreeNode = this.tree.parse({
|
||||
id: this.getCompoundId(defGeometry.id, this.instanceCounter++),
|
||||
@@ -494,7 +512,7 @@ export default class SpeckleConverter {
|
||||
children: [],
|
||||
instanced: true
|
||||
})
|
||||
this.tree.addNode(childNode, transformNode)
|
||||
this.addNode(childNode, transformNode)
|
||||
|
||||
await this.displayableLookup(defGeometry, childNode, true)
|
||||
}
|
||||
@@ -510,7 +528,7 @@ export default class SpeckleConverter {
|
||||
atomic: false,
|
||||
children: []
|
||||
})
|
||||
this.tree.addNode(childNode, instanceNode)
|
||||
this.addNode(childNode, instanceNode)
|
||||
await this.displayableLookup(elementObj, childNode, false)
|
||||
}
|
||||
|
||||
@@ -637,7 +655,7 @@ export default class SpeckleConverter {
|
||||
const definition = this.instanceDefinitionLookupTable[definitionId]
|
||||
const transformNode = this.createTransformNode(obj)
|
||||
|
||||
this.tree.addNode(transformNode, node)
|
||||
this.addNode(transformNode, node)
|
||||
const objectApplicationIds = this.getInstanceProxyDefinitionObjects(
|
||||
definition.model.raw
|
||||
)
|
||||
@@ -658,7 +676,7 @@ export default class SpeckleConverter {
|
||||
children: [],
|
||||
instanced: true
|
||||
})
|
||||
this.tree.addNode(instancedNode, transformNode)
|
||||
this.addNode(instancedNode, transformNode)
|
||||
await this.convertToNode(speckleData, instancedNode)
|
||||
}
|
||||
}
|
||||
@@ -875,7 +893,7 @@ export default class SpeckleConverter {
|
||||
...(node.model.instanced && { instanced: node.model.instanced })
|
||||
})
|
||||
await this.convertToNode(ref, nestedNode)
|
||||
this.tree.addNode(nestedNode, node)
|
||||
this.addNode(nestedNode, node)
|
||||
|
||||
// deletes known unneeded fields
|
||||
delete obj.Edges
|
||||
@@ -940,7 +958,7 @@ export default class SpeckleConverter {
|
||||
...(node.model.instanced && { instanced: node.model.instanced })
|
||||
})
|
||||
await this.convertToNode(ref, nestedNode)
|
||||
this.tree.addNode(nestedNode, node)
|
||||
this.addNode(nestedNode, node)
|
||||
} catch (e) {
|
||||
Logger.warn(`Failed to convert Region id: ${obj.id}`)
|
||||
throw e
|
||||
@@ -960,7 +978,7 @@ export default class SpeckleConverter {
|
||||
atomic: false,
|
||||
children: []
|
||||
})
|
||||
this.tree.addNode(childNode, node)
|
||||
this.addNode(childNode, node)
|
||||
await this.convertToNode(displayValue, childNode)
|
||||
}
|
||||
/**
|
||||
@@ -990,7 +1008,7 @@ export default class SpeckleConverter {
|
||||
atomic: false,
|
||||
children: []
|
||||
})
|
||||
this.tree.addNode(textNode, node)
|
||||
this.addNode(textNode, node)
|
||||
await this.convertToNode(textObj, textNode)
|
||||
}
|
||||
|
||||
@@ -1071,4 +1089,32 @@ export default class SpeckleConverter {
|
||||
private async EllipseToNode(_obj: SpeckleObject, _node: TreeNode) {
|
||||
return
|
||||
}
|
||||
|
||||
/** We shouldn't need to work with duplicates */
|
||||
public handleDuplicates(): Promise<void> {
|
||||
/** We're generally interested in handling renderable duplicates. Otherwise we're overbloat everything with millions of parameters and such */
|
||||
const SpeckleTypeDuplicableRenderables: SpeckleType[] =
|
||||
SpeckleTypeAllRenderables.slice()
|
||||
/** We remove Point because speckle data contains tons of points that are not really renderable */
|
||||
SpeckleTypeDuplicableRenderables.splice(
|
||||
SpeckleTypeAllRenderables.indexOf(SpeckleType.Point),
|
||||
1
|
||||
)
|
||||
const duplicates = this.tree.getRenderTree(this.subtree.model.id)?.getDuplicates()
|
||||
for (const k in duplicates) {
|
||||
const baseObject = this.tree.findId(k)
|
||||
if (!baseObject) {
|
||||
Logger.warn(`Base duplicated object ${k} not found!`)
|
||||
continue
|
||||
}
|
||||
const speckleType = this.getSpeckleType(baseObject[0].model.raw) as SpeckleType
|
||||
if (!SpeckleTypeDuplicableRenderables.includes(speckleType)) continue
|
||||
|
||||
for (const m in duplicates[k]) {
|
||||
/** Normally we'd only need the geometry related data cloned, but this covers 100% */
|
||||
duplicates[k][m].model.raw = structuredClone(duplicates[k][m].model.raw)
|
||||
}
|
||||
}
|
||||
return Promise.resolve()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,6 +140,7 @@ export class SpeckleLoader extends Loader {
|
||||
|
||||
await this.converter.convertInstances()
|
||||
await this.converter.applyMaterials()
|
||||
await this.converter.handleDuplicates()
|
||||
await this.loader.disposeAsync()
|
||||
|
||||
const t0 = performance.now()
|
||||
|
||||
@@ -3,9 +3,11 @@ import { type TreeNode } from './WorldTree.js'
|
||||
|
||||
export class NodeMap {
|
||||
public static readonly COMPOUND_ID_CHAR = '~'
|
||||
public static readonly DUPLICATE_ID_CHAR = '#'
|
||||
|
||||
private all: { [id: string]: TreeNode } = {}
|
||||
public instances: { [id: string]: { [id: string]: TreeNode } } = {}
|
||||
public duplicates: { [id: string]: { [id: string]: TreeNode } } = {}
|
||||
|
||||
public get nodeCount() {
|
||||
return Object.keys(this.all).length
|
||||
@@ -23,7 +25,12 @@ export class NodeMap {
|
||||
// console.warn(`Duplicate id ${node.model.id}, skipping!`)
|
||||
return false
|
||||
}
|
||||
|
||||
this.registerNode(node)
|
||||
|
||||
if (node.model.id.includes(NodeMap.DUPLICATE_ID_CHAR)) {
|
||||
this.registerDuplicate(node)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -53,12 +60,28 @@ export class NodeMap {
|
||||
return null
|
||||
}
|
||||
}
|
||||
if (id.includes(NodeMap.DUPLICATE_ID_CHAR)) {
|
||||
const baseId = id.substring(0, id.indexOf(NodeMap.DUPLICATE_ID_CHAR))
|
||||
if (this.duplicates[baseId]) {
|
||||
if (this.duplicates[baseId][id]) {
|
||||
return [this.duplicates[baseId][id]]
|
||||
}
|
||||
} else {
|
||||
Logger.warn('Could not find duplicate with baseID: ', baseId)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
if (this.all[id]) {
|
||||
if (this.duplicates[id]) {
|
||||
return [this.all[id], ...Object.values(this.duplicates[id])]
|
||||
}
|
||||
return [this.all[id]]
|
||||
}
|
||||
if (this.instances[id]) {
|
||||
return Object.values(this.instances[id])
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -67,6 +90,14 @@ export class NodeMap {
|
||||
}
|
||||
|
||||
public hasId(id: string): boolean {
|
||||
return this.hasNodeId(id) || this.hasInstanceId(id)
|
||||
}
|
||||
|
||||
public hasNodeId(id: string): boolean {
|
||||
return this.all[id] !== undefined
|
||||
}
|
||||
|
||||
public hasInstanceId(id: string): boolean {
|
||||
if (id.includes(NodeMap.COMPOUND_ID_CHAR)) {
|
||||
const baseId = id.substring(0, id.indexOf(NodeMap.COMPOUND_ID_CHAR))
|
||||
if (this.instances[baseId]) {
|
||||
@@ -75,12 +106,6 @@ export class NodeMap {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if (this.all[id]) {
|
||||
return true
|
||||
}
|
||||
if (this.instances[id]) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -95,6 +120,17 @@ export class NodeMap {
|
||||
this.instances[baseId][node.model.id] = node
|
||||
}
|
||||
|
||||
private registerDuplicate(node: TreeNode): void {
|
||||
const baseId = node.model.id.substring(
|
||||
0,
|
||||
node.model.id.indexOf(NodeMap.DUPLICATE_ID_CHAR)
|
||||
)
|
||||
if (!this.duplicates[baseId]) {
|
||||
this.duplicates[baseId] = {}
|
||||
}
|
||||
this.duplicates[baseId][node.model.id] = node
|
||||
}
|
||||
|
||||
private registerNode(node: TreeNode) {
|
||||
this.all[node.model.id] = node
|
||||
}
|
||||
|
||||
@@ -174,6 +174,10 @@ export class RenderTree {
|
||||
return this.tree.getInstances(this.root.model.subtreeId)
|
||||
}
|
||||
|
||||
public getDuplicates() {
|
||||
return this.tree.getDuplicates(this.root.model.subtreeId)
|
||||
}
|
||||
|
||||
public getRenderableRenderViews(...types: SpeckleType[]): NodeRenderView[] {
|
||||
return this.getRenderableNodes(...types).map(
|
||||
(val: TreeNode) => val.model.renderView
|
||||
|
||||
@@ -119,6 +119,18 @@ export class WorldTree {
|
||||
}
|
||||
}
|
||||
|
||||
public hasNodeId(id: string, subtreeId: number = 1) {
|
||||
return this.nodeMaps[subtreeId] && this.nodeMaps[subtreeId].hasNodeId(id)
|
||||
}
|
||||
|
||||
public hasInstanceId(id: string, subtreeId: number = 1) {
|
||||
return this.nodeMaps[subtreeId] && this.nodeMaps[subtreeId].hasInstanceId(id)
|
||||
}
|
||||
|
||||
public hasId(id: string, subtreeId: number = 1) {
|
||||
return this.nodeMaps[subtreeId] && this.nodeMaps[subtreeId].hasId(id)
|
||||
}
|
||||
|
||||
public findAll(predicate: SearchPredicate, node?: TreeNode): Array<TreeNode> {
|
||||
if (!node && !this.supressWarnings) {
|
||||
Logger.warn(`Root will be used for searching. You might not want that`)
|
||||
@@ -158,6 +170,10 @@ export class WorldTree {
|
||||
return this.nodeMaps[subtreeId].instances
|
||||
}
|
||||
|
||||
public getDuplicates(subtreeId: string): { [id: string]: Record<string, TreeNode> } {
|
||||
return this.nodeMaps[subtreeId].duplicates
|
||||
}
|
||||
|
||||
/** TO DO: We might want to add boolean as return type here too */
|
||||
public walk(predicate: SearchPredicate, node?: TreeNode): void {
|
||||
if (!node && !this.supressWarnings) {
|
||||
|
||||
@@ -40,11 +40,17 @@ spec:
|
||||
livenessProbe:
|
||||
initialDelaySeconds: 60
|
||||
periodSeconds: 60
|
||||
{{- if .Values.featureFlags.nextGenFileImporterEnabled }}
|
||||
httpGet:
|
||||
path: /healthz
|
||||
port: 9080
|
||||
{{- else }}
|
||||
exec:
|
||||
command:
|
||||
- /usr/bin/node
|
||||
- -e
|
||||
- "process.exit((Date.now() - require('fs').readFileSync('/tmp/last_successful_query', 'utf8') > 25 * 60 * 1000) ? 1 : 0)"
|
||||
{{- end }}
|
||||
|
||||
resources:
|
||||
{{- with .Values.fileimport_service.requests }}
|
||||
|
||||
Reference in New Issue
Block a user