diff --git a/.circleci/build_publish_fe2_sourcemaps.sh b/.circleci/build_publish_fe2_sourcemaps.sh new file mode 100755 index 000000000..fa7e37693 --- /dev/null +++ b/.circleci/build_publish_fe2_sourcemaps.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +set -eo pipefail + +GIT_ROOT="$(git rev-parse --show-toplevel)" +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) + +# shellcheck disable=SC1090,SC1091 +source "${SCRIPT_DIR}/common.sh" + +FE2_DIR_PATH="${FE2_DIR_PATH:-"packages/frontend-2"}" +FE2_DATADOG_SERVICE="${FE2_DATADOG_SERVICE:-"web-app-2"}" +DATADOG_SITE="${DATADOG_SITE:-"datadoghq.eu"}" + +if [[ -z "${DATADOG_API_KEY}" ]]; then + echo "DATADOG_API_KEY is not set" + exit 1 +fi + +# Build same prod docker image just w/ sourcemaps enabled +export DOCKER_BUILDKIT=1 +docker build --build-arg BUILD_SOURCEMAPS=true --build-arg SPECKLE_SERVER_VERSION="${IMAGE_VERSION_TAG}" --tag "${DOCKER_IMAGE_TAG}:${IMAGE_VERSION_TAG}-sourcemaps" --file "${FE2_DIR_PATH}/Dockerfile" . +container_id=$(docker create "${DOCKER_IMAGE_TAG}:${IMAGE_VERSION_TAG}-sourcemaps") + +# Clean target location and copy sourcemaps into it +rm -rf "${GIT_ROOT}/${FE2_DIR_PATH}/.output" +docker cp "$container_id":/speckle-server "${GIT_ROOT}/${FE2_DIR_PATH}/.output" +docker rm "$container_id" + +# Publish sourcemaps +pushd "${GIT_ROOT}/${FE2_DIR_PATH}" +DATADOG_SITE="${DATADOG_SITE}" npx --yes @datadog/datadog-ci sourcemaps upload ./.output/public/_nuxt \ +--service="${FE2_DATADOG_SERVICE}" \ +--release-version="${IMAGE_VERSION_TAG}" \ +--minified-path-prefix=/_nuxt +popd + +# Clean up +rm -rf "${GIT_ROOT}/${FE2_DIR_PATH}/.output" diff --git a/.circleci/config.yml b/.circleci/config.yml index 6b78f11d7..db0f14761 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -44,6 +44,9 @@ workflows: - test-objectsender: filters: *filters-allow-all + - test-shared: + filters: *filters-allow-all + - test-preview-service: filters: *filters-allow-all @@ -184,6 +187,7 @@ workflows: - test-frontend-2 - test-viewer - test-objectsender + - test-shared - test-server - test-server-no-ff - test-server-multiregion @@ -200,11 +204,20 @@ workflows: - test-frontend-2 - test-viewer - test-objectsender + - test-shared - test-server - test-server-no-ff - test-server-multiregion - test-preview-service + - docker-publish-frontend-2-sourcemaps: + context: + - github-readonly-public-repos + - datadog-sourcemaps-publish + filters: *filters-publish + requires: + - get-version + - docker-publish-webhooks: context: *docker-hub-context filters: *filters-publish @@ -216,6 +229,7 @@ workflows: - test-frontend-2 - test-viewer - test-objectsender + - test-shared - test-server - test-server-no-ff - test-server-multiregion @@ -232,6 +246,7 @@ workflows: - test-frontend-2 - test-viewer - test-objectsender + - test-shared - test-server - test-server-no-ff - test-server-multiregion @@ -248,6 +263,7 @@ workflows: - test-frontend-2 - test-viewer - test-objectsender + - test-shared - test-server - test-server-no-ff - test-server-multiregion @@ -264,6 +280,7 @@ workflows: - test-frontend-2 - test-viewer - test-objectsender + - test-shared - test-server - test-server-no-ff - test-server-multiregion @@ -286,6 +303,7 @@ workflows: - test-frontend-2 - test-viewer - test-objectsender + - test-shared - test-server - test-server-no-ff - test-server-multiregion @@ -302,6 +320,7 @@ workflows: - test-frontend-2 - test-viewer - test-objectsender + - test-shared - test-server - test-server-no-ff - test-server-multiregion @@ -349,6 +368,7 @@ workflows: - test-frontend-2 - test-viewer - test-objectsender + - test-shared - test-preview-service - publish-viewer-sandbox-cloudflare-pages: @@ -358,16 +378,6 @@ workflows: requires: - test-viewer - - frontend-2-sourcemaps: - context: - - datadog-sourcemaps-publish - filters: *filters-publish - requires: - - get-version - - docker-build-frontend-2 - - test-frontend-2 - - publish-helm-chart - jobs: get-version: docker: &docker-base-image @@ -729,6 +739,36 @@ jobs: command: yarn test working_directory: 'packages/preview-service' + test-shared: + docker: *docker-node-browsers-image + resource_class: medium+ + steps: + - checkout + - restore_cache: + name: Restore Yarn Package Cache + keys: + - yarn-packages-server-{{ checksum "yarn.lock" }} + - run: + name: Install Dependencies + command: yarn + + - save_cache: + name: Save Yarn Package Cache + key: yarn-packages-server-{{ checksum "yarn.lock" }} + paths: + - .yarn/cache + - .yarn/unplugged + + - run: + name: Lint + command: yarn lint:ci + working_directory: 'packages/shared' + + - run: + name: Run tests + command: yarn test:single-run + working_directory: 'packages/shared' + test-objectsender: docker: *docker-node-browsers-image resource_class: large @@ -1005,6 +1045,24 @@ jobs: environment: SPECKLE_SERVER_PACKAGE: frontend-2 + docker-publish-frontend-2-sourcemaps: + docker: *docker-node-image + resource_class: xlarge + working_directory: *work-dir + environment: + SPECKLE_SERVER_PACKAGE: frontend-2 + steps: + - checkout + - attach_workspace: + at: /tmp/ci/workspace + - run: cat workspace/env-vars >> $BASH_ENV + - setup_remote_docker: + version: default + docker_layer_caching: true + - run: + name: Build and Save + command: ./.circleci/build_publish_fe2_sourcemaps.sh + docker-build-previews: <<: *build-job environment: @@ -1216,36 +1274,3 @@ jobs: environment: CLOUDFLARE_PAGES_PROJECT_NAME: viewer VIEWER_SANDBOX_DIR_PATH: packages/viewer-sandbox - - frontend-2-sourcemaps: - docker: *docker-node-image - resource_class: large - working_directory: *work-dir - steps: - - checkout - - attach_workspace: - at: /tmp/ci/workspace - - run: cat workspace/env-vars >> $BASH_ENV - - restore_cache: - name: Restore Yarn Package Cache - keys: - - yarn-packages-server-{{ checksum "yarn.lock" }} - - run: - name: Install Dependencies - command: yarn - - save_cache: - name: Save Yarn Package Cache - key: yarn-packages-server-{{ checksum "yarn.lock" }} - paths: - - .yarn/cache - - .yarn/unplugged - - run: - name: Build public packages - command: yarn build:public - - run: - name: Build FE2 - command: NODE_ENV=production SPECKLE_SERVER_VERSION="${IMAGE_VERSION_TAG}" yarn build:sourcemaps - working_directory: 'packages/frontend-2' - - run: - name: Upload source maps - command: ./.circleci/publish_fe2_sourcemaps.sh diff --git a/.circleci/publish_fe2_sourcemaps.sh b/.circleci/publish_fe2_sourcemaps.sh deleted file mode 100755 index aa51b2beb..000000000 --- a/.circleci/publish_fe2_sourcemaps.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env bash -set -eo pipefail - -GIT_ROOT="$(git rev-parse --show-toplevel)" -SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) -# shellcheck disable=SC1090,SC1091 -source "${SCRIPT_DIR}/common.sh" - -FE2_DIR_PATH="${FE2_DIR_PATH:-"packages/frontend-2"}" -FE2_DATADOG_SERVICE="${FE2_DATADOG_SERVICE:-"web-app-2"}" - -if [[ -z "${DATADOG_API_KEY}" ]]; then - echo "DATADOG_API_KEY is not set" - exit 1 -fi - -pushd "${GIT_ROOT}/${FE2_DIR_PATH}" -DATADOG_SITE="${DATADOG_SITE:-"datadoghq.eu"}" yarn datadog-ci sourcemaps upload ./.output/public/_nuxt \ ---service="${FE2_DATADOG_SERVICE}" \ ---release-version="${IMAGE_VERSION_TAG}" \ ---minified-path-prefix=/_nuxt -popd diff --git a/package.json b/package.json index 5964d2d2c..ccce840dc 100644 --- a/package.json +++ b/package.json @@ -63,6 +63,7 @@ "prettier": "^2.5.1", "ts-node": "^10.9.1", "tsconfig-paths": "^4.0.0", + "vitest": "^3.0.7", "zx": "^8.1.2" }, "resolutions": { @@ -91,7 +92,8 @@ "typescript": "^5.7.3", "typescript-eslint": "^8.20.0", "wait-on": ">=7.2.0", - "vue-tsc@npm:2.2.2/@vue/language-core": "2.2.0" + "vue-tsc@npm:2.2.2/@vue/language-core": "2.2.0", + "vitest": "^3.0.7" }, "config": { "commitizen": { diff --git a/packages/frontend-2/.env.example b/packages/frontend-2/.env.example index b2ff12ddf..6caf83c7b 100644 --- a/packages/frontend-2/.env.example +++ b/packages/frontend-2/.env.example @@ -37,8 +37,5 @@ NUXT_PUBLIC_ENABLE_AUTOMATE_MODULE=false # Survicate NUXT_PUBLIC_SURVICATE_WORKSPACE_KEY= -# Enable direct preview image loading - way quicker, but requres server & frontend to be on the same origin -NUXT_PUBLIC_ENABLE_DIRECT_PREVIEWS=true - # Ghost API NUXT_PUBLIC_GHOST_API_KEY= \ No newline at end of file diff --git a/packages/frontend-2/Dockerfile b/packages/frontend-2/Dockerfile index cc87bc112..73e0ad4a3 100644 --- a/packages/frontend-2/Dockerfile +++ b/packages/frontend-2/Dockerfile @@ -1,6 +1,7 @@ FROM node:18-bookworm-slim@sha256:408f8cbbb7b33a5bb94bdb8862795a94d2b64c2d516856824fd86c4a5594a443 AS build-stage ARG NODE_ENV=production ARG SPECKLE_SERVER_VERSION=custom +ARG BUILD_SOURCEMAPS=false WORKDIR /speckle-server @@ -30,8 +31,6 @@ COPY packages/frontend-2 ./packages/frontend-2/ RUN yarn workspaces focus -A # hadolint ignore=DL3059 RUN yarn workspaces foreach -W run build -# hadolint ignore=DL3059 -RUN find ./packages/frontend-2/.output/ -type f \( -name "*.js.map" -o -name "*.mjs.map" -o -name "*.cjs.map" \) -exec rm -f {} \; ENV TINI_VERSION v0.19.0 RUN apt-get update -y \ diff --git a/packages/frontend-2/codegen.ts b/packages/frontend-2/codegen.ts index 04279dfe8..afbde2729 100644 --- a/packages/frontend-2/codegen.ts +++ b/packages/frontend-2/codegen.ts @@ -23,6 +23,7 @@ const config: CodegenConfig = { './lib/common/generated/gql/': { preset: 'client', config: { + enumsAsConst: true, useTypeImports: true, fragmentMasking: false, dedupeFragments: true, diff --git a/packages/frontend-2/components/auth/RegisterWithEmailBlock.vue b/packages/frontend-2/components/auth/RegisterWithEmailBlock.vue index 2b339b982..96bc6bbd5 100644 --- a/packages/frontend-2/components/auth/RegisterWithEmailBlock.vue +++ b/packages/frontend-2/components/auth/RegisterWithEmailBlock.vue @@ -6,7 +6,7 @@ v-model="email" type="email" name="email" - label="Email" + label="Work email" placeholder="Email" size="lg" color="foundation" @@ -69,17 +69,14 @@ import { ToastNotificationType, useGlobalToast } from '~~/lib/common/composables import { ensureError } from '@speckle/shared' import { useAuthManager } from '~~/lib/auth/composables/auth' import { loginRoute } from '~~/lib/common/helpers/route' -import { passwordRules } from '~~/lib/auth/helpers/validation' +import { + passwordRules, + doesNotContainBlockedDomain +} from '~~/lib/auth/helpers/validation' import { graphql } from '~~/lib/common/generated/gql' import type { ServerTermsOfServicePrivacyPolicyFragmentFragment } from '~~/lib/common/generated/gql/graphql' import { useMounted } from '@vueuse/core' -/** - * TODO: - * - (BE) Password strength check? Do we want to use it anymore? - * - Dim's answer: no, `passwordRules` are legit enough for now. - */ - graphql(` fragment ServerTermsOfServicePrivacyPolicyFragment on ServerInfo { termsOfService @@ -99,13 +96,18 @@ const router = useRouter() const { signUpWithEmail, inviteToken } = useAuthManager() const { triggerNotification } = useGlobalToast() const isMounted = useMounted() +const isNoPersonalEmailsEnabled = useIsNoPersonalEmailsEnabled() const newsletterConsent = defineModel('newsletterConsent', { required: true }) const loading = ref(false) const password = ref('') const email = ref('') -const emailRules = [isEmail] +const emailRules = computed(() => + inviteToken.value || !isNoPersonalEmailsEnabled.value + ? [isEmail] + : [isEmail, doesNotContainBlockedDomain] +) const nameRules = [isRequired] const isEmailDisabled = computed(() => !!props.inviteEmail?.length || loading.value) diff --git a/packages/frontend-2/components/automate/runs/trigger-status/dialog/FunctionRun.vue b/packages/frontend-2/components/automate/runs/trigger-status/dialog/FunctionRun.vue index c344250d6..4a951242b 100644 --- a/packages/frontend-2/components/automate/runs/trigger-status/dialog/FunctionRun.vue +++ b/packages/frontend-2/components/automate/runs/trigger-status/dialog/FunctionRun.vue @@ -22,13 +22,7 @@
Function is {{ functionRun.status.toLowerCase() }}. @@ -141,4 +135,14 @@ const results = useAutomationFunctionRunResults({ const showAttachmentDialog = ref(false) const attachments = computed(() => results.value?.values.blobIds || []) + +const isStartingOrRunning = computed(() => + ( + [ + AutomateRunStatus.Initializing, + AutomateRunStatus.Running, + AutomateRunStatus.Pending + ] as string[] + ).includes(props.functionRun.status) +) diff --git a/packages/frontend-2/components/automate/viewer/panel/FunctionRunRow.vue b/packages/frontend-2/components/automate/viewer/panel/FunctionRunRow.vue index ace26f50f..774e02c23 100644 --- a/packages/frontend-2/components/automate/viewer/panel/FunctionRunRow.vue +++ b/packages/frontend-2/components/automate/viewer/panel/FunctionRunRow.vue @@ -156,11 +156,13 @@ const hasValidContextView = computed(() => { }) const statusMessage = computed(() => { - const isFinished = ![ - AutomateRunStatus.Initializing, - AutomateRunStatus.Running, - AutomateRunStatus.Pending - ].includes(props.functionRun.status) + const isFinished = !( + [ + AutomateRunStatus.Initializing, + AutomateRunStatus.Running, + AutomateRunStatus.Pending + ] as string[] + ).includes(props.functionRun.status) return isFinished ? props.functionRun.statusMessage ?? 'No status message' diff --git a/packages/frontend-2/components/common/TransitioningContents.vue b/packages/frontend-2/components/common/TransitioningContents.vue index 9c6f2f8c3..614d26772 100644 --- a/packages/frontend-2/components/common/TransitioningContents.vue +++ b/packages/frontend-2/components/common/TransitioningContents.vue @@ -3,7 +3,8 @@ import { waitIntervalUntil, type Nullable, timeoutAt, - WaitIntervalUntilCanceledError + WaitIntervalUntilCanceledError, + TimeoutError } from '@speckle/shared' import { until } from '@vueuse/core' import type { CSSProperties } from 'vue' @@ -26,6 +27,8 @@ export default defineComponent({ } }, setup(props, { slots, expose }) { + const logger = useLogger() + const transitioning = ref(false) const newWrapperRef = ref(null as Nullable) const oldWrapperRef = ref(null as Nullable) @@ -102,6 +105,8 @@ export default defineComponent({ * Cause default slot to update with an opacity transition */ const updateContents = async () => { + transitioning.value = true + // Stage 1: Just move new -> old w/o any transitions (visually should look the same) oldContents.value = newContents.value newContents.value = slots.default?.() @@ -155,15 +160,23 @@ export default defineComponent({ const triggerTransition = async () => { if (!transitioning.value) { - transitioning.value = true await updateContents() return } - await Promise.race([ - until(transitioning).toBe(false), - timeoutAt(props.duration + 1000) - ]) + try { + await Promise.race([ + until(transitioning).toBe(false), + timeoutAt(props.duration + 1000, 'Waiting for transition to finish timed out') + ]) + } catch (e) { + if (!(e instanceof TimeoutError)) { + throw e + } else { + logger.warn(e) + } + } + await updateContents() } diff --git a/packages/frontend-2/components/connectors/Page.vue b/packages/frontend-2/components/connectors/Page.vue index eaa38a347..80c1a2d07 100644 --- a/packages/frontend-2/components/connectors/Page.vue +++ b/packages/frontend-2/components/connectors/Page.vue @@ -36,6 +36,8 @@ class="md:min-w-80" allow-unset :items="categories" + size="base" + color="foundation" >