Skip to content

Commit

Permalink
[Th2-5208] Add ability to cancel notebooks, option to change default …
Browse files Browse the repository at this point in the history
…view type of result group,#display-table field, option to view last N results of Notebook, file path and timestamp types (#571)

* fix parameters type parsing

* add ability to cancel launch and proccess failed notebook request

* created DisplayTeble component

* created ParametersRow component

* added option to change viewType of group

* added remove node to store

* change position of Results Result

* add display-table view type

* update launchNotebook and getResults api

* change display when none node is selected

* change split behaviour on selection

* added filepath and timestamp parameters
  • Loading branch information
molotgor authored Jul 8, 2024
1 parent 2c52433 commit 3bac042
Show file tree
Hide file tree
Showing 21 changed files with 1,191 additions and 323 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "th2-rpt-viewer",
"version": "5.2.7",
"version": "5.2.8",
"description": "",
"main": "index.tsx",
"private": true,
Expand Down
6 changes: 4 additions & 2 deletions src/api/ApiSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,10 @@ export interface BooksApiSchema {
export interface JSONViewerApiSchema {
getLinks: (type: string, dir?: string) => Promise<{ directories: string[]; files: string[] }>;
getParameters: (path: string) => Promise<NotebookParameters>;
getResults: (path: string) => Promise<{ result: string }>;
launchNotebook: (path: string, parameters?: Object) => Promise<{ path: string }>;
getResults: (taskId: string) => Promise<{ status: string; result: string; path?: string }>;
getFile: (path: string) => Promise<{ result: string }>;
launchNotebook: (path: string, parameters?: Object) => Promise<{ task_id: string }>;
stopNotebook: (taskId: string) => Promise<boolean>;
}

export interface SSESchema {
Expand Down
26 changes: 22 additions & 4 deletions src/api/JSONViewer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,21 @@ const JSONViewerHttpApi: JSONViewerApiSchema = {
notificationsStore.handleRequestError(res);
return {};
},
getResults: async (path: string): Promise<{ result: string }> => {
const res = await fetch(`json-stream-provider/result?path=${path}`);
getResults: async (taskId: string): Promise<{ status: string; result: string }> => {
const res = await fetch(`json-stream-provider/result?id=${taskId}`);
if (res.ok) {
return res.json();
}
notificationsStore.handleRequestError(res);
return { result: path };
return { status: 'error', result: taskId };
},
getFile: async (path: string): Promise<{ result: string }> => {
const res = await fetch(`json-stream-provider/file?path=${path}`);
if (res.ok) {
return res.json();
}
notificationsStore.handleRequestError(res);
return { result: '' };
},
launchNotebook: async (path: string, parameters = {}) => {
const res = await fetch(`json-stream-provider/execute?path=${path}`, {
Expand All @@ -48,7 +56,17 @@ const JSONViewerHttpApi: JSONViewerApiSchema = {
return res.json();
}
notificationsStore.handleRequestError(res);
return { path: '' };
return { path: '', task_id: '' };
},
stopNotebook: async (taskId: string) => {
const res = await fetch(`json-stream-provider/stop?id=${taskId}`, {
method: 'POST',
});
if (res.ok) {
return true;
}
// notificationsStore.handleRequestError(res);
return false;
},
};

Expand Down
56 changes: 56 additions & 0 deletions src/components/JSONViewer/DisplayTable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import React from 'react';

const shownCapacity = 50;

const DisplayTable = ({ value }: { value: string[][] | undefined }) => {
const [shownSize, setShownSize] = React.useState(shownCapacity);
if (!value) return <div className='display-table-error'>#display-table is undefined</div>;

const header = value[0];
const rows = value.slice(1);

return (
<div className='display-table'>
<table style={{ gridTemplateColumns: `repeat(${header.length}, 1fr) 16px` }}>
<thead>
<tr>
{header.map((key, index) => (
<th key={index}>{key}</th>
))}
<th style={{ width: '16px' }}></th>
</tr>
</thead>
<tbody>
{rows.slice(0, shownSize).map((row, index) => (
<tr key={index}>
{row.slice(0, header.length).map((val, ind) => (
<td key={ind}>{typeof val === 'string' ? `"${val}"` : String(val)}</td>
))}
{row.length < header.length &&
Array(header.length - row.length)
.fill('')
.map((_val, ind) => <td key={ind}></td>)}
<td style={{ width: '16px' }}>
{header.length < row.length && (
<div
className='display-table-info'
title={`Not included extra cells: ${JSON.stringify(row.slice(header.length))}`}
/>
)}
</td>
</tr>
))}
</tbody>
</table>
{shownSize < rows.length && (
<button
onClick={() => setShownSize(shownSize + shownCapacity)}
className='actions-list__load-button'>
Show More
</button>
)}
</div>
);
};

export default DisplayTable;
40 changes: 25 additions & 15 deletions src/components/JSONViewer/FileChoosing.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@ import { parseText } from '../../helpers/JSONViewer';

const FileChoosing = ({
type,
multiple,
onSubmit,
close,
}: {
type: 'notebooks' | 'results';
type: 'notebooks' | 'results' | 'all';
multiple: boolean;
onSubmit: (t: TreeNode[], n: string[]) => void;
close: () => void;
}) => {
Expand Down Expand Up @@ -75,11 +77,11 @@ const FileChoosing = ({
const promises: Promise<void>[] = [];
if (selectedFiles.length > 0) {
setIsLoading(true);
if (type === 'notebooks') onSubmit([], selectedFiles);
if (type === 'notebooks' || type === 'all') onSubmit([], selectedFiles);
else {
selectedFiles.forEach(filePath =>
promises.push(
api.jsonViewer.getResults(filePath).then(({ result }) => {
api.jsonViewer.getFile(filePath).then(({ result }) => {
if (filePath.endsWith('.ipynb')) {
notebookData.push(filePath);
return;
Expand Down Expand Up @@ -118,6 +120,10 @@ const FileChoosing = ({

const selectFile = (fileName: string) => {
const fileIndex = selectedFiles.indexOf(fileName);
if (!multiple) {
onSubmit([], [fileName]);
return;
}

if (fileIndex > -1) {
setSelectedFiles([
Expand Down Expand Up @@ -154,18 +160,22 @@ const FileChoosing = ({
value={search}
onChange={e => setSearch(e.target.value)}
/>
<button
disabled={selectedFiles.length === 0 || isLoading}
className='load-JSON-button'
onClick={() => setSelectedFiles([])}>
Reset Selection
</button>
<button
disabled={selectedFiles.length === 0 || isLoading}
className='load-JSON-button'
onClick={getFiles}>
Load {selectedFiles.length} Files
</button>
{multiple && (
<>
<button
disabled={selectedFiles.length === 0 || isLoading}
className='load-JSON-button'
onClick={() => setSelectedFiles([])}>
Reset Selection
</button>
<button
disabled={selectedFiles.length === 0 || isLoading}
className='load-JSON-button'
onClick={getFiles}>
Load {selectedFiles.length} Files
</button>
</>
)}
</div>
{isLoading ? (
<div style={{ marginLeft: 5 }} className='fileChoosing__loading' />
Expand Down
112 changes: 112 additions & 0 deletions src/components/JSONViewer/LeafTools.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/** ****************************************************************************
* Copyright 2020-2020 Exactpro (Exactpro Systems Limited)
*
* 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 React, { useRef, useState } from 'react';
import { AnimatePresence, motion } from 'framer-motion';
import { TreeViewType } from '../../models/JSONSchema';
import { useOutsideClickListener } from '../../hooks';
import { createBemElement } from '../../helpers/styleCreators';
import { MessageViewType } from '../../models/EventMessage';

export type LeafToolsConfig = {
activeViewType: TreeViewType | MessageViewType;
toggleViewType: (viewType: any) => void;
viewTypes: TreeViewType[] | MessageViewType[];
};

const LeafTools = ({ activeViewType, toggleViewType, viewTypes }: LeafToolsConfig) => {
const [isViewMenuOpen, setIsViewMenuOpen] = useState(false);
const rootRef = useRef<HTMLDivElement>(null);

useOutsideClickListener(
rootRef,
(e: MouseEvent) => {
if (e.target instanceof Element && rootRef.current && !rootRef.current.contains(e.target)) {
setIsViewMenuOpen(false);
}
},
isViewMenuOpen,
);

return (
<div className='message-card-tools' ref={rootRef}>
<div
className={createBemElement(
'message-card-tools',
'button',
isViewMenuOpen ? 'active' : null,
)}
onClick={e => {
e.stopPropagation();
setIsViewMenuOpen(isOpen => !isOpen);
}}>
<div className='message-card-tools__ellipsis' style={{ display: 'block' }} />
</div>
<ToolsPopup isOpen={isViewMenuOpen}>
<div className='message-card-tools__controls-group'>
{viewTypes.map(viewType => {
const iconClassName = createBemElement('message-card-tools', 'icon', viewType);
const indicatorClassName = createBemElement(
'message-card-tools',
'indicator',
viewType === activeViewType ? 'active' : null,
);

return (
<div
title={viewType}
className='message-card-tools__item'
key={viewType}
onClick={e => {
e.stopPropagation();
toggleViewType(viewType);
}}>
<span className='message-card-tools__item-title'>{viewType}</span>
<div className={iconClassName} />
<div className={indicatorClassName} />
</div>
);
})}
</div>
</ToolsPopup>
</div>
);
};

export default LeafTools;

interface ToolsPopupProps {
isOpen: boolean;
children: React.ReactNode;
}

function ToolsPopup({ isOpen, children }: ToolsPopupProps) {
return (
<AnimatePresence>
{isOpen && (
<motion.div
className='message-card-tools__controls'
style={{ transformOrigin: 'top' }}
initial={{ opacity: 0, scale: 0.5 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.5 }}
transition={{ duration: 0.15, ease: 'easeOut' }}>
{children}
</motion.div>
)}
</AnimatePresence>
);
}
Loading

0 comments on commit 3bac042

Please sign in to comment.