Skip to content

Commit

Permalink
Merge pull request #1257 from GnsP/error-classification-ui
Browse files Browse the repository at this point in the history
Error classification UI
  • Loading branch information
itsankit-google authored Jan 10, 2025
2 parents 51eb45a + 2e3b563 commit efd6574
Show file tree
Hide file tree
Showing 22 changed files with 707 additions and 111 deletions.
2 changes: 2 additions & 0 deletions app/cdap/api/pipeline.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ export const MyPipelineApi = {
getStatistics: apiCreator(dataSrc, 'GET', 'REQUEST', statsPath),
getMetadataEndpoints: apiCreator(dataSrc, 'GET', 'REQUEST', metadataPath),
getRunDetails: apiCreator(dataSrc, 'GET', 'REQUEST', `${programPath}/runs/:runid`),
getRunErrorDetails: apiCreator(dataSrc, 'POST', 'REQUEST', `${programPath}/runs/:runid/classify`),

getRuns: apiCreator(dataSrc, 'GET', 'REQUEST', `${programPath}/runs`),
getVersionedRuns: apiCreator(dataSrc, 'GET', 'REQUEST', `${versionedProgramPath}/runs`),
pollRuns: apiCreator(dataSrc, 'GET', 'POLL', `${programPath}/runs`),
Expand Down
34 changes: 34 additions & 0 deletions app/cdap/components/LogViewer/LogsUrlUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright © 2024 Cask Data, Inc.
*
* 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.
*/

import IDataFetcher from 'components/LogViewer/DataFetcher';

export function getRawLogsBasePath(dataFetcher: IDataFetcher) {
const backendUrl = dataFetcher.getRawLogsUrl();
const encodedBackendUrl = encodeURIComponent(backendUrl);

const url = `/downloadLogs?backendPath=${encodedBackendUrl}`;
return url;
}

export function getRawLogsUrl(dataFetcher: IDataFetcher) {
return `${getRawLogsBasePath(dataFetcher)}&type=raw`;
}

export function getDownloadLogsUrl(dataFetcher: IDataFetcher) {
const fileName = dataFetcher.getDownloadFileName();
return `${getRawLogsBasePath(dataFetcher)}&type=download&filename=${fileName}.log`;
}
173 changes: 92 additions & 81 deletions app/cdap/components/LogViewer/TopPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import ArrowDropDown from '@material-ui/icons/ArrowDropDown';
import Popover from 'components/shared/Popover';
import IconSVG from 'components/shared/IconSVG';
import LoadingSVG from 'components/shared/LoadingSVG';
import { getDownloadLogsUrl, getRawLogsUrl } from './LogsUrlUtils';
import RunLogsStatsChips from 'components/PipelineDetails/RunLevelInfo/RunLogsStatsChips';

export const TOP_PANEL_HEIGHT = '50px';

Expand All @@ -33,15 +35,27 @@ const styles = (theme): StyleRules => {
root: {
backgroundColor: theme.palette.grey[900],
display: 'flex',
justifyContent: 'flex-end',
justifyContent: 'space-between',
alignItems: 'center',
height: TOP_PANEL_HEIGHT,
paddingLeft: '20px',
paddingRight: '20px',
position: 'relative',
},
loadingContainer: {
marginRight: 'auto',
margin: '0 auto',
},
leftContainer: {
display: 'flex',
alignItems: 'center',
justifyContent: 'flex-start',
height: TOP_PANEL_HEIGHT,
},
rightContainer: {
display: 'flex',
alignItems: 'center',
justifyContent: 'flex-end',
height: TOP_PANEL_HEIGHT,
},
actionButton: {
margin: theme.spacing(1),
Expand Down Expand Up @@ -144,100 +158,97 @@ const TopPanelView: React.FC<ITopPanelProps> = ({
dataFetcher.getIncludeSystemLogs()
);

function getRawLogsBasePath() {
const backendUrl = dataFetcher.getRawLogsUrl();
const encodedBackendUrl = encodeURIComponent(backendUrl);

const url = `/downloadLogs?backendPath=${encodedBackendUrl}`;
return url;
}

function getRawLogsUrl() {
return `${getRawLogsBasePath()}&type=raw`;
}

function getDownloadLogsUrl() {
const fileName = dataFetcher.getDownloadFileName();
return `${getRawLogsBasePath()}&type=download&filename=${fileName}.log`;
}

function handleToggleSystemLogs() {
const newState = !includeSystemLogs;
setLocalIncludeSystemLogs(newState);
setSystemLogs(newState);
}

return (
<div className={classes.root} data-cy="log-viewer-top-panel" data-testid="log-viewer-top-panel">
<If condition={loading}>
if (loading) {
return (
<div
className={classes.root}
data-cy="log-viewer-top-panel"
data-testid="log-viewer-top-panel"
>
<div className={classes.loadingContainer}>
<LoadingSVG />
</div>
</If>
<Button
variant="contained"
className={classnames(classes.actionButton, { [classes.disabled]: isPolling })}
disabled={isPolling}
onClick={getLatestLogs}
data-testid="scroll-to-latest"
>
Scroll to Latest Logs
<ArrowDownward className={classes.downArrow} />
</Button>
<Button
variant="contained"
className={classes.actionButton}
onClick={handleToggleSystemLogs}
data-testid="view-advanced-logs"
>
{includeSystemLogs ? 'Hide' : 'View'} Advanced Logs
</Button>
<div className={classes.btnGroup}>
</div>
);
}

return (
<div className={classes.root} data-cy="log-viewer-top-panel" data-testid="log-viewer-top-panel">
<div className={classes.leftContainer}>
<RunLogsStatsChips />
</div>
<div className={classes.rightContainer}>
<Button
variant="contained"
className={`${classes.actionButton} ${classes.downloadBtn}`}
href={getDownloadLogsUrl()}
target="_blank"
data-testid="download-all"
className={classnames(classes.actionButton, { [classes.disabled]: isPolling })}
disabled={isPolling}
onClick={getLatestLogs}
data-testid="scroll-to-latest"
>
Download All
Scroll to Latest Logs
<ArrowDownward className={classes.downArrow} />
</Button>
<Popover
target={({ className }) => {
return (
<Button
variant="contained"
className={`${className} ${classes.actionButton} ${classes.dropdownBtn}`}
>
<ArrowDropDown />
</Button>
);
}}
modifiers={{
preventOverflow: {
enabled: true,
boundariesElement: 'viewport',
},
}}
className={classes.popover}
placement="bottom"
showOn="Click"
<Button
variant="contained"
className={classes.actionButton}
onClick={handleToggleSystemLogs}
data-testid="view-advanced-logs"
>
<a href={getRawLogsUrl()} target="_blank">
View Raw Logs
</a>
</Popover>
{includeSystemLogs ? 'Hide' : 'View'} Advanced Logs
</Button>
<div className={classes.btnGroup}>
<Button
variant="contained"
className={`${classes.actionButton} ${classes.downloadBtn}`}
href={getDownloadLogsUrl(dataFetcher)}
target="_blank"
data-testid="download-all"
>
Download All
</Button>
<Popover
target={({ className }) => {
return (
<Button
variant="contained"
className={`${className} ${classes.actionButton} ${classes.dropdownBtn}`}
>
<ArrowDropDown />
</Button>
);
}}
modifiers={{
preventOverflow: {
enabled: true,
boundariesElement: 'viewport',
},
}}
className={classes.popover}
placement="bottom"
showOn="Click"
>
<a href={getRawLogsUrl(dataFetcher)} target="_blank">
View Raw Logs
</a>
</Popover>
</div>
<If condition={typeof onClose === 'function'}>
<span
onClick={onClose}
className={classes.closeButton}
data-cy="log-viewer-close-btn"
data-testid="log-viewer-close-btn"
>
<IconSVG name="icon-close" />
</span>
</If>
</div>
<If condition={typeof onClose === 'function'}>
<span
onClick={onClose}
className={classes.closeButton}
data-cy="log-viewer-close-btn"
data-testid="log-viewer-close-btn"
>
<IconSVG name="icon-close" />
</span>
</If>
</div>
);
};
Expand Down
Loading

0 comments on commit efd6574

Please sign in to comment.