fitzee commented on code in PR #43633:
URL: https://github.com/apache/superset/pull/43633#discussion_r3902010122


##########
superset-frontend/src/components/Datasource/components/DatasourceEditor/DatasourceEditor.tsx:
##########
@@ -1631,6 +1635,49 @@ function DatasourceEditor({
     onDatasourceChange,
   ]);
 
+  const renderCertificationFieldset = useCallback(() => {
+    const certification = getDatasetCertification(datasource.extra);
+
+    return isSqla ? (
+      <Fieldset
+        title={t('Certification')}
+        item={certification}
+        onChange={updatedCertification => {
+          onDatasourceChange({
+            ...datasource,
+            extra: setDatasetCertification(
+              datasource.extra,
+              updatedCertification,
+            ),
+          });

Review Comment:
   Confirmed and fixed in . Dataset certification is hydrated into editor state 
instead of rewriting  per keystroke, and its handler merges only certification 
fields through a functional  update. The save path applies those fields to the 
latest .  fires Default URL, Certified by, and details changes back-to-back and 
waits for the committed datasource to contain all three values.



##########
superset-frontend/src/components/Datasource/components/DatasourceEditor/datasetCertification.ts:
##########
@@ -0,0 +1,87 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+export type DatasetCertification = Record<string, unknown> & {
+  certified_by?: string;
+  certification_details?: string;
+};
+
+type JsonObject = Record<string, unknown>;
+
+const isJsonObject = (value: unknown): value is JsonObject =>
+  typeof value === 'object' && value !== null && !Array.isArray(value);
+
+const parseExtra = (extra?: string): JsonObject | undefined => {
+  if (!extra?.trim()) {
+    return {};
+  }
+
+  try {
+    const parsed: unknown = JSON.parse(extra);
+    return isJsonObject(parsed) ? parsed : undefined;
+  } catch {
+    return undefined;
+  }
+};
+
+export const getDatasetCertification = (
+  extra?: string,
+): DatasetCertification => {
+  const certification = parseExtra(extra)?.certification;
+  if (!isJsonObject(certification)) {
+    return {};
+  }
+
+  return {
+    certified_by:
+      typeof certification.certified_by === 'string'
+        ? certification.certified_by
+        : undefined,
+    certification_details:
+      typeof certification.details === 'string'
+        ? certification.details
+        : undefined,
+  };
+};
+
+export const setDatasetCertification = (
+  extra: string | undefined,
+  { certified_by, certification_details }: DatasetCertification,
+): string => {
+  const parsedExtra = parseExtra(extra);
+
+  // Do not replace malformed raw metadata while the user is correcting it in
+  // the adjacent Extra editor.
+  if (!parsedExtra) {
+    return extra ?? '';
+  }

Review Comment:
   Confirmed and fixed in . Malformed or non-object Extra is detected with ; 
both certification inputs are disabled and show “Fix the Extra JSON to edit 
certification” rather than accepting a value that cannot be saved. The new 
editor regression test covers the disabled/error state, and helper tests cover 
malformed and non-object roots.



##########
superset-frontend/src/components/Datasource/components/DatasourceEditor/datasetCertification.ts:
##########
@@ -0,0 +1,87 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+export type DatasetCertification = Record<string, unknown> & {
+  certified_by?: string;
+  certification_details?: string;
+};
+
+type JsonObject = Record<string, unknown>;
+
+const isJsonObject = (value: unknown): value is JsonObject =>
+  typeof value === 'object' && value !== null && !Array.isArray(value);
+
+const parseExtra = (extra?: string): JsonObject | undefined => {
+  if (!extra?.trim()) {
+    return {};
+  }
+
+  try {
+    const parsed: unknown = JSON.parse(extra);
+    return isJsonObject(parsed) ? parsed : undefined;
+  } catch {
+    return undefined;
+  }
+};
+
+export const getDatasetCertification = (
+  extra?: string,
+): DatasetCertification => {
+  const certification = parseExtra(extra)?.certification;
+  if (!isJsonObject(certification)) {
+    return {};
+  }
+
+  return {
+    certified_by:
+      typeof certification.certified_by === 'string'
+        ? certification.certified_by
+        : undefined,
+    certification_details:
+      typeof certification.details === 'string'
+        ? certification.details
+        : undefined,
+  };
+};
+
+export const setDatasetCertification = (
+  extra: string | undefined,
+  { certified_by, certification_details }: DatasetCertification,
+): string => {
+  const parsedExtra = parseExtra(extra);
+
+  // Do not replace malformed raw metadata while the user is correcting it in
+  // the adjacent Extra editor.
+  if (!parsedExtra) {
+    return extra ?? '';
+  }
+
+  if (certified_by || certification_details) {
+    const existingCertification = parsedExtra.certification;
+    parsedExtra.certification = {
+      ...(isJsonObject(existingCertification) ? existingCertification : {}),
+      certified_by: certified_by || undefined,
+      details: certification_details || undefined,
+    };
+  } else {
+    delete parsedExtra.certification;

Review Comment:
   Fixed in . The merge removes only  and ; it preserves unknown certification 
subkeys and deletes the  object only when nothing remains. The clear regression 
uses the UI's actual  shape and verifies  survives.



##########
superset-frontend/src/components/Datasource/components/DatasourceEditor/datasetCertification.ts:
##########
@@ -0,0 +1,87 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+export type DatasetCertification = Record<string, unknown> & {
+  certified_by?: string;
+  certification_details?: string;
+};
+
+type JsonObject = Record<string, unknown>;
+
+const isJsonObject = (value: unknown): value is JsonObject =>
+  typeof value === 'object' && value !== null && !Array.isArray(value);
+
+const parseExtra = (extra?: string): JsonObject | undefined => {
+  if (!extra?.trim()) {
+    return {};
+  }
+
+  try {
+    const parsed: unknown = JSON.parse(extra);
+    return isJsonObject(parsed) ? parsed : undefined;
+  } catch {
+    return undefined;
+  }
+};
+
+export const getDatasetCertification = (
+  extra?: string,
+): DatasetCertification => {
+  const certification = parseExtra(extra)?.certification;
+  if (!isJsonObject(certification)) {
+    return {};
+  }
+
+  return {
+    certified_by:
+      typeof certification.certified_by === 'string'
+        ? certification.certified_by
+        : undefined,
+    certification_details:
+      typeof certification.details === 'string'
+        ? certification.details
+        : undefined,
+  };
+};
+
+export const setDatasetCertification = (
+  extra: string | undefined,
+  { certified_by, certification_details }: DatasetCertification,
+): string => {
+  const parsedExtra = parseExtra(extra);
+
+  // Do not replace malformed raw metadata while the user is correcting it in
+  // the adjacent Extra editor.
+  if (!parsedExtra) {
+    return extra ?? '';
+  }
+
+  if (certified_by || certification_details) {
+    const existingCertification = parsedExtra.certification;
+    parsedExtra.certification = {
+      ...(isJsonObject(existingCertification) ? existingCertification : {}),
+      certified_by: certified_by || undefined,
+      details: certification_details || undefined,
+    };
+  } else {
+    delete parsedExtra.certification;
+  }
+
+  return JSON.stringify(parsedExtra, null, 2);

Review Comment:
   Fixed in . Certification edits no longer serialize Extra on each keystroke; 
they remain in first-class editor state and merge once at save. The helper 
returns the original raw string when values are unchanged (including preserving 
formatting and ), so type-then-clear on an empty dataset does not create . Unit 
coverage pins both cases.



##########
superset-frontend/src/components/Datasource/DatasourceModal/DatasourceModal.test.tsx:
##########
@@ -135,6 +136,81 @@ describe('DatasourceModal', () => {
     expect(JSON.parse(putCall?.options?.body as string).editors).toEqual([1]);
   });
 
+  test('saves dataset certification from Settings without dropping Extra 
metadata', async () => {
+    cleanup();
+    renderAndWait({
+      ...mockedProps,
+      datasource: {
+        ...mockedProps.datasource,
+        extra: JSON.stringify({
+          custom_key: { enabled: true },
+          warning_markdown: 'Use only finalized records',
+        }),
+      } as typeof mockedProps.datasource & { extra: string },
+    });
+
+    await userEvent.click(await screen.findByRole('tab', { name: 'Settings' 
}));
+
+    const certifiedBy = await screen.findByPlaceholderText('Certified by');
+    fireEvent.change(certifiedBy, { target: { value: 'E2E Team' } });
+    await new Promise(resolve => setTimeout(resolve, 500));

Review Comment:
   Fixed in . The two real-time 500 ms sleeps are gone. The modal test fires 
Default URL and both certification changes in one debounce window, advances 
only the configured debounce with fake timers, and s on the PUT body. A 
separate editor test fires the same changes back-to-back and s on committed  
state. The focused run is 4 suites / 30 tests passing.



##########
superset-frontend/src/components/Datasource/components/DatasourceEditor/DatasourceEditor.tsx:
##########
@@ -1631,6 +1635,49 @@ function DatasourceEditor({
     onDatasourceChange,
   ]);
 
+  const renderCertificationFieldset = useCallback(() => {
+    const certification = getDatasetCertification(datasource.extra);

Review Comment:
   Confirmed and addressed structurally in . The certification controls no 
longer write  during editing, so they cannot make the uncontrolled Ace buffer 
stale. They hydrate once into dedicated editor fields and serialize at save 
against the latest raw Extra only when those fields were actually edited; raw 
Extra remains authoritative otherwise. The Extra help text now directs 
certification edits to the adjacent section.



##########
superset-frontend/src/components/Datasource/components/DatasourceEditor/datasetCertification.ts:
##########
@@ -0,0 +1,87 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+export type DatasetCertification = Record<string, unknown> & {
+  certified_by?: string;
+  certification_details?: string;
+};
+
+type JsonObject = Record<string, unknown>;
+
+const isJsonObject = (value: unknown): value is JsonObject =>
+  typeof value === 'object' && value !== null && !Array.isArray(value);
+
+const parseExtra = (extra?: string): JsonObject | undefined => {
+  if (!extra?.trim()) {
+    return {};
+  }
+
+  try {
+    const parsed: unknown = JSON.parse(extra);
+    return isJsonObject(parsed) ? parsed : undefined;
+  } catch {
+    return undefined;
+  }
+};
+
+export const getDatasetCertification = (
+  extra?: string,
+): DatasetCertification => {
+  const certification = parseExtra(extra)?.certification;
+  if (!isJsonObject(certification)) {
+    return {};
+  }
+
+  return {
+    certified_by:
+      typeof certification.certified_by === 'string'
+        ? certification.certified_by
+        : undefined,
+    certification_details:
+      typeof certification.details === 'string'
+        ? certification.details
+        : undefined,
+  };
+};
+
+export const setDatasetCertification = (
+  extra: string | undefined,
+  { certified_by, certification_details }: DatasetCertification,
+): string => {
+  const parsedExtra = parseExtra(extra);
+
+  // Do not replace malformed raw metadata while the user is correcting it in
+  // the adjacent Extra editor.
+  if (!parsedExtra) {
+    return extra ?? '';
+  }
+
+  if (certified_by || certification_details) {
+    const existingCertification = parsedExtra.certification;
+    parsedExtra.certification = {
+      ...(isJsonObject(existingCertification) ? existingCertification : {}),

Review Comment:
   Covered in . Tests now verify unknown  survives both edit and clear, 
details-only writes work, UI-style empty-string clears work, 
malformed/non-object Extra and non-object certification are handled, and 
unchanged formatting is preserved.  is also narrowed to only its two supported 
fields instead of intersecting .



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to