Skip to content

Commit

Permalink
Merge pull request #784 from AlexsLemonade/arielsvn/internal-sample-page
Browse files Browse the repository at this point in the history
Samples page with processing information
  • Loading branch information
arielsvn authored Dec 12, 2019
2 parents 0b2ab35 + 90f4157 commit 17db5eb
Show file tree
Hide file tree
Showing 13 changed files with 530 additions and 4 deletions.
11 changes: 11 additions & 0 deletions src/api/computedFiles.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Ajax } from '../common/helpers';

export async function getComputedFiles(sampleId) {
try {
return await Ajax.get('/v1/computed_files/', {
samples: sampleId,
});
} catch {
return [];
}
}
25 changes: 25 additions & 0 deletions src/api/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
export {
getAllDetailedSamples,
getGenomeBuild,
getDetailedSample,
} from './samples';

export { getExperiment } from './experiments';

export { fetchCompendiaData, fetchCompendium } from './compendia';

export { fetchDashboardData } from './dashboad';

export {
createDataSet,
formatExperiments,
getDataSet,
getDataSetDetails,
updateDataSet,
} from './dataSet';

export { getDownloaderJobs, getProcessorJobs, getRunningJobs } from './jobs';

export { getComputedFiles } from './computedFiles';

export { getOriginalFiles } from './originalFiles';
31 changes: 31 additions & 0 deletions src/api/jobs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { Ajax } from '../common/helpers';

export async function getDownloaderJobs(accessionCode) {
return Ajax.get(`/v1/jobs/downloader/`, {
sample_accession_code: accessionCode,
});
}

export async function getProcessorJobs(accessionCode) {
return Ajax.get(`/v1/jobs/processor/`, {
sample_accession_code: accessionCode,
});
}

export async function getRunningJobs(accessionCode) {
const [processorJobs, downloaderJobs] = await Promise.all([
Ajax.get(`/v1/jobs/downloader/`, {
sample_accession_code: accessionCode,
nomad: true,
}),
Ajax.get(`/v1/jobs/processor/`, {
sample_accession_code: accessionCode,
nomad: true,
}),
]);

return [
...processorJobs.results.map(job => job.nomad_job_id),
...downloaderJobs.results.map(job => job.nomad_job_id),
];
}
11 changes: 11 additions & 0 deletions src/api/originalFiles.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Ajax } from '../common/helpers';

