Skip to content

Conversation

@KD23243
Copy link
Contributor

@KD23243 KD23243 commented Jan 23, 2026

Related Issue

Implementation

This PR introduces the ability for administrators to Export and Import application configurations directly from the Console UI. This is a key step towards supporting Configuration as Code (CasC), allowing users to migrate application settings between different environments or back up configurations easily.
Key Changes

  • API Integration
    Added exportApplication and importApplication functions in features/admin.applications.v1/api/application.ts.
    Support for multiple file formats: XML, JSON, and YAML.
    Implemented dynamic Accept and Content-Type header handling based on the selected file format.
    Added support for the exportSecrets flag to include/exclude sensitive credentials during export.
  • UI Components
    Export Application Modal: A new modal component (export-application-modal.tsx) that allows users to:
    Choose whether to include secrets (with a security warning).
    Select the desired file format (XML, JSON, YAML).
    Application List Enhancements: Added an "Export" action icon to the application list table.
    Import Functionality: Added an "Import" button to the main Applications page with a file picker that accepts .xml, .json, .yaml, .yml files.
  • User Experience & i18n
    Added comprehensive translation strings for both Export and Import workflows, including success/error notifications and helpful hints regarding security risks when exporting secrets.
    Included a Changeset to track minor version updates for the affected packages.

Demonstration

Screen.Recording.2026-01-23.at.8.57.19.AM.mov

Summary by CodeRabbit

  • New Features

    • Export application configurations in XML, JSON, or YAML with optional secret inclusion via a new export UI and modal.
    • Import applications from configuration files via a new import flow and file picker, with progress state and user feedback.
  • Documentation / Localization

    • Added translation strings and a new "Import" action label to support the export/import user flows.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 23, 2026

Walkthrough

Implements application configuration export and import (CasC) by adding API methods for export/import, a new export modal, list and page UI integrations for export/import flows, and corresponding i18n keys and a changeset.

Changes

