diff --git a/services/api/src/resources/problem/sql.ts b/services/api/src/resources/problem/sql.ts
index e20dcd28ef..8a50e1b59f 100644
--- a/services/api/src/resources/problem/sql.ts
+++ b/services/api/src/resources/problem/sql.ts
@@ -38,7 +38,7 @@ export const Sql = {
let q = knex('environment_problem as p')
.join('environment as e', {environment: 'e.id'}, '=', {environment: 'p.environment'})
.where('p.deleted', '=', '0000-00-00 00:00:00')
- .select('p.*', {environment: 'e.id'}, { name: 'e.name', project: 'e.project',
+ .select('p.*', {service: 'p.lagoon_service'}, {environment: 'e.id'}, { name: 'e.name', project: 'e.project',
environmentType: 'e.environment_type', openshiftProjectName: 'e.openshift_project_name'});
if (environmentType.length > 0) {
diff --git a/services/ui/src/components/Dates/index.js b/services/ui/src/components/Dates/index.js
index f88bfff008..64d127744a 100644
--- a/services/ui/src/components/Dates/index.js
+++ b/services/ui/src/components/Dates/index.js
@@ -1,9 +1,9 @@
import moment from "moment";
export const getFromNowTime = (date) => {
- return moment(date).fromNow();
+ return moment.utc(date).fromNow();
};
export const getCreatedDate = (date) => {
- return moment.utc(date).local().format('DD MM YYYY, HH:mm:ss');
+ return moment.utc(date).format('DD MM YYYY, HH:mm:ssZ');
};
\ No newline at end of file
diff --git a/services/ui/src/components/Problem/ContentDisplay/DrutinyContent.js b/services/ui/src/components/Problem/ContentDisplay/DrutinyContent.js
index 9f8d28eaaa..6c0dccbe60 100644
--- a/services/ui/src/components/Problem/ContentDisplay/DrutinyContent.js
+++ b/services/ui/src/components/Problem/ContentDisplay/DrutinyContent.js
@@ -44,7 +44,7 @@ const DrutinyDisplay = ({ problem }) => {
)}
{data.service && (
-
{data.service}
+
{data.service}
)}
{data.created && (
diff --git a/services/ui/src/components/Problem/index.js b/services/ui/src/components/Problem/index.js
index 18f7169cfa..bc34559043 100644
--- a/services/ui/src/components/Problem/index.js
+++ b/services/ui/src/components/Problem/index.js
@@ -11,7 +11,7 @@ const Problem = ({ problem, display }) => {
severity: problem.severity,
source: problem.source,
created: fromNowDate,
- severityScore: problem.severityScore,
+ service: problem.service,
associatedPackage: problem.version ? `${problem.associatedPackage}:${problem.version}` : problem.associatedPackage
};
diff --git a/services/ui/src/components/Problems/index.js b/services/ui/src/components/Problems/index.js
index 681164c9e1..01b66666d3 100644
--- a/services/ui/src/components/Problems/index.js
+++ b/services/ui/src/components/Problems/index.js
@@ -1,229 +1,354 @@
-import React, { useState } from 'react';
+import React, { useState, useEffect } from 'react';
+import { useQuery } from "@apollo/react-hooks";
+import getSeverityEnumQuery, {getProjectOptions, getSourceOptions} from 'components/Filters/helpers';
import { bp, color, fontSize } from 'lib/variables';
import useSortableProblemsData from './sortedItems';
-import { getFromNowTime } from "components/Dates";
import Problem from "components/Problem";
+import SelectFilter from 'components/Filters';
-const Problems = ({ problems }) => {
- const { sortedItems, requestSort, getClassNamesFor } = useSortableProblemsData(problems);
+const getOptionsFromProblems = (problems, key) => {
+ let uniqueOptions= problems &&
+ new Set(problems.filter(p => p[key]).map(p => p[key]));
- const [problemTerm, setProblemTerm] = useState('');
- const [hasFilter, setHasFilter] = React.useState(false);
+ return [...uniqueOptions];
+};
+
+const Problems = ({problems}) => {
+ const { sortedItems, requestSort, getClassNamesFor } = useSortableProblemsData(problems);
+ const [severitySelected, setSeverity] = useState([]);
+ const [sourceSelected, setSource] = useState([]);
+ const [servicesSelected, setService] = useState([]);
+
+ const [problemTerm, setProblemTerm] = useState('');
+ const [hasFilter, setHasFilter] = useState(false);
+ const [problemStats, setProblemStats] = useState([]);
+
+ const severities = getOptionsFromProblems(problems, 'severity');
+ const sources = getOptionsFromProblems(problems, 'source');
+ const services = getOptionsFromProblems(problems, 'service');
+
+
+ // Handlers
+ const handleSort = (key) => requestSort(key);
+
+ const handleTextFilterChange = (event) => {
+ setHasFilter(false);
+
+ if (event.target.value !== null || event.target.value !== '') {
+ setHasFilter(true);
+ }
+ setProblemTerm(event.target.value);
+ };
+
+ const handleSourceChange = (source) => {
+ let values = source && source.map(s => s.value) || [];
+ setSource(values);
+ };
+
+ const handleSeverityChange = (severity) => {
+ let values = severity && severity.map(s => s.value) || [];
+ setSeverity(values);
+ };
+
+ const handleServiceChange = (service) => {
+ let values = service && service.map(s => s.value) || [];
+ setService(values);
+ };
+
+ // Options
+ const severityOptions = (severity) => {
+ return severity && severity.map(s => ({ value: s, label: s}));
+ };
+
+ const sourceOptions = (sources) => {
+ return sources && sources.map(s => ({ value: s, label: s}));
+ };
+
+ const serviceOptions = (services) => {
+ return services && services.map(s => ({ value: s, label: s}));
+ };
+
+ // Selector filtering
+ const matchesSeveritySelector = (item) => {
+ return (severitySelected.length > 0) ?
+ Object.keys(item).some(key => {
+ if (item[key] !== null) {
+ return severitySelected.indexOf(item['severity'].toString()) > -1;
+ };
+ })
+ : true;
+ }
- const handleProblemFilterChange = (event) => {
- setHasFilter(false);
+ const matchesSourceSelector = (item) => {
+ return (sourceSelected.length > 0) ?
+ Object.keys(item).some(key => {
+ if (item[key] !== null) {
+ return sourceSelected.indexOf(item['source'].toString()) > -1;
+ };
+ })
+ : true;
+ }
- if (event.target.value !== null || event.target.value !== '') {
- setHasFilter(true);
+ const matchesServiceSelector = (item) => {
+ return (servicesSelected.length > 0) ?
+ Object.keys(item).some(key => {
+ if (item[key] !== null) {
+ return servicesSelected.indexOf(item['service'].toString()) > -1;
+ };
+ })
+ : true;
+ }
+
+ const matchesTextFilter = (item) => {
+ return (problemTerm != null || problemTerm !== '') ?
+ Object.keys(item).some(key => {
+ if (item[key] !== null) {
+ return item[key].toString().toLowerCase().includes(problemTerm.toLowerCase());
}
- setProblemTerm(event.target.value);
- };
+ })
+ : true;
+ }
- const handleSort = (key) => {
- return requestSort(key);
+ const shouldItemBeShown = (item) => {
+ return (matchesSeveritySelector(item) && matchesServiceSelector(item) && matchesSourceSelector(item) && matchesTextFilter(item));
+ };
+
+ useEffect(() => {
+ let stats = {
+ 'critical': sortedItems.filter(p => p.severity === 'CRITICAL').length,
+ 'high': sortedItems.filter(p => p.severity === 'HIGH').length,
+ 'medium': sortedItems.filter(p => p.severity === 'MEDIUM').length,
+ 'low': sortedItems.filter(p => p.severity === 'LOW').length
};
- const filterResults = (item) => {
- const lowercasedFilter = problemTerm.toLowerCase();
- if (problemTerm == null || problemTerm === '') {
- return problems;
+ if (stats != problemStats) {
+ setProblemStats(stats);
+ }
+ }, []);
+
+ return (
+
+
+
+ - {Object.keys(sortedItems).length}
+ - {problemStats.critical}
+ - {problemStats.high}
+ - {problemStats.medium}
+ - {problemStats.low}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {sortedItems.filter(item => shouldItemBeShown(item)).length == 0 &&
+
}
+ {sortedItems
+ .filter(item => shouldItemBeShown(item))
+ .map((problem) =>
)
+ }
+
+
-
- );
+ .filters-wrapper {
+ .select-filters {
+ display: flex;
+ flex-direction: column;
+ @media ${bp.wideUp} {
+ flex-flow: row;
+ }
+
+ &:first-child {
+ padding-bottom: 1em;
+ }
+ }
+ }
+
+ .text-large {
+ font-size: 1.4em;
+ }
+
+ .red {
+ color: ${color.red};
+ }
+
+ .blue {
+ color: ${color.blue};
+ }
+
+ .yellow {
+ color: ${color.lightestBlue};
+ }
+
+ .grey {
+ color: ${color.grey};
+ }
+
+ input#filter {
+ width: 100%;
+ border: none;
+ padding: 10px 20px;
+ margin: 0;
+ }
+
+ .button-sort {
+ color: #5f6f7a;
+ font-family: 'source-code-pro',sans-serif;
+ font-size: 12px;
+ font-size: 0.8125rem;
+ line-height: 1.4;
+ text-transform: uppercase;
+ text-align: center;
+ border: none;
+ background: none;
+ cursor: pointer;
+ padding: 0;
+ width: calc(100% / 6);
+
+ &.identifier {
+ text-align: left;
+ }
+
+ &.ascending:after {
+ content: ' \\25B2';
+ }
+
+ &.descending:after {
+ content: ' \\25BC';
+ }
+ }
+
+ .overview {
+ .overview-list {
+ display: flex;
+ justify-content: space-between;
+ padding: 10px 20px;
+ margin: 0 0 20px;
+ background: #f3f3f3;
+
+ li.result {
+ display: flex;
+ flex-direction: column;
+ margin: 0;
+ }
+ }
+ }
+
+ .data-none {
+ border: 1px solid ${color.white};
+ border-bottom: 1px solid ${color.lightestGrey};
+ border-radius: 3px;
+ line-height: 1.5rem;
+ padding: 8px 0 7px 0;
+ text-align: center;
+ }
+ `}
+
+ );
};
-export default Problems;
+export default Problems;
\ No newline at end of file
diff --git a/services/ui/src/components/Problems/sortedItems.js b/services/ui/src/components/Problems/sortedItems.js
index b9ba246ca6..fa9ee4cfaf 100644
--- a/services/ui/src/components/Problems/sortedItems.js
+++ b/services/ui/src/components/Problems/sortedItems.js
@@ -41,10 +41,10 @@ const useSortableProblemsData = (initialItems) => {
}
const requestSort = (key) => {
- let direction = 'ascending';
+ let direction = key !== 'created' ? 'ascending' : 'descending';
- if (sortConfig && sortConfig.key === key && sortConfig.direction === 'ascending') {
- direction = 'descending';
+ if (sortConfig && sortConfig.key === key && sortConfig.direction === direction) {
+ direction = direction === 'ascending' ? 'descending' : 'ascending';
}
setCurrentItems(sortedItems);