-
Notifications
You must be signed in to change notification settings - Fork 8.5k
[Response Ops] Fix single file connector auth type on edit #244635
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
jcger
merged 111 commits into
elastic:main
from
jcger:issue-244390-discriminator-on-edit
Dec 3, 2025
+413
−12
Merged
Changes from 108 commits
Commits
Show all changes
111 commits
Select commit
Hold shift + click to select a range
08c2d9e
first commit
jcger 219f507
Changes from node scripts/capture_oas_snapshot --include-path /api/…
kibanamachine ea7f019
add default from meta and better submit handling
jcger 8fc160c
Merge branch 'issue-ro-479-connector-form-generator' of https://githu…
jcger 671277f
remove not used spec
jcger 61ac131
add bearer token + improve union layout
jcger 353ac04
add schema related comment for clarification
jcger 362d918
add key-pair widget
jcger 64fa931
fix error handling in nested fields + chore
jcger 35dbfda
centralized state management
jcger 408eb05
Changes from node scripts/lint_ts_projects --fix
kibanamachine 16c21a7
infer types
jcger d58af27
Changes from node scripts/generate codeowners
kibanamachine ccaa4c9
fix nested validation
jcger 3b6cb69
Merge branch 'issue-ro-479-connector-form-generator' of https://githu…
jcger b23e09d
fix lint
jcger f3ec12e
add basic fields tests
jcger bdebfb1
Changes from node scripts/lint_ts_projects --fix
kibanamachine 87f28f6
add discriminated union field test
jcger d540d4d
add single option discr. union + refactor
jcger 136f48c
form and form state tests + remove discr. transform for single option
jcger 9165398
Merge branch 'main' into issue-ro-479-connector-form-generator
jcger 54a72a2
migrate uimeta to meta
jcger cb1be2c
tmp metadata + label under widget options
jcger 963b6a4
add other connector demos
jcger f8e7a99
rename nested to options, simplify validation, add basic helptext sup…
jcger 764990f
Merge branch 'issue-ro-479-connector-form-generator' of https://githu…
jcger 92c55e6
state management + error validation refactor
jcger 509027a
Merge branch 'main' of https://github.com/jcger/kibana into issue-ro-…
jcger e7f792e
add test + bugfix
jcger 607d993
type refactor
jcger 90ed94a
remove duplication + cleaner root level error code
jcger 82f7350
simplify default value + nested no more than 2 levels"
jcger 572f057
discriminated union works with the defined discriminator
jcger e4e7b6b
key-value tests
jcger e0fbe5d
cleaning
jcger 31a181a
translations
jcger 42104f2
adds default widgets for schemas
jcger 5590d14
improve getting component from schema
jcger cfe0abb
create options from initial field generation + remove not needed option
jcger 581776c
add aria-label
jcger 1cf5495
fix tests
jcger a92b729
Merge branch 'main' into issue-ro-479-connector-form-generator
jcger 3b45167
Changes from node scripts/lint_ts_projects --fix
kibanamachine 6ba587e
Merge branch 'issue-ro-479-connector-form-generator' of https://githu…
jcger d4dd39a
use connector_spec
jcger 245a294
remove not used variable
jcger 6353363
simplify state type and error handling
jcger 84b0ff4
cleanup
jcger e8bf67b
Changes from node scripts/lint_ts_projects --fix
kibanamachine 9fa2e48
Merge branch 'main' of https://github.com/jcger/kibana into issue-ro-…
jcger 44cbc61
use shared form
jcger c72cd7c
Changes from node scripts/lint_ts_projects --fix
kibanamachine 766379f
Merge branch 'main' of https://github.com/jcger/kibana into issue-ro-…
jcger 3809531
remove response-ops dependencies
jcger 739685a
Changes from node scripts/lint_ts_projects --fix
kibanamachine bff0fad
add global form config with readonly prop
jcger 9f9c89c
Merge branch 'issue-ro-479-connector-form-generator' of https://githu…
jcger 469810c
make formConfig available in generator
jcger a55b30a
working UI
jcger fdafae9
better default handling + literal and url defaults
jcger 6ab8f65
cleaner default approach
jcger d1bcf07
fix tests
jcger 46071d1
docs + restructure
jcger 8a5a25d
mend
jcger 17f865e
add schema extract test
jcger 99c8a85
add registry tests
jcger ba29e73
add field builder test
jcger 5262382
readonly -> disabled + form test
jcger d5bd761
Merge branch 'main' of https://github.com/jcger/kibana into issue-ro-…
jcger 0545387
fix disable and update generated auth schema
jcger 1ba8e3f
Merge branch 'main' of https://github.com/jcger/kibana into issue-ro-…
jcger 40b3234
Changes from node scripts/lint_ts_projects --fix
kibanamachine 3e3d8d7
Changes from node scripts/regenerate_moon_projects.js --update
kibanamachine 6a52ff7
review + missing tests
jcger b8dd200
Merge branch 'issue-ro-479-connector-form-generator' of https://githu…
jcger cb13fc1
update limits
jcger f3aa5df
update test snapshots
jcger 25cf7ef
add i18n to authSchema messages and labels
jcger dc357cf
Merge branch 'main' into issue-ro-479-connector-form-generator
jcger b381c75
Changes from node scripts/lint_ts_projects --fix
kibanamachine 9fa8a47
Changes from node scripts/regenerate_moon_projects.js --update
kibanamachine c44f510
auth types i18n
jcger af83bf2
Merge branch 'issue-ro-479-connector-form-generator' of https://githu…
jcger 5368dce
add z.object widget
jcger 9b88653
Merge branch 'main' of https://github.com/jcger/kibana into issue-ro-…
jcger 026d1e8
fix tests
jcger 3768333
fix test
jcger b0e6efc
lazy load zod and generator
jcger b643c36
use single webpackChunk name
jcger 4982c19
fix core extraction
jcger 900f13e
fix spacer
jcger ae19299
add optional label for optional fields
jcger 5b59310
Changes from node scripts/lint_ts_projects --fix
kibanamachine cab1fe1
Changes from node scripts/regenerate_moon_projects.js --update
kibanamachine 5ff71e0
Merge branch 'main' into issue-ro-479-connector-form-generator
jcger a14cc37
change file extension
jcger 60b9b17
Merge branch 'main' of https://github.com/jcger/kibana into issue-ro-…
jcger 8ad26af
Merge branch 'issue-ro-479-connector-form-generator' of https://githu…
jcger da33ee5
first commit
jcger 377d2a9
Merge branch 'issue-ro-479-connector-form-generator' of https://githu…
jcger 31024f6
Merge branch 'main' of https://github.com/jcger/kibana into issue-244…
jcger 427e9a6
add tests
jcger 43e140c
fix tests
jcger ed26d81
add config.auth to server schema
jcger de0767c
fix test
jcger 55bd26d
fix type
jcger 0d8c54f
fix types
jcger 452cfc8
fix tests
jcger 2c0f9cb
Merge branch 'main' into issue-244390-discriminator-on-edit
jcger c2fa480
Merge branch 'main' into issue-244390-discriminator-on-edit
jcger File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
1 change: 0 additions & 1 deletion
1
.../kbn-connector-specs/src/lib/__snapshots__/generate_secrets_schema_from_spec.test.ts.snap
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
153 changes: 153 additions & 0 deletions
153
...dgets/components/discriminated_union_widget/multi_option_union_widget.serializer.test.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,153 @@ | ||
| /* | ||
| * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
| * or more contributor license agreements. Licensed under the "Elastic License | ||
| * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side | ||
| * Public License v 1"; you may not use this file except in compliance with, at | ||
| * your election, the "Elastic License 2.0", the "GNU Affero General Public | ||
| * License v3.0 only", or the "Server Side Public License, v 1". | ||
| */ | ||
|
|
||
| import React from 'react'; | ||
| import { render, screen, waitFor } from '@testing-library/react'; | ||
| import userEvent from '@testing-library/user-event'; | ||
| import { z } from '@kbn/zod/v4'; | ||
| import { Form, useForm } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; | ||
| import { addMeta } from '../../../schema_connector_metadata'; | ||
| import { MultiOptionUnionWidget } from './multi_option_union_widget'; | ||
|
|
||
| describe('MultiOptionUnionWidget - Serializer/Deserializer Integration', () => { | ||
| // Simulates a connector schema with secrets discriminated union | ||
| const createSchema = () => { | ||
| const noneOption = z.object({ authType: z.literal('none') }); | ||
| addMeta(noneOption, { label: 'None' }); | ||
|
|
||
| const basicOption = z.object({ | ||
| authType: z.literal('basic'), | ||
| username: z.string(), | ||
| password: z.string(), | ||
| }); | ||
| addMeta(basicOption, { label: 'Basic Auth' }); | ||
|
|
||
| const bearerOption = z.object({ | ||
| authType: z.literal('bearer'), | ||
| token: z.string(), | ||
| }); | ||
| addMeta(bearerOption, { label: 'Bearer Token' }); | ||
|
|
||
| return z.object({ | ||
| config: z.object({ | ||
| url: z.string(), | ||
| authType: z.string().optional(), | ||
| }), | ||
| secrets: z.discriminatedUnion('authType', [noneOption, basicOption, bearerOption]), | ||
| }); | ||
| }; | ||
|
|
||
| // Simulates the deserializer that reconstructs secrets from config | ||
| const deserializer = (apiData: any) => { | ||
| if (!apiData?.config?.authType || apiData.secrets?.authType) { | ||
| return apiData; | ||
| } | ||
|
|
||
| // Reconstruct secrets from config.authType | ||
| return { | ||
| ...apiData, | ||
| secrets: { authType: apiData.config.authType }, | ||
| }; | ||
| }; | ||
|
|
||
| // Simulates the serializer that copies authType to config | ||
| const serializer = (formData: any) => { | ||
| if (!formData?.secrets?.authType) { | ||
| return formData; | ||
| } | ||
|
|
||
| return { | ||
| ...formData, | ||
| config: { | ||
| ...formData.config, | ||
| authType: formData.secrets.authType, | ||
| }, | ||
| }; | ||
| }; | ||
|
|
||
| const TestComponent = ({ initialData }: { initialData: any }) => { | ||
| const schema = createSchema(); | ||
| const { form } = useForm({ | ||
| defaultValue: initialData, | ||
| serializer, | ||
| deserializer, | ||
| }); | ||
|
|
||
| return ( | ||
| <Form form={form}> | ||
| <MultiOptionUnionWidget | ||
| path="secrets" | ||
| options={schema.shape.secrets.options} | ||
| discriminatorKey="authType" | ||
| schema={schema.shape.secrets} | ||
| fieldConfig={{ | ||
| defaultValue: undefined, | ||
| validations: [{ validator: () => undefined }], | ||
| }} | ||
| fieldProps={{ label: 'Authentication', euiFieldProps: {} }} | ||
| formConfig={{}} | ||
| /> | ||
| <button | ||
| type="button" | ||
| onClick={async () => { | ||
| const { data } = await form.submit(); | ||
| // Expose serialized data for testing | ||
| (window as any).serializedData = data; | ||
| }} | ||
| > | ||
| Submit | ||
| </button> | ||
| </Form> | ||
| ); | ||
| }; | ||
|
|
||
| beforeEach(() => { | ||
| (window as any).serializedData = undefined; | ||
| }); | ||
|
|
||
| it('should initialize from async form data (deserializer)', async () => { | ||
| const apiData = { | ||
| config: { url: 'https://example.com', authType: 'basic' }, | ||
| secrets: {}, // Secrets are stripped by API | ||
| }; | ||
|
|
||
| render(<TestComponent initialData={apiData} />); | ||
|
|
||
| await waitFor(() => { | ||
| const basicCard = screen.getByLabelText('Basic Auth'); | ||
| expect(basicCard).toBeChecked(); | ||
| }); | ||
| }); | ||
|
|
||
| it('should not overwrite user selection when form re-renders (ref flag pattern)', async () => { | ||
| const apiData = { | ||
| config: { url: 'https://example.com', authType: 'basic' }, | ||
| secrets: {}, | ||
| }; | ||
|
|
||
| const { rerender } = render(<TestComponent initialData={apiData} />); | ||
|
|
||
| await waitFor(() => { | ||
| expect(screen.getByLabelText('Basic Auth')).toBeChecked(); | ||
| }); | ||
|
|
||
| await userEvent.click(screen.getByLabelText('Bearer Token')); | ||
|
|
||
| await waitFor(() => { | ||
| expect(screen.getByLabelText('Bearer Token')).toBeChecked(); | ||
| }); | ||
|
|
||
| // simulating parent re-render | ||
| rerender(<TestComponent initialData={apiData} />); | ||
|
|
||
| await waitFor(() => { | ||
| expect(screen.getByLabelText('Bearer Token')).toBeChecked(); | ||
| }); | ||
| }); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
157 changes: 157 additions & 0 deletions
157
...ared/stack_connectors/public/connector_types_from_spec/connector_form_serializers.test.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,157 @@ | ||
| /* | ||
| * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
| * or more contributor license agreements. Licensed under the Elastic License | ||
| * 2.0; you may not use this file except in compliance with the Elastic License | ||
| * 2.0. | ||
| */ | ||
|
|
||
| import { z } from '@kbn/zod/v4'; | ||
| import { | ||
| createConnectorFormSerializer, | ||
| createConnectorFormDeserializer, | ||
| } from './connector_form_serializers'; | ||
|
|
||
| describe('createConnectorFormSerializer', () => { | ||
| it('should copy authType from secrets to config', () => { | ||
| const serializer = createConnectorFormSerializer(); | ||
| const formData = { | ||
| config: { url: 'https://example.com' }, | ||
| secrets: { authType: 'basic', username: 'user', password: 'pass' }, | ||
| }; | ||
|
|
||
| const result = serializer(formData); | ||
|
|
||
| expect(result.config.authType).toBe('basic'); | ||
| expect(result.secrets.authType).toBe('basic'); | ||
| }); | ||
|
|
||
| it('should return data unchanged when secrets.authType is missing', () => { | ||
| const serializer = createConnectorFormSerializer(); | ||
| const formData = { | ||
| config: { url: 'https://example.com' }, | ||
| secrets: {}, | ||
| }; | ||
|
|
||
| const result = serializer(formData); | ||
|
|
||
| expect(result).toEqual(formData); | ||
| expect(result.config.authType).toBeUndefined(); | ||
| }); | ||
|
|
||
| it('should preserve existing config.authType if secrets.authType exists', () => { | ||
| const serializer = createConnectorFormSerializer(); | ||
| const formData = { | ||
| config: { url: 'https://example.com', authType: 'old' }, | ||
| secrets: { authType: 'bearer', token: 'xyz' }, | ||
| }; | ||
|
|
||
| const result = serializer(formData); | ||
|
|
||
| expect(result.config.authType).toBe('bearer'); | ||
| }); | ||
|
|
||
| it('should handle undefined formData', () => { | ||
| const serializer = createConnectorFormSerializer(); | ||
|
|
||
| const result = serializer(undefined); | ||
|
|
||
| expect(result).toBeUndefined(); | ||
| }); | ||
| }); | ||
|
|
||
| describe('createConnectorFormDeserializer', () => { | ||
| const commonApiData = { | ||
| actionTypeId: 'test-connector', | ||
| isDeprecated: false, | ||
| }; | ||
|
|
||
| const createTestSchema = () => { | ||
| return z.object({ | ||
| config: z.object({ | ||
| url: z.string(), | ||
| authType: z.string().optional(), | ||
| }), | ||
| secrets: z.discriminatedUnion('authType', [ | ||
| z.object({ authType: z.literal('none') }), | ||
| z.object({ authType: z.literal('basic'), username: z.string(), password: z.string() }), | ||
| z.object({ authType: z.literal('bearer'), token: z.string() }), | ||
| ]), | ||
| }); | ||
| }; | ||
|
|
||
| it('should copy authType from config to secrets when editing', () => { | ||
| const schema = createTestSchema(); | ||
| const deserializer = createConnectorFormDeserializer(schema); | ||
| const apiData = { | ||
| ...commonApiData, | ||
| config: { url: 'https://example.com', authType: 'basic' }, | ||
| secrets: {}, | ||
| }; | ||
|
|
||
| const result = deserializer(apiData); | ||
|
|
||
| expect(result.secrets.authType).toBe('basic'); | ||
| expect(result.config.authType).toBe('basic'); | ||
| }); | ||
|
|
||
| it('should return data unchanged when config.authType is missing', () => { | ||
| const schema = createTestSchema(); | ||
| const deserializer = createConnectorFormDeserializer(schema); | ||
| const apiData = { | ||
| ...commonApiData, | ||
| config: { url: 'https://example.com' }, | ||
| secrets: {}, | ||
| }; | ||
|
|
||
| const result = deserializer(apiData); | ||
|
|
||
| expect(result).toEqual(apiData); | ||
| expect(result.secrets.authType).toBeUndefined(); | ||
| }); | ||
|
|
||
| it('should return data unchanged when secrets.authType already exists', () => { | ||
| const schema = createTestSchema(); | ||
| const deserializer = createConnectorFormDeserializer(schema); | ||
| const apiData = { | ||
| ...commonApiData, | ||
| config: { url: 'https://example.com', authType: 'basic' }, | ||
| secrets: { authType: 'bearer', token: 'xyz' }, | ||
| }; | ||
|
|
||
| const result = deserializer(apiData); | ||
|
|
||
| expect(result).toEqual(apiData); | ||
| expect(result.secrets.authType).toBe('bearer'); | ||
| }); | ||
|
|
||
| it('should handle schema without discriminated union gracefully', () => { | ||
| const schema = z.object({ | ||
| config: z.object({ url: z.string() }), | ||
| secrets: z.object({ token: z.string() }), | ||
| }); | ||
| const deserializer = createConnectorFormDeserializer(schema); | ||
| const apiData = { | ||
| ...commonApiData, | ||
| config: { url: 'https://example.com', authType: 'basic' }, | ||
| secrets: {}, | ||
| }; | ||
|
|
||
| const result = deserializer(apiData); | ||
|
|
||
| expect(result).toEqual(apiData); | ||
| }); | ||
|
|
||
| it('should handle errors', () => { | ||
| const invalidSchema = {} as z.ZodObject<z.ZodRawShape>; | ||
| const deserializer = createConnectorFormDeserializer(invalidSchema); | ||
| const apiData = { | ||
| ...commonApiData, | ||
| config: { url: 'https://example.com', authType: 'basic' }, | ||
| secrets: {}, | ||
| }; | ||
|
|
||
| const result = deserializer(apiData); | ||
|
|
||
| expect(result).toEqual(apiData); | ||
| }); | ||
| }); | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should this test be called
should overwrite existing config.authType...? the naming doesn't seem to match the outcome