Skip to content
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

Application doc panel #19625

Merged
merged 4 commits into from
Feb 5, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion openmetadata-ui/src/main/resources/ui/.husky/pre-commit
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@ else
echo "No changes in JSON schema files. Skipping TypeScript generation."
fi


cd openmetadata-ui/src/main/resources/ui
yarn generate:app-docs
yarn pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
* Copyright 2025 Collate.
* Licensed 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.
*/
/* eslint-disable */
const fs = require('fs');
const path = require('path');

const SCHEMA_DIR = path.join(__dirname, './src/utils/ApplicationSchemas');
const DOCS_DIR = path.join(__dirname, './public/locales/en-US/Applications');

const processProperty = (key, prop) => {
let markdown = `$$section\n`;
markdown += `### ${prop.title || key} $(id="${key}")\n\n`;

if (prop.description) {
markdown += `${prop.description}\n\n`;
}

// Handle nested properties if they exist
if (prop.properties) {
for (const [nestedKey, nestedProp] of Object.entries(prop.properties)) {
markdown += processProperty(`${key}.${nestedKey}`, nestedProp);
}
}

markdown += `$$\n\n`;
return markdown;
};

const generateMarkdown = (schema) => {
let markdown = `# ${schema.title || 'Application Configuration'}\n\n`;

if (schema.description) {
markdown += `${schema.description}\n\n`;
}

if (schema.properties) {
for (const [key, prop] of Object.entries(schema.properties)) {
markdown += processProperty(key, prop);
}
}

return markdown.trim();
};

const parseAndGenerateDocs = () => {
// Ensure docs directory exists
if (!fs.existsSync(DOCS_DIR)) {
fs.mkdirSync(DOCS_DIR, { recursive: true });
}

// Read all JSON files from schema directory
const schemaFiles = fs
.readdirSync(SCHEMA_DIR)
.filter((file) => file.endsWith('.json'));

schemaFiles.forEach((schemaFile) => {
try {
const schemaPath = path.join(SCHEMA_DIR, schemaFile);
const schema = JSON.parse(fs.readFileSync(schemaPath, 'utf-8'));
const baseName = path
.basename(schemaFile, '.json')
.replace('-collate', '');

const markdown = generateMarkdown(schema);

fs.writeFileSync(
path.join(DOCS_DIR, `${baseName}.md`),
markdown,
'utf-8'
);

console.log(`✓ Generated documentation for ${baseName}`);
} catch (error) {
console.error(`✗ Error processing ${schemaFile}:`, error);
}
});
};

