...
 
Commits (4)
......@@ -9,7 +9,7 @@ stages:
- prepare
- review
- deploy:staging
- test:e2e
- qa
- deploy:canary
- deploy:production
- cleanup
......@@ -34,9 +34,13 @@ lint:
- prettier --check "src/**/*.scss"
- prettier --check "src/**/*.html"
e2e:chrome:
############
# QA Stage #
############
qa:e2e:
image: cypress/browsers:chrome67
stage: test:e2e
stage: qa
variables:
CYPRESS_INSTALL_BINARY: 3.4.1
script:
......@@ -61,6 +65,17 @@ e2e:chrome:
- cache/Cypress
allow_failure: true #manual inspection in case of timeouts
qa:manual:
stage: qa
script:
- echo "Manually approved"
when: manual
only:
refs:
- master
- test/gitlab-ci
allow_failure: false
###############
# Build Stage #
###############
......
<div class="chart-container" #chartContainer>
<m-graph [data]="data4" [layout]="layout"></m-graph>
<div id="chartContainer" class="chart-container"></div>
<div
id="hoverInfo"
class="hoverInfo"
[style.top]="'closestHoverPointPositionX' + 'px'"
[style.left]="'closestHoverPointPositionY' + 'px'"
>
<div id="hoverInfo__x" class="hoverInfo__row--secondary">x</div>
<div id="hoverInfo__y" class="hoverInfo__row--primary">y</div>
<div id="hoverInfo__comparisonXy" class="hoverInfo__row--secondary">z</div>
</div>
<!-- <div *ngIf="(the vm.metric with the visualisation is type=chart)"> -->
plotly-plot {
m-analytics__chart {
position: relative;
.js-plotly-plot,
.plot-container {
height: 44vh;
}
}
#chartContainer {
position: relative;
g,
g > * {
cursor: default;
}
}
.hoverInfo {
// display: none; // TODO: uncomment
padding: 16px;
position: absolute;
pointer-events: none;
border-radius: 2px;
font-size: 11px;
@include m-theme() {
background-color: themed($m-white);
box-shadow: 0 0 4px rgba(themed($m-black), 0.3);
color: themed($m-grey-200);
}
.hoverInfo__row--primary {
font-size: 14px;
font-weight: bold;
@include m-theme() {
color: themed($m-grey-500);
}
}
}
import {
Component,
OnInit,
OnDestroy,
HostListener,
ViewChild,
ElementRef,
ChangeDetectionStrategy,
ChangeDetectorRef,
} from '@angular/core';
import { Observable } from 'rxjs';
import { Observable, Subscription } from 'rxjs';
import {
AnalyticsDashboardService,
Category,
......@@ -22,86 +22,120 @@ import {
UserState,
} from '../../dashboard.service';
import * as Plotly from 'plotly.js';
import { Config, Data, Layout } from 'plotly.js'; // TODO: remove this?
import { ThemeService } from '../../../../../common/services/theme.service';
@Component({
selector: 'm-analytics__chart',
templateUrl: 'chart.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AnalyticsChartComponent implements OnInit {
export class AnalyticsChartComponent implements OnInit, OnDestroy {
response = this.analyticsService.getData();
visualisation: Visualisation;
vm$: Observable<UserState> = this.analyticsService.vm$;
data4 = [
isDark: boolean = false;
themeSubscription: Subscription;
// TODO: make these come from vm$ (or analyticsService.getVisualisation()?);
x1: Array<any> = ['08/30', '08/31', '09/01', '09/02', '09/03'];
y1: Array<any> = [4, 9, 16, 17, 15];
y2: Array<any> = [3, 7, 14, 18, 16];
// x2 would be the actual dates for y2
analyticsPlot: any = {};
hoverInfoTextX: string;
hoverInfoTextY: string;
hoverInfoTextComparisonXY: string;
closestHoverPointPositionX: number = 0;
closestHoverPointPositionY: number = 0;
hoverStyles: any = {
marker: {
size: 6,
// size: [40, 60, 80, 100],
},
};
unHoverStyles: any = {
marker: {
size: 1,
},
};
globalSegmentSettings: any = {
type: 'scatter',
mode: 'lines+markers',
line: {
width: 2,
color: '#4690df',
},
marker: {
size: 4,
},
showlegend: false,
hoverinfo: 'text',
};
// segmentColors: string[] = ['#555', 'salmon', 'cadetblue', 'gold']; //TODO: add more colors from palette?
// TODO: theme colors array for intra-Plotly styles
responseData = [
{
x: ['08/30', '08/31', '09/01', '09/02', '09/03'],
y: [4, 9, 16, 17, 15],
type: 'scatter',
mode: 'lines+markers',
line: {
color: '#4690df',
width: 2,
},
marker: {
size: 1,
},
showlegend: false,
hovertemplate:
'Fri Aug 30th 2019<br>' +
'<b class="test">750 Active Users</b><br>' +
'<span>vs 450 Fri August 23rd</span>' +
'<extra></extra>',
...this.globalSegmentSettings,
x: this.x1,
y: this.y1,
},
{
x: ['08/30', '08/31', '09/01', '09/02', '09/03'],
y: [3, 7, 14, 18, 16],
type: 'scatter',
mode: 'line',
line: {
color: '#ccc',
width: 2,
dash: 'dot',
},
marker: {
size: 1,
},
showlegend: false,
hovertemplate: '<extra></extra>',
...this.globalSegmentSettings,
line: { ...this.globalSegmentSettings, color: '#ccc', dash: 'dot' },
x: this.x1,
y: this.y2,
hoverinfo: 'none', // nothing is displayed but event still fires
},
];
// *******************************************
// ***********************************************************************************
// layout: Layout = {
layout: any = {
width: 0,
height: 0,
title: '',
hovermode: 'closest', // or False, for historical line
hoverlabel: { bgcolor: '#eee' },
// title: '',
hovermode: 'closest',
paper_bgcolor: 'white',
plot_bgcolor: 'white',
font: {
family: 'Roboto',
},
titlefont: {
family: 'Roboto',
size: 24,
weight: 'bold',
},
// titlefont: {
// family: 'Roboto',
// size: 24,
// },
xaxis: {
// dtick: 1, //comparison_interval(1, 7, 28, etc.)
automargin: true,
// automargin: true,
tickangle: -45,
tickfont: {
color: '#ccc',
},
// zeroline: false,
showgrid: false,
// gridcolor: 'white' // instead of false?
},
yaxis: {
automargin: true,
// automargin: true,
ticks: '',
showgrid: true,
zeroline: true,
zerolinecolor: '#666',
showticklabels: false,
zerolinecolor: '#222',
// showticklabels: false,
side: 'right',
tickfont: {
color: '#ccc',
},
},
margin: {
t: 16,
......@@ -111,26 +145,103 @@ export class AnalyticsChartComponent implements OnInit {
},
};
// *******************************************
@ViewChild('chartContainer', { static: true }) chartContainer: ElementRef;
// ***********************************************************************************
constructor(private analyticsService: AnalyticsDashboardService) {}
constructor(
private analyticsService: AnalyticsDashboardService,
private themeService: ThemeService,
protected cd: ChangeDetectorRef
) {}
ngOnInit() {
this.applyDimensions();
this.analyticsPlot = document.getElementById('chartContainer');
// TODO: make sure these outliers end up with proper m- prefix
const hoverInfo = document.getElementById('hoverInfo');
const hoverInfoX = document.getElementById('hoverInfo__x'); // Date
const hoverInfoY = document.getElementById('hoverInfo__y'); // Value
const hoverInfoComparisonXy = document.getElementById(
'hoverInfo__comparisonXy'
);
Plotly.plot('chartContainer', this.responseData, this.layout, {
displayModeBar: false,
});
// ** HOVER *******************************************************
this.analyticsPlot
.on('plotly_hover', function(eventData) {
hoverInfo.style.display = 'block';
Plotly.restyle(this.analyticsPlot, this.hoverStyles, 0);
this.hoverInfoXText = eventData.points.map(function(d) {
return '' + d.x;
});
this.hoverInfoYText = eventData.points.map(function(d) {
return d.y.toPrecision(3) + ' borks';
});
this.hoverInfoComparisonXyText = eventData.points.map(function(d) {
// TODO: how best to connect current with comparison segment data
return 'vs 700 Tues Oct 1st';
});
console.log(eventData);
console.log(eventData.points[0].data.marker.size);
eventData.points[0].data.marker.size = 4;
console.log(eventData.points[0].data.marker.size);
const xaxis = eventData.points[0].xaxis,
yaxis = eventData.points[0].yaxis;
eventData.points.forEach(function(p) {
// note: 'l2p' means 'linear to pixel'
console.log('pixel position', xaxis.l2p(p.x), yaxis.l2p(p.y));
console.log('pixel position', xaxis.l2p(p.x), yaxis.l2p(p.y));
this.closestHoverPointPositionX = xaxis.l2p(p.x);
this.closestHoverPointPositionY = yaxis.l2p(p.x);
});
hoverInfoX.textContent = this.hoverInfoXText.join();
hoverInfoY.textContent = this.hoverInfoYText.join();
hoverInfoComparisonXy.textContent = this.hoverInfoComparisonXyText.join();
})
// ** UNHOVER *******************************************************
.on('plotly_unhover', function(eventData) {
hoverInfo.style.display = 'none';
Plotly.restyle(this.analyticsPlot, this.unHoverStyles, 0);
// eventData.xaxes[0].showline = true;
// eventData.yaxes[0].showline = true;
console.log(eventData);
// this.hoverInfoX.textContent = '';
// this.hoverInfoY.textContent = '';
// this.hoverInfoComparisonXy.textContent = '';
});
this.themeService.isDark$.subscribe(isDark => (this.isDark = isDark));
}
// TODO: reimplement?
// @HostListener('window:resize')
// applyDimensions() {
// this.layout = {
// ...this.layout,
// width: this.chartContainer.nativeElement.clientWidth,
// height: this.chartContainer.nativeElement.clientHeight - 35,
// };
// }
// this.visualisation = this.response.metrics.find(
// metric => metric.id === this.vm$.metric
// ).visualisation;
detectChanges() {
this.cd.markForCheck();
this.cd.detectChanges();
this.themeService.applyThemePreference();
}
@HostListener('window:resize')
applyDimensions() {
this.layout = {
...this.layout,
width: this.chartContainer.nativeElement.clientWidth,
height: this.chartContainer.nativeElement.clientHeight - 35,
};
ngOnDestroy() {
this.themeSubscription.unsubscribe();
}
}
<div class="page" *ngIf="vm$ | async as vm">
<div class="page" *ngIf="vm$ | async as vm" [ngClass]="{ isMobile: isMobile }">
<section class="sidebar">
<h3>Analytics</h3>
<div class="catContainer">
......@@ -7,9 +7,9 @@
{{ selectedCat.label }}
</div>
<div class="cat" *ngFor="let cat of cats">
<!-- <span [ngClass]="{ selected: cat.id === selectedCat.id }">{{
vm$.category.label
}}</span> -->
<span [ngClass]="{ selected: cat.id === selectedCat.id }">{{
cat.label
}}</span>
</div>
</div>
</section>
......@@ -35,15 +35,14 @@
</div>
<m-analytics__layout--chart
class="m-analytics__layout"
*ngIf="selectedCat.type === 'chart'"
></m-analytics__layout--chart>
<m-analytics__layout--table
<!-- <m-analytics__layout--table
class="m-analytics__layout"
*ngIf="selectedCat.type === 'table'"
></m-analytics__layout--table>
<m-analytics__layout--summary
class="m-analytics__layout"
*ngIf="selectedCat.type === 'summary'"
></m-analytics__layout--summary>
></m-analytics__layout--summary> -->
</section>
</div>
......@@ -48,8 +48,7 @@ export class AnalyticsDashboardComponent implements OnInit, OnDestroy {
label: 'timespan',
options: [],
};
// vm$: Observable<UserState> = this.analyticsService.vm$;
vm$ = this.analyticsService.vm$;
vm$: Observable<UserState> = this.analyticsService.vm$;
constructor(
public client: Client,
......@@ -63,8 +62,8 @@ export class AnalyticsDashboardComponent implements OnInit, OnDestroy {
this.isMobile = isMobileOrTablet();
this.title.setTitle('Analytics');
console.log(this.route);
console.log(this.route.snapshot.url);
// console.log(this.route);
// console.log(this.route.snapshot.url);
// TODO: implement channel filter
// const {channelGuid} = this.analyticsService.getStateSnapshot();
......@@ -81,7 +80,7 @@ export class AnalyticsDashboardComponent implements OnInit, OnDestroy {
// TODO: something similar for category
this.selectedCat = this.cats.find(
// cat => cat.id === this.vm$.category;
// cat => cat.id === this.vm$.category
cat => cat.id === 'traffic'
);
});
......