/*
Copyright (C) 2020 Cloudbase Solutions SRL
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see .
*/
import * as React from "react";
import styled from "styled-components";
import {
MinionPoolDetails,
MinionPoolEventProgressUpdate,
} from "@src/@types/MinionPool";
import { ThemePalette, ThemeProps } from "@src/components/Theme";
import DropdownLink from "@src/components/ui/Dropdowns/DropdownLink";
import InfoIcon from "@src/components/ui/InfoIcon";
import Pagination from "@src/components/ui/Pagination";
import StatusIcon from "@src/components/ui/StatusComponents/StatusIcon";
import configLoader from "@src/utils/Config";
import DateUtils from "@src/utils/DateUtils";
const Wrapper = styled.div``;
const Filters = styled.div`
margin-bottom: 24px;
display: flex;
`;
const FilterDropdownWrapper = styled.div`
margin-left: 24px;
`;
const EventsTable = styled.div`
background: ${ThemePalette.grayscale[1]};
border-radius: ${ThemeProps.borderRadius};
margin-bottom: 16px;
`;
const Header = styled.div`
display: flex;
border-bottom: 1px solid ${ThemePalette.grayscale[5]};
padding: 4px 8px;
`;
type DataDivProps = {
width?: string;
grow?: boolean;
secondary?: boolean;
};
const HeaderData = styled.div`
${props => (props.width ? ThemeProps.exactWidth(props.width) : "")}
${props => (props.grow ? "flex-grow: 1;" : "")}
font-size: 10px;
color: ${ThemePalette.grayscale[5]};
font-weight: ${ThemeProps.fontWeights.medium};
text-transform: uppercase;
`;
const Body = styled.div``;
const Row = styled.div`
display: flex;
padding: 8px;
border-bottom: 1px solid white;
`;
const RowData = styled.div`
${props => (props.width ? ThemeProps.exactWidth(props.width) : "")}
${props => (props.grow ? "flex-grow: 1;" : "")}
${props => (props.secondary ? `color: ${ThemePalette.grayscale[4]};` : "")}
`;
const Message = styled.pre`
font-family: inherit;
white-space: pre-line;
margin: inherit;
`;
const NoData = styled.div`
text-align: center;
`;
type FilterType = "all" | "events" | "progress";
type EventLevel = "DEBUG" | "INFO" | "ERROR";
type OrderDir = "asc" | "desc";
type Props = {
item?: MinionPoolDetails | null;
};
type State = {
allEvents: MinionPoolEventProgressUpdate[];
prevLenghts: number[];
currentPage: number;
filterBy: FilterType;
eventLevel: EventLevel;
orderDir: OrderDir;
};
class MinionPoolEvents extends React.Component {
state = {
allEvents: [] as MinionPoolEventProgressUpdate[],
prevLenghts: [0, 0],
currentPage: 1,
filterBy: "events" as FilterType,
eventLevel: "INFO" as EventLevel,
orderDir: "desc" as OrderDir,
};
get filteredEventsWithoutPagination() {
const shouldFilterByEventType = (event: any): boolean => {
if (this.state.filterBy === "events") {
return event.level;
}
if (this.state.filterBy === "progress") {
return event.current_step != null;
}
return true;
};
const shouldFilterByLevel = (event: any): boolean => {
if (!event.level) {
return true;
}
if (this.state.eventLevel === "INFO") {
return event.level === "INFO" || event.level === "ERROR";
}
if (this.state.eventLevel === "ERROR") {
return event.level === "ERROR";
}
return true;
};
return this.state.allEvents
.filter(
(event: any) =>
shouldFilterByEventType(event) && shouldFilterByLevel(event),
)
.sort((a: any, b: any) => {
if (a.index && b.index && this.state.filterBy !== "all") {
return this.state.orderDir === "asc"
? a.index - b.index
: b.index - a.index;
}
const aTime = new Date(a.created_at).getTime();
const bTime = new Date(b.created_at).getTime();
return this.state.orderDir === "asc" ? aTime - bTime : bTime - aTime;
});
}
get filteredEvents() {
return this.filteredEventsWithoutPagination.filter((_, i) => {
const minI =
configLoader.config.maxMinionPoolEventsPerPage *
(this.state.currentPage - 1);
const maxI = minI + configLoader.config.maxMinionPoolEventsPerPage;
return i >= minI && i < maxI;
});
}
static getDerivedStateFromProps(
props: Props,
state: State,
): Partial | null {
if (!props.item) {
return null;
}
const events = props.item?.events || [];
const progressUpdates = props.item?.progress_updates || [];
if (
events.length === state.prevLenghts[0] &&
progressUpdates.length === state.prevLenghts[1]
) {
return null;
}
return {
allEvents: events.concat(progressUpdates as any),
prevLenghts: [events.length, progressUpdates.length],
};
}
setOrderDir(orderDir: OrderDir) {
this.setState({ orderDir, currentPage: 1 });
}
filterByType(filterBy: FilterType) {
this.setState({ filterBy, currentPage: 1 });
}
filterByLevel(eventLevel: EventLevel) {
this.setState({ eventLevel, currentPage: 1 });
}
handlePreviousPageClick() {
this.setState(state => ({ currentPage: state.currentPage - 1 }));
}
handleNextPageClick() {
this.setState(state => ({ currentPage: state.currentPage + 1 }));
}
renderHeader() {
return (
Event / Progress Update Message
Timestamp
);
}
renderBody() {
return (
{this.filteredEvents.map((event: any) => {
let status = "INFO";
status = event.level || status;
if (event.level === "DEBUG") {
status = "WARNING";
}
const title = event.current_step ? "Progress Update" : "Event";
return (
{event.message}
{DateUtils.getLocalDate(event.created_at).toFormat(
"yyyy-LL-dd HH:mm:ss",
)}
);
})}
);
}
renderPagination() {
if (
this.filteredEventsWithoutPagination.length <=
configLoader.config.maxMinionPoolEventsPerPage
) {
return null;
}
const totalPages = Math.ceil(
this.filteredEventsWithoutPagination.length /
configLoader.config.maxMinionPoolEventsPerPage,
);
return (
{
this.handlePreviousPageClick();
}}
onNextClick={() => {
this.handleNextPageClick();
}}
currentPage={this.state.currentPage}
totalPages={totalPages}
/>
);
}
renderEventsTable() {
return (
{this.renderHeader()}
{this.renderBody()}
);
}
renderFilters() {
return (
{
this.filterByType(item.value as FilterType);
}}
/>
{
this.filterByLevel(item.value as EventLevel);
}}
/>
{
this.setOrderDir(item.value as OrderDir);
}}
/>
);
}
renderNoData() {
return (
There are no events or progress updates associated with this minion
pool.
);
}
renderNoDataFound() {
return No events found;
}
render() {
const isNoData = this.state.allEvents.length === 0;
const isNoDataFound = this.filteredEvents.length === 0;
return (
{!isNoData ? this.renderFilters() : null}
{!isNoData && !isNoDataFound ? this.renderEventsTable() : null}
{!isNoData && !isNoDataFound ? this.renderPagination() : null}
{isNoData ? this.renderNoData() : null}
{isNoDataFound && !isNoData ? this.renderNoDataFound() : null}
);
}
}
export default MinionPoolEvents;