Skip to content

External Service Credentials #12454

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 63 commits into from
Apr 8, 2025
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
7157845
Fix Bazel on NixOS
somebody1234 Jan 16, 2025
40a5d0d
`useFormWithComponents`
somebody1234 Feb 4, 2025
04d3347
WIP: `SnowflakeCredentialsDialog`
somebody1234 Feb 4, 2025
db4414b
Initial implementation of `SnowflakeCredentialsDialog`
somebody1234 Feb 5, 2025
b3a7167
WIP: Add credentials dialog
somebody1234 Feb 7, 2025
d9bef42
WIP: Add `UpsertCredentialModal`
somebody1234 Feb 18, 2025
4922c97
[dashboard/UpsertCredentialModal] WIP: Integrate into dashboard
somebody1234 Feb 19, 2025
d3dc711
[dashboard/FormDropdown] Fix `selectedIndex` being wrong for objects
somebody1234 Feb 19, 2025
132b8e5
Merge branch 'develop' into wip/sb/credentials
somebody1234 Feb 20, 2025
0363555
[dashboard/CredentialsFormButtons] Refactor out of `SnowflakeCredenti…
somebody1234 Feb 21, 2025
50fb650
[dashboard/DriveBar] Add `subtype` to new credential function
somebody1234 Feb 21, 2025
3ce7c8c
[ts/Backend] Add `createCredential` method to backend
somebody1234 Feb 21, 2025
a3e599c
[dashboard/GoogleCredentialsDialog] WIP: Implement
somebody1234 Feb 21, 2025
b99b208
Merge branch 'develop' into wip/sb/credentials
somebody1234 Feb 21, 2025
91e185d
Add "new credential" context menu entry
somebody1234 Feb 26, 2025
f2f9c1c
[dashboard/GoogleCredentialsDialog] Remove grouped checkboxes
somebody1234 Mar 4, 2025
829f6f6
[dashboard/serviceCredentials] Rename `Dialog` to `Form`
somebody1234 Mar 4, 2025
e89588e
Merge branch 'develop' into wip/sb/credentials
somebody1234 Mar 7, 2025
cde3b2e
[dashboard/serviceCredentials] Rename `./hooks` to `./utilities`
somebody1234 Mar 7, 2025
7282bb4
[dashboard/serviceCredentials] Add OAuth URLs to credentials forms
somebody1234 Mar 7, 2025
7af8251
[ts/Backend] Fix shape for request to "create credential" endpoint
somebody1234 Mar 10, 2025
5dfbf2a
[nix] Update to Bazel 7.6.0
somebody1234 Mar 26, 2025
4c0bccb
Merge branch 'develop' into wip/sb/credentials
somebody1234 Mar 27, 2025
c05836a
[dashboard/useFormWithComponents] Return raw components with unsafe t…
somebody1234 Mar 27, 2025
9a8c6c1
[dashboard] Fix lint errors
somebody1234 Mar 27, 2025
c29d474
fixing urls
radeusgd Mar 28, 2025
bf8d3bc
WIP: trying to refactor dialog to avoid nested form
radeusgd Mar 28, 2025
af527ff
WIP: simplifying, towards separate forms
radeusgd Mar 31, 2025
c4689ab
WIP
radeusgd Apr 1, 2025
918478b
checkpoint: fixed changing forms
radeusgd Apr 1, 2025
c098702
some validations
radeusgd Apr 1, 2025
5f2b04d
proper forms submit logic for Google and Snowflake
radeusgd Apr 1, 2025
6516323
rename, log errors
radeusgd Apr 1, 2025
3219516
better location for error handling and invariant check BEFORE request
radeusgd Apr 1, 2025
ff91545
fixing paths and query schemas
radeusgd Apr 2, 2025
134e22b
for now mark role as required as otherwise auth seems to fail
radeusgd Apr 2, 2025
83d3021
mark client secret as password, try disabling autocomplete
radeusgd Apr 2, 2025
b76fd69
Merge branch 'develop' into wip/sb/credentials
radeusgd Apr 2, 2025
1b5b7b2
Merge remote-tracking branch 'origin/develop' into wip/sb/credentials
radeusgd Apr 2, 2025
4dc6c73
namespace for creds i18n
radeusgd Apr 2, 2025
1c533d7
some comments
radeusgd Apr 2, 2025
1fe1377
rename imports
radeusgd Apr 2, 2025
b40b1f4
update view (TODO but better change backend)
radeusgd Apr 2, 2025
dc0ce06
simplify schema, disallow editing creds
radeusgd Apr 2, 2025
2e8012b
some cleanup
radeusgd Apr 2, 2025
bbcf356
updating docs
radeusgd Apr 2, 2025
316f8f8
fmt
radeusgd Apr 2, 2025
66ba80d
fmt2
radeusgd Apr 2, 2025
a6658a8
remove unused util
radeusgd Apr 2, 2025
d85a753
display credential metadata in panel
radeusgd Apr 2, 2025
49d1ecc
fmt
radeusgd Apr 2, 2025
5f847b1
unflatten credential metadata in asset
radeusgd Apr 3, 2025
7b56d29
fmt
radeusgd Apr 3, 2025
024586f
Merge branch 'develop' into wip/sb/credentials
somebody1234 Apr 7, 2025
6f233c2
add Snowflake guide link placeholder
radeusgd Apr 7, 2025
8c7fff5
update link
radeusgd Apr 7, 2025
2a4be65
[dashboard/CreateCredentialModal] Split off `CreateCredentialForm`
somebody1234 Apr 7, 2025
b20d9ed
[dashboard/serviceCredentials] Inline credentials forms
somebody1234 Apr 7, 2025
5516186
[dashboard/backendHooks] Remove `useNewSecret`/`Credential`/`Datalink…
somebody1234 Apr 7, 2025
3afac54
[dashboard/CredentialsFormFooter] Add `FormError`
somebody1234 Apr 7, 2025
483b4d4
remove unused noDialog
radeusgd Apr 7, 2025
d2a1900
add icon for creds
radeusgd Apr 7, 2025
6104b64
fmt
radeusgd Apr 7, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app/common/src/backendQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export type BackendMutationMethod = DefineBackendMethods<
| 'closeProject'
| 'copyAsset'
| 'createCheckoutSession'
| 'createCredential'
| 'createDatalink'
| 'createDirectory'
| 'createPermission'
Expand Down
38 changes: 38 additions & 0 deletions app/common/src/services/Backend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1400,13 +1400,49 @@ export interface UpdateProjectExecutionRequestBody {
readonly enabled?: boolean | undefined
}