Cohort / File(s) Summary
API Layer
features/admin.applications.v1/api/application.ts
Add ExportFormat type (`"xml"
Export UI Component
features/admin.applications.v1/components/modals/export-application-modal.tsx
New ExportApplicationModal component with controls for including secrets and selecting export format; exposes onExport(exportSecrets, format) and onClose
Application List Integration
features/admin.applications.v1/components/application-list.tsx
Add per-item export action, export modal wiring, showExportModal/exportingApplication state, and new props onApplicationImport?: () => void, isImporting?: boolean
Application Page Integration
features/admin.applications.v1/pages/applications.tsx
Add import flow: hidden file input (accept .xml/.json/.yaml/.yml), handleApplicationImport to call importApplication, loading state isImporting, and wiring to ApplicationList via onApplicationImport
Internationalization
modules/i18n/src/models/namespaces/applications-ns.ts, modules/i18n/src/translations/en-US/portals/applications.ts
Add i18n keys for export/import (confirmations, notifications, format/secrets labels) and a new import action label in list actions
Changeset
.changeset/plenty-otters-swim.md
Add changeset entry noting minor bumps and "Implement Application Configuration Export & Import (CasC Support)."
sequenceDiagram
  autonumber
  participant User as User
  participant FE as Frontend (UI)
  participant API as API Client
  participant BE as Backend (Server)

  rect rgba(200,220,255,0.5)
  User->>FE: Click "Export" on application
  FE->>FE: Open ExportApplicationModal (choose format & secrets)
  FE->>API: POST /applications/{id}/export?format=&exportSecrets=
  API->>BE: HTTP request (export)
  BE-->>API: 200 OK + file blob
  API-->>FE: Blob response
  FE->>User: Trigger file download
  end

  rect rgba(200,255,200,0.5)
  User->>FE: Click "Import" and select file
  FE->>API: POST /applications/import (multipart/form-data)
  API->>BE: HTTP request (upload file)
  BE-->>API: 200/201 OK (import result)
  API-->>FE: Response (success/error)
  FE->>FE: Refresh application list / show alert
  FE->>User: Show success or error message
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 A rabbit hops with files in tow,
XML, JSON, YAML in a row.
Exports hum and imports sing,
CasC carrots for code to bring.
Hoppy changes—down the burrow we go!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The PR title 'Implement Application Configuration Export & Import (CasC Support)' accurately and clearly summarizes the main change: adding export and import capabilities for application configurations.
Description check ✅ Passed The PR description includes a clear purpose section, related issue reference, implementation details with file changes, and a demonstration link, meeting most template requirements.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@features/admin.applications.v1/api/application.ts`:
- Around line 1389-1408: The getImportContentType function is dead code (defined
as getImportContentType) and never referenced by importApplication; either
delete getImportContentType to remove unused code, or update importApplication
to call getImportContentType(fileName) and use its result for the request
Content-Type header instead of the hardcoded "multipart/form-data" value so the
correct MIME is sent based on the file extension.

In `@features/admin.applications.v1/components/application-list.tsx`:
- Around line 353-372: The catch block in application-list.tsx assumes
error.response.data is JSON, but when exportApplication uses responseType:
"blob" the server error comes back as a Blob; update the error handling in that
catch to detect if error.response.data is a Blob (instanceof Blob) and, if so,
read it (e.g. via .text() or FileReader) and JSON.parse the text to extract
error.response.data.description before dispatching addAlert; otherwise keep the
existing checks for error.response.data.description. You can implement this
parsing inline in the catch or extract it to a helper like parseBlobError to
return the parsed object and then dispatch the alert with the parsed description
when available.
🧹 Nitpick comments (2)
features/admin.applications.v1/pages/applications.tsx (1)

647-664: Consider adding aria-label for the hidden file input for accessibility.

While the file input is hidden and triggered programmatically, adding an aria-label helps screen readers understand the control's purpose.

Suggested improvement
 <input
     ref={ fileInputRef }
     type="file"
     accept=".xml,.json,.yaml,.yml"
     hidden
     onChange={ handleApplicationImport }
     data-testid={ `${ testId }-import-file-input` }
+    aria-label={ t("applications:list.actions.import") }
 />
features/admin.applications.v1/components/modals/export-application-modal.tsx (1)

64-96: Consider resetting state when modal is opened fresh.

The component's exportSecrets and exportFormat state persists between modal opens. If a user opens the modal, changes options, closes without exporting, then opens for a different application, the previous selections remain. Consider resetting state when the modal opens or when applicationName changes.

Suggested improvement using useEffect
+import React, { ChangeEvent, FunctionComponent, ReactElement, useState, useEffect } from "react";
 ...
 const ExportApplicationModal: FunctionComponent<ExportApplicationModalProps> = ({
     "data-componentid": componentId = "export-application-modal",
     applicationName,
     onExport,
     onClose,
     open,
     ...rest
 }: ExportApplicationModalProps): ReactElement => {
     const { t } = useTranslation();
     const [ exportSecrets, setExportSecrets ] = useState<boolean>(false);
     const [ exportFormat, setExportFormat ] = useState<ExportFormat>("xml");
+
+    // Reset state when modal opens
+    useEffect(() => {
+        if (open) {
+            setExportSecrets(false);
+            setExportFormat("xml");
+        }
+    }, [ open ]);

Comment on lines +1389 to +1408
/**
* Gets the content type based on file extension.
*
* @param fileName - The name of the file.
* @returns The appropriate content type for the file.
*/
const getImportContentType = (fileName: string): string => {
const extension: string = fileName.split(".").pop()?.toLowerCase() || "";

switch (extension) {
case "json":
return "application/json";
case "yaml":
case "yml":
return "application/yaml";
case "xml":
default:
return "application/xml";
}
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Dead code: getImportContentType is defined but never used.

The getImportContentType function determines the content type based on file extension, but importApplication uses hardcoded "multipart/form-data" as the Content-Type. Either use this function or remove it.

Suggested fix: Remove unused function
-/**
- * Gets the content type based on file extension.
- *
- * `@param` fileName - The name of the file.
- * `@returns` The appropriate content type for the file.
- */
-const getImportContentType = (fileName: string): string => {
-    const extension: string = fileName.split(".").pop()?.toLowerCase() || "";
-
-    switch (extension) {
-        case "json":
-            return "application/json";
-        case "yaml":
-        case "yml":
-            return "application/yaml";
-        case "xml":
-        default:
-            return "application/xml";
-    }
-};
-
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/**
* Gets the content type based on file extension.
*
* @param fileName - The name of the file.
* @returns The appropriate content type for the file.
*/
const getImportContentType = (fileName: string): string => {
const extension: string = fileName.split(".").pop()?.toLowerCase() || "";
switch (extension) {
case "json":
return "application/json";
case "yaml":
case "yml":
return "application/yaml";
case "xml":
default:
return "application/xml";
}
};
🤖 Prompt for AI Agents
In `@features/admin.applications.v1/api/application.ts` around lines 1389 - 1408,
The getImportContentType function is dead code (defined as getImportContentType)
and never referenced by importApplication; either delete getImportContentType to
remove unused code, or update importApplication to call
getImportContentType(fileName) and use its result for the request Content-Type
header instead of the hardcoded "multipart/form-data" value so the correct MIME
is sent based on the file extension.

Comment on lines +353 to +372
.catch((error: AxiosError) => {
if (error.response && error.response.data && error.response.data.description) {
dispatch(addAlert({
description: error.response.data.description,
level: AlertLevels.ERROR,
message: t("applications:notifications.exportApplication.error" +
".message")
}));

return;
}

dispatch(addAlert({
description: t("applications:notifications.exportApplication" +
".genericError.description"),
level: AlertLevels.ERROR,
message: t("applications:notifications.exportApplication.genericError" +
".message")
}));
})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Blob response error handling may not extract error description correctly.

When exportApplication uses responseType: "blob", error responses from the server will also be received as a Blob. Accessing error.response.data.description on a Blob object will return undefined, causing the generic error message to always be shown instead of the server's specific error message.

Suggested fix: Parse blob error response
         .catch((error: AxiosError) => {
-            if (error.response && error.response.data && error.response.data.description) {
-                dispatch(addAlert({
-                    description: error.response.data.description,
-                    level: AlertLevels.ERROR,
-                    message: t("applications:notifications.exportApplication.error" +
-                        ".message")
-                }));
-
-                return;
-            }
-
-            dispatch(addAlert({
-                description: t("applications:notifications.exportApplication" +
-                    ".genericError.description"),
-                level: AlertLevels.ERROR,
-                message: t("applications:notifications.exportApplication.genericError" +
-                    ".message")
-            }));
+            if (error.response?.data instanceof Blob) {
+                error.response.data.text().then((text: string) => {
+                    try {
+                        const errorData = JSON.parse(text);
+                        dispatch(addAlert({
+                            description: errorData.description || t("applications:notifications.exportApplication.genericError.description"),
+                            level: AlertLevels.ERROR,
+                            message: t("applications:notifications.exportApplication.error.message")
+                        }));
+                    } catch {
+                        dispatch(addAlert({
+                            description: t("applications:notifications.exportApplication.genericError.description"),
+                            level: AlertLevels.ERROR,
+                            message: t("applications:notifications.exportApplication.genericError.message")
+                        }));
+                    }
+                });
+            } else {
+                dispatch(addAlert({
+                    description: t("applications:notifications.exportApplication.genericError.description"),
+                    level: AlertLevels.ERROR,
+                    message: t("applications:notifications.exportApplication.genericError.message")
+                }));
+            }
         })
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
.catch((error: AxiosError) => {
if (error.response && error.response.data && error.response.data.description) {
dispatch(addAlert({
description: error.response.data.description,
level: AlertLevels.ERROR,
message: t("applications:notifications.exportApplication.error" +
".message")
}));
return;
}
dispatch(addAlert({
description: t("applications:notifications.exportApplication" +
".genericError.description"),
level: AlertLevels.ERROR,
message: t("applications:notifications.exportApplication.genericError" +
".message")
}));
})
.catch((error: AxiosError) => {
if (error.response?.data instanceof Blob) {
error.response.data.text().then((text: string) => {
try {
const errorData = JSON.parse(text);
dispatch(addAlert({
description: errorData.description || t("applications:notifications.exportApplication.genericError.description"),
level: AlertLevels.ERROR,
message: t("applications:notifications.exportApplication.error.message")
}));
} catch {
dispatch(addAlert({
description: t("applications:notifications.exportApplication.genericError.description"),
level: AlertLevels.ERROR,
message: t("applications:notifications.exportApplication.genericError.message")
}));
}
});
} else {
dispatch(addAlert({
description: t("applications:notifications.exportApplication.genericError.description"),
level: AlertLevels.ERROR,
message: t("applications:notifications.exportApplication.genericError.message")
}));
}
})
🤖 Prompt for AI Agents
In `@features/admin.applications.v1/components/application-list.tsx` around lines
353 - 372, The catch block in application-list.tsx assumes error.response.data
is JSON, but when exportApplication uses responseType: "blob" the server error
comes back as a Blob; update the error handling in that catch to detect if
error.response.data is a Blob (instanceof Blob) and, if so, read it (e.g. via
.text() or FileReader) and JSON.parse the text to extract
error.response.data.description before dispatching addAlert; otherwise keep the
existing checks for error.response.data.description. You can implement this
parsing inline in the catch or extract it to a helper like parseBlobError to
return the parsed object and then dispatch the alert with the parsed description
when available.

@codecov
Copy link

codecov bot commented Jan 23, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 55.88%. Comparing base (990145c) to head (2078007).
⚠️ Report is 42 commits behind head on master.

Additional details and impacted files
@@           Coverage Diff           @@
##           master    #9566   +/-   ##
=======================================
  Coverage   55.88%   55.88%           
=======================================
  Files          42       42           
  Lines        1020     1020           
  Branches      254      231   -23     
=======================================
  Hits          570      570           
  Misses        450      450           
Flag Coverage Δ
@wso2is/core 55.88% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant