Improve Sentry support (#11776)

- Close https://github.com/enso-org/cloud-v2/issues/1590
- Add source maps to built bundle for sentry to pick up
- Close https://github.com/enso-org/cloud-v2/issues/1589
- Add global Sentry filter to avoid sending 400 responses from backend
- Wrap settings user and organization profile picture inputs in form
- Upgrade from deprecated sentry APIs

# Important Notes
None
This commit is contained in:
somebody1234 2024-12-19 19:26:09 +10:00 committed by GitHub
parent 42cfa69c7e
commit 20c066ac65
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 528 additions and 451 deletions

View File

@ -701,10 +701,13 @@ jobs:
ENSO_CLOUD_ENVIRONMENT: ${{ vars.ENSO_CLOUD_ENVIRONMENT }}
ENSO_CLOUD_GOOGLE_ANALYTICS_TAG: ${{ vars.ENSO_CLOUD_GOOGLE_ANALYTICS_TAG }}
ENSO_CLOUD_SENTRY_DSN: ${{ vars.ENSO_CLOUD_SENTRY_DSN }}
ENSO_CLOUD_SENTRY_ORGANIZATION: ${{ vars.ENSO_CLOUD_SENTRY_ORGANIZATION }}
ENSO_CLOUD_SENTRY_PROJECT: ${{ vars.ENSO_CLOUD_SENTRY_PROJECT }}
ENSO_CLOUD_STRIPE_KEY: ${{ vars.ENSO_CLOUD_STRIPE_KEY }}
ENSO_IDE_AG_GRID_LICENSE_KEY: ${{ vars.ENSO_AG_GRID_LICENSE_KEY }}
ENSO_IDE_MAPBOX_API_TOKEN: ${{ vars.ENSO_MAPBOX_API_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
WIN_CSC_KEY_PASSWORD: ${{ secrets.MICROSOFT_CODE_SIGNING_CERT_PASSWORD }}
WIN_CSC_LINK: ${{ secrets.MICROSOFT_CODE_SIGNING_CERT }}
- if: failure() && runner.os == 'Windows'

3
app/gui/.gitignore vendored
View File

@ -33,3 +33,6 @@ src/project-view/util/iconName.ts
*storybook.log
storybook-static
# Sentry Config File
.env.sentry-build-plugin

View File

@ -15,10 +15,13 @@
"bugs": {
"url": "https://github.com/enso-org/enso/issues"
},
"//": {},
"//": [
"--max-old-space-size=4096 is required when sourcemaps are enabled,",
"otherwise Rollup runs out of memory when Vite is rendering chunks."
],
"scripts": {
"typecheck": "vue-tsc --noEmit -p tsconfig.app.json",
"build": "vite build",
"build": "cross-env NODE_OPTIONS=--max-old-space-size=4096 vite build",
"build-cloud": "cross-env CLOUD_BUILD=true corepack pnpm run build",
"preview": "vite preview",
"lint": "eslint . --cache --max-warnings=0",
@ -41,6 +44,11 @@
"playwright:install": "playwright install chromium"
},
"dependencies": {
"@ag-grid-community/client-side-row-model": "^32.3.3",
"@ag-grid-community/core": "^32.3.3",
"@ag-grid-community/styles": "^32.3.3",
"@ag-grid-enterprise/core": "^32.3.3",
"@ag-grid-enterprise/range-selection": "^32.3.3",
"@aws-amplify/auth": "5.6.5",
"@aws-amplify/core": "5.8.5",
"@hookform/resolvers": "^3.4.0",
@ -48,24 +56,46 @@
"@lexical/html": "^0.21.0",
"@lexical/link": "^0.21.0",
"@lexical/markdown": "^0.21.0",
"@lezer/common": "^1.1.0",
"@lezer/highlight": "^1.1.6",
"@monaco-editor/react": "4.6.0",
"@sentry/react": "^7.74.0",
"@noble/hashes": "^1.4.0",
"@react-aria/interactions": "^3.22.3",
"@sentry/react": "^7.74.0",
"@sentry/vite-plugin": "^2.22.7",
"@stripe/react-stripe-js": "^2.7.1",
"@stripe/stripe-js": "^3.5.0",
"@tanstack/react-query": "5.55.0",
"@tanstack/vue-query": ">= 5.54.0 < 5.56.0",
"@vueuse/core": "^10.4.1",
"@vueuse/gesture": "^2.0.0",
"ag-grid-community": "^32.3.3",
"ag-grid-enterprise": "^32.3.3",
"ag-grid-vue3": "^32.3.3",
"ajv": "^8.12.0",
"amazon-cognito-identity-js": "6.3.6",
"babel-plugin-react-compiler": "19.0.0-beta-a7bf2bd-20241110",
"clsx": "^2.1.1",
"papaparse": "^5.4.1",
"codemirror": "^6.0.1",
"culori": "^3.2.0",
"enso-common": "workspace:*",
"events": "^3.3.0",
"framer-motion": "11.3.0",
"hash-sum": "^2.0.0",
"idb-keyval": "^6.2.1",
"input-otp": "1.2.4",
"install": "^0.13.0",
"is-network-error": "^1.0.1",
"isomorphic-ws": "^5.0.0",
"lexical": "^0.21.0",
"lib0": "^0.2.85",
"magic-string": "^0.30.3",
"marked": "14.1.3",
"monaco-editor": "0.48.0",
"murmurhash": "^2.0.1",
"papaparse": "^5.4.1",
"postcss-inline-svg": "^6.0.0",
"postcss-nesting": "^12.0.1",
"qrcode.react": "3.1.0",
"react": "^18.3.1",
"react-aria": "^3.34.3",
@ -83,15 +113,7 @@
"tiny-invariant": "^1.3.3",
"ts-results": "^3.3.0",
"validator": "^13.12.0",
"zod": "^3.23.8",
"zustand": "^4.5.4",
"@ag-grid-community/client-side-row-model": "^32.3.3",
"@ag-grid-community/core": "^32.3.3",
"@ag-grid-community/styles": "^32.3.3",
"@ag-grid-enterprise/core": "^32.3.3",
"@ag-grid-enterprise/range-selection": "^32.3.3",
"@babel/parser": "^7.24.7",
"babel-plugin-react-compiler": "19.0.0-beta-a7bf2bd-20241110",
"@codemirror/commands": "^6.7.1",
"@codemirror/language": "^6.10.6",
"@codemirror/lang-markdown": "^v6.3.0",
@ -101,25 +123,6 @@
"@codemirror/view": "^6.35.3",
"@fast-check/vitest": "^0.0.8",
"@floating-ui/vue": "^1.0.6",
"@lezer/common": "^1.1.0",
"@lezer/highlight": "^1.1.6",
"@noble/hashes": "^1.4.0",
"@vueuse/core": "^10.4.1",
"@vueuse/gesture": "^2.0.0",
"ag-grid-community": "^32.3.3",
"ag-grid-enterprise": "^32.3.3",
"ag-grid-vue3": "^32.3.3",
"codemirror": "^6.0.1",
"culori": "^3.2.0",
"events": "^3.3.0",
"hash-sum": "^2.0.0",
"install": "^0.13.0",
"isomorphic-ws": "^5.0.0",
"lib0": "^0.2.85",
"magic-string": "^0.30.3",
"murmurhash": "^2.0.1",
"postcss-inline-svg": "^6.0.0",
"postcss-nesting": "^12.0.1",
"sucrase": "^3.34.0",
"veaury": "^2.3.18",
"vue": "^3.5.2",
@ -129,14 +132,19 @@
"y-websocket": "^1.5.0",
"ydoc-shared": "workspace:*",
"yjs": "^13.6.7",
"marked": "14.1.3"
"zod": "^3.23.8",
"zustand": "^4.5.4"
},
"devDependencies": {
"@chromatic-com/storybook": "^3.2.2",
"@fast-check/vitest": "^0.0.8",
"@modyfi/vite-plugin-yaml": "^1.0.4",
"@playwright/test": "^1.40.0",
"@babel/plugin-syntax-import-attributes": "^7.24.7",
"@chromatic-com/storybook": "^3.2.2",
"@codemirror/theme-one-dark": "^6.1.2",
"@danmarshall/deckgl-typings": "^4.9.28",
"@fast-check/vitest": "^0.0.8",
"@histoire/plugin-vue": "^0.17.12",
"@modyfi/vite-plugin-yaml": "^1.0.4",
"@open-rpc/server-js": "^1.9.4",
"@playwright/test": "^1.40.0",
"@react-types/shared": "^3.22.1",
"@storybook/addon-essentials": "^8.4.2",
"@storybook/addon-interactions": "^8.4.2",
@ -148,30 +156,10 @@
"@storybook/vue3": "^8.4.2",
"@storybook/vue3-vite": "^8.4.2",
"@tanstack/react-query-devtools": "5.45.1",
"@types/node": "^22.9.0",
"@types/papaparse": "^5.3.15",
"@types/react": "^18.0.27",
"@types/react-dom": "^18.0.10",
"@types/validator": "^13.11.7",
"@vitejs/plugin-react": "^4.3.3",
"chalk": "^5.3.0",
"cross-env": "^7.0.3",
"fast-check": "^3.15.0",
"playwright": "^1.39.0",
"postcss": "^8.4.29",
"prettier-plugin-organize-imports": "^4.0.0",
"prettier-plugin-tailwindcss": "^0.5.11",
"react-toastify": "^9.1.3",
"tailwindcss": "^3.4.1",
"tailwindcss-animate": "1.0.7",
"tailwindcss-react-aria-components": "^1.1.1",
"typescript": "^5.5.3",
"vite": "^5.4.10",
"vitest": "^1.3.1",
"@codemirror/theme-one-dark": "^6.1.2",
"@danmarshall/deckgl-typings": "^4.9.28",
"@histoire/plugin-vue": "^0.17.12",
"@open-rpc/server-js": "^1.9.4",
"@testing-library/jest-dom": "6.6.3",
"@testing-library/react": "16.0.1",
"@testing-library/react-hooks": "8.0.1",
"@testing-library/user-event": "14.5.2",
"@tsconfig/node20": "^20.1.4",
"@types/css.escape": "^1.5.2",
"@types/culori": "^2.0.1",
@ -179,37 +167,53 @@
"@types/hash-sum": "^1.0.0",
"@types/jsdom": "^21.1.1",
"@types/mapbox-gl": "^3.4.1",
"@types/node": "^22.9.0",
"@types/papaparse": "^5.3.15",
"@types/react": "^18.0.27",
"@types/react-dom": "^18.0.10",
"@types/shuffle-seed": "^1.1.0",
"@types/tar": "^6.1.4",
"@types/validator": "^13.11.7",
"@types/wicg-file-system-access": "^2023.10.2",
"@types/ws": "^8.5.5",
"@vitejs/plugin-react": "^4.3.3",
"@vitejs/plugin-vue": "^5.0.4",
"@vitest/coverage-v8": "^1.3.1",
"@vue/test-utils": "^2.4.6",
"@vue/tsconfig": "^0.5.1",
"@testing-library/jest-dom": "6.6.3",
"@testing-library/react": "16.0.1",
"@testing-library/user-event": "14.5.2",
"@testing-library/react-hooks": "8.0.1",
"chalk": "^5.3.0",
"chromatic": "11.18.1",
"cross-env": "^7.0.3",
"css.escape": "^1.5.1",
"d3": "^7.4.0",
"enso-common": "workspace:*",
"fast-check": "^3.15.0",
"floating-vue": "^2.0.0-beta.24",
"hash-wasm": "^4.11.0",
"histoire": "^0.17.2",
"jsdom": "^24.1.0",
"playwright": "^1.39.0",
"postcss": "^8.4.29",
"postcss-nesting": "^12.0.1",
"prettier": "^3.3.2",
"prettier-plugin-organize-imports": "^4.0.0",
"prettier-plugin-tailwindcss": "^0.5.11",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-toastify": "^9.1.3",
"shuffle-seed": "^1.1.6",
"sql-formatter": "^13.0.0",
"storybook": "^8.4.2",
"chromatic": "11.18.1",
"tailwindcss": "^3.4.1",
"tailwindcss-animate": "1.0.7",
"tailwindcss-react-aria-components": "^1.1.1",
"tar": "^6.2.1",
"tsx": "^4.7.1",
"typescript": "^5.5.3",
"vite": "^5.4.10",
"vite-plugin-vue-devtools": "7.6.3",
"vite-plugin-wasm": "^3.3.0",
"vitest": "^1.3.1",
"vue-react-wrapper": "^0.3.1",
"vue-tsc": "^2.0.24",
"yaml": "^2.4.5",

View File

@ -0,0 +1,62 @@
/** @file A hidden file input. */
import {
Form,
type FieldPath,
type FieldProps,
type FieldStateProps,
type FieldValues,
type FieldVariantProps,
type TSchema,
} from '#/components/AriaComponents'
import { Input, type InputProps } from '#/components/aria'
/** Props for {@link HiddenFile}. */
export interface HiddenFileProps<Schema extends TSchema, TFieldName extends FieldPath<Schema>>
extends FieldStateProps<
Omit<InputProps, 'children' | 'value'> & { value: FieldValues<Schema>[TFieldName] },
Schema,
TFieldName
>,
FieldProps,
FieldVariantProps {
/** When true, triggers `form.submit()` on input. */
readonly autoSubmit?: boolean | undefined
readonly accept?: string | undefined
}
/** A hidden file input. */
export function HiddenFile<Schema extends TSchema, TFieldName extends FieldPath<Schema>>(
props: HiddenFileProps<Schema, TFieldName>,
) {
const {
form,
autoSubmit = false,
accept,
// eslint-disable-next-line @typescript-eslint/naming-convention
defaultValue: _defaultValue,
// eslint-disable-next-line @typescript-eslint/naming-convention
disabled: _disabled,
...inputProps
} = props
const formInstance = Form.useFormContext(form)
return (
<Form.Controller
{...inputProps}
control={formInstance.control}
render={({ field }) => (
<Input
type="file"
className="focus-child w-0"
accept={accept}
onChange={(event) => {
field.onChange(event.target.files?.[0])
if (autoSubmit) {
void formInstance.submit()
}
}}
/>
)}
/>
)
}

View File

@ -0,0 +1,2 @@
/** @file Barrel file for HiddenFile component. */
export * from './HiddenFile'

View File

@ -7,6 +7,7 @@
export * from './ComboBox'
export * from './DatePicker'
export * from './Dropdown'
export * from './HiddenFile'
export * from './Input'
export * from './MultiSelector'
export * from './OTPInput'

View File

@ -3,12 +3,17 @@
*
* This module declares the main DOM structure for the authentication/dashboard app.
*/
import * as React from 'react'
import { startTransition, StrictMode, useEffect } from 'react'
import * as sentry from '@sentry/react'
import { QueryClientProvider } from '@tanstack/react-query'
import * as reactDOM from 'react-dom/client'
import * as reactRouter from 'react-router-dom'
import {
createRoutesFromChildren,
matchRoutes,
useLocation,
useNavigationType,
} from 'react-router-dom'
import invariant from 'tiny-invariant'
import * as detect from 'enso-common/src/detect'
@ -32,6 +37,9 @@ import { MotionGlobalConfig } from 'framer-motion'
export type { GraphEditorRunner } from '#/layouts/Editor'
const HTTP_STATUS_BAD_REQUEST = 400
const API_HOST =
process.env.ENSO_CLOUD_API_URL != null ? new URL(process.env.ENSO_CLOUD_API_URL).host : null
const ARE_ANIMATIONS_DISABLED =
window.DISABLE_ANIMATIONS === true ||
localStorage.getItem('disableAnimations') === 'true' ||
@ -54,23 +62,15 @@ const ROOT_ELEMENT_ID = 'enso-dashboard'
/** The fraction of non-erroring interactions that should be sampled by Sentry. */
const SENTRY_SAMPLE_RATE = 0.005
// ======================
// === DashboardProps ===
// ======================
/** Props for the dashboard. */
export interface DashboardProps extends app.AppProps {
readonly logger: Logger
}
// ===========
// === run ===
// ===========
/**
* Entrypoint for the authentication/dashboard app.
*
* Running this function finds a `div` element with the ID `dashboard`, and renders the
* Running this function finds a `div` element with the ID `enso-dashboard`, and renders the
* authentication/dashboard UI using React. It also handles routing and other interactions (e.g.,
* for redirecting the user to/from the login page).
*/
@ -81,18 +81,18 @@ export function run(props: DashboardProps) {
process.env.ENSO_CLOUD_SENTRY_DSN != null &&
process.env.ENSO_CLOUD_API_URL != null
) {
const version: unknown = import.meta.env.ENSO_IDE_VERSION
sentry.init({
dsn: process.env.ENSO_CLOUD_SENTRY_DSN,
environment: process.env.ENSO_CLOUD_ENVIRONMENT,
release: version?.toString() ?? 'dev',
integrations: [
new sentry.BrowserTracing({
routingInstrumentation: sentry.reactRouterV6Instrumentation(
React.useEffect,
reactRouter.useLocation,
reactRouter.useNavigationType,
reactRouter.createRoutesFromChildren,
reactRouter.matchRoutes,
),
sentry.reactRouterV6BrowserTracingIntegration({
useEffect,
useLocation,
useNavigationType,
createRoutesFromChildren,
matchRoutes,
}),
sentry.extraErrorDataIntegration({ captureErrorCause: true }),
sentry.replayIntegration(),
@ -103,6 +103,22 @@ export function run(props: DashboardProps) {
tracePropagationTargets: [process.env.ENSO_CLOUD_API_URL.split('//')[1] ?? ''],
replaysSessionSampleRate: SENTRY_SAMPLE_RATE,
replaysOnErrorSampleRate: 1.0,
beforeSend: (event) => {
if (
(event.breadcrumbs ?? []).some(
(breadcrumb) =>
breadcrumb.type === 'http' &&
breadcrumb.category === 'fetch' &&
breadcrumb.data &&
breadcrumb.data.status_code === HTTP_STATUS_BAD_REQUEST &&
typeof breadcrumb.data.url === 'string' &&
new URL(breadcrumb.data.url).host === API_HOST,
)
) {
return null
}
return event
},
})
}
@ -123,9 +139,9 @@ export function run(props: DashboardProps) {
const httpClient = new HttpClient()
React.startTransition(() => {
startTransition(() => {
reactDOM.createRoot(root).render(
<React.StrictMode>
<StrictMode>
<QueryClientProvider client={queryClient}>
<ErrorBoundary>
<UIProviders locale="en-US" portalRoot={portalRoot}>
@ -143,7 +159,7 @@ export function run(props: DashboardProps) {
</UIProviders>
</ErrorBoundary>
</QueryClientProvider>
</React.StrictMode>,
</StrictMode>,
)
})
}

View File

@ -28,7 +28,7 @@ export default function InfoMenu(props: InfoMenuProps) {
const { getText } = useText()
return (
<Popover {...(!hidden ? { testId: 'info-menu' } : {})} size="xxsmall">
<Popover {...(!hidden ? { 'data-testid': 'info-menu' } : {})} size="xxsmall">
<div className="mb-2 flex items-center gap-icons overflow-hidden px-menu-entry transition-all duration-user-menu">
<SvgMask src={LogoIcon} className="pointer-events-none h-7 w-7 text-primary" />
<Text>{PRODUCT_NAME}</Text>

View File

@ -1,18 +1,16 @@
/** @file The input for viewing and changing the organization's profile picture. */
import * as React from 'react'
import { useMutation } from '@tanstack/react-query'
import DefaultUserIcon from '#/assets/default_user.svg'
import { backendMutationOptions, useBackendQuery } from '#/hooks/backendHooks'
import * as toastAndLogHooks from '#/hooks/toastAndLogHooks'
import * as textProvider from '#/providers/TextProvider'
import * as aria from '#/components/aria'
import FocusRing from '#/components/styled/FocusRing'
import { Form, HiddenFile } from '#/components/AriaComponents'
import type Backend from '#/services/Backend'
// =======================================
@ -29,28 +27,22 @@ export default function OrganizationProfilePictureInput(
props: OrganizationProfilePictureInputProps,
) {
const { backend } = props
const toastAndLog = toastAndLogHooks.useToastAndLog()
const { getText } = textProvider.useText()
const { data: organization } = useBackendQuery(backend, 'getOrganization', [])
const uploadOrganizationPicture = useMutation(
backendMutationOptions(backend, 'uploadOrganizationPicture'),
).mutate
)
const doUploadOrganizationPicture = (event: React.ChangeEvent<HTMLInputElement>) => {
const image = event.target.files?.[0]
if (image == null) {
toastAndLog('noNewProfilePictureError')
} else {
uploadOrganizationPicture([{ fileName: image.name }, image])
}
// Reset selected files, otherwise the file input will do nothing if the same file is
// selected again. While technically not undesired behavior, it is unintuitive for the user.
event.target.value = ''
}
const form = Form.useForm({
schema: (z) => z.object({ picture: z.instanceof(File) }),
onSubmit: async ({ picture }) => {
await uploadOrganizationPicture.mutateAsync([{ fileName: picture.name }, picture])
},
})
return (
<>
<Form form={form}>
<FocusRing within>
<aria.Label
data-testid="organization-profile-picture-input"
@ -60,17 +52,12 @@ export default function OrganizationProfilePictureInput(
src={organization?.picture ?? DefaultUserIcon}
className="pointer-events-none h-full w-full"
/>
<aria.Input
type="file"
className="focus-child w-0"
accept="image/*"
onChange={doUploadOrganizationPicture}
/>
<HiddenFile autoSubmit form={form} name="picture" />
</aria.Label>
</FocusRing>
<aria.Text className="w-profile-picture-caption py-profile-picture-caption-y">
{getText('organizationProfilePictureWarning')}
</aria.Text>
</>
</Form>
)
}

View File

@ -6,13 +6,13 @@ import { useMutation } from '@tanstack/react-query'
import DefaultUserIcon from '#/assets/default_user.svg'
import { backendMutationOptions, useBackendQuery } from '#/hooks/backendHooks'
import * as toastAndLogHooks from '#/hooks/toastAndLogHooks'
import * as textProvider from '#/providers/TextProvider'
import * as aria from '#/components/aria'
import FocusRing from '#/components/styled/FocusRing'
import { Form, HiddenFile } from '#/components/AriaComponents'
import type Backend from '#/services/Backend'
// ===========================
@ -27,26 +27,20 @@ export interface ProfilePictureInputProps {
/** The input for viewing and changing the user's profile picture. */
export default function ProfilePictureInput(props: ProfilePictureInputProps) {
const { backend } = props
const toastAndLog = toastAndLogHooks.useToastAndLog()
const { data: user } = useBackendQuery(backend, 'usersMe', [])
const { getText } = textProvider.useText()
const uploadUserPicture = useMutation(backendMutationOptions(backend, 'uploadUserPicture')).mutate
const uploadUserPicture = useMutation(backendMutationOptions(backend, 'uploadUserPicture'))
const doUploadUserPicture = (event: React.ChangeEvent<HTMLInputElement>) => {
const image = event.target.files?.[0]
if (image == null) {
toastAndLog('noNewProfilePictureError')
} else {
uploadUserPicture([{ fileName: image.name }, image])
}
// Reset selected files, otherwise the file input will do nothing if the same file is
// selected again. While technically not undesired behavior, it is unintuitive for the user.
event.target.value = ''
}
const form = Form.useForm({
schema: (z) => z.object({ picture: z.instanceof(File) }),
onSubmit: async ({ picture }) => {
await uploadUserPicture.mutateAsync([{ fileName: picture.name }, picture])
},
})
return (
<>
<Form form={form}>
<FocusRing within>
<aria.Label
data-testid="user-profile-picture-input"
@ -56,17 +50,12 @@ export default function ProfilePictureInput(props: ProfilePictureInputProps) {
src={user?.profilePicture ?? DefaultUserIcon}
className="pointer-events-none h-full w-full"
/>
<aria.Input
type="file"
className="focus-child w-0"
accept="image/*"
onChange={doUploadUserPicture}
/>
<HiddenFile autoSubmit form={form} name="picture" />
</aria.Label>
</FocusRing>
<aria.Text className="w-profile-picture-caption py-profile-picture-caption-y">
{getText('profilePictureWarning')}
</aria.Text>
</>
</Form>
)
}

View File

@ -1,3 +1,4 @@
import { sentryVitePlugin } from '@sentry/vite-plugin'
/// <reference types="histoire" />
import react from '@vitejs/plugin-react'
@ -67,6 +68,18 @@ export default defineConfig({
},
}),
...(process.env.NODE_ENV === 'development' ? [await projectManagerShim()] : []),
...((
process.env.SENTRY_AUTH_TOKEN != null &&
process.env.ENSO_CLOUD_SENTRY_ORGANIZATION != null &&
process.env.ENSO_CLOUD_SENTRY_PROJECT != null
) ?
[
sentryVitePlugin({
org: process.env.ENSO_CLOUD_SENTRY_ORGANIZATION,
project: process.env.ENSO_CLOUD_SENTRY_PROJECT,
}),
]
: []),
],
optimizeDeps: {
entries: fileURLToPath(new URL('./index.html', import.meta.url)),
@ -109,6 +122,7 @@ export default defineConfig({
build: {
// dashboard chunk size is larger than the default warning limit
chunkSizeWarningLimit: 700,
sourcemap: true,
},
})
async function projectManagerShim(): Promise<Plugin> {

View File

@ -150,6 +150,10 @@ pub mod secret {
// === Github Token ===
/// A token created for the `enso-ci` user.
pub const CI_PRIVATE_TOKEN: &str = "CI_PRIVATE_TOKEN";
// === Sentry ===
/// The authentication token for pushing source maps to Sentry.
pub const SENTRY_AUTH_TOKEN: &str = "SENTRY_AUTH_TOKEN";
}
pub mod variables {
@ -446,7 +450,7 @@ impl JobArchetype for UploadIde {
"ide upload --backend-source release --backend-release ${{env.ENSO_RELEASE_ID}} --sign-artifacts",
)
.cleaning(RELEASE_CLEANING_POLICY)
.customize(with_packaging_steps(target.0))
.customize(with_packaging_steps(target.0, job::PackagingTarget::Release))
.build_job("Build IDE", target)
}
}

View File

@ -154,6 +154,18 @@ pub fn expose_gui_vars(step: Step) -> Step {
expose_cloud_vars(step)
}
/// Expose variables for debugging purposes.
pub fn expose_debugging_vars(os: OS, step: Step) -> Step {
use crate::ide::web::env::*;
match os {
OS::Windows => step
.with_secret_exposed(secret::SENTRY_AUTH_TOKEN)
.with_variable_exposed(ENSO_CLOUD_SENTRY_ORGANIZATION)
.with_variable_exposed(ENSO_CLOUD_SENTRY_PROJECT),
_ => step,
}
}
#[derive(Clone, Copy, Debug)]
pub struct CancelWorkflow;
@ -552,13 +564,28 @@ pub fn expose_os_specific_signing_secret(os: OS, step: Step) -> Step {
}
}
/// Whether the artifact is being built for a release build (nightlies, releases)
/// or a development build (PRs).
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum PackagingTarget {
/// The artifact is being built for a release build (nightlies, releases).
Development,
/// The artifact is being built for a development build (PRs).
Release,
}
/// Prepares the packaging steps for the given OS.
///
/// This involves:
/// * exposing secrets necessary for code signing and notarization;
/// * exposing variables defining cloud environment for dashboard.
pub fn prepare_packaging_steps(os: OS, step: Step) -> Vec<Step> {
pub fn prepare_packaging_steps(os: OS, step: Step, packaging_target: PackagingTarget) -> Vec<Step> {
let step = expose_gui_vars(step);
let step = if packaging_target == PackagingTarget::Release {
expose_debugging_vars(os, step)
} else {
step
};
let step = expose_os_specific_signing_secret(os, step);
vec![step]
}
@ -566,8 +593,11 @@ pub fn prepare_packaging_steps(os: OS, step: Step) -> Vec<Step> {
/// Convenience for [`prepare_packaging_steps`].
///
/// This function is useful when you want to use [`prepare_packaging_steps`] as a closure.
pub fn with_packaging_steps(os: OS) -> impl FnOnce(Step) -> Vec<Step> {
move |step| prepare_packaging_steps(os, step)
pub fn with_packaging_steps(
os: OS,
packaging_target: PackagingTarget,
) -> impl FnOnce(Step) -> Vec<Step> {
move |step| prepare_packaging_steps(os, step, packaging_target)
}
#[derive(Clone, Copy, Debug)]
@ -579,7 +609,7 @@ impl JobArchetype for PackageIde {
"ide build --backend-source current-ci-run --gui-upload-artifact false",
)
.customize(move |step| {
let mut steps = prepare_packaging_steps(target.0, step);
let mut steps = prepare_packaging_steps(target.0, step, PackagingTarget::Development);
const TEST_COMMAND: &str = "corepack pnpm -r --filter enso exec playwright test";
let test_step = match target.0 {
OS::Linux => shell(format!("xvfb-run {TEST_COMMAND}"))

View File

@ -86,6 +86,12 @@ pub mod env {
/// The Google Analytics tag to which Google Analytics events should be sent.
ENSO_CLOUD_GOOGLE_ANALYTICS_TAG, String;
/// The Sentry organization to push the source maps to.
ENSO_CLOUD_SENTRY_ORGANIZATION, String;
/// The Sentry project to push the source maps to.
ENSO_CLOUD_SENTRY_PROJECT, String;
}
// GUI-specific environment variables

View File

@ -30,6 +30,9 @@
buildInputs = with pkgs; [
# === Graal dependencies ===
libxcrypt-legacy
# === Rust dependencies ===
openssl.dev
pkg-config
] ++ (if !isOnLinux then [
# === macOS-specific dependencies ===
darwin.apple_sdk.frameworks.IOKit # Required by `enso-formatter`.
@ -55,7 +58,7 @@
# `sccache` can be used to speed up compile times for Rust crates.
# `~/.cargo/bin/sccache` is provided by `cargo install sccache`.
# `~/.cargo/bin` must be in the `PATH` for the binary to be accessible.
export PATH=$SHIMS_PATH:${rust.out}:$HOME/.cargo/bin:$PATH
export PATH=$SHIMS_PATH:$HOME/.cargo/bin:$PATH
export LD_LIBRARY_PATH="${pkgs.lib.makeLibraryPath buildInputs}:$LD_LIBRARY_PATH"
# `rustup` shim

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
[toolchain]
channel = "nightly-2024-08-08"
components = ["clippy", "rustfmt"]
components = ["clippy", "rustfmt", "rust-src"]
profile = "default"
targets = ["wasm32-unknown-unknown"]