mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-07-24 09:50:01 +00:00
fix(ui): API endpoints to fetch elastic search data on settings page. (#10946)
* Made changes to use the newly created APIs for fetching elasticSearch re-indexing data * DOcs Fix and add validation * Fixed APIs to fetch the batchJob reIndex data worked on comments --------- Co-authored-by: Sriharsha Chintalapani <harshach@users.noreply.github.com> Co-authored-by: mohitdeuex <mohit.y@deuexsolutions.com>
This commit is contained in:
parent
c997d8c80b
commit
d112b40a3f
@ -418,40 +418,34 @@ public class SearchResource {
|
||||
return Response.status(OK).entity(response).build();
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/reindex")
|
||||
@GET
|
||||
@Path("/reindex/latest")
|
||||
@Operation(
|
||||
operationId = "reindexEntities",
|
||||
summary = "Reindex entities",
|
||||
description = "Reindex Elastic Search Entities",
|
||||
operationId = "getLatestReindexBatchJob",
|
||||
summary = "Get Latest Reindexing Batch Job",
|
||||
description = "Fetches the Latest Reindexing Job",
|
||||
responses = {
|
||||
@ApiResponse(responseCode = "200", description = "Success"),
|
||||
@ApiResponse(responseCode = "404", description = "Bot for instance {id} is not found")
|
||||
@ApiResponse(responseCode = "404", description = "No Job Found")
|
||||
})
|
||||
public Response reindexEntities(
|
||||
@Context UriInfo uriInfo,
|
||||
@Context SecurityContext securityContext,
|
||||
@Valid CreateEventPublisherJob createRequest) {
|
||||
public Response reindexLatestJob(@Context UriInfo uriInfo, @Context SecurityContext securityContext)
|
||||
throws IOException {
|
||||
// Only admins can issue a reindex request
|
||||
authorizer.authorizeAdmin(securityContext);
|
||||
return Response.status(Response.Status.CREATED)
|
||||
.entity(
|
||||
ReIndexingHandler.getInstance()
|
||||
.createReindexingJob(securityContext.getUserPrincipal().getName(), createRequest))
|
||||
.build();
|
||||
return Response.status(Response.Status.OK).entity(ReIndexingHandler.getInstance().getLatestJob()).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/reindex/stream/status")
|
||||
@Operation(
|
||||
operationId = "getStreamJobCurrentStatus",
|
||||
summary = "Get Stream Job Current Status",
|
||||
description = "Reindex all job last status",
|
||||
operationId = "getStreamJobStatus",
|
||||
summary = "Get Stream Job Latest Status",
|
||||
description = "Stream Job Status",
|
||||
responses = {
|
||||
@ApiResponse(responseCode = "200", description = "Success"),
|
||||
@ApiResponse(responseCode = "404", description = "Run model {runMode} is not found")
|
||||
@ApiResponse(responseCode = "404", description = "Status not found")
|
||||
})
|
||||
public Response reindexAllJobLastStatus(
|
||||
@Context UriInfo uriInfo, @Context SecurityContext securityContext, @PathParam("runMode") String runMode)
|
||||
public Response reindexAllJobLastStatus(@Context UriInfo uriInfo, @Context SecurityContext securityContext)
|
||||
throws IOException {
|
||||
// Only admins can issue a reindex request
|
||||
authorizer.authorizeAdmin(securityContext);
|
||||
@ -466,30 +460,11 @@ public class SearchResource {
|
||||
return Response.status(Response.Status.NOT_FOUND).entity("No Last Run.").build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/reindex/latest")
|
||||
@Operation(
|
||||
operationId = "getReindexLatestJob",
|
||||
summary = "Get last reindex job status",
|
||||
tags = "search",
|
||||
description = "Last Reindex job last status",
|
||||
responses = {
|
||||
@ApiResponse(responseCode = "200", description = "Success"),
|
||||
@ApiResponse(responseCode = "404", description = "Run model {runMode} is not found")
|
||||
})
|
||||
public Response reindexLatestJob(@Context UriInfo uriInfo, @Context SecurityContext securityContext)
|
||||
throws IOException {
|
||||
// Only admins can issue a reindex request
|
||||
authorizer.authorizeAdmin(securityContext);
|
||||
return Response.status(Response.Status.OK).entity(ReIndexingHandler.getInstance().getLatestJob()).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/reindex/{jobId}")
|
||||
@Operation(
|
||||
operationId = "getReindexJobId",
|
||||
summary = "Get reindex job with Id",
|
||||
tags = "search",
|
||||
operationId = "getBatchReindexBatchJobWithId",
|
||||
summary = "Get Batch Reindexing Job with Id",
|
||||
description = "Get reindex job with Id",
|
||||
responses = {
|
||||
@ApiResponse(responseCode = "200", description = "Success"),
|
||||
@ -508,10 +483,10 @@ public class SearchResource {
|
||||
@GET
|
||||
@Path("/reindex")
|
||||
@Operation(
|
||||
operationId = "getAllReindexJob",
|
||||
summary = "Get all reindex job",
|
||||
operationId = "getAllReindexBatchJobs",
|
||||
summary = "Get all reindex batch jobs",
|
||||
tags = "search",
|
||||
description = "Get all reindex",
|
||||
description = "Get all reindex batch jobs",
|
||||
responses = {
|
||||
@ApiResponse(responseCode = "200", description = "Success"),
|
||||
@ApiResponse(responseCode = "404", description = "Not found")
|
||||
@ -523,6 +498,28 @@ public class SearchResource {
|
||||
return Response.status(Response.Status.OK).entity(ReIndexingHandler.getInstance().getAllJobs()).build();
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/reindex")
|
||||
@Operation(
|
||||
operationId = "runBatchReindexing",
|
||||
summary = "Run Batch Reindexing",
|
||||
description = "Reindex Elastic Search Reindexing Entities",
|
||||
responses = {
|
||||
@ApiResponse(responseCode = "200", description = "Success"),
|
||||
@ApiResponse(responseCode = "404", description = "Bot for instance {id} is not found")
|
||||
})
|
||||
public Response reindexEntities(
|
||||
@Context UriInfo uriInfo,
|
||||
@Context SecurityContext securityContext,
|
||||
@Valid CreateEventPublisherJob createRequest) {
|
||||
authorizer.authorizeAdmin(securityContext);
|
||||
return Response.status(Response.Status.CREATED)
|
||||
.entity(
|
||||
ReIndexingHandler.getInstance()
|
||||
.createReindexingJob(securityContext.getUserPrincipal().getName(), createRequest))
|
||||
.build();
|
||||
}
|
||||
|
||||
private SearchSourceBuilder buildAggregateSearchBuilder(String query, int from, int size) {
|
||||
QueryStringQueryBuilder queryBuilder = QueryBuilders.queryStringQuery(query).lenient(true);
|
||||
SearchSourceBuilder searchSourceBuilder = searchBuilder(queryBuilder, null, from, size);
|
||||
|
@ -18,9 +18,11 @@ import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
@ -36,8 +38,10 @@ import org.openmetadata.schema.api.CreateEventPublisherJob;
|
||||
import org.openmetadata.schema.system.EventPublisherJob;
|
||||
import org.openmetadata.schema.system.Failure;
|
||||
import org.openmetadata.schema.system.Stats;
|
||||
import org.openmetadata.service.Entity;
|
||||
import org.openmetadata.service.elasticsearch.ElasticSearchIndexDefinition;
|
||||
import org.openmetadata.service.jdbi3.CollectionDAO;
|
||||
import org.openmetadata.service.workflows.searchIndex.ReindexingUtil;
|
||||
import org.openmetadata.service.workflows.searchIndex.SearchIndexWorkflow;
|
||||
|
||||
@Slf4j
|
||||
@ -80,6 +84,9 @@ public class ReIndexingHandler {
|
||||
// Remove jobs in case they are completed
|
||||
clearCompletedJobs();
|
||||
|
||||
// validate current job
|
||||
validateJob(createReindexingJob);
|
||||
|
||||
// Create new Task
|
||||
if (taskQueue.size() >= 5) {
|
||||
throw new RuntimeException("Cannot create new Reindexing Jobs. There are pending jobs.");
|
||||
@ -125,6 +132,23 @@ public class ReIndexingHandler {
|
||||
&& entry.getValue().getJobData().getStatus() != EventPublisherJob.Status.RUNNING);
|
||||
}
|
||||
|
||||
private void validateJob(CreateEventPublisherJob job) {
|
||||
Objects.requireNonNull(job);
|
||||
Set<String> storedEntityList = new HashSet<>(Entity.getEntityList());
|
||||
if (job.getEntities().size() > 0) {
|
||||
job.getEntities()
|
||||
.forEach(
|
||||
(entityType) -> {
|
||||
if (!storedEntityList.contains(entityType) && !ReindexingUtil.isDataInsightIndex(entityType)) {
|
||||
throw new IllegalArgumentException(
|
||||
String.format("Entity Type : %s is not a valid Entity", entityType));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
throw new IllegalArgumentException("Entities cannot be Empty");
|
||||
}
|
||||
}
|
||||
|
||||
public void removeCompletedJob(UUID jobId) {
|
||||
REINDEXING_JOB_MAP.remove(jobId);
|
||||
}
|
||||
|
@ -29,6 +29,13 @@
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.loader.loader-x-sm {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border-width: 1.5px;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.loader.loader-success {
|
||||
border-left-color: #51c41a;
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ import React, { CSSProperties, FunctionComponent } from 'react';
|
||||
import './Loader.css';
|
||||
|
||||
type Props = {
|
||||
size?: 'default' | 'small';
|
||||
size?: 'default' | 'small' | 'x-small';
|
||||
type?: 'default' | 'success' | 'error' | 'white';
|
||||
className?: string;
|
||||
style?: CSSProperties;
|
||||
@ -34,6 +34,11 @@ const Loader: FunctionComponent<Props> = ({
|
||||
classes += ' loader-sm';
|
||||
|
||||
break;
|
||||
case 'x-small':
|
||||
classes += ' loader-x-sm';
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -77,7 +77,6 @@ export const ELASTIC_SEARCH_INDEX_ENTITIES = [
|
||||
export const ELASTIC_SEARCH_INITIAL_VALUES = {
|
||||
entities: ['all'],
|
||||
batchSize: 100,
|
||||
flushIntervalInSec: 30,
|
||||
recreateIndex: true,
|
||||
searchIndexMappingLanguage: SearchIndexMappingLanguage.En,
|
||||
};
|
||||
|
@ -75,16 +75,6 @@ const ReIndexAllModal = ({
|
||||
treeData={ENTITY_TREE_OPTIONS}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t('label.flush-interval-secs')}
|
||||
name="flushIntervalInSec">
|
||||
<Input
|
||||
data-testid="flush-interval-in-sec"
|
||||
placeholder={t('label.enter-entity', {
|
||||
entity: t('label.second-plural'),
|
||||
})}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label={`${t('label.batch-size')}:`} name="batchSize">
|
||||
<Input
|
||||
|
@ -25,7 +25,8 @@ import { isEmpty, isEqual, startCase } from 'lodash';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
getAllReIndexStatus,
|
||||
getBatchJobReIndexStatus,
|
||||
getStreamJobReIndexStatus,
|
||||
reIndexByPublisher,
|
||||
} from 'rest/elasticSearchReIndexAPI';
|
||||
import { SOCKET_EVENTS } from '../../constants/constants';
|
||||
@ -40,7 +41,6 @@ import {
|
||||
getEventPublisherStatusText,
|
||||
getStatusResultBadgeIcon,
|
||||
} from '../../utils/EventPublisherUtils';
|
||||
import SVGIcons from '../../utils/SvgUtils';
|
||||
import { getDateTimeByTimeStampWithZone } from '../../utils/TimeUtils';
|
||||
import { showErrorToast, showSuccessToast } from '../../utils/ToastUtils';
|
||||
import './ElasticSearchReIndex.style.less';
|
||||
@ -63,7 +63,7 @@ const ElasticSearchIndexPage = () => {
|
||||
const fetchBatchReIndexedData = async () => {
|
||||
try {
|
||||
setBatchLoading(true);
|
||||
const response = await getAllReIndexStatus(RunMode.Batch);
|
||||
const response = await getBatchJobReIndexStatus();
|
||||
|
||||
setBatchJobData(response);
|
||||
} catch {
|
||||
@ -76,7 +76,7 @@ const ElasticSearchIndexPage = () => {
|
||||
const fetchStreamReIndexedData = async () => {
|
||||
try {
|
||||
setStreamLoading(true);
|
||||
const response = await getAllReIndexStatus(RunMode.Stream);
|
||||
const response = await getStreamJobReIndexStatus();
|
||||
|
||||
setStreamJobData(response);
|
||||
} catch {
|
||||
@ -191,24 +191,15 @@ const ElasticSearchIndexPage = () => {
|
||||
<span className="text-grey-muted">{`${t(
|
||||
'label.status'
|
||||
)}:`}</span>
|
||||
<span className="m-l-xs">
|
||||
<Space size={8}>
|
||||
{batchJobData?.status && (
|
||||
<SVGIcons
|
||||
alt="result"
|
||||
className="w-4"
|
||||
icon={getStatusResultBadgeIcon(
|
||||
batchJobData?.status
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
<span>
|
||||
{getEventPublisherStatusText(
|
||||
batchJobData?.status
|
||||
) || '--'}
|
||||
</span>
|
||||
</Space>
|
||||
</span>
|
||||
|
||||
<Space align="center" className="m-l-xs" size={8}>
|
||||
{getStatusResultBadgeIcon(batchJobData?.status)}
|
||||
<span>
|
||||
{getEventPublisherStatusText(
|
||||
batchJobData?.status
|
||||
) || '--'}
|
||||
</span>
|
||||
</Space>
|
||||
</div>
|
||||
<Divider type="vertical" />
|
||||
<div className="flex">
|
||||
@ -232,15 +223,13 @@ const ElasticSearchIndexPage = () => {
|
||||
<Badge
|
||||
className="request-badge success"
|
||||
count={
|
||||
batchJobData?.stats?.jobStats
|
||||
?.totalSuccessRecords
|
||||
batchJobData?.stats?.jobStats?.successRecords
|
||||
}
|
||||
overflowCount={99999999}
|
||||
title={`${t('label.entity-index', {
|
||||
entity: t('label.success'),
|
||||
})}: ${
|
||||
batchJobData?.stats?.jobStats
|
||||
?.totalSuccessRecords
|
||||
batchJobData?.stats?.jobStats?.successRecords
|
||||
}`}
|
||||
/>
|
||||
|
||||
@ -248,15 +237,13 @@ const ElasticSearchIndexPage = () => {
|
||||
showZero
|
||||
className="request-badge failed"
|
||||
count={
|
||||
batchJobData?.stats?.jobStats
|
||||
?.totalFailedRecords
|
||||
batchJobData?.stats?.jobStats?.failedRecords
|
||||
}
|
||||
overflowCount={99999999}
|
||||
title={`${t('label.entity-index', {
|
||||
entity: t('label.failed'),
|
||||
})}: ${
|
||||
batchJobData?.stats?.jobStats
|
||||
?.totalFailedRecords
|
||||
batchJobData?.stats?.jobStats?.failedRecords
|
||||
}`}
|
||||
/>
|
||||
</Space>
|
||||
@ -346,7 +333,7 @@ const ElasticSearchIndexPage = () => {
|
||||
title={t('label.elasticsearch')}>
|
||||
<Row gutter={[16, 8]}>
|
||||
<Col span={24}>
|
||||
<Space direction="horizontal" size={16}>
|
||||
<Space direction="horizontal" size={0}>
|
||||
<div className="flex">
|
||||
<span className="text-grey-muted">{`${t(
|
||||
'label.mode'
|
||||
@ -355,30 +342,21 @@ const ElasticSearchIndexPage = () => {
|
||||
{startCase(streamJobData?.runMode) || '--'}
|
||||
</span>
|
||||
</div>
|
||||
<Divider type="vertical" />
|
||||
<div className="flex">
|
||||
<span className="text-grey-muted">{`${t(
|
||||
'label.status'
|
||||
)}:`}</span>
|
||||
<span className="m-l-xs">
|
||||
<Space size={8}>
|
||||
{streamJobData?.status && (
|
||||
<SVGIcons
|
||||
alt="result"
|
||||
className="w-4"
|
||||
icon={getStatusResultBadgeIcon(
|
||||
streamJobData?.status
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
<span>
|
||||
{getEventPublisherStatusText(
|
||||
streamJobData?.status
|
||||
) || '--'}
|
||||
</span>
|
||||
</Space>
|
||||
</span>
|
||||
<Space align="center" className="m-l-xs" size={8}>
|
||||
{getStatusResultBadgeIcon(streamJobData?.status)}
|
||||
<span>
|
||||
{getEventPublisherStatusText(
|
||||
streamJobData?.status
|
||||
) || '--'}
|
||||
</span>
|
||||
</Space>
|
||||
</div>
|
||||
|
||||
<Divider type="vertical" />
|
||||
<div className="flex">
|
||||
<span className="text-grey-muted">{`${t(
|
||||
'label.last-updated'
|
||||
@ -391,6 +369,7 @@ const ElasticSearchIndexPage = () => {
|
||||
: '--'}
|
||||
</span>
|
||||
</div>
|
||||
<Divider type="vertical" />
|
||||
<div className="flex">
|
||||
<span className="text-grey-muted">{`${t(
|
||||
'label.last-failed-at'
|
||||
|
@ -17,17 +17,22 @@ import { CreateEventPublisherJob } from '../generated/api/createEventPublisherJo
|
||||
import {
|
||||
EventPublisherJob,
|
||||
PublisherType,
|
||||
RunMode,
|
||||
} from '../generated/system/eventPublisherJob';
|
||||
|
||||
export const getAllReIndexStatus = async (mode: RunMode) => {
|
||||
export const getStreamJobReIndexStatus = async () => {
|
||||
const res = await axiosClient.get<EventPublisherJob>(
|
||||
`/indexResource/reindex/status/${mode}`
|
||||
`/search/reindex/stream/status`
|
||||
);
|
||||
|
||||
return res.data;
|
||||
};
|
||||
|
||||
export const getBatchJobReIndexStatus = async () => {
|
||||
const res = await axiosClient.get<EventPublisherJob>(`search/reindex/latest`);
|
||||
|
||||
return res.data;
|
||||
};
|
||||
|
||||
export const reIndexByPublisher = async (data: CreateEventPublisherJob) => {
|
||||
const payload = {
|
||||
...data,
|
||||
@ -37,7 +42,7 @@ export const reIndexByPublisher = async (data: CreateEventPublisherJob) => {
|
||||
const res = await axiosClient.post<
|
||||
CreateEventPublisherJob,
|
||||
AxiosResponse<EventPublisherJob>
|
||||
>('/indexResource/reindex', payload);
|
||||
>('/search/reindex', payload);
|
||||
|
||||
return res.data;
|
||||
};
|
||||
|
@ -11,26 +11,30 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import Loader from 'components/Loader/Loader';
|
||||
import { Status } from 'generated/system/eventPublisherJob';
|
||||
import { t } from 'i18next';
|
||||
import { Icons } from './SvgUtils';
|
||||
import React from 'react';
|
||||
import { ReactComponent as IconFailBadge } from '../assets/svg/fail-badge.svg';
|
||||
import { ReactComponent as IconTaskOpen } from '../assets/svg/in-progress.svg';
|
||||
import { ReactComponent as IconSuccessBadge } from '../assets/svg/success-badge.svg';
|
||||
|
||||
export const getStatusResultBadgeIcon = (status: string) => {
|
||||
export const getStatusResultBadgeIcon = (status?: string) => {
|
||||
switch (status) {
|
||||
case Status.Running:
|
||||
case Status.Started:
|
||||
case Status.Active:
|
||||
return Icons.TASK_OPEN;
|
||||
|
||||
case Status.Completed:
|
||||
return Icons.SUCCESS_BADGE;
|
||||
return <IconSuccessBadge height={14} width={14} />;
|
||||
|
||||
case Status.Failed:
|
||||
case Status.ActiveWithError:
|
||||
return Icons.FAIL_BADGE;
|
||||
return <IconFailBadge height={14} width={14} />;
|
||||
|
||||
case Status.Running:
|
||||
case Status.Started:
|
||||
return <Loader size="x-small" />;
|
||||
|
||||
case Status.Active:
|
||||
default:
|
||||
return '';
|
||||
return <IconTaskOpen height={14} width={14} />;
|
||||
}
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user