/** HTTP request body for the "create secret or credential" endpoint. */
export type CreateSecretOrCredentialRequestBody =
| CreateSecretRequestBody
| CreateCredentialRequestBody

/** HTTP request body for the "create secret" endpoint. */
export interface CreateSecretRequestBody {
readonly name: string
readonly value: string
readonly parentDirectoryId: DirectoryId | null
}

/** Metadata for a Snowflake credential. */
export interface SnowflakeCredentialInput {
readonly type: 'Snowflake'
readonly account: string
readonly client_id: string
readonly client_secret: string
readonly role: string | null
}

/** Metadata for a Google credential. */
export interface GoogleCredentialInput {
readonly type: 'Google'
readonly scopes: readonly string[]
}

/** Metadata for an arbitrary credential. */
export type CredentialInput = SnowflakeCredentialInput | GoogleCredentialInput

/** Metadata for an arbitrary credential, plus a nonce for authentication purposes. */
export interface CredentialMetadata {
readonly nonce: string
readonly input: CredentialInput
}

/** HTTP request body for the "create credential" endpoint. */
export interface CreateCredentialRequestBody {
readonly name: string
readonly value: CredentialMetadata
readonly parentDirectoryId: DirectoryId | null
}

/** HTTP request body for the "update secret" endpoint. */
export interface UpdateSecretRequestBody {
readonly title: string | null
Expand Down Expand Up @@ -1842,6 +1878,8 @@ export default abstract class Backend {
abstract deleteDatalink(datalinkId: DatalinkId, title: string | null): Promise<void>
/** Create a secret environment variable. */
abstract createSecret(body: CreateSecretRequestBody): Promise<SecretId>
/** Create an OAuth credential. */
abstract createCredential(body: CreateCredentialRequestBody): Promise<SecretId>
/** Return a secret environment variable. */
abstract getSecret(secretId: SecretId, title: string): Promise<Secret>
/** Change the value of a secret. */
Expand Down
1 change: 1 addition & 0 deletions app/common/src/text.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ interface PlaceholderOverrides {
readonly getDatalinkBackendError: [datalinkTitle: string]
readonly deleteDatalinkBackendError: [datalinkTitle: string]
readonly createSecretBackendError: [secretTitle: string]
readonly createCredentialBackendError: [credentialTitle: string]
readonly getSecretBackendError: [secretTitle: string]
readonly updateSecretBackendError: [secretTitle: string]
readonly createLabelBackendError: [labelName: string]
Expand Down
19 changes: 19 additions & 0 deletions app/common/src/text/english.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"createProjectError": "Could not create new project",
"createDatalinkError": "Could not create new Datalink",
"createSecretError": "Could not create new secret",
"createCredentialError": "Could not create new credential",
"renameFolderError": "Could not rename folder",
"renameProjectError": "Could not rename project",
"uploadProjectError": "Could not upload project",
Expand Down Expand Up @@ -165,6 +166,7 @@
"uploadFileWithNameBackendError": "Could not upload file '$0'",
"getFileDetailsBackendError": "Could not get details of project '$0'",
"createSecretBackendError": "Could not create secret with name '$0'",
"createCredentialBackendError": "Could not create credential with name '$0'",
"createDatalinkBackendError": "Could not create Datalink with name '$0'",
"getDatalinkBackendError": "Could not get Datalink '$0'",
"deleteDatalinkBackendError": "Could not delete Datalink '$0'",
Expand Down Expand Up @@ -305,6 +307,18 @@
"more": "More",
"close": "Close",

"account": "Account",
"clientId": "Client ID",
"clientSecret": "Client Secret",
"role": "Role",
"snowflakeCredentialType": "Snowflake",
"googleCredentialType": "Google",
"googleCredentialSheetsScope": "Sheets",
"googleCredentialSheetsReadScope": "Read sheets",
"googleCredentialSheetsWriteScope": "Write sheets",
"googleCredentialSheetsDriveBrowseScope": "Browse files on drive",
"googleCredentialAnalyticsScope": "Analytics",

"enterSecretPath": "Enter secret path",
"enterText": "Enter text",
"enterNumber": "Enter number",
Expand Down Expand Up @@ -358,6 +372,8 @@
"uploadFiles": "Import",
"downloadFiles": "Download",
"newDatalink": "New Datalink",
"newCredential": "New Credential",
"editCredential": "Edit Credential",
"newSecret": "New Secret",
"newLabel": "New Label",
"newEmptyProject": "New Project",
Expand Down Expand Up @@ -555,6 +571,8 @@
"secretValueHidden": "●●●●●●●●",
"secretNamePlaceholder": "Enter the name of the secret",
"secretValuePlaceholder": "Enter the value of the secret",
"credentialNamePlaceholder": "Enter the name of the credential",
"credentialTypeLabel": "Credential type",
"datalinkNamePlaceholder": "Enter the name of the Datalink",
"labelNamePlaceholder": "Enter the name of the label",
"labelSearchPlaceholder": "Type labels to search",
Expand Down Expand Up @@ -832,6 +850,7 @@
"newFolderShortcut": "New Folder",
"newDatalinkShortcut": "New Datalink",
"newSecretShortcut": "New Secret",
"newCredentialShortcut": "New Credential",
"useInNewProjectShortcut": "Use in New Project",
"closeModalShortcut": "Close",
"cancelEditNameShortcut": "Cancel Editing",
Expand Down
40 changes: 0 additions & 40 deletions app/gui/env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,6 @@
import type * as saveAccessToken from 'enso-common/src/accessToken'
import type { $Config } from './src/config'

// =============
// === Types ===
// =============

/** Nested configuration options with `string` values. */
interface StringConfig {
[key: string]: StringConfig | string
Expand All @@ -21,10 +17,6 @@ interface Enso {
readonly main: (inputConfig?: StringConfig) => Promise<void>
}

// ===================
// === Backend API ===
// ===================

/**
* `window.backendApi` is a context bridge to the main process, when we're running in an
* Electron context. It contains non-authentication-related functionality.
Expand All @@ -38,10 +30,6 @@ interface BackendApi {
) => Promise<ProjectInfo>
}

// ==========================
// === Authentication API ===
// ==========================

/**
* `window.authenticationApi` is a context bridge to the main process, when we're running in an
* Electron context.
Expand All @@ -65,10 +53,6 @@ interface AuthenticationApi {
readonly saveAccessToken: (accessToken: saveAccessToken.AccessToken | null) => void
}

// ======================
// === Navigation API ===
// ======================

/**
* `window.navigationApi` is a context bridge to the main process, when we're running in an
* Electron context. It contains navigation-related functionality.
Expand All @@ -80,30 +64,18 @@ interface NavigationApi {
readonly goForward: () => void
}

// ================
// === Menu API ===
// ================

/** `window.menuApi` exposes functionality related to the system menu. */
interface MenuApi {
/** Set the callback to be called when the "about" entry is clicked in the "help" menu. */
readonly setShowAboutModalHandler: (callback: () => void) => void
}

// ==================
// === System API ===
// ==================

/** `window.systemApi` exposes functionality related to the operating system. */
interface SystemApi {
readonly downloadURL: (url: string, headers?: Record<string, string>) => void
readonly showItemInFolder: (fullPath: string) => void
}

// ==============================
// === Project Management API ===
// ==============================

/** Metadata for a newly imported project. */
interface ProjectInfo {
readonly id: string
Expand All @@ -119,10 +91,6 @@ interface ProjectManagementApi {
readonly setOpenProjectHandler: (handler: (projectInfo: ProjectInfo) => void) => void
}

// ========================
// === File Browser API ===
// ========================

/**
* `window.fileBrowserApi` is a context bridge to the main process, when we're running in an
* Electron context.
Expand All @@ -144,10 +112,6 @@ interface FileBrowserApi {
) => Promise<string[] | undefined>
}

// ====================
// === Version Info ===
// ====================

/** Versions of the app, and selected software bundled with Electron. */
interface VersionInfo {
readonly version: string
Expand All @@ -156,10 +120,6 @@ interface VersionInfo {
readonly chrome: string
}

// =====================================
// === Global namespace augmentation ===
// =====================================

// JSDocs here are intentionally empty as these interfaces originate from elsewhere.
declare global {
const $config: $Config
Expand Down
1 change: 1 addition & 0 deletions app/gui/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const $config = {
YDOC_SERVER_URL: import.meta.env.ENSO_IDE_YDOC_SERVER_URL,
CLOUD_BUILD: import.meta.env.ENSO_IDE_CLOUD_BUILD,
AG_GRID_LICENSE_KEY: import.meta.env.ENSO_IDE_AG_GRID_LICENSE_KEY,
GOOGLE_OAUTH_CLIENT_ID: import.meta.env.ENSO_IDE_GOOGLE_OAUTH_CLIENT_ID,
MAPBOX_API_TOKEN: window.mapBoxApiToken?.() || import.meta.env.ENSO_IDE_MAPBOX_API_TOKEN,
} as const

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,29 +35,36 @@ import type { TestIdProps } from '../types'
import { CheckboxStandaloneProvider, useCheckboxContext } from './CheckboxContext'
import { CheckboxGroup } from './CheckboxGroup'

/** Props common between all {@link CheckboxProps} variants. */
interface CheckboxSharedProps
extends Omit<VariantProps<typeof CHECKBOX_STYLES>, 'isDisabled' | 'isInvalid'>,
TestIdProps {
readonly className?: string
readonly style?: CSSProperties
readonly checkboxRef?: MutableRefObject<HTMLInputElement>
}

/** Props for the {@link Checkbox} component. */
export type CheckboxProps<
Schema extends TSchema,
TFieldName extends FieldPath<Schema, boolean>,
> = Omit<VariantProps<typeof CHECKBOX_STYLES>, 'isDisabled' | 'isInvalid'> &
TestIdProps & {
readonly className?: string
readonly style?: CSSProperties
readonly checkboxRef?: MutableRefObject<HTMLInputElement>
} & (CheckboxGroupCheckboxProps | StandaloneCheckboxProps<Schema, TFieldName>)
export type CheckboxProps<Schema extends TSchema, TFieldName extends FieldPath<Schema, boolean>> =
| CheckboxGroupCheckboxProps
| StandaloneCheckboxProps<Schema, TFieldName>

/** Props for the {@link Checkbox} component when used inside a {@link CheckboxGroup}. */
interface CheckboxGroupCheckboxProps extends AriaCheckboxProps {
readonly value: string
readonly form?: never
readonly name?: never
}
export type CheckboxGroupCheckboxProps = AriaCheckboxProps &
CheckboxSharedProps & {
readonly value: string
readonly form?: never
readonly name?: never
}

/** Props for the {@link Checkbox} component when used outside of a {@link CheckboxGroup}. */
type StandaloneCheckboxProps<
export type StandaloneCheckboxProps<
Schema extends TSchema,
TFieldName extends FieldPath<Schema, boolean>,
> = FieldProps & FieldStateProps<AriaCheckboxProps, Schema, TFieldName, boolean> & FieldVariantProps
> = CheckboxSharedProps &
FieldProps &
FieldStateProps<AriaCheckboxProps, Schema, TFieldName, boolean> &
FieldVariantProps

export const CHECKBOX_STYLES = tv({
base: 'group flex gap-2 items-center cursor-pointer select-none',
Expand Down
2 changes: 2 additions & 0 deletions app/gui/src/dashboard/components/AriaComponents/Form/Form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ export const Form = forwardRef(function Form<
/* eslint-disable @typescript-eslint/naming-convention */
schema: typeof components.schema
useForm: typeof components.useForm
useFormWithComponents: typeof components.useFormWithComponents
useField: typeof components.useField
makeUseField: typeof components.makeUseField
Submit: typeof components.Submit
Expand All @@ -146,6 +147,7 @@ export const Form = forwardRef(function Form<

Form.schema = components.schema
Form.useForm = components.useForm
Form.useFormWithComponents = components.useFormWithComponents
Form.useField = components.useField
Form.makeUseField = components.makeUseField
Form.useFormSchema = components.useFormSchema
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
/**
* @file
*
* Barrel file for form components.
*/
/** @file Barrel file for form components. */
export { Controller, useWatch } from 'react-hook-form'
export * from './Field'
export * from './FieldValue'
Expand All @@ -18,3 +14,4 @@ export * from './useFieldState'
export * from './useForm'
export * from './useFormError'
export * from './useFormSchema'
export * from './useFormWithComponents'
Loading
Loading