Skip to content

Commit

Permalink
[Improvement][UI] Improve job view link (#452)
Browse files Browse the repository at this point in the history
  • Loading branch information
735140144 authored Sep 11, 2024
1 parent 67174d3 commit fcd49fa
Show file tree
Hide file tree
Showing 9 changed files with 231 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -177,8 +177,14 @@ private String getTextTypeMessage(String content) {
ArrayNode list = JSONUtils.parseArray(content);
StringBuilder contents = new StringBuilder(100);
for (JsonNode jsonNode : list) {
String nodeMessage = jsonNode.toString().replace("\"", "");
contents.append(EmailConstants.TR);
contents.append(EmailConstants.TD).append(jsonNode.toString().replace("\"", "")).append(EmailConstants.TD_END);
if (nodeMessage.startsWith("Task Execution Record")||nodeMessage.startsWith("任务执行记录")){
String formatMessage = String.format("%s : <a href=\"%s\">%s</a>", nodeMessage.substring(0,nodeMessage.indexOf(" : ")),nodeMessage.substring(nodeMessage.indexOf(":")+2),nodeMessage.substring(nodeMessage.indexOf(":")+2));
contents.append(EmailConstants.TD).append(formatMessage).append(EmailConstants.TD_END);
}else {
contents.append(EmailConstants.TD).append(jsonNode.toString().replace("\"", "")).append(EmailConstants.TD_END);
}
contents.append(EmailConstants.TR_END);
}
return EmailConstants.HTML_HEADER_PREFIX + contents.toString() + EmailConstants.TABLE_HTML_TAIL + EmailConstants.BODY_HTML_TAIL;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,4 +73,6 @@ private EmailConstants() {
public static final String EXCEL_SUFFIX_XLSX = ".xlsx";

public static final String SINGLE_SLASH = "/";

public static final String URL="<a href=\"mailto:";
}
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,13 @@ private String getMarkdownMessage(String subject, String content) {
if (StringUtils.isNotEmpty(content)) {
ArrayNode list = JSONUtils.parseArray(content);
for (JsonNode jsonNode : list) {
contents.append(WecomBotConstants.QUOTE_START).append(jsonNode.toString().replace("\"", "")).append(WecomBotConstants.END);
String nodeMessage = jsonNode.toString().replace("\"", "");
if (nodeMessage.startsWith("Task Execution Record")||nodeMessage.startsWith("任务执行记录")){
String formatMessage = String.format("%s : [%s](%s)", nodeMessage.substring(0,nodeMessage.indexOf(" : ")),nodeMessage.substring(nodeMessage.indexOf(":")+2),nodeMessage.substring(nodeMessage.indexOf(":")+2));
contents.append(WecomBotConstants.QUOTE_START).append(formatMessage).append(WecomBotConstants.END);
}else {
contents.append(WecomBotConstants.QUOTE_START).append(nodeMessage).append(WecomBotConstants.END);
}
}
}
return contents.toString();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*
* 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.
*/
package io.datavines.server.api.controller;

import io.datavines.core.constant.DataVinesConstants;
import io.datavines.core.entity.ResultMap;
import io.datavines.core.enums.Status;
import io.datavines.core.exception.DataVinesServerException;
import io.datavines.server.api.annotation.AuthIgnore;
import io.datavines.server.dqc.coordinator.log.LogService;
import io.datavines.server.repository.entity.JobExecution;
import io.datavines.server.repository.service.JobExecutionService;
import io.datavines.server.utils.FileUtils;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

import static io.datavines.common.utils.OSUtils.judgeConcurrentHost;


@Slf4j
@Api(value = "job", tags = "job", produces = MediaType.APPLICATION_JSON_VALUE)
@RestController
@RequestMapping(value = DataVinesConstants.BASE_API_PATH + "/history/job/execution", produces = MediaType.APPLICATION_JSON_VALUE)
public class JobHistoryExecutionController {

@Autowired
private JobExecutionService jobExecutionService;

@Resource
private LogService logService;


@AuthIgnore
@ApiOperation(value = "queryLogWithOffsetLine", notes = "query task log with offsetLine")
@GetMapping(value = "/queryLogWithOffsetLine")
public Object queryLogWithOffsetLine(@RequestParam("taskId") Long taskId,
@RequestParam("offsetLine") int offsetLine,
HttpServletRequest request, HttpServletResponse response) throws IOException {
String taskHost = jobExecutionService.getJobExecutionHost(taskId);
Boolean isConcurrentHost = judgeConcurrentHost(taskHost);
if (isConcurrentHost) {
return ResponseEntity.ok(new ResultMap().success().payload(logService.queryLog(taskId, offsetLine)));
}

response.sendRedirect(request.getScheme() + "://" + taskHost +
"/api/v1/history/job/execution/queryLogWithOffsetLine?offsetLine=" + offsetLine + "&taskId=" + taskId + "&Authorization=" + request.getHeader("Authorization"));
return null;
}

@AuthIgnore
@ApiOperation(value = "download", notes = "download log file")
@GetMapping(value = "/download")
public void download(@RequestParam("taskId") Long taskId, HttpServletRequest request, HttpServletResponse response) throws IOException {
JobExecution jobExecution = jobExecutionService.getById(taskId);
if(null == jobExecution){
throw new DataVinesServerException(Status.TASK_NOT_EXIST_ERROR, taskId);
}
String taskHost = jobExecution.getExecuteHost();
if(StringUtils.isEmpty(taskHost)){
throw new DataVinesServerException(Status.TASK_EXECUTE_HOST_NOT_EXIST_ERROR, taskId);
}
Boolean isConcurrentHost = judgeConcurrentHost(taskHost);
if (isConcurrentHost) {
if(StringUtils.isEmpty(jobExecution.getLogPath())){
throw new DataVinesServerException(Status.TASK_LOG_PATH_NOT_EXIST_ERROR, taskId);
}
FileUtils.downloadToResp(jobExecution.getLogPath(), response);
return;
}
response.sendRedirect(request.getScheme() + "://" + taskHost + "/api/v1/history/job/execution/download?taskId=" + taskId+"&Authorization="+request.getHeader("Authorization"));
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.nio.charset.StandardCharsets;
import java.util.*;

import static io.datavines.common.ConfigConstants.FIX_VALUE;
Expand Down Expand Up @@ -142,7 +143,7 @@ private void sendErrorEmail(Long jobExecutionId) {
dataSourceName = dataSource.getName();
dataSourceType = dataSource.getType();
if (!CommonPropertyUtils.DATAVINES_FQDN_DEFAULT.equals(CommonPropertyUtils.getString(CommonPropertyUtils.DATAVINES_FQDN))) {
fqdn = CommonPropertyUtils.getString(CommonPropertyUtils.DATAVINES_FQDN) + String.format("/#/main/detail/%s/jobs/instance?jobId=%s", dataSourceId, jobId);
fqdn = CommonPropertyUtils.getString(CommonPropertyUtils.DATAVINES_FQDN) + String.format("/#/history?%s", Base64.getEncoder().encodeToString(String.format("jobId=%s&executionId=%s",jobId,jobExecutionId).getBytes(StandardCharsets.UTF_8)));
}
}

Expand Down
1 change: 1 addition & 0 deletions datavines-ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"ahooks": "^3.7.4",
"antd": "^5.0.5",
"axios": "^0.21.1",
"base-64": "^1.0.0",
"dayjs": "^1.11.7",
"echarts": "^5.4.0",
"moment": "^2.29.4",
Expand Down
4 changes: 4 additions & 0 deletions datavines-ui/src/router/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ export const routerNoLogin: TRouterItem[] = [
path: '/forgetPwd',
component: lazy(() => import(/* webpackChunkName: 'view-forgetPwd' */ '@/view/ForgetPassword')),
},
{
path: '/history',
component: lazy(()=>import('@/view/JobHistory'))
}
];

export {
Expand Down
11 changes: 11 additions & 0 deletions datavines-ui/src/utils/base64.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// @ts-ignore
import { decode } from "base-64";

export function base64Decode(encodedString: string): string | null {
try {
return decode(encodedString);
} catch (error) {
console.error('Base64 decoding error:', error);
return null;
}
}
98 changes: 98 additions & 0 deletions datavines-ui/src/view/JobHistory/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/* eslint-disable react/no-danger */
import React, {useRef, useState, useImperativeHandle} from 'react';
import { DownloadOutlined, SyncOutlined} from '@ant-design/icons';
import {usePersistFn, useMount,} from '@/common';
import {useIntl} from 'react-intl';
import {$http} from '@/http';
import {download} from '@/utils';
import querystring from "querystring";
import {base64Decode} from "utils/base64";

const JobHistory = () => {
const intl = useIntl();
const innerRef = useRef<any>();
const dealMsg = (msg: string) => {
if (msg) {
return msg.replace(/\r\n/g, '<br>');
}
return '';
};
const [loading, setLoading] = useState(false);
const [wholeLog, setWholeLog] = useState<{ offsetLine: number, msg: string }[]>([]);
let executionId = querystring.parse(base64Decode(window.location.href.split('?')[1] as string) || '').executionId;
const getData = async (offsetLine: number) => {
try {
setLoading(true);
const res = (await $http.get('history/job/execution/queryLogWithOffsetLine', {
taskId: executionId,
offsetLine,
})) || [];
res.msg = dealMsg(res.msg);
if (offsetLine === 0) {
setWholeLog([res]);
} else {
setWholeLog([...wholeLog, res]);
}
} catch (error) {
} finally {
setLoading(false);
}
};

const onDownload = usePersistFn(async () => {
try {
const blob = await $http.get('history/job/execution/download', { taskId: executionId }, {
responseType: 'blob',
});
download(blob);
} catch (error) {
}
});

useMount(async () => {
getData(0);
});
useImperativeHandle(innerRef, () => ({
onRefresh() {
getData(wholeLog[wholeLog.length - 1]?.offsetLine || 0);
},
}));
return (
<div className={"ant-modal-content"}>
<div style={{position: 'fixed', padding: "10px 0", top: 0, left: 0, width: '100%', zIndex: 1000, display: 'flex', justifyContent: 'space-between', alignItems: 'center',fontSize:16,fontWeight:600,lineHeight:1.5}}>
<span style={{marginLeft: 20}}>{intl.formatMessage({id: 'job_log_view_log'})}</span>
<div style={{marginRight: 30,color:'#1677ff'}}>
<a
style={{marginRight: 10}}
onClick={() => {
innerRef.current.onRefresh();
}}
>
<SyncOutlined style={{marginRight: 5}}/>
{intl.formatMessage({id: 'job_log_refresh'})}
</a>
<a style={{marginRight: 10}} onClick={onDownload}>
<DownloadOutlined style={{marginRight: 5}}/>
{intl.formatMessage({id: 'job_log_download'})}
</a>

</div>
</div>
<div style={{minHeight: 300, padding: "60px 20px",lineHeight:1.5,fontSize:14}}>
{
wholeLog.map((item) => (
<div dangerouslySetInnerHTML={{__html: item.msg}}/>
))
}
<div/>
</div>
</div>
)

}
export default JobHistory;





0 comments on commit fcd49fa

Please sign in to comment.