Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ api/logs/*
dist/
dump.rdb
node_modules/
node_modules
npm-debug.log
npm-debug.log*
pids/
static/logos/*.*
storage/*.*
Expand Down
4 changes: 4 additions & 0 deletions api/src/auth/jwt.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ const createOrgJWT = async (user, organisationId, provider) => {
};

const createDashboardTokenPayload = async (dashboard, shareableId, provider) => {
if (!shareableId && dashboard.shareable.length > 0) {
shareableId = dashboard.shareable[0]._id;
}

const visualisationIds = getVisualisationIdsFromDashboard(dashboard);
return payloadDefaults({
provider,
Expand Down
22 changes: 21 additions & 1 deletion api/src/routes/HttpRoutes.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ import express from 'express';
import restify from 'express-restify-mongoose';
import git from 'git-rev';
import Promise from 'bluebird';
import { omit, findIndex } from 'lodash';
import getAuthFromRequest from 'lib/helpers/getAuthFromRequest';
import getScopesFromRequest from 'lib/services/auth/authInfoSelectors/getScopesFromAuthInfo';
import { SITE_ADMIN } from 'lib/constants/scopes';
import getUserIdFromAuthInfo from 'lib/services/auth/authInfoSelectors/getUserIdFromAuthInfo';
import { jsonSuccess, serverError } from 'api/utils/responses';
import passport from 'api/auth/passport';
import {
Expand Down Expand Up @@ -199,7 +204,22 @@ restify.serve(router, Export);
restify.serve(router, Download);
restify.serve(router, Query);
restify.serve(router, ImportCsv);
restify.serve(router, User);
restify.serve(router, User, {
preUpdate: (req, res, next) => {
const authInfo = getAuthFromRequest(req);
const scopes = getScopesFromRequest(authInfo);

if (
findIndex(scopes, item => item === SITE_ADMIN) < 0 &&
(req.body._id !== getUserIdFromAuthInfo(authInfo).toString())
) {
// Don't allow changing of passwords
req.body = omit(req.body, 'password');
}

next();
}
});
restify.serve(router, Client);
restify.serve(router, Visualisation);
restify.serve(router, Dashboard);
Expand Down
5 changes: 2 additions & 3 deletions api/src/routes/tests/http-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,11 @@ let jwtToken;
let orgJwtToken;
const provider = 'native';

describe('API HTTP Route tests', () => {
describe('API HTTP Route tests', function describeTest() {
this.timeout(10000);
before((done) => {
console.log('readyState', connection.readyState);
if (connection.readyState !== 1) {
connection.on('connected', () => {
console.log('connected');
done();
});
} else {
Expand Down
2 changes: 1 addition & 1 deletion cli/src/commands/bulkInsert.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import moment from 'moment';
import hash from 'object-hash';
import uuid from 'uuid';

TinCan.DEBUG = false;
TinCan.DEBUG = true;

let lrs;
let _batchSize;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import logger from 'lib/logger';
import mongoose from 'mongoose';
import { getConnection } from 'lib/connections/mongoose';
import Dashboard, { schema } from 'lib/models/dashboard';

import { map } from 'lodash';

const objectId = mongoose.Types.ObjectId;

schema.set('strict', false);
const OldDashboardModel = getConnection().model('Dashboard', schema, 'dashboards');

const up = async () => {
logger.info('Moving existing shared dashboards into new array format.');
const dashboards = await Dashboard.find({}).lean().exec();

const updatePromises = map(dashboards, async (dashboard) => {
if (!dashboard.shareable) {
dashboard.shareable = [];
}

const shareable = dashboard.shareable;
shareable.unshift({
title: '~ Shareable',
filter: dashboard.filter,
visibility: dashboard.visibility,
validDomains: dashboard.validDomains,
createdAt: new Date(),
});

return OldDashboardModel.update(
{ _id: objectId(dashboard._id) },
{
shareable,
},
{
safe: false,
strict: false
}
);
});

await Promise.all(updatePromises);
};

const down = async () => {
logger.info('Moving first shared link in dashboards back into old format.');
const dashboards = await OldDashboardModel.find({}).exec();

const updatePromises = map(dashboards, (dashboard) => {
if (!dashboard.shareable || dashboard.shareable.length < 1) {
return Promise.resolve();
}

const shareable = dashboard.shareable.shift();

return OldDashboardModel.update(
{ _id: objectId(dashboard._id) },
{
filter: shareable.filter,
visibility: shareable.visibility,
validDomains: shareable.validDomains,
shareable: dashboard.shareable,
},
{
safe: false,
strict: false
}
);
});

await Promise.all(updatePromises);
};

export default { up, down };
4 changes: 3 additions & 1 deletion cli/src/commands/v2-migrations/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ import updateRefs from './20171121153300_update_refs';
import migrateIdentifiers from './20171127214900_migrate_identifiers';
import personasIndexes from './20171008104700_personas_indexes';
// import removeOldIdents from './20171128144900_remove_old_idents';
import shareableDashboards from './20180320093000_shareable_dashboards';

export default new OrderedMap()
.set('20171122100800_common_indexes', commonIndexesMigration)
.set('20171121153300_update_refs', updateRefs)
.set('20171008104700_personas_indexes', personasIndexes)
.set('20171127214900_migrate_identifiers', migrateIdentifiers);
.set('20171127214900_migrate_identifiers', migrateIdentifiers)
// .set('20171127214500_remove_unused_persona_props', removeUnusedPersonaProps)
// .set('20171128144900_remove_old_idents', removeOldIdents)
.set('20180320093000_shareable_dashboards', shareableDashboards);
5 changes: 3 additions & 2 deletions lib/models/dashboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,11 @@ const shareableSchema = new mongoose.Schema({
title: { type: String, default: '~ Shareable' },
filter: { type: String, default: '{}' },
visibility: { type: String, enum: sharingScopes, default: NOWHERE },
validDomains: { type: String }
validDomains: { type: String },
createdAt: { type: Date, default: new Date() },
});

const schema = new mongoose.Schema({
export const schema = new mongoose.Schema({
title: { type: String },
widgets: [widgetSchema],
shareable: [shareableSchema],
Expand Down
3 changes: 2 additions & 1 deletion lib/models/plugins/tests/softDelete-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ const schemaFactory = (fields, pluginOpts = {}) => {
};

describe('softDeletePlugin', () => {
describe('default options', () => {
describe('default options', function describeTest() {
this.timeout(10000);
let TestModel;
before('setup schema', (done) => {
TestModel = schemaFactory({
Expand Down
37 changes: 20 additions & 17 deletions lib/models/statement.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,10 @@ schema.plugin(filterByOrg);
schema.plugin(addCRUDFunctions);

const streamAggregation = ({ pipeline, skip, limit, batchSize, maxTimeMS, maxScan }) => {
let query = Statement.aggregate(pipeline).allowDiskUse(ALLOW_AGGREGATION_DISK_USE);
let query = Statement
.aggregate(pipeline)
.read('secondaryPreferred')
.allowDiskUse(ALLOW_AGGREGATION_DISK_USE);
if (skip !== -1) query = query.skip(skip);
if (limit !== -1) query = query.limit(limit);
if (!query.options) {
Expand Down Expand Up @@ -154,19 +157,19 @@ const setCachedAggregation = ({ client, dataKey, isRunningKey, stream }) =>
* @return {Stream} stream
*/
schema.statics.aggregateByAuth = function aggregateByAuth(
authInfo,
pipeline = [],
{
skip = 0,
limit = -1,
cache = false,
batchSize = 100,
getStream = false,
maxTimeMS = MAX_TIME_MS,
maxScan = MAX_SCAN,
},
cb = () => {}
) {
authInfo,
pipeline = [],
{
skip = 0,
limit = -1,
cache = false,
batchSize = 100,
getStream = false,
maxTimeMS = MAX_TIME_MS,
maxScan = MAX_SCAN,
},
cb = () => { }
) {
return parseQuery(pipeline, {
organisation: getOrgFromAuthInfo(authInfo)
}).then(async (parsedPipeline) => {
Expand Down Expand Up @@ -228,9 +231,9 @@ schema.statics.aggregateByAuth = function aggregateByAuth(
}
return dataKeyTTL >= 5 ? cachedStreamPromise : streamPromise;
})
.then((stringResult) => {
cb(null, stringResult);
});
.then((stringResult) => {
cb(null, stringResult);
});
}).catch(cb);
};