export async function getOriginalFiles(sampleId) {
try {
return await Ajax.get('/v1/original_files/', {
samples: sampleId,
});
} catch {
return [];
}
}
5 changes: 4 additions & 1 deletion src/components/Accordion.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react';
import classnames from 'classnames';
import isFunction from 'lodash/isFunction';
import { IoIosArrowUp, IoIosArrowDown } from 'react-icons/io';
import Button from './Button';
import Checkbox from './Checkbox';
Expand Down Expand Up @@ -73,7 +74,9 @@ export function AccordionItem({ title, children, onToggle, isExpanded }) {
return (
<div className={classnames('accordion-item')}>
<div className="accordion-item__header">
<div className="accourdion-item__title">{title(isExpanded)}</div>
<div className="accourdion-item__title">
{isFunction(title) ? title(isExpanded) : title}
</div>

{hasChildren && (
<Button onClick={onToggle} buttonStyle="transparent">
Expand Down
13 changes: 11 additions & 2 deletions src/components/SamplesTable/SamplesTable.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ import React from 'react';
import 'react-table/react-table.css';
import debounce from 'lodash/debounce';
import { IoIosArrowUp, IoIosArrowDown } from 'react-icons/io';
import { Link } from 'react-router-dom';

import RefineTable from '../RefineTable';

import Pagination from '../Pagination';
import Dropdown from '../Dropdown';

import InfoIcon from '../../common/icons/info-badge.svg';

import { PAGE_SIZES } from '../../common/constants';
Expand Down Expand Up @@ -90,7 +91,7 @@ class SamplesTable extends React.Component {
minWidth: 160,
width: 175,
style: { textAlign: 'right' },
Cell: CustomCell,
Cell: AccessionCodeCell,
},
{
Header: 'Title',
Expand Down Expand Up @@ -263,6 +264,14 @@ const NoDataComponent = ({ hasError, children, fetchData }) =>
<div className="samples-table__message">{children}</div>
);

function AccessionCodeCell({ value }) {
return (
<HText>
<Link to={`/samples/${value}`}>{value}</Link>
</HText>
);
}

/**
* Custom cell component used to display N/A when there's no value
*/
Expand Down
4 changes: 4 additions & 0 deletions src/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,10 @@ fieldset {
align-content: center;
align-items: center;
justify-content: space-between;

&--top {
align-items: flex-start;
}
}

@import 'common/styles/type';
Expand Down
3 changes: 2 additions & 1 deletion src/pages/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { ThemeProvider } from '../common/ThemeContext';
import Main from './Main';
import Search from './Search';
import Experiment from './Experiment';
import Sample from './Sample';
import Compendia from './Compendia';
import CompendiaDownload from './Compendia/DownloadPage';
import Downloads from './Downloads';
Expand Down Expand Up @@ -44,7 +45,7 @@ const AppContent = () => (
<Route path="/privacy" component={Privacy} />
<Route path="/terms" component={Terms} />
<Route path="/license" component={License} />

<Route path="/samples/:id/:slug?" component={Sample} />
{/* Custom route to be able to redirect to the 404 page */}
<Route path="/not-found" component={NoMatch} />
<Route path="*" component={NoMatch} />
Expand Down
188 changes: 188 additions & 0 deletions src/pages/Sample/SampleDebug.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
import React from 'react';
import classnames from 'classnames';
import moment from 'moment';
import { IoMdSync } from 'react-icons/io';
import { Ajax } from '../../common/helpers';
import './sample.scss';
import SampleDebugContext, { SampleDebugProvider } from './SampleDebugContext';
import Checkbox from '../../components/Checkbox';

const DATE_FORMAT = 'MM/DD/YYYY HH:mm';

export default function SampleDebug({ accessionCode }) {
return (
<SampleDebugProvider accessionCode={accessionCode}>
<div className="flex-row flex-row--top">
<OriginalFileFilters />
<ComputedFiles />
</div>
<JobTable />
</SampleDebugProvider>
);
}

function OriginalFileFilters() {
const {
data: { originalFiles },
isFileSelected,
toggleFile,
} = React.useContext(SampleDebugContext);

return (
<div className="sample-debug-section">
<h3>Original Files</h3>
{originalFiles && originalFiles.length > 0
? originalFiles.map(file => (
<Checkbox
key={file.id}
name={file.id}
onChange={() => toggleFile(file.id)}
checked={isFileSelected(file.id)}
className={classnames({
'sample-debug__cancel':
file.processor_jobs.length + file.downloader_jobs.length ===
0,
})}
>
{file.filename}
{file.filename !== file.source_filename
? ' (from archive) '
: ' '}
<a href={file.source_url} className="link">
download
</a>
</Checkbox>
))
: 'None'}
</div>
);
}

function ComputedFiles() {
const {
data: { computedFiles },
} = React.useContext(SampleDebugContext);

return (
<div className="sample-debug-section">
<h3>Computed Files</h3>
{computedFiles && computedFiles.length > 0
? computedFiles.map(file => <div>{file.filename}</div>)
: 'None'}
</div>
);
}

function JobTable() {
const {
data: { jobs },
} = React.useContext(SampleDebugContext);

return (
<div className="sample-debug-section">
<h3>Downloader and Processor Job Info</h3>

<table className="jobs-table">
<thead>
<tr>
<th scope="col">id</th>
<th scope="col" />
<th scope="col">num retries</th>
<th scope="col">retried</th>
<th scope="col">worker_version</th>
<th scope="col">created at</th>
<th scope="col">running time</th>
</tr>
</thead>
<tbody>
{jobs && jobs.map(job => <JobRow key={job.id} job={job} />)}
</tbody>
</table>
</div>
);
}

function JobRow({ job }) {
const { isFileSelected } = React.useContext(SampleDebugContext);
const jobOomKilled = job.start_time && !job.end_time && !job.failure_reason;

return (
<>
<tr
key={job.id}
className={classnames({
'jobs-table__job': true,
error: job.success === false,
'color-success': job.success === true,
'jobs-table__job--select': job.original_files.some(isFileSelected),
})}
>
<td>
<a
target="_blank"
rel="noopener noreferrer"
href={
job.type === 'processor'
? `https://api.refine.bio/v1/jobs/processor/?format=json&id=${
job.id
}`
: `https://api.refine.bio/v1/jobs/downloader/?format=json&id=${
job.id
}`
}
>
{job.id}
</a>
</td>
<td title={job.isRunning ? 'This job is currently running' : null}>
{job.type === 'processor'
? `${job.pipeline_applied} (${job.ram_amount})`
: job.downloader_task}{' '}
{job.isRunning ? <IoMdSync /> : null}
</td>
<td>{job.num_retries}</td>
<td>{job.retried ? 'yes' : 'no'}</td>
<td>{job.worker_version}</td>
<td>{job.created_at && moment(job.created_at).format(DATE_FORMAT)}</td>
<td>
{job.start_time
? moment(job.start_time).format(DATE_FORMAT)
: '(no start_time)'}
{job.start_time && job.end_time
? ` (${moment
.duration(moment(job.end_time).diff(job.start_time))
.humanize()})`
: ' (no end_time)'}
</td>
</tr>
{(job.failure_reason || job.success === false) && (
<tr
className={classnames({
error: job.success === false,
'color-success': job.success === true,
'jobs-table__job--select': job.original_files.some(isFileSelected),
})}
>
<td />
<td colSpan="8">
{jobOomKilled ? (
'Looks like the job was OOM-Killed (no failure_reason)'
) : (
<p>
{job.failure_reason
? job.failure_reason
.split('\\n')
.map(fragment => <div>{fragment}</div>)
: 'No failure reason'}
</p>
)}
</td>
</tr>
)}
</>
);
}

export async function getSample(accessionCode) {
return Ajax.get(`/v1/samples/${accessionCode}/`);
}
Loading

0 comments on commit 17db5eb

Please sign in to comment.