Commit aa2f0263 authored by Olivia Madrid's avatar Olivia Madrid

(feat): analytics v2 continues

1 merge request!579WIP: Entity centric metrics (analytics v2)
Pipeline #88589996 failed with stages
in 4 minutes and 56 seconds
......@@ -4,8 +4,8 @@
<!-- TODO: then all this becomes m-plotlyChart -->
<!-- <div *ngIf="vm$ | async as vm"> -->
<div>
<!-- <div id="graphDiv"></div> -->
<plotly-plot
<div #graphDiv id="graphDiv"></div>
<!-- <plotly-plot
id="graphDiv"
[divId]="graphDiv"
[data]="data"
......@@ -15,17 +15,40 @@
[style]="{ position: 'relative' }"
(hover)="onHover($event)"
(unhover)="onUnhover($event)"
(afterPlot)="afterPlot()"
>
</plotly-plot>
<!-- (afterPlot)="afterPlot()" -->
</plotly-plot> -->
<div id="hoverInfo" class="hoverInfo">
<div id="hoverInfo__x" class="hoverInfo__row">
Wed 2 October 2019
<div #hoverInfoDiv id="hoverInfoDiv" class="hoverInfoDiv">
<div class="hoverInfo__row">
{{ hoverInfo.date | date: datePipe }}
</div>
<div id="hoverInfo__y" class="hoverInfo__row--primary">789012</div>
<div id="hoverInfo__comparisonXy" class="hoverInfo__row">
vs 567890 on Tues 1 October
<div [ngSwitch]="selectedMetric.unit" class="hoverInfo__row--primary">
<ng-template ngSwitchCase="number">
{{ hoverInfo.value | number }} {{ selectedMetric.label | lowercase }}
</ng-template>
<ng-template ngSwitchCase="USD">
{{ hoverInfo.value | currency }} USD
</ng-template>
<ng-template ngSwitchDefault>
{{ hoverInfo.value | number: '1.1-3' }} {{ selectedMetric.unit }}
</ng-template>
</div>
<div class="hoverInfo__row" *ngIf="isComparison">
vs
<ng-container [ngSwitch]="selectedMetric.unit" class="hoverInfo__row">
<ng-template ngSwitchCase="number">
{{ hoverInfo.comparisonValue | number }}
</ng-template>
<ng-template ngSwitchCase="USD">
{{ hoverInfo.comparisonValue | currency }} USD
</ng-template>
<ng-template ngSwitchDefault>
{{ hoverInfo.comparisonValue | number: '1.1-3' }}
{{ selectedMetric.unit }}
</ng-template>
</ng-container>
on {{ hoverInfo.comparisonDate | date: datePipe }}
</div>
</div>
</div>
......@@ -12,11 +12,15 @@ m-analytics__chart {
g > * {
cursor: default;
}
> * {
transition: background-color 0.3s ease-in;
}
}
.hoverInfo {
.hoverInfoDiv {
width: 160px;
padding: 16px;
padding: 12px;
position: absolute;
pointer-events: none;
border-radius: 3px;
......@@ -30,11 +34,14 @@ m-analytics__chart {
color: themed($m-grey-200);
}
[class*='hoverInfo__row'] {
padding-bottom: 4px;
}
.hoverInfo__row--primary {
font-size: 14px;
font-weight: bold;
font-size: 15px;
// font-weight: bold;
@include m-theme() {
color: themed($m-grey-500);
color: themed($m-grey-600);
}
}
}
$rounded-top: 3px 3px 0 0;
$rounded-bottom: 0 0 3px 3px;
m-analytics__filter {
position: relative;
margin: 16px 16px 0 0;
z-index: 2;
}
.filterHeader,
.unselectedOptionsContainer {
padding: 10px 8px 8px 10px;
}
.filterWrapper {
cursor: pointer;
border-radius: 3px;
// display: flex;
// flex-direction: column;
transition: border-color 0.3s cubic-bezier(0.075, 0.82, 0.165, 1);
@include m-theme() {
background-color: themed($m-white);
border: 1px solid themed($m-grey-100);
color: rgba(themed($m-grey-200), 0.7);
}
&.dropUp {
flex-direction: column-reverse;
}
&.expanded {
@include m-theme() {
border-color: themed($m-blue);
box-shadow: 0px 1px 15px 0 rgba(themed($m-black), 0.1);
box-shadow: 0px 1px 15px 0 rgba(themed($m-black), 0.15);
}
&:not(.dropUp) {
border-bottom-left-radius: 0px;
border-bottom-right-radius: 0px;
.filterHeader {
@include m-theme() {
border-color: themed($m-blue);
}
}
.unselectedOptionsContainer {
display: block;
// display: block;
visibility: visible;
@include m-theme() {
box-shadow: 0px 1px 15px 0 rgba(themed($m-black), 0.15);
}
}
.option {
&:hover:not(.unavailable) {
&:not(.dropUp) {
.filterHeader {
@include m-theme() {
color: themed($m-grey-500);
// border-bottom: none;
border-radius: $rounded-top;
}
}
.unselectedOptionsContainer {
border-top: none;
border-radius: $rounded-bottom;
}
}
&.dropUp {
.filterHeader {
border-radius: $rounded-bottom;
// border-top: none;
}
.unselectedOptionsContainer {
bottom: 100%;
border-radius: $rounded-top;
border-bottom: none;
}
}
}
.row {
display: flex;
}
}
.option {
border-radius: 3px;
@include m-theme() {
background-color: themed($m-white);
}
&.unavailable {
text-decoration: line-through;
.filterHeader {
position: relative;
// display: flex;
width: 100%;
// justify-content: space-between;
padding: 10px 8px 8px 10px;
border-radius: 3px;
transition: all 0.3s cubic-bezier(0.075, 0.82, 0.165, 1);
@include m-theme() {
background-color: themed($m-white);
color: rgba(themed($m-grey-200), 0.9);
}
@include m-theme() {
color: themed($m-grey-50);
border: 1px solid themed($m-grey-100);
}
.filterLabel {
margin-right: 10px;
}
i {
flex-grow: 0;
}
.option--selected {
margin-right: 8px;
@include m-theme() {
color: themed($m-grey-500);
}
}
}
}
.filterHeader {
position: relative;
display: flex;
justify-content: space-between;
.filterLabel {
margin-right: 10px;
}
i {
flex-grow: 0;
}
}
.option--selected {
margin-right: 8px;
@include m-theme() {
color: themed($m-grey-500);
.unselectedOptionsContainer {
position: absolute;
// display: none;
visibility: hidden;
width: 100%;
padding: 10px 8px 8px 10px;
border-radius: 3px;
left: 0px;
transition: box-shadow 0.3s cubic-bezier(0.075, 0.82, 0.165, 1);
@include m-theme() {
border: 1px solid themed($m-blue);
border-top: 1px solid themed($m-blue);
background-color: themed($m-white);
}
.option {
padding: 5px 0;
}
}
}
.unselectedOptionsContainer {
position: absolute;
display: none;
width: 100%;
border-bottom-left-radius: 3px;
border-bottom-right-radius: 3px;
box-sizing: border-box;
left: 0px;
@include m-theme() {
border: 1px solid themed($m-blue);
border-top: 1px solid themed($m-blue);
background-color: themed($m-white);
.row {
display: flex;
justify-content: space-between;
}
.option {
padding: 5px 0;
border-radius: 3px;
@include m-theme() {
background-color: themed($m-white);
color: rgba(themed($m-grey-200), 0.9);
}
&.unavailable {
text-decoration: line-through;
@include m-theme() {
color: themed($m-grey-50);
}
}
&:hover:not(.unavailable) {
@include m-theme() {
color: themed($m-grey-500);
}
}
}
}
......
<div class="filtersContainer">
<ng-container *ngFor="let filter of filters$ | async">
<m-analytics__filter class="filter" [filter]="filter"></m-analytics__filter>
<m-analytics__filter
class="filter"
[filter]="filter"
[dropUp]="true"
></m-analytics__filter>
</ng-container>
</div>
......@@ -5,37 +5,45 @@
</div> -->
<div class="metricsWrapper">
<div class="metricsContainer" *ngIf="metrics$ | async as metrics">
<div
class="metric"
*ngFor="let metric of metrics"
(click)="updateMetric(metric)"
[ngClass]="{ active: metric.visualisation }"
>
<div class="metricLabel">{{ metric.label }}</div>
<!-- TODO the "number" pipe should be from backend so it can dynamically handle diff decimals/currency formats -->
<div class="metricSummary" *ngIf="metric.summary">
<ng-container *ngIf="metric.unit === 'number'">
{{ metric.summary.current_value | number }}
</ng-container>
<ng-container *ngIf="metric.unit === 'usd'">
<span>$</span
>{{ metric.summary.current_value / 100 | number: '1.2-2' }}
</ng-container>
</div>
<ng-container *ngFor="let metric of metrics">
<div
*ngIf="metric.summary"
class="metricDelta"
[ngClass]="{
goodChange: metric.hasChanged && metric.positiveTrend,
badChange: metric.hasChanged && !metric.positiveTrend
}"
class="metric"
(click)="updateMetric(metric)"
[ngClass]="{ active: metric.visualisation }"
*ngIf="metric.permissionGranted"
>
<i class="material-icons" *ngIf="metric.delta > 0">arrow_upward</i>
<i class="material-icons" *ngIf="metric.delta < 0">arrow_downward</i>
<span>{{ metric.delta | percent: '1.0-1' }}</span>
<div class="metricLabel">
<span>{{ metric.label }}</span>
<m-tooltip [anchor]="top" icon="help">
{{ metric.description }}
</m-tooltip>
</div>
<div class="metricSummary" *ngIf="metric.summary">
<ng-container *ngIf="metric.unit === 'number'">
{{ metric.summary.current_value | number }}
</ng-container>
<ng-container *ngIf="metric.unit === 'usd'">
<span>$</span
>{{ metric.summary.current_value / 100 | number: '1.2-2' }}
</ng-container>
</div>
<div
*ngIf="metric.summary"
class="metricDelta"
[ngClass]="{
goodChange: metric.hasChanged && metric.positiveTrend,
badChange: metric.hasChanged && !metric.positiveTrend
}"
>
<i class="material-icons" *ngIf="metric.delta > 0">arrow_upward</i>
<i class="material-icons" *ngIf="metric.delta < 0"
>arrow_downward</i
>
<span>{{ metric.delta | percent: '1.0-1' }}</span>
</div>
</div>
</div>
</ng-container>
</div>
</div>
<!-- <div class="overflowFade--right"></div>
......
......@@ -84,17 +84,22 @@ m-analytics__metrics {
// }
.metric {
cursor: pointer;
scroll-snap-align: start;
flex: 0 0 auto;
width: 20%;
padding: 24px 20px 20px 20px;
font-size: 14px;
box-sizing: border-box;
@include m-theme() {
border-bottom: 8px solid themed($m-white);
}
@include m-theme() {
color: themed($m-grey-200);
}
&.active {
@include m-theme() {
background-color: rgba(themed($m-blue-grey-100), 0.15);
border-bottom: 8px solid themed($m-blue);
}
}
......@@ -106,11 +111,10 @@ m-analytics__metrics {
}
&:hover:not(.active) {
cursor: pointer;
@include m-theme() {
background-color: rgba(themed($m-blue-grey-300), 0.08);
border-bottom: 8px solid themed($m-blue);
transition: all 0.3s cubic-bezier(0.23, 1, 0.32, 1);
background-color: rgba(themed($m-blue-grey-100), 0.15);
border-bottom: 8px solid rgba(0, 0, 0, 0);
transition: background-color 0.3s cubic-bezier(0.23, 1, 0.32, 1);
}
}
.metricSummary {
......@@ -121,9 +125,13 @@ m-analytics__metrics {
}
}
.metricDelta {
display: flex;
align-items: baseline;
padding-top: 4px;
font-size: 11px;
.material-icons {
transform: scaleX(0.7);
font-size: 12px;
font-size: 11px;
font-weight: bold;
}
@include m-theme() {
......@@ -136,4 +144,17 @@ m-analytics__metrics {
}
}
}
.m-tooltip {
i {
font-size: 14px;
}
.m-tooltip--bubble {
z-index: 9999;
font-size: 11px;
@include m-theme() {
color: themed($m-white);
background-color: themed($m-blue);
}
}
}
}
......@@ -21,12 +21,14 @@ import {
Timespan,
UserState,
} from '../../dashboard.service';
import { Session } from '../../../../../services/session';
import isMobileOrTablet from '../../../../../helpers/is-mobile-or-tablet';
interface MetricExtended extends MetricBase {
delta: number;
hasChanged: boolean;
positiveTrend: boolean;
permissionGranted: boolean;
}
export { MetricExtended as Metric };
......@@ -39,22 +41,44 @@ export class AnalyticsMetricsComponent implements OnInit, OnDestroy {
data;
subscription: Subscription;
isMobile: boolean;
user;
userRoles: string[] = ['user'];
metrics$;
isOverflown = { left: false, right: false };
constructor(private analyticsService: AnalyticsDashboardService) {}
constructor(
private analyticsService: AnalyticsDashboardService,
public session: Session
) {}
ngOnInit() {
this.user = this.session.getLoggedInUser();
if (this.user.isAdmin) {
this.userRoles.push('admin');
}
if (this.user.pro) {
this.userRoles.push('pro');
}
this.metrics$ = this.analyticsService.metrics$.pipe(
map(_metrics => {
const metrics = _metrics.map(metric => ({ ...metric })); // Clone to avoid updating
for (let metric of metrics) {
for (const metric of metrics) {
metric['permissionGranted'] = metric.permissions.some(role =>
this.userRoles.includes(role)
);
if (metric.summary) {
const delta =
(metric.summary.current_value - metric.summary.comparison_value) /
(metric.summary.comparison_value || 0);
let delta;
if (metric.summary.comparison_value !== 0) {
delta =
(metric.summary.current_value -
metric.summary.comparison_value) /
(metric.summary.comparison_value || 0);
} else {
delta = 1;
}
metric['delta'] = delta;
metric['hasChanged'] = delta === 0 ? false : true;
......
<div class="tableWrapper">
<div class="spinnerContainer">
<div
class="mdl-spinner mdl-js-spinner is-active"
[mdl]
[hidden]="!loading"
></div>
</div>
<div class="tableWrapper" [hidden]="loading">
<div class="header row">
<div
*ngFor="let col of visualisation.columns; let first = first"
[ngClass]="{
first: first
}"
class="col"
class="cell"
>
{{ col.label }}
</div>
</div>
<div class="body">
<!-- TODO: make some sort of a map thing to make the entity fields have the same name for different entity types. Currently only made it to 'work' with fake image and fake blog but even those are streching it -->
<ng-container *ngFor="let row of reformattedBuckets">
<div class="row">
<div class="entity col" *ngIf="row.entity">
<a class="row" [routerLink]="row.entity.route">
<div class="entity cell" *ngIf="row.entity">
<div class="entityTitle">
<a [routerLink]="'/newsfeed/' + row.entity?.guid">
<span>{{ row.entity.title || 'Post' }}</span>
<i class="material-icons">open_in_new</i>
</a>
<span>{{ row.entity.title }}</span>
<i class="material-icons">open_in_new</i>
</div>
<div class="entityDetails">
<a [routerLink]="'/' + row.entity.ownerObj?.guid">{{
row.entity.ownerObj?.name
}}</a>
<span>{{ row.entity.subtype | titlecase }}</span>
<span>Published {{ row.entity.time_created * 1000 | date }}</span>
<span class="usernameWrapper">
<a [routerLink]="'/' + row.entity.name"
>@{{ row.entity.username }}</a
>
</span>
<span *ngIf="!isMobile">{{ row.entity.type | titlecase }}</span>
<span *ngIf="!isMobile"
>Published {{ row.entity.time_created * 1000 | date }}</span
>
</div>
</div>
<ng-container *ngFor="let value of row.values">
<div class="col">{{ value }}</div>
</ng-container>
</div>
<div class="valuesContainer">
<ng-container *ngFor="let value of row.values">
<div class="value cell">{{ value | abbr }}</div>
</ng-container>
</div>
</a>
</ng-container>
</div>
</div>
<!-- <ng-template #entityCol>
</ng-template> -->
.filterableChartWrapper {
div.filterableChartWrapper {
padding: 0;
width: 100%;
}
div.filtersContainer {
display: none;
}
.tableWrapper {
font-size: 13px;
font-weight: 400;
@include m-theme() {
color: themed($m-grey-800);
}
a {
text-decoration: none;
font-weight: normal;
@include m-theme() {
color: themed($m-grey-800);
}
}
.row {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
padding: 16px 48px;
&.header {
@include m-theme() {
border-bottom: 1px solid themed($m-grey-50);
color: themed($m-grey-200);
}
.col {
.cell {
flex: 1 1 0;
&.first {
flex: 4 1 0;
flex: 7 2 0;
}
}
}
.col {
.cell {
flex: 1 1 0;
display: flex;
// align-items: center;
flex-direction: column;
overflow: hidden;
white-space: nowrap;
height: 50px;
padding: 8px 8px 8px 0;
&.entity {
flex: 4 1 0;
// @include m-theme() {
// border-right: 1px solid themed($m-grey-50);
// }
padding-left: 5%;
min-width: 200px;
flex: 7 2 0;
@include m-theme() {
border-right: 1px solid themed($m-grey-50);
}
a {
font-weight: 400;
text-decoration: none;
}
.entityTitle {
display: flex;
align-items: center;
// align-items: center;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
i {
font-size: 13px;
display: none;
}
a,
i {
@include m-theme() {
color: themed($m-grey-800);
}
......@@ -54,22 +74,43 @@
}
.entityDetails {
display: inline;
//justify-content: space-between;
@include m-theme() {
color: themed($m-grey-200);
}
> * {
margin-right: $minds-margin;
}
.usernameWrapper {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
a {
@include m-theme() {
color: themed($m-grey-200);
}
margin-right: $minds-margin;
}
}
}
&.value {
&:first-of-type {
padding-left: 8px;
}
&:last-of-type {
padding-right: 5%;
}
}
}
}
.valuesContainer {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
flex: 3 1 0;
}
.body {
.row {
transition: background-color 0.3s cubic-bezier(0.23, 1, 0.32, 1);
&:hover {
@include m-theme() {
background-color: rgba(themed($m-blue-grey-50), 0.5);
......@@ -81,3 +122,13 @@
}
}
}
.spinnerContainer {
height: 30%;
width: 100%;
display: flex;
justify-content: center;
align-items: center;
@include m-theme() {
background-color: themed($m-white);
}
}
......@@ -11,38 +11,29 @@ import { Observable, Subscription } from 'rxjs';
import { map } from 'rxjs/operators';
import {
AnalyticsDashboardService,
Category,
Response,
Dashboard,
Filter,
Option,
Metric,
Summary,
Visualisation,
Bucket,
Timespan,
UserState,
Buckets,
} from '../../dashboard.service';
import isMobileOrTablet from '../../../../../helpers/is-mobile-or-tablet';
@Component({
selector: 'm-analytics__table',
templateUrl: './table.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AnalyticsTableComponent implements OnInit, OnDestroy {
subscription: Subscription;
metricSubscription: Subscription;
visualisation: Visualisation;
columns: Array<any>;
rows: Array<any>;
reformattedBuckets: Array<any> = [];
minds = window.Minds;
user;
isMobile: boolean;
loadingSubscription: Subscription;
loading: boolean;
selectedMetric$ = this.analyticsService.metrics$.pipe(
map(metrics => {
console.log(
metrics,
metrics.find(metric => metric.visualisation !== null)
);
return metrics.find(metric => metric.visualisation !== null);
})
);
......@@ -50,15 +41,24 @@ export class AnalyticsTableComponent implements OnInit, OnDestroy {
constructor(
private analyticsService: AnalyticsDashboardService,
protected cd: ChangeDetectorRef
protected cd: ChangeDetectorRef // public session: Session
) {}
ngOnInit() {
this.subscription = this.selectedMetric$.subscribe(metric => {
this.isMobile = isMobileOrTablet();
this.metricSubscription = this.selectedMetric$.subscribe(metric => {
this.selectedMetric = metric;
this.visualisation = metric.visualisation;
this.columns = metric.visualisation.columns.sort((a, b) =>
a.order > b.order ? 1 : -1
this.columns = metric.visualisation.columns;
// .sort((a, b) =>
// a.order > b.order ? 1 : -1
// );
this.loadingSubscription = this.analyticsService.loading$.subscribe(
loading => {
this.loading = loading;
this.detectChanges();
}
);
this.reformatBuckets();
......@@ -71,9 +71,12 @@ export class AnalyticsTableComponent implements OnInit, OnDestroy {
this.visualisation.buckets.forEach(bucket => {
const reformattedBucket = {};
const reformattedValues = [];
this.columns.forEach((column, i) => {
if (i === 0) {
reformattedBucket['entity'] = bucket.values[column.id];
reformattedBucket['entity'] = this.reformatEntity(
bucket.values['entity']
);
} else {
reformattedValues.push(bucket.values[column.id]);
}
......@@ -81,8 +84,63 @@ export class AnalyticsTableComponent implements OnInit, OnDestroy {
reformattedBucket['values'] = reformattedValues;
this.reformattedBuckets.push(reformattedBucket);
});
console.log(this.reformattedBuckets);
// TODO: reformat diff entity objs so template fields match
}
reformatEntity(entity) {
let type, username, name, titleType;
if (entity.remind_object) {
type = 'remind';
} else {
type = entity.urn.split(':')[1];
}
if (type === 'user') {
type = 'channel';
username = entity.username;
name = entity.name;
} else {
username = entity.ownerObj.username;
name = entity.ownerObj.name;
}
titleType = type;
if (type === 'activity') {
titleType = 'post';
}
const reformattedEntity = {
type: type,
time_created: entity.time_created || entity.time_published,
title: entity.title || entity.message || `${username}'s ${titleType}`,
route: this.getEntityRoute(type, entity),
username: username,
name: name,
};
return reformattedEntity;
}
getEntityRoute(type, entity) {
const routesByType = [
{
ids: ['image', 'video'],
route: 'media/' + entity.urn.split(':')[2],
},
{
ids: ['activity', 'remind'],
route: `newsfeed/${entity.urn.split(':')[2]}`,
},
{
ids: ['blog'],
route: entity.route,
},
{
ids: ['channel'],
route: entity.name,
},
];
return routesByType.find(t => t.ids.indexOf(type) > -1).route;
}
detectChanges() {
......@@ -91,6 +149,7 @@ export class AnalyticsTableComponent implements OnInit, OnDestroy {
}
ngOnDestroy() {
this.subscription.unsubscribe();
this.metricSubscription.unsubscribe();
this.loadingSubscription.unsubscribe();
}
}
......@@ -21,6 +21,8 @@
padding: 16px;
display: flex;
flex-direction: row;
height: 100%;
// width: 100%;
@include m-theme() {
background-color: themed($m-white);
color: themed($m-grey-800);
......
......@@ -15,6 +15,7 @@ import {
} from 'rxjs/operators';
import { Client } from '../../../services/api/client';
import fakeData from './fake-data';
// TEMPORARY
import { HttpClient, HttpHeaders } from '@angular/common/http';
......@@ -52,10 +53,11 @@ export interface Option {
label: string;
available?: boolean;
selected?: boolean;
interval?: string;
comparison_interval?: number;
from_ts_ms?: number;
from_ts_iso?: string;
description?: string;
// interval?: string;
// comparison_interval?: number;
// from_ts_ms?: number;
// from_ts_iso?: string;
}
export interface Metric {
......@@ -63,6 +65,8 @@ export interface Metric {
label: string;
permissions?: string[];
summary?: Summary;
unit?: string;
description?: string;
visualisation: Visualisation | null;
}
......@@ -110,272 +114,15 @@ export interface UserState {
loading: boolean;
}
// ¯\_(ツ)_/¯
let _state: UserState = {
loading: false,
category: 'traffic',
timespan: '30d',
timespans: [
{
id: '30d',
label: 'Last 30 days',
interval: 'day',
comparison_interval: 28,
from_ts_ms: 1567296000000,
from_ts_iso: '2019-09-01T00:00:00+00:00',
},
{
id: '1y',
label: '1 year ago',
interval: 'month',
comparison_interval: 365,
from_ts_ms: 1538352000000,
from_ts_iso: '2018-10-01T00:00:00+00:00',
},
],
filter: ['channel::all'],
// filter: ['platform::all', 'view_type::single', 'channel::all'],
// filters: [
// {
// id: 'platform',
// label: 'Platform',
// options: [
// { id: 'all', label: 'All', available: true, selected: false },
// {
// id: 'browser',
// label: 'Browser',
// available: true,
// selected: false,
// },
// { id: 'mobile', label: 'Mobile', available: true, selected: false },
// ],
// },
// {
// id: 'view_type',
// label: 'View Type',
// options: [
// { id: 'total', label: 'Total', available: true, selected: false },
// {
// id: 'organic',
// label: 'Organic',
// available: true,
// selected: true,
// },
// {
// id: 'boosted',
// label: 'Boosted',
// available: false,
// selected: false,
// },
// { id: 'single', label: 'Single', available: true, selected: false },
// ],
// },
// ],
// metric: 'views',
metric: 'views_table',
metrics: [
{
id: 'active_users',
label: 'Active Users',
permissions: ['admin'],
// summary: {
// current_value: 120962,
// comparison_value: 120962,
// comparison_interval: 28,
// comparison_positive_inclination: true,
// },
visualisation: null,
},
{
id: 'signups',
label: 'Signups',
permissions: ['admin'],
// summary: {
// current_value: 53060,
// comparison_value: 60577,
// comparison_interval: 28,
// comparison_positive_inclination: true,
// },
visualisation: null,
},
// {
// id: 'views',
// label: 'Pageviews',
// permissions: ['admin'],
// summary: {
// current_value: 83898,
// comparison_value: 0,
// comparison_interval: 28,
// comparison_positive_inclination: true,
// },
// visualisation: {
// type: 'chart',
// segments: [
// {
// buckets: [
// {
// key: 1567296000000,
// date: '2019-09-01T00:00:00+00:00',
// value: 11,
// },
// {
// key: 1567382400000,
// date: '2019-09-02T00:00:00+00:00',
// value: 12,
// },
// {
// key: 1567468800000,
// date: '2019-09-03T00:00:00+00:00',
// value: 13,
// },
// {
// key: 1567555200000,
// date: '2019-09-04T00:00:00+00:00',
// value: 9,
// },
// {
// key: 1567641600000,
// date: '2019-09-05T00:00:00+00:00',
// value: 5,
// },
// ],
// },
// {
// buckets: [
// {
// key: 1567296000000,
// date: '2019-09-01T00:00:00+00:00',
// value: 1,
// },
// {
// key: 1567382400000,
// date: '2019-09-02T00:00:00+00:00',
// value: 2,
// },
// {
// key: 1567468800000,
// date: '2019-09-03T00:00:00+00:00',
// value: 3,
// },
// {
// key: 1567555200000,
// date: '2019-09-04T00:00:00+00:00',
// value: 4,
// },
// {
// key: 1567641600000,
// date: '2019-09-05T00:00:00+00:00',
// value: 5,
// },
// ],
// },
// ],
// },
// },
{
id: 'views_table',
label: 'Views',
permissions: ['admin'],
// summary: {
// current_value: 83898,
// comparison_value: 0,
// comparison_interval: 28,
// comparison_positive_inclination: true,
// },
visualisation: {
type: 'table',
buckets: [
// {
// key: 'urn:video:937185401872453632',
// values: {
// 'views::total': 26,
// 'views::organic': 26,
// 'views::single': 26,
// },
// },
{
key: 'urn:image:937185477660028928',
values: {
'views::total': 22,
'views::organic': 22,
'views::single': 16,
entity: {
time_created: '1570569175',
title: 'My cool image',
subtype: 'image',
route: 'media/937185477660028928',
ownerObj: {
guid: '1234567',
name: 'dogPhotographer',
},
},
},
},
{
key: 'urn:blog:920262483603046400',
values: {
'views::total': 11,
'views::organic': 11,
'views::single': 11,
entity: {
time_created: '1568665493',
title: 'My cool blog',
route:
'minds/blog/announcing-post-scheduling-bitcoin-usd-and-ethereum-payments-1020417032624504832',
subtype: 'blog',
ownerObj: {
guid: '100000000000000000',
name: 'Minds Official',
},
},
},
},
// {
// key: 'urn:video:895432241855463424',
// values: {
// 'views::total': 9,
// 'views::organic': 9,
// 'views::single': 9,
// },
// },
// {
// key: 'urn:image:1017095547862175744',
// values: {
// 'views::total': 6,
// 'views::organic': 6,
// 'views::single': 6,
// },
// },
// {
// key: 'urn:video:1001848211025559552',
// values: {
// 'views::total': 6,
// 'views::organic': 6,
// 'views::single': 6,
// },
// },
// {
// key: 'urn:activity:895432367722332160',
// values: {
// 'views::total': 5,
// 'views::organic': 5,
// 'views::single': 3,
// },
],
columns: [
{ id: 'views::total', label: 'Total', order: 1 },
{ id: 'views::organic', label: 'Organic', order: 2 },
{ id: 'views::single', label: 'Single', order: 3 },
{ id: 'entity', label: 'Views', order: 0 },
],
},
},
],
// filter: ['platform::browser'],
// filters: [],
// metric: 'views',
// metrics: [],
};
// ʕ •ᴥ•ʔ
let _state: UserState = fakeData[0];
// {
// // loading: false,
// // filter: ['platform::browser'],
// // filters: [],
// // metric: 'views',
// // metrics: [],
// };
const deepDiff = (prev, curr) => JSON.stringify(prev) === JSON.stringify(curr);
......@@ -421,7 +168,7 @@ export class AnalyticsDashboardService {
map(state => state.metrics),
//distinctUntilChanged(deepDiff),
distinctUntilChanged((prev, curr) => {
//console.log(JSON.stringify(prev), JSON.stringify(curr));
console.log(JSON.stringify(prev), JSON.stringify(curr));
return deepDiff(prev, curr);
}),
tap(metrics => console.log('metrics changed', metrics))
......@@ -568,13 +315,12 @@ export class AnalyticsDashboardService {
updateCategory(category: string) {
console.log('update category called: ' + category);
this.updateState({ ..._state, category, metrics: [], loading: true });
// TODO: uncomment this
// this.updateState({ ..._state, category, metrics: [], loading: true });
this.updateState({ ..._state, category, loading: true });
}
updateTimespan(timespan: string) {
console.log('update timespan called: ' + timespan);
// if (timespan === _state.timespan) {
// return;
// }
this.updateState({ ..._state, timespan, loading: true });
}
updateMetric(metric: string) {
......
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment