| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360 |
- /*
- 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 <http://www.gnu.org/licenses/>.
- */
- 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<any>``;
- const Title = styled.div<any>`
- font-size: 24px;
- font-weight: ${ThemeProps.fontWeights.light};
- margin-bottom: 12px;
- `;
- const Module = styled.div<any>`
- position: relative;
- display: flex;
- background: ${ThemePalette.grayscale[0]};
- border-radius: ${ThemeProps.borderRadius};
- height: 240px;
- `;
- const ChartWrapper = styled.div<any>`
- display: flex;
- flex-direction: column;
- height: 100%;
- width: 100%;
- `;
- const BarChartWrapper = styled.div<any>`
- display: flex;
- height: 100%;
- width: 100%;
- `;
- const LoadingWrapper = styled.div<any>`
- display: flex;
- width: 100%;
- height: 100%;
- overflow: hidden;
- justify-content: center;
- align-items: center;
- `;
- const DropdownWrapper = styled.div<any>`
- display: flex;
- justify-content: flex-end;
- margin: 16px;
- `;
- const Tooltip = styled.div<any>`
- 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<any>`
- font-size: 24px;
- font-weight: ${ThemeProps.fontWeights.light};
- text-align: center;
- border-bottom: 1px solid;
- padding-bottom: 4px;
- `;
- const TooltipBody = styled.div<any>`
- font-size: 12px;
- `;
- const TooltipRow = styled.div<any>`
- display: flex;
- justify-content: space-between;
- margin-top: 16px;
- `;
- const TooltipRowLabel = styled.div<any>``;
- const TooltipTip = styled.div<any>`
- position: absolute;
- width: 16px;
- height: 16px;
- bottom: -8px;
- background: ${ThemePalette.black};
- left: calc(50% - 16px);
- transform: rotate(45deg);
- `;
- const NoData = styled.div<any>`
- padding: 0 16px;
- position: relative;
- `;
- const NoDataMessage = styled.div<any>`
- 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<any>`
- 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<Props, State> {
- 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 (
- <DropdownWrapper>
- <DropdownLink
- items={items}
- selectedItem={selectedItem && selectedItem.value}
- onChange={item => {
- this.handleDropdownChange(item.value);
- }}
- />
- </DropdownWrapper>
- );
- }
- renderTooltip() {
- const data = this.state.tooltipData;
- if (!data) {
- return null;
- }
- return (
- <Tooltip position={this.state.tooltipPosition}>
- <TooltipHeader>{data.title}</TooltipHeader>
- <TooltipBody>
- <TooltipRow>
- <TooltipRowLabel>Created</TooltipRowLabel>
- <TooltipRowLabel>{data.replicas + data.migrations}</TooltipRowLabel>
- </TooltipRow>
- <TooltipRow>
- <TooltipRowLabel>Replicas</TooltipRowLabel>
- <TooltipRowLabel>{data.replicas}</TooltipRowLabel>
- </TooltipRow>
- <TooltipRow>
- <TooltipRowLabel>Migrations</TooltipRowLabel>
- <TooltipRowLabel>{data.migrations}</TooltipRowLabel>
- </TooltipRow>
- </TooltipBody>
- <TooltipTip />
- </Tooltip>
- );
- }
- renderBarChart() {
- return (
- <BarChartWrapper>
- <DashboardBarChart
- style={{ height: "164px" }}
- yNumTicks={3}
- data={this.state.groupedData}
- colors={COLORS}
- onBarMouseEnter={(position, item) => {
- this.handleBarMouseEnter(position, item);
- }}
- onBarMouseLeave={() => {
- this.handleBarMouseLeave();
- }}
- />
- {this.renderTooltip()}
- </BarChartWrapper>
- );
- }
- renderChart() {
- return (
- <ChartWrapper>
- {this.renderDropdown()}
- {this.state.groupedData.length
- ? this.renderBarChart()
- : this.renderNoData()}
- </ChartWrapper>
- );
- }
- renderLoading() {
- return (
- <LoadingWrapper>
- <StatusImage status="RUNNING" />
- </LoadingWrapper>
- );
- }
- renderNoData() {
- return (
- <NoData>
- <EmptyBackgroundImage />
- <NoDataMessage>No recent activity in this project</NoDataMessage>
- </NoData>
- );
- }
- render() {
- return (
- <Wrapper>
- <Title>Items Created</Title>
- <Module>
- {this.props.replicas.length === 0 && this.props.loading
- ? this.renderLoading()
- : this.renderChart()}
- </Module>
- </Wrapper>
- );
- }
- }
- export default DashboardExecutions;
|