parseAndGenerateDocs();
5 changes: 3 additions & 2 deletions openmetadata-ui/src/main/resources/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@
"check-i18n": "npm run i18n -- --check",
"playwright:run": "playwright test",
"playwright:open": "playwright test --ui",
"playwright:codegen": "playwright codegen"
"playwright:codegen": "playwright codegen",
"generate:app-docs": "node generateApplicationDocs.js"
},
"dependencies": {
"@analytics/session-utils": "^0.1.17",
Expand Down Expand Up @@ -251,4 +252,4 @@
"jsonpath-plus": "10.2.0",
"cross-spawn": "7.0.5"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,12 @@ test('Search Index Application', async ({ page }) => {
);

await page.click('[data-testid="configuration"]');
await page.waitForLoadState('networkidle');

await expect(page.locator('#search-indexing-application')).toContainText(
'Search Indexing Application'
);

await page.fill('#root\\/batchSize', '0');

await page.getByTestId('tree-select-widget').click();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# DataInsightsAppConfig

This schema defines configuration for the Data Insights Application.

$$section
### Application Type $(id="type")

Application Type

$$

$$section
### batchSize $(id="batchSize")

Maximum number of events processed at a time (Default 100).

$$

$$section
### Recreate DataInsights DataAssets Index $(id="recreateDataAssetsIndex")

Recreates the DataAssets index on DataInsights. Useful if you changed a Custom Property Type and are facing errors. Bear in mind that recreating the index will delete your DataAssets and a backfill will be needed.

$$

$$section
### backfillConfiguration $(id="backfillConfiguration")

$$
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Data Insights Report Application

This schema defines configuration for Data Insights Report Application.

$$section
### Send To Admins $(id="sendToAdmins")

$$

$$section
### Send To Teams $(id="sendToTeams")

$$
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Retention Configuration

Configure retention policies for each entity.

$$section
### Change Event Retention Period (days) $(id="changeEventRetentionPeriod")

Enter the retention period for change event records in days (e.g., 7 for one week, 30 for one month).

$$
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# Search Indexing Application

This schema defines configuration for Search Reindexing Application.

$$section
### Batch Size $(id="batchSize")

Maximum number of events entities in a batch (Default 100).

$$

$$section
### Payload Size $(id="payLoadSize")

Maximum number of events entities in a batch (Default 100).

$$

$$section
### Number of Producer Threads $(id="producerThreads")

Number of threads to use for reindexing

$$

$$section
### Number of Consumer Threads $(id="consumerThreads")

Number of threads to use for reindexing

$$

$$section
### Queue Size to use. $(id="queueSize")

Queue Size to use internally for reindexing.

$$

$$section
### Max Concurrent Requests $(id="maxConcurrentRequests")

Maximum number of concurrent requests to the search index

$$

$$section
### Max Retries $(id="maxRetries")

Maximum number of retries for a failed request

$$

$$section
### Initial Backoff Millis $(id="initialBackoff")

Initial backoff time in milliseconds

$$

$$section
### Max Backoff Millis $(id="maxBackoff")

Maximum backoff time in milliseconds

$$

$$section
### Entities $(id="entities")

List of entities that you need to reindex

$$

$$section
### Recreate Indexes $(id="recreateIndex")

$$

$$section
### Search Index Language $(id="searchIndexMappingLanguage")

Recreate Indexes with updated Language

$$
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import {
import Icon from '@ant-design/icons/lib/components/Icon';
import { IChangeEvent } from '@rjsf/core';
import { RJSFSchema } from '@rjsf/utils';
import validator from '@rjsf/validator-ajv8';
import {
Button,
Col,
Expand All @@ -33,7 +32,7 @@ import {
import { ItemType } from 'antd/lib/menu/hooks/useItems';
import { AxiosError } from 'axios';
import { compare } from 'fast-json-patch';
import { isEmpty, noop } from 'lodash';
import { isEmpty } from 'lodash';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useHistory } from 'react-router-dom';
Expand All @@ -45,7 +44,6 @@ import { ICON_DIMENSION } from '../../../../constants/constants';
import { GlobalSettingOptions } from '../../../../constants/GlobalSettings.constants';
import { useLimitStore } from '../../../../context/LimitsProvider/useLimitsStore';
import { TabSpecificField } from '../../../../enums/entity.enum';
import { ServiceCategory } from '../../../../enums/service.enum';
import {
App,
ScheduleTimeline,
Expand All @@ -67,12 +65,12 @@ import { getEntityName } from '../../../../utils/EntityUtils';
import { formatFormDataForSubmit } from '../../../../utils/JSONSchemaFormUtils';
import { getSettingPath } from '../../../../utils/RouterUtils';
import { showErrorToast, showSuccessToast } from '../../../../utils/ToastUtils';
import FormBuilder from '../../../common/FormBuilder/FormBuilder';
import Loader from '../../../common/Loader/Loader';
import { ManageButtonItemLabel } from '../../../common/ManageButtonContentItem/ManageButtonContentItem.component';
import TabsLabel from '../../../common/TabsLabel/TabsLabel.component';
import ConfirmationModal from '../../../Modals/ConfirmationModal/ConfirmationModal';
import PageLayoutV1 from '../../../PageLayoutV1/PageLayoutV1';
import ApplicationConfiguration from '../ApplicationConfiguration/ApplicationConfiguration';
import AppLogo from '../AppLogo/AppLogo.component';
import AppRunsHistory from '../AppRunsHistory/AppRunsHistory.component';
import AppSchedule from '../AppSchedule/AppSchedule.component';
Expand All @@ -97,7 +95,6 @@ const AppDetails = () => {
isSaveLoading: false,
});
const { getResourceLimit } = useLimitStore();
const UiSchema = applicationsClassBase.getJSONUISchema();

const fetchAppDetails = useCallback(async () => {
setLoadingState((prev) => ({ ...prev, isFetchLoading: true }));
Expand Down Expand Up @@ -339,22 +336,12 @@ const AppDetails = () => {
),
key: ApplicationTabs.CONFIGURATION,
children: (
<div className="m-auto max-width-md w-9/10 p-lg p-y-0">
<FormBuilder
hideCancelButton
useSelectWidget
cancelText={t('label.back')}
formData={appData.appConfiguration}
isLoading={loadingState.isSaveLoading}
okText={t('label.submit')}
schema={jsonSchema}
serviceCategory={ServiceCategory.DASHBOARD_SERVICES}
uiSchema={UiSchema}
validator={validator}
onCancel={noop}
onSubmit={onConfigSave}
/>
</div>
<ApplicationConfiguration
appData={appData}
isLoading={loadingState.isSaveLoading}
jsonSchema={jsonSchema}
onConfigSave={onConfigSave}
/>
),
},
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,16 @@ const mockPush = jest.fn();
const mockPatchApplication = jest.fn().mockReturnValue(mockApplicationData);
const mockGetApplicationByName = jest.fn().mockReturnValue(mockApplicationData);

jest.mock('../ApplicationConfiguration/ApplicationConfiguration', () =>
jest.fn().mockImplementation(({ onConfigSave }) => (
<div data-testid="application-configuration">
<button onClick={() => onConfigSave({ formData: {} })}>
Save Config
</button>
</div>
))
);

jest.mock('../../../../rest/applicationAPI', () => ({
configureApp: mockConfigureApp,
deployApp: jest.fn().mockImplementation(() => mockDeployApp()),
Expand Down Expand Up @@ -210,15 +220,6 @@ describe('AppDetails component', () => {
expect(mockRestoreApp).toHaveBeenCalled();
});

it('Configuration tab actions check', async () => {
await renderAppDetails();

userEvent.click(screen.getByRole('tab', { name: 'label.configuration' }));
userEvent.click(screen.getByRole('button', { name: 'Configure Save' }));

expect(mockPatchApplication).toHaveBeenCalled();
});

it('Schedule and Recent Runs tab should not be visible for NoScheduleApps', async () => {
mockGetApplicationByName.mockReturnValueOnce({
...mockApplicationData,
Expand Down
Loading
Loading