/*
Copyright (C) 2017 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 { DateTime, Duration } from "luxon";
import { observer } from "mobx-react";
import * as React from "react";
import styled from "styled-components";
import { MigrationItem, ReplicaItem, TransferItem } from "@src/@types/MainItem";
import DashboardBarChart from "@src/components/modules/DashboardModule/DashboardBarChart";
import { ThemePalette, ThemeProps } from "@src/components/Theme";
import DropdownLink from "@src/components/ui/Dropdowns/DropdownLink";
import StatusImage from "@src/components/ui/StatusComponents/StatusImage";
import DateUtils from "@src/utils/DateUtils";
import emptyBackgroundImage from "./images/empty-background.svg";
const INTERVALS = [
{ label: "Last {x} days", value: "30-days" },
{ label: "Last 12 months", value: "1-years" },
];
const Wrapper = styled.div``;
const Title = styled.div`
font-size: 24px;
font-weight: ${ThemeProps.fontWeights.light};
margin-bottom: 12px;
`;
const Module = styled.div`
position: relative;
display: flex;
background: ${ThemePalette.grayscale[0]};
border-radius: ${ThemeProps.borderRadius};
height: 240px;
`;
const ChartWrapper = styled.div`
display: flex;
flex-direction: column;
height: 100%;
width: 100%;
`;
const BarChartWrapper = styled.div`
display: flex;
height: 100%;
width: 100%;
`;
const LoadingWrapper = styled.div`
display: flex;
width: 100%;
height: 100%;
overflow: hidden;
justify-content: center;
align-items: center;
`;
const DropdownWrapper = styled.div`
display: flex;
justify-content: flex-end;
margin: 16px;
`;
const Tooltip = styled.div`
position: absolute;
bottom: ${props => props.position.y}px;
left: ${props => props.position.x}px;
background: ${ThemePalette.black};
padding: 8px 16px 16px 16px;
border-radius: ${ThemeProps.borderRadius};
color: white;
${ThemeProps.exactWidth("174px")}
box-shadow: rgba(0,0,0,0.1) 0 0 6px 1px;
`;
const TooltipHeader = styled.div`
font-size: 24px;
font-weight: ${ThemeProps.fontWeights.light};
text-align: center;
border-bottom: 1px solid;
padding-bottom: 4px;
`;
const TooltipBody = styled.div`
font-size: 12px;
`;
const TooltipRow = styled.div`
display: flex;
justify-content: space-between;
margin-top: 16px;
`;
const TooltipRowLabel = styled.div``;
const TooltipTip = styled.div`
position: absolute;
width: 16px;
height: 16px;
bottom: -8px;
background: ${ThemePalette.black};
left: calc(50% - 16px);
transform: rotate(45deg);
`;
const NoData = styled.div`
padding: 0 16px;
position: relative;
`;
const NoDataMessage = styled.div`
position: absolute;
font-size: 17px;
color: ${ThemePalette.grayscale[4]};
display: flex;
top: 0;
bottom: 0;
right: 0;
left: 0;
justify-content: center;
align-items: center;
text-shadow: rgba(255, 255, 255, 1) 0px 0px 20px;
`;
const EmptyBackgroundImage = styled.div`
width: 100%;
height: 146px;
background: url("${emptyBackgroundImage}");
`;
type Props = {
// eslint-disable-next-line react/no-unused-prop-types
replicas: ReplicaItem[];
migrations: MigrationItem[];
loading: boolean;
};
type GroupedData = {
label: string;
values: number[];
data?: string;
};
type TooltipData = {
title: string;
migrations: number;
replicas: number;
};
type State = {
selectedPeriod: string;
groupedData: GroupedData[];
tooltipPosition: { x: number; y: number };
tooltipData: TooltipData | null;
};
const COLORS = [ThemePalette.alert, ThemePalette.primary];
@observer
class DashboardExecutions extends React.Component {
state: State = {
selectedPeriod: INTERVALS[0].value,
groupedData: [],
tooltipData: null,
tooltipPosition: { x: 0, y: 0 },
};
componentDidMount() {
this.groupCreations(this.props);
}
UNSAFE_componentWillReceiveProps(props: Props) {
this.groupCreations(props);
}
groupCreations(props: Props) {
let creations: TransferItem[] = [...props.replicas, ...props.migrations];
const periodUnit: any = this.state.selectedPeriod.split("-")[1];
const periodValue: any = Number(this.state.selectedPeriod.split("-")[0]);
const oldestDate: Date = DateTime.local()
.minus(Duration.fromObject({ [periodUnit]: periodValue }))
.toJSDate();
creations = creations.filter(
e => new Date(e.created_at).getTime() >= oldestDate.getTime()
);
creations.sort(
(a, b) =>
new Date(a.created_at).getTime() - new Date(b.created_at).getTime()
);
this.groupByPeriod(creations, periodUnit);
}
groupByPeriod(transferItems: TransferItem[], periodUnit: string) {
const groupedData: GroupedData[] = [];
const periods: {
[period: string]: { replicas: number; migrations: number };
} = {};
transferItems.forEach(item => {
const date = DateUtils.getUtcDate(item.created_at);
const period: string =
periodUnit === "days"
? date.toFormat("dd-LLL-yyyy_dd LLLL")
: date.toFormat("LLL-yyyy_LLLL yyyy");
if (!periods[period]) {
periods[period] = { replicas: 0, migrations: 0 };
}
if (item.type === "replica") {
periods[period].replicas += 1;
} else if (item.type === "migration") {
periods[period].migrations += 1;
}
});
Object.keys(periods).forEach(period => {
if (!periods[period].replicas && !periods[period].migrations) {
return;
}
const label = period.split("_")[0];
const title = period.split("_")[1];
groupedData.push({
label:
periodUnit === "days"
? `${label.split("-")[0]} ${label.split("-")[1]}`
: label.split("-")[0],
values: [periods[period].migrations, periods[period].replicas],
data: title,
});
});
this.setState({ groupedData });
}
handleDropdownChange(selectedPeriod: string) {
this.setState({ selectedPeriod }, () => {
this.groupCreations(this.props);
});
}
handleBarMouseEnter(position: { x: number; y: number }, item: GroupedData) {
this.setState({
tooltipPosition: { x: position.x - 86, y: position.y },
tooltipData: {
replicas: item.values[1],
migrations: item.values[0],
title: item.data || "-",
},
});
}
handleBarMouseLeave() {
this.setState({ tooltipData: null });
}
renderDropdown() {
const items = INTERVALS.map(interval => ({
value: interval.value,
label: interval.label.replace("{x}", interval.value.split("-")[0]),
}));
const selectedItem = INTERVALS.find(
i => i.value === this.state.selectedPeriod
);
return (
{
this.handleDropdownChange(item.value);
}}
/>
);
}
renderTooltip() {
const data = this.state.tooltipData;
if (!data) {
return null;
}
return (
{data.title}
Created
{data.replicas + data.migrations}
Replicas
{data.replicas}
Migrations
{data.migrations}
);
}
renderBarChart() {
return (
{
this.handleBarMouseEnter(position, item);
}}
onBarMouseLeave={() => {
this.handleBarMouseLeave();
}}
/>
{this.renderTooltip()}
);
}
renderChart() {
return (
{this.renderDropdown()}
{this.state.groupedData.length
? this.renderBarChart()
: this.renderNoData()}
);
}
renderLoading() {
return (
);
}
renderNoData() {
return (
No recent activity in this project
);
}
render() {
return (
Items Created
{this.props.replicas.length === 0 && this.props.loading
? this.renderLoading()
: this.renderChart()}
);
}
}
export default DashboardExecutions;