Expand Down
3 changes: 2 additions & 1 deletion lib/routes/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@ const routes = [
{ name: 'organisation.settings.roles', path: '/roles', canActivate: requireModelViewScope('role') },

// Embedded dashboards
{ name: 'embedded-dashboard', path: '/dashboards/:dashboardId/:shareableId/*splat' }
{ name: 'embedded-dashboard', path: '/dashboards/:dashboardId' },
{ name: 'embedded-dashboard.shareable', path: '/:shareableId/*splat' }
];

const router = createRouter(
Expand Down
3 changes: 3 additions & 0 deletions lib/tools/getWebpackConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const AssetsPlugin = require('assets-webpack-plugin');
const StatsPlugin = require('stats-webpack-plugin');
const CopyPlugin = require('copy-webpack-plugin');
const nodeExternals = require('webpack-node-externals');
const HardSourceWebpackPlugin = require('hard-source-webpack-plugin');

// Common configuration chunk to be used for both
// client-side and server-side bundles
Expand Down Expand Up @@ -148,6 +149,8 @@ function getWebpackConfig(args) {
},

plugins: [
new HardSourceWebpackPlugin(),
new webpack.optimize.ModuleConcatenationPlugin(),
// Emit a file with assets paths
// https://github.com/sporto/assets-webpack-plugin#options
new AssetsPlugin({
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@
"file-loader": "^0.8.5",
"file-stream-rotator": "^0.0.6",
"git-rev": "^0.2.1",
"hard-source-webpack-plugin": "^0.5.15",
"helmet": "^2.1.1",
"highland": "^2.8.1",
"hoist-non-react-statics": "^1.0.3",
Expand Down Expand Up @@ -232,7 +233,7 @@
"velocity-react": "1.1.4",
"victory": "^0.12.1",
"warning": "^2.1.0",
"webpack": "^2.2.1",
"webpack": "^3.10",
"webpack-dev-middleware": "^1.10.1",
"webpack-node-externals": "^1.5.4",
"winston": "^2.1.1",
Expand Down
4 changes: 3 additions & 1 deletion ui/src/containers/App/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,9 @@ const renderPage = (routeName) => {
loader: System.import('ui/containers/ResetPassword')
}));
}
if (testRoute('embedded-dashboard')) {
if (
testRoute('embedded-dashboard')
) {
return React.createElement(createAsyncComponent({
loader: System.import('ui/containers/EmbeddableDashboard')
}));
Expand Down
48 changes: 48 additions & 0 deletions ui/src/containers/DashboardSharing/OpenLinkButton/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/* eslint-disable react/jsx-indent */
import React, { Component, PropTypes } from 'react';
import classNames from 'classnames';

class OpenLinkButton extends Component {
static propTypes = {
className: PropTypes.string,
openLink: PropTypes.func,
white: PropTypes.bool,
small: PropTypes.bool,
disabled: PropTypes.bool,
}

static defaultProps = {
className: '',
disabled: false,
}

onClick = (e) => {
e.preventDefault();
e.stopPropagation();
this.props.openLink();
}

render = () => {
const { className } = this.props;

const classes = classNames({
[className]: true,
btn: true,
'btn-sm': !this.props.small,
'btn-xs': this.props.small,
'btn-inverse': !this.props.white && !this.props.small,
'flat-white flat-btn': this.props.white,
'btn-default': this.props.small || this.props.white
});
const width = this.props.small ? '22.5px' : '33px';


return (
<button className={classes} title="Open link" onClick={this.onClick.bind(null)} style={{ width }} disabled={this.props.disabled}>
<i className="icon ion-eye" />
</button>
);
}
}

export default OpenLinkButton;
Loading