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

Fix 19499: feat(apps): add app config in run record #19544

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@ public void jobToBeExecuted(JobExecutionContext jobExecutionContext) {
.withTimestamp(jobStartTime)
.withRunType(runType)
.withStatus(AppRunRecord.Status.RUNNING)
.withScheduleInfo(jobApp.getAppSchedule());
.withScheduleInfo(jobApp.getAppSchedule())
.withConfig(JsonUtils.getMap(jobApp.getAppConfiguration()));

boolean update = false;
if (jobExecutionContext.isRecovering()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ public ResultList<App> list(
mediaType = "application/json",
schema = @Schema(implementation = AppRunList.class)))
})
public Response listAppRuns(
public ResultList<AppRunRecord> listAppRuns(
@Context UriInfo uriInfo,
@Context SecurityContext securityContext,
@Parameter(description = "Name of the App", schema = @Schema(type = "string"))
Expand Down Expand Up @@ -281,9 +281,7 @@ public Response listAppRuns(
Long endTs) {
App installation = repository.getByName(uriInfo, name, repository.getFields("id,pipelines"));
if (installation.getAppType().equals(AppType.Internal)) {
return Response.status(Response.Status.OK)
.entity(repository.listAppRuns(installation, limitParam, offset))
.build();
return repository.listAppRuns(installation, limitParam, offset);
}
if (!installation.getPipelines().isEmpty()) {
EntityReference pipelineRef = installation.getPipelines().get(0);
Expand All @@ -292,13 +290,27 @@ public Response listAppRuns(
IngestionPipeline ingestionPipeline =
ingestionPipelineRepository.get(
uriInfo, pipelineRef.getId(), ingestionPipelineRepository.getFields(FIELD_OWNERS));
return Response.ok(
ingestionPipelineRepository.listPipelineStatus(
ingestionPipeline.getFullyQualifiedName(), startTs, endTs),
MediaType.APPLICATION_JSON_TYPE)
.build();
return ingestionPipelineRepository
.listPipelineStatus(ingestionPipeline.getFullyQualifiedName(), startTs, endTs)
.map(pipelineStatus -> convertPipelineStatus(installation, pipelineStatus));
}
throw new IllegalArgumentException("App does not have an associated pipeline.");
throw new IllegalArgumentException("App does not have a scheduled deployment");
}

private static AppRunRecord convertPipelineStatus(App app, PipelineStatus pipelineStatus) {
return new AppRunRecord()
.withAppId(app.getId())
.withAppName(app.getName())
.withExecutionTime(pipelineStatus.getStartDate())
.withEndTime(pipelineStatus.getEndDate())
.withStatus(
switch (pipelineStatus.getPipelineState()) {
case QUEUED -> AppRunRecord.Status.PENDING;
case SUCCESS -> AppRunRecord.Status.SUCCESS;
case FAILED, PARTIAL_SUCCESS -> AppRunRecord.Status.FAILED;
case RUNNING -> AppRunRecord.Status.RUNNING;
})
.withConfig(pipelineStatus.getConfig());
}

@GET
Expand Down Expand Up @@ -617,7 +629,7 @@ public Response create(
limits.enforceLimits(
securityContext,
getResourceContext(),
new OperationContext(Entity.APPLICATION, MetadataOperation.CREATE));
new OperationContext(APPLICATION, MetadataOperation.CREATE));
if (SCHEDULED_TYPES.contains(app.getScheduleType())) {
ApplicationHandler.getInstance()
.installApplication(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
import javax.validation.constraints.NotNull;
import org.openmetadata.schema.system.EntityError;
import org.openmetadata.schema.type.Paging;
Expand Down Expand Up @@ -95,6 +97,11 @@ public ResultList(List<T> data, Integer offset, int total) {
paging = new Paging().withBefore(null).withAfter(null).withTotal(total).withOffset(offset);
}

/* Conveniently map the data to another type without the need to create a new ResultList */
public <S> ResultList<S> map(Function<T, S> mapper) {
return new ResultList<>(data.stream().map(mapper).collect(Collectors.toList()), paging);
}

public ResultList(List<T> data, Integer offset, Integer limit, Integer total) {
this.data = data;
paging =
Expand All @@ -106,6 +113,17 @@ public ResultList(List<T> data, Integer offset, Integer limit, Integer total) {
.withLimit(limit);
}

public ResultList(List<T> data, Paging other) {
this.data = data;
paging =
new Paging()
.withBefore(null)
.withAfter(null)
.withTotal(other.getTotal())
.withOffset(other.getOffset())
.withLimit(other.getLimit());
}

public ResultList(
List<T> data, List<EntityError> errors, String beforeCursor, String afterCursor, int total) {
this.data = data;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@
"active",
"activeError",
"stopped",
"success"
"success",
"pending"
]
},
"runType": {
Expand Down Expand Up @@ -63,6 +64,10 @@
},
"scheduleInfo": {
"$ref": "./app.json#/definitions/appSchedule"
},
"config": {
"descripton": "The configuration used for this application run. It's type will be based on the application type. Old runs might not be compatible with schema of app configuration.",
"$ref": "../../type/basic.json#/definitions/map"
}
},
"additionalProperties": false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@
"status": {
"description": "Ingestion Pipeline summary status. Informed at the end of the execution.",
"$ref": "status.json#/definitions/ingestionStatus"
},
"config": {
"description": "Pipeline configuration for this particular execution.",
"$ref": "../../../type/basic.json#/definitions/map"
}
},
"additionalProperties": false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,22 @@ test('Search Index Application', async ({ page }) => {
await verifyLastExecutionRun(page);
});

await test.step('View App Run Config', async () => {
await page.getByTestId('app-historical-config').click();
await page.waitForSelector('[role="dialog"].ant-modal');

await expect(page.locator('[role="dialog"].ant-modal')).toBeVisible();

await expect(page.locator('.ant-modal-title')).toContainText(
'Search Indexing Configuration'
);

await page.click('[data-testid="app-run-config-close"]');
await page.waitForSelector('[role="dialog"].ant-modal', {
state: 'detached',
});
});

await test.step('Edit application', async () => {
await page.click('[data-testid="edit-button"]');
await page.waitForSelector('[data-testid="schedular-card-container"]');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,7 @@ const AppDetails = () => {
{appData && (
<AppSchedule
appData={appData}
jsonSchema={jsonSchema as RJSFSchema}
loading={{
isRunLoading: loadingState.isRunLoading,
isDeployLoading: loadingState.isDeployLoading,
Expand Down Expand Up @@ -405,7 +406,10 @@ const AppDetails = () => {
key: ApplicationTabs.RECENT_RUNS,
children: (
<div className="p-lg">
<AppRunsHistory appData={appData} />
<AppRunsHistory
appData={appData}
jsonSchema={jsonSchema as RJSFSchema}
/>
</div>
),
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Button, Col, Row, Typography } from 'antd';
import validator from '@rjsf/validator-ajv8';
import { Button, Col, Modal, Row, Space, Typography } from 'antd';
import { ColumnsType } from 'antd/lib/table';
import { AxiosError } from 'axios';
import { isNull } from 'lodash';
import { isNull, noop } from 'lodash';
import React, {
forwardRef,
useCallback,
Expand All @@ -31,8 +32,12 @@ import {
} from '../../../../constants/constants';
import { GlobalSettingOptions } from '../../../../constants/GlobalSettings.constants';
import { useWebSocketConnector } from '../../../../context/WebSocketProvider/WebSocketProvider';
import { ServiceCategory } from '../../../../enums/service.enum';
import { AppType } from '../../../../generated/entity/applications/app';
import { Status } from '../../../../generated/entity/applications/appRunRecord';
import {
AppRunRecord,
Status,
} from '../../../../generated/entity/applications/appRunRecord';
import {
PipelineState,
PipelineStatus,
Expand All @@ -51,24 +56,33 @@ import {
getEpochMillisForPastDays,
getIntervalInMilliseconds,
} from '../../../../utils/date-time/DateTimeUtils';
import { getEntityName } from '../../../../utils/EntityUtils';
import { getLogsViewerPath } from '../../../../utils/RouterUtils';
import { showErrorToast } from '../../../../utils/ToastUtils';
import ErrorPlaceHolder from '../../../common/ErrorWithPlaceholder/ErrorPlaceHolder';
import FormBuilder from '../../../common/FormBuilder/FormBuilder';
import NextPrevious from '../../../common/NextPrevious/NextPrevious';
import { PagingHandlerParams } from '../../../common/NextPrevious/NextPrevious.interface';
import StatusBadge from '../../../common/StatusBadge/StatusBadge.component';
import { StatusType } from '../../../common/StatusBadge/StatusBadge.interface';
import Table from '../../../common/Table/Table';
import StopScheduleModal from '../../../Modals/StopScheduleRun/StopScheduleRunModal';
import applicationsClassBase from '../AppDetails/ApplicationsClassBase';
import AppLogsViewer from '../AppLogsViewer/AppLogsViewer.component';
import './app-run-history.less';
import {
AppRunRecordWithId,
AppRunsHistoryProps,
} from './AppRunsHistory.interface';

const AppRunsHistory = forwardRef(
(
{ appData, maxRecords, showPagination = true }: AppRunsHistoryProps,
{
appData,
maxRecords,
jsonSchema,
showPagination = true,
}: AppRunsHistoryProps,
ref
) => {
const { socket } = useWebSocketConnector();
Expand All @@ -80,6 +94,17 @@ const AppRunsHistory = forwardRef(
>([]);
const [expandedRowKeys, setExpandedRowKeys] = useState<string[]>([]);
const [isStopModalOpen, setIsStopModalOpen] = useState<boolean>(false);
const [showConfigModal, setShowConfigModal] = useState<boolean>(false);
const [appRunRecordConfig, setAppRunRecordConfig] = useState<
AppRunRecord['config']
>({});
const UiSchema = {
...applicationsClassBase.getJSONUISchema(),
'ui:submitButtonProps': {
showButton: false,
buttonText: 'submit',
},
};

const {
currentPage,
Expand Down Expand Up @@ -132,6 +157,11 @@ const AppRunsHistory = forwardRef(
return false;
}, []);

const showAppRunConfig = (record: AppRunRecordWithId) => {
setShowConfigModal(true);
setAppRunRecordConfig(record.config ?? {});
};

const getActionButton = useCallback(
(record: AppRunRecordWithId, index: number) => {
if (
Expand All @@ -149,6 +179,14 @@ const AppRunsHistory = forwardRef(
onClick={() => handleRowExpandable(record.id)}>
{t('label.log-plural')}
</Button>
<Button
className="m-l-xs p-0"
data-testid="app-historical-config"
size="small"
type="link"
onClick={() => showAppRunConfig(record)}>
{t('label.config')}
</Button>
{/* For status running or activewitherror and supportsInterrupt is true, show stop button */}
{(record.status === Status.Running ||
record.status === Status.ActiveError) &&
Expand Down Expand Up @@ -214,7 +252,7 @@ const AppRunsHistory = forwardRef(
return record.status ? (
<StatusBadge
dataTestId="pipeline-status"
label={STATUS_LABEL[record.status]}
label={STATUS_LABEL[record.status as keyof typeof STATUS_LABEL]}
status={status}
/>
) : (
Expand Down Expand Up @@ -408,6 +446,52 @@ const AppRunsHistory = forwardRef(
}}
/>
)}
<Modal
centered
destroyOnClose
bodyStyle={{
maxHeight: 700,
overflowY: 'scroll',
}}
className="app-config-modal"
closable={false}
data-testid="edit-table-type-property-modal"
footer={
<Space className="w-full justify-end">
<Button
data-testid="app-run-config-close"
type="primary"
onClick={() => setShowConfigModal(false)}>
{t('label.close')}
</Button>
</Space>
}
maskClosable={false}
open={showConfigModal}
title={
<Typography.Text>
{t('label.entity-configuration', {
entity: getEntityName(appData) ?? t('label.application'),
})}
</Typography.Text>
}
width={800}>
<FormBuilder
hideCancelButton
readonly
useSelectWidget
cancelText={t('label.back')}
formData={appRunRecordConfig}
isLoading={false}
okText={t('label.submit')}
schema={jsonSchema}
serviceCategory={ServiceCategory.DASHBOARD_SERVICES}
uiSchema={UiSchema}
validator={validator}
onCancel={noop}
onSubmit={noop}
/>
</Modal>
</>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
* limitations under the License.
*/

import { RJSFSchema } from '@rjsf/utils';
import { App } from '../../../../generated/entity/applications/app';
import { AppRunRecord } from '../../../../generated/entity/applications/appRunRecord';

Expand All @@ -26,4 +27,5 @@ export interface AppRunsHistoryProps {
maxRecords?: number;
appData?: App;
showPagination?: boolean;
jsonSchema: RJSFSchema;
}
Loading
Loading