Merge pull request #294 from CatalystCode/add-dialog-template

Add dialog template
This commit is contained in:
Mor Shemesh 2017-08-15 15:31:00 +03:00 коммит произвёл GitHub
Родитель faf976476d f27e0f6fab
Коммит e05ec65b1e
25 изменённых файлов: 591 добавлений и 4041 удалений

4
client/@types/types.d.ts поставляемый
Просмотреть файл

@ -10,6 +10,10 @@ interface IDataSource {
id: string,
dependencies?: IStringDictionary,
params?: IDictionary,
format?: string | {
type: string,
args?: IDictionary
}
calculated?: (state: any, dependencies?: any, prevState?: any) => IDictionary
}

Просмотреть файл

@ -181,11 +181,11 @@ class SettingsStore extends AbstractStoreModel<ISettingsStoreState> implements I
const forkedQueryComponents = dependencyProperty.split('-');
const params = datasources[datasource].config.params;
const isForked = !params.query && !!params.table;
const isForked = params && !params.query && !!params.table;
if (!isForked) {
// unforked
queryFn = params.query;
queryFn = params && params.query || 'n/a';
} else {
// forked
if (!params.queries[queryId] && forkedQueryComponents.length === 2) {

Просмотреть файл

@ -9,8 +9,6 @@ import Snackbar from 'react-md/lib/Snackbars';
import SpinnerStore, { ISpinnerStoreState } from './SpinnerStore';
import SpinnerActions from './SpinnerActions';
import {Toast, ToastActions, IToast} from '../Toast';
interface ISpinnerState extends ISpinnerStoreState {
}
@ -22,34 +20,6 @@ export default class Spinner extends React.Component<any, ISpinnerState> {
this.state = SpinnerStore.getState();
this.onChange = this.onChange.bind(this);
this._429ApplicationInsights = this._429ApplicationInsights.bind(this);
var self = this;
var openOriginal = XMLHttpRequest.prototype.open;
var sendOriginal = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.open = function(method: string, url: string, async?: boolean, _?: string, __?: string) {
SpinnerActions.startRequestLoading.defer(null);
openOriginal.apply(this, arguments);
};
XMLHttpRequest.prototype.send = function(data: any) {
let _xhr: XMLHttpRequest = this;
_xhr.onreadystatechange = (response) => {
// readyState === 4: means the response is complete
if (_xhr.readyState === 4) {
SpinnerActions.endRequestLoading.defer(null);
if (_xhr.status === 429) {
self._429ApplicationInsights();
}
}
};
sendOriginal.apply(_xhr, arguments);
};
// Todo: Add timeout to requests - if no reply received, turn spinner off
}
componentDidMount() {
@ -61,11 +31,6 @@ export default class Spinner extends React.Component<any, ISpinnerState> {
SpinnerStore.unlisten(this.onChange);
}
_429ApplicationInsights() {
let toast: IToast = { text: 'You have reached the maximum number of Application Insights requests.' };
ToastActions.addToast(toast);
}
onChange(state: ISpinnerState) {
this.setState(state);
}

Просмотреть файл

@ -1,5 +1,6 @@
import alt, { AbstractStoreModel } from '../../alt';
import {Toast, ToastActions, IToast} from '../Toast';
import spinnerActions from './SpinnerActions';
export interface ISpinnerStoreState {
@ -7,6 +8,35 @@ export interface ISpinnerStoreState {
requestLoading?: number;
}
const openOriginal = XMLHttpRequest.prototype.open;
const sendOriginal = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.open = function(method: string, url: string, async?: boolean, _?: string, __?: string) {
spinnerActions.startRequestLoading.defer(null);
openOriginal.apply(this, arguments);
};
XMLHttpRequest.prototype.send = function(data: any) {
let _xhr: XMLHttpRequest = this;
_xhr.onreadystatechange = (response) => {
// readyState === 4: means the response is complete
if (_xhr.readyState === 4) {
spinnerActions.endRequestLoading.defer(null);
if (_xhr.status === 429) {
_429ApplicationInsights();
}
}
};
sendOriginal.apply(_xhr, arguments);
};
function _429ApplicationInsights() {
let toast: IToast = { text: 'You have reached the maximum number of Application Insights requests.' };
ToastActions.addToast(toast);
}
class SpinnerStore extends AbstractStoreModel<ISpinnerStoreState> implements ISpinnerStoreState {
pageLoading: number;
@ -43,6 +73,6 @@ class SpinnerStore extends AbstractStoreModel<ISpinnerStoreState> implements ISp
}
}
const spinnerStore = alt.createStore<ISpinnerStoreState>(SpinnerStore, 'SpinnerStore');
const spinnerStore = alt.createStore<ISpinnerStoreState>(SpinnerStore as any, 'SpinnerStore');
export default spinnerStore;

Просмотреть файл

@ -30,7 +30,7 @@ export default class BarData extends GenericComponent<IBarProps, IBarState> {
static editor = settings;
static fromSource(source: string) {
return {
values: GenericComponent.sourceFormat(source, 'values'),
values: GenericComponent.sourceFormat(source, 'bar-values'),
bars: GenericComponent.sourceFormat(source, 'bars')
};
}

Просмотреть файл

@ -63,6 +63,10 @@ export default class Dialog extends React.PureComponent<IDialogProps, IDialogSta
DialogsStore.listen(this.onChange);
}
componentWillUnmount() {
DialogsStore.unlisten(this.onChange);
}
componentDidUpdate() {
const { dialogData } = this.props;
var { dialogId, dialogArgs } = this.state;

Просмотреть файл

@ -234,18 +234,30 @@ export class DataSourceConnector {
sourceDS.store.listen((state) => {
Object.keys(this.dataSources).forEach(checkDSId => {
var checkDS = this.dataSources[checkDSId];
var dependencies = checkDS.plugin.getDependencies() || {};
let checkDS = this.dataSources[checkDSId];
let dependencies = checkDS.plugin.getDependencies() || {};
let populatedDependencies = {};
let connected = _.find(_.keys(dependencies), dependencyKey => {
let dependencyValue = dependencies[dependencyKey] || '';
return (dependencyValue === sourceDS.id || dependencyValue.startsWith(sourceDS.id + ':'));
if (typeof dependencyValue === 'string' && dependencyValue.length > 0) {
if (dependencyValue === sourceDS.id) {
let defaultProperty = sourceDS.plugin.defaultProperty || 'value';
populatedDependencies[dependencyKey] = state[defaultProperty];
return true;
} else if (dependencyValue.startsWith(sourceDS.id + ':')) {
let property = dependencyValue.substr(sourceDS.id.length + 1);
populatedDependencies[dependencyKey] = _.get(state, property);
return true;
}
}
return false;
});
if (connected) {
// Todo: add check that all dependencies are met
checkDS.action.updateDependencies.defer(state);
checkDS.action.updateDependencies.defer(populatedDependencies);
}
});
@ -338,7 +350,7 @@ export class DataSourceConnector {
private static callibrateResult(result: any, plugin: IDataSourcePlugin, dependencies: IDictionary): any {
var defaultProperty = plugin.defaultProperty || 'value';
let defaultProperty = plugin.defaultProperty || 'value';
// In case result is not an object, push result into an object
if (typeof result !== 'object') {
@ -353,24 +365,26 @@ export class DataSourceConnector {
let state = DataSourceConnector.dataSources[plugin._props.id].store.getState();
state = _.extend(state, result);
if (typeof calculated === 'function') {
let additionalValues = calculated(state, dependencies) || {};
Object.assign(result, additionalValues);
}
if (Array.isArray(calculated)) {
calculated.forEach(calc => {
let additionalValues = calc(state, dependencies) || {};
Object.assign(result, additionalValues);
});
}
state = _.extend(state, result);
let format = plugin.getFormat();
let formatExtract = DataSourceConnector.handleDataFormat(format, plugin, state, dependencies);
if (formatExtract) {
Object.assign(result, formatExtract);
}
if (typeof calculated === 'function') {
let additionalValues = calculated(state, dependencies) || {};
Object.keys(additionalValues).forEach(key => { result[key] = additionalValues[key]; });
}
if (Array.isArray(calculated)) {
calculated.forEach(calc => {
let additionalValues = calc(state, dependencies) || {};
Object.keys(additionalValues).forEach(key => { result[key] = additionalValues[key]; });
});
}
return result;
}
}

Просмотреть файл

@ -1,3 +1,4 @@
import * as _ from 'lodash';
import { IDataSource } from '../DataSourceConnector';
import { ToastActions } from '../../components/Toast';
import { DataFormatTypes, IDataFormat } from '../../utils/data-formats';
@ -142,7 +143,7 @@ export abstract class DataSourcePlugin<T> implements IDataSourcePlugin {
}
getParams(): T {
return this._props.params;
return _.cloneDeep(this._props.params);
}
getFormat(): string | IDataFormat {

Просмотреть файл

@ -8,8 +8,8 @@ interface ISampleParams {
export default class Sample extends DataSourcePlugin<ISampleParams> {
type = 'Constant';
defaultProperty = 'selectedValue';
type = 'Sample';
defaultProperty = 'values';
constructor(options: IOptions<ISampleParams>, connections: IDict<IStringDictionary>) {
super(options, connections);
@ -21,7 +21,7 @@ export default class Sample extends DataSourcePlugin<ISampleParams> {
}
initialize() {
let { samples } = <any> this._props.params;
let { samples } = _.cloneDeep(this._props.params);
return samples || {};
}
@ -29,7 +29,10 @@ export default class Sample extends DataSourcePlugin<ISampleParams> {
* updateDependencies - called when dependencies are created
*/
dependenciesUpdated(dependencies: IDictionary, args: IDictionary, callback: (result: any) => void) {
var result = _.extend(dependencies, args);
let result = _.extend(dependencies, args);
let { samples } = this.getParams();
_.extend(result, samples);
if (typeof callback === 'function') {
return callback(result);

Просмотреть файл

@ -0,0 +1,30 @@
import { IDataSourceDictionary } from '../../data-sources';
import { setupTests } from '../utils/setup';
import Sample from '../../data-sources/plugins/Sample';
import * as testCases from './format-values';
import * as formats from '../../utils/data-formats';
describe('Data Source: Application Insights: Forked Query', () => {
let mockPlugin = new Sample(<any>{
params: {
values: [ 'value 1', 'value 2', 'value 3' ],
samples: {
values: [ 'value1', 'value2', 'value3' ]
}
}
}, {});
Object.keys(testCases.tests).forEach(testFormat => {
it ('Check data format ' + testFormat, () => {
let test = testCases.tests[testFormat];
let values = test.state;
let result = formats[testFormat](test.format, test.state, {}, mockPlugin, {});
expect(result).toMatchObject(test.expected);
});
});
});

Просмотреть файл

@ -0,0 +1,91 @@
export interface IFormatTest {
format: any,
state: any,
expected: any
};
export const tests: IDict<IFormatTest> = {
bars: {
format: {
type: 'bars',
args: {
barsField: 'barField',
seriesField: 'seriesField'
}
},
state: {
values: [
{ count: 10, barField: 'bar 1', seriesField: 'series1Value' },
{ count: 15, barField: 'bar 2', seriesField: 'series1Value' },
{ count: 20, barField: 'bar 1', seriesField: 'series2Value' },
{ count: 44, barField: 'bar 3', seriesField: 'series2Value' },
]
},
expected: {
'bars': ['series1Value', 'series2Value'],
'bar-values': [
{'series1Value': 10, 'series2Value': 20, 'value': 'bar 1'},
{'series1Value': 15, 'value': 'bar 2'},
{'series2Value': 44, 'value': 'bar 3'}
]
}
},
filter: {
format: 'filter',
state: {
values: [
{ value: 'value 1' },
{ value: 'value 2' },
{ value: 'value 3' }
]
},
expected: {
'values-all': [ 'value 1', 'value 2', 'value 3' ],
'values-selected': [ ],
}
},
flags: {
format: 'flags',
state: { values: [ 'value 1', 'value 2', 'value 3' ] },
expected: {
'value 1': false,
'value 2': false,
'value 3': false,
'values-all': [ 'value 1', 'value 2', 'value 3' ],
'values-selected': [],
}
},
retention: {
format: 'retention',
state: {
values: [
{
totalUnique: 10,
totalUniqueUsersIn24hr: 5,
totalUniqueUsersIn7d: 7,
totalUniqueUsersIn30d: 10,
returning24hr: 3,
returning7d: 3,
returning30d: 6
}
]
},
expected: {
"returning": 0,
"returning24hr": 3,
"returning30d": 6,
"returning7d": 3,
"total": 0,
"totalUnique": 10,
"totalUniqueUsersIn24hr": 5,
"totalUniqueUsersIn30d": 10,
"totalUniqueUsersIn7d": 7,
"values": [
{ "retention": "60%", "returning": 3, "timespan": "24 hours", "unique": 5 },
{ "retention": "43%", "returning": 3, "timespan": "7 days", "unique": 7 },
{ "retention": "60%", "returning": 6, "timespan": "30 days", "unique": 10 }
]
}
}
};

Просмотреть файл

@ -32,6 +32,17 @@ describe('SplitPanel', () => {
})
it('Render inside a Card (+ Table inside a Card)', () => {
dataSources['samples'].action.updateDependencies({
groups: [
{ title: "value1", subtitle: "subvalue1", count: 60 },
{ title: "value2", subtitle: "subvalue2", count: 60 },
],
values: [
{ id: "value1", count: 60 },
{ id: "value2", count: 10 },
{ id: "value3", count: 30 }
]
});
let card = TestUtils.scryRenderedComponentsWithType(splitpanel, Card);
expect(card).toHaveLength(2);
});

Просмотреть файл

@ -30,6 +30,9 @@ describe('Table', () => {
})
it('Render inside a Card', () => {
dataSources['samples'].action.updateDependencies({
'table-values': dataSources['samples'].store.state['values']
});
let card = TestUtils.scryRenderedComponentsWithType(table, Card);
expect(card.length).toBe(1);
});
@ -46,7 +49,7 @@ describe('Table', () => {
it('Rows == 0', () => {
dataSources['samples'].action.updateDependencies({
values: []
'table-values': []
});
let rows = TestUtils.scryRenderedComponentsWithType(table, TableRow);
expect(rows.length).toBe(1);

Просмотреть файл

@ -6,17 +6,7 @@ dashboard.dataSources.push(
id: "samples",
type: "Sample",
params: {
samples: {
groups: [
{ title: "value1", subtitle: "subvalue1", count: 60 },
{ title: "value2", subtitle: "subvalue2", count: 60 },
],
values: [
{ id: "value1", count: 60 },
{ id: "value2", count: 10 },
{ id: "value3", count: 30 }
]
}
samples: { }
}
}
);

Просмотреть файл

@ -8,7 +8,7 @@ dashboard.elements.push({
size: { w: 1, h: 1 },
title: 'Table',
subtitle: 'Table',
dependencies: { values: 'samples:values' },
dependencies: { values: 'samples:table-values' },
props: {
cols: [
{

Просмотреть файл

@ -63,7 +63,7 @@ export function bars(
const threshold = args.threshold || 0;
const othersValue = args.othersValue || 'Others';
let values: any[] = state.values;
let values: any[] = state.values || [];
// Concating values with '...'
if (values && values.length && valueMaxLength && (seriesField || barsField)) {
@ -98,11 +98,11 @@ export function bars(
});
result[prefix + 'bars'] = _.keys(series);
result[prefix + 'values'] = _.values(barValues);
result[prefix + 'bar-values'] = _.values(barValues);
} else {
result[prefix + 'bars'] = [ valueField ];
result[prefix + 'values'] = values;
result[prefix + 'bar-values'] = values;
}
return result;

Просмотреть файл

@ -53,14 +53,12 @@ export function filter (
// is going to be reset into an empty set.
// For this reason, using previous state to copy filter
const filters = values.map(x => x[field] || unknown);
let selectedValues = [];
if (prevState[prefix + 'values-selected'] !== undefined) {
selectedValues = prevState[prefix + 'values-selected'];
}
let result = {};
result[prefix + 'values-all'] = filters;
result[prefix + 'values-selected'] = selectedValues;
if (prevState[prefix + 'values-selected'] === undefined) {
result[prefix + 'values-selected'] = [];
}
return result;
}

Просмотреть файл

@ -0,0 +1,84 @@
import * as _ from 'lodash';
import utils from '../../index';
import { DataFormatTypes, IDataFormat, formatWarn, getPrefix } from '../common';
import { IDataSourcePlugin } from '../../../data-sources/plugins/DataSourcePlugin';
/**
* Applies selected filters to sample JSON data
* Returns filtered data using a prefix.
*
* @param format {
* type: 'filtered_samples',
* args: {
* prefix: string - a prefix string for the filtered sample data (defaults to 'filtered').
* }
* }
* @param state Current received state from data source
* @param dependencies Dependencies for the plugin
* @param plugin The entire plugin (for id generation, params etc...)
* @param prevState The previous state to compare for changing filters
*/
export function filtered_samples (
format: string | IDataFormat,
state: any,
dependencies: IDictionary,
plugin: IDataSourcePlugin,
prevState: any) {
let result = {};
if (typeof format === 'string') {
return formatWarn('format should be an object with args', 'bars', plugin);
}
const args = format.args || {};
const prefix = args['prefix'] || 'filtered';
const params = plugin.getParams();
const filters = params.filters;
// Check for active filters selected
const hasSelectedFilters = filters.some(filter => (
dependencies[filter.dependency] && dependencies[filter.dependency].length > 0)
);
// Apply filter to sample data
Object.keys(dependencies).forEach(key => {
// Skip filters
if (filters.find(f => f.dependency === key)) {
return;
}
// Apply filter data
const values = dependencies[key];
let filteredData = {};
// Return early if no active filters
if (!hasSelectedFilters) {
filteredData[prefix + '_' + key] = values;
Object.assign(result, filteredData);
return;
}
// Get active selected filters
const activeFilters = filters.reduce((acc, filter) => {
if (dependencies[filter.dependency].length > 0) {
acc.push(filter);
}
return acc;
}, []);
// Apply active selected filters to sample data
const filteredValues = values.filter(value => {
return activeFilters.every(filter => {
const queryProperty = filter.queryProperty;
const selectedFilters = dependencies[filter.dependency];
return value[queryProperty] === selectedFilters.find(selectedFilter => selectedFilter === value[queryProperty]);
});
});
filteredData[prefix + '_' + key] = filteredValues;
Object.assign(result, filteredData);
});
return result;
}

Просмотреть файл

@ -52,7 +52,7 @@ export function flags(
params.values.forEach(key => { flags[key] = state.selectedValue === key; });
flags[prefix + 'values-all'] = params.values;
flags[prefix + 'values-selected'] = state.selectedValue;
flags[prefix + 'values-selected'] = state.selectedValue || [];
return flags;
}

Просмотреть файл

@ -5,4 +5,5 @@ export * from './formats/flags';
export * from './formats/retention';
export * from './formats/scorecard';
export * from './formats/timeline';
export * from './formats/timespan';
export * from './formats/timespan';
export * from './formats/filtered-samples';

Просмотреть файл

@ -0,0 +1,104 @@
/// <reference path="../../../client/@types/types.d.ts"/>
import * as _ from 'lodash';
// The following line is important to keep in that format so it can be rendered into the page
export const config: IDashboardConfig = /*return*/ {
id: "sample_with_dialog",
name: "Sample with Dialog",
icon: "extension",
url: "sample_with_dialog",
description: "A basic sample to understand a basic dashboard",
preview: "/images/sample.png",
category: "Samples",
html: `<div>
This is a basic sample dashboard, with JSON based sample data source, to show how data from different data sources
can be manipulated and connected to visual components.
</div>`,
config: {
connections: { },
layout: {
isDraggable: true,
isResizable: true,
rowHeight: 30,
verticalCompact: false,
cols: { lg: 12,md: 10,sm: 6,xs: 4,xxs: 2 },
breakpoints: { lg: 1200,md: 996,sm: 768,xs: 480,xxs: 0 }
}
},
dataSources: [
{
id: "samples",
type: "Sample",
params: {
samples: {
values: [
{ count: 10,barField: "bar 1",seriesField: "series1Value" },
{ count: 15,barField: "bar 2",seriesField: "series1Value" },
{ count: 20,barField: "bar 1",seriesField: "series2Value" },
{ count: 44,barField: "bar 3",seriesField: "series2Value" }
]
}
},
format: {
type: "bars",
args: { valueField: "count",barsField: "barField",seriesField: "seriesField",threshold: 10 }
}
}
],
filters: [],
elements: [
{
id: "bar_sample1",
type: "BarData",
title: "Bars Sample 1",
subtitle: "Description of bars sample 1",
source: "samples",
size: { w: 6,h: 8 },
actions: { onBarClick: { action: "dialog:sample_dlg",params: { title: "args:value",param1: "args:value" } } }
}
],
dialogs: [
{
id: "sample_dlg",
params: ["title","param1"],
dataSources: [
{
id: "dialog-data",
type: "Sample",
dependencies: { param1_dependency: "dialog_sample_dlg:param1" },
params: {
samples: {
values: [
{ count: 199,barField: "bar 1",seriesField: "series1Value" },
{ count: 105,barField: "bar 1",seriesField: "series1Value" },
{ count: 203,barField: "bar 2",seriesField: "series2Value" },
{ count: 445,barField: "bar 2",seriesField: "series2Value" }
]
}
},
calculated: (state, dependencies) => {
let { values } = state;
let { param1_dependency } = dependencies;
return {
values: _.filter(values, { barField: param1_dependency })
};
},
format: {
type: "bars",
args: { valueField: "count",barsField: "barField",seriesField: "seriesField",threshold: 10 }
}
}
],
elements: [
{
id: "dialog-sample-bars",
type: "BarData",
title: "Sample bars in dialog",
subtitle: "Sample bars in dialog with some param filter",
size: { w: 6,h: 8 },
source: "dialog-data"
}
]
}
]
}

Просмотреть файл

@ -0,0 +1,173 @@
/// <reference path="../../../client/@types/types.d.ts"/>
import * as _ from 'lodash';
// The following line is important to keep in that format so it can be rendered into the page
export const config: IDashboardConfig = /*return*/ {
id: 'basic_sample_filters',
name: 'Basic Sample with Filters',
icon: 'extension',
url: 'basic_sample_filters',
description: 'A basic sample to understand a basic dashboard',
preview: '/images/sample.png',
category: 'Samples',
html: `
<div>
This is a basic sample dashboard, with JSON based sample data source, to show how data from different data sources
can be filtered and connected to visual components.
</div>
`,
config: {
connections: {},
layout: {
isDraggable: true,
isResizable: true,
rowHeight: 30,
verticalCompact: false,
cols: { lg: 12, md: 10, sm: 6, xs: 4, xxs: 2 },
breakpoints: { lg: 1200, md: 996, sm: 768, xs: 480, xxs: 0 }
}
},
dataSources: [
{
id: 'filter1',
type: 'Sample',
format: 'filter',
params: {
samples: {
'values': [
{value: 'en'},
{value: 'fr'},
{value: 'de'}
]
}
}
},
{
id: 'filter2',
type: 'Sample',
format: 'filter',
params: {
samples: {
'values': [
{value: 'skype'},
{value: 'messenger'},
{value: 'slack'}
]
}
}
},
{
id: 'samples',
type: 'Sample',
dependencies: {
selectedFilter1: 'filter1:values-selected',
selectedFilter2: 'filter2:values-selected',
},
params: {
samples: {
data_for_pie: [
{ name: 'skype-en', value: 9, locale: 'en', channel: 'skype' },
{ name: 'skype-fr', value: 8, locale: 'fr', channel: 'skype' },
{ name: 'skype-de', value: 7, locale: 'de', channel: 'skype' },
{ name: 'messenger-en', value: 6, locale: 'en', channel: 'messenger' },
{ name: 'messenger-fr', value: 5, locale: 'fr', channel: 'messenger' },
{ name: 'messenger-de', value: 4, locale: 'de', channel: 'messenger' },
{ name: 'slack-en', value: 3, locale: 'en', channel: 'slack' },
{ name: 'slack-fr', value: 2, locale: 'fr', channel: 'slack' },
{ name: 'slack-de', value: 1, locale: 'de', channel: 'slack' }
]
},
filters: [
{ dependency: 'selectedFilter1', queryProperty: 'locale' },
{ dependency: 'selectedFilter2', queryProperty: 'channel' }
],
},
format: {
type: 'filtered_samples'
},
},
{
id: 'scorecard',
type: 'Sample',
dependencies: {
filteredData: 'samples:filtered_data_for_pie',
},
calculated: (state, dependencies) => {
const {filteredData} = state;
const filteredDataValue = filteredData && filteredData.reduce((a, c) => a + c.value, 0) || 0;
const filteredDataSubvalue = filteredData && filteredData.length || 0;
return {
'filtered_data_value': filteredDataValue,
'filtered_data_subvalue': filteredDataSubvalue,
};
}
}
],
filters: [
{
type: 'MenuFilter',
title: 'Locale',
subtitle: 'Select locale',
icon: 'forum',
source: 'filter1',
actions: { onChange: 'filter1:updateSelectedValues:values-selected' },
first: true
},
{
type: 'MenuFilter',
title: 'Channel',
subtitle: 'Select channel',
icon: 'forum',
source: 'filter2',
actions: { onChange: 'filter2:updateSelectedValues:values-selected' },
first: true
},
],
elements: [
{
id: 'pie_sample1',
type: 'PieData',
title: 'Pie Sample 1',
subtitle: 'Description of pie sample 1',
size: { w: 5, h: 8 },
dependencies: { values: 'samples:filtered_data_for_pie' },
props: { entityType: 'Sessions', showLegend: true }
},
{
id: 'pie_sample2',
type: 'PieData',
title: 'Pie Sample 2',
subtitle: 'Hover on the values to see the difference from sample 1',
size: { w: 5, h: 8 },
dependencies: { values: 'samples:filtered_data_for_pie' },
props: { entityType: 'Sessions', showLegend: true, compact: true }
},
{
id: 'scorecard1',
type: 'Scorecard',
title: 'Value',
size: { w: 1, h: 3 },
dependencies: {
value: 'scorecard:filtered_data_value',
color: '::#2196F3',
icon: '::av_timer'
}
},
{
id: 'scorecard2',
type: 'Scorecard',
title: 'Same Value',
size: { w: 1, h: 3 },
dependencies: {
value: 'scorecard:filtered_data_value',
color: '::#2196F3',
icon: '::av_timer',
subvalue: 'scorecard:filtered_data_subvalue'
},
props: {
subheading: 'segments'
}
}
],
dialogs: []
};

Просмотреть файл

@ -1,11 +0,0 @@
.md-toolbar .md-select-field--toolbar.md-select-field--toolbar {
margin-bottom: 6px;
margin-top: 6px; }
.md-toolbar .md-select-field--toolbar.md-select-field--toolbar .md-floating-label {
padding-left: 6px; }
.md-toolbar .md-select-field--toolbar.md-select-field--toolbar .md-text-field {
padding-bottom: 0;
padding-left: 6px;
padding-right: 16px;
padding-top: 0;
margin: 20px 0 0 0; }

Просмотреть файл

@ -1,88 +0,0 @@
.md-card .widgets {
position: absolute;
top: 10px;
right: 10px; }
.md-card-scorecard .scorecard {
width: 100px;
height: 90px;
white-space: nowrap;
float: left;
margin: 10px 8px 20px; }
.md-card-scorecard .scorecard.clickable-card {
cursor: pointer; }
.md-card-scorecard .scorecard.clickable-card:hover {
background-color: #ECEFF1; }
.md-card-scorecard .scorecard.color-bottom .md-subheading-2 {
margin-bottom: 3px; }
.md-card-scorecard .scorecard.color-bottom .scorecard-subheading {
padding-bottom: 4px;
border-bottom: solid 4px transparent; }
.md-card-scorecard .scorecard.color-left {
padding-left: 5px;
border-left: solid 3px transparent; }
.md-card-scorecard .scorecard .md-icon {
float: left;
padding: 3px 3px 0 0; }
.md-card-scorecard .scorecard .md-headline {
margin-bottom: 0;
font-weight: 500; }
.md-card-scorecard .scorecard .md-subheading-2 {
text-overflow: ellipsis;
overflow-x: hidden; }
.md-card-scorecard .scorecard b {
margin-right: 5px;
margin-bottom: 0;
font-weight: 500; }
/* MenuFilter */
@media screen and (min-width: 320px) {
.md-multiselect-menu {
margin-top: 50px; } }
@media screen and (min-width: 1025px) {
.md-multiselect-menu {
margin-top: 58px; } }
.md-value {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis; }
/* Table */
.table > .primary {
display: block;
color: black; }
.table > .secondary {
color: grey; }
td.text {
white-space: normal; }
td.summary {
max-width: 240px;
overflow: hidden;
text-overflow: ellipsis; }
/* Sentiment */
.sentiment_very_dissatisfied {
color: red !important; }
.sentiment_dissatisfied {
color: orange !important; }
.sentiment_neutral {
color: grey !important; }
.sentiment_satisfied {
color: yellow !important; }
.sentiment_very_satisfied {
color: green !important; }
.error_outline {
color: lightgrey !important; }
.tooltip {
visibility: visible !important; }

Разница между файлами не показана из-за своего большого размера Загрузить разницу