Skip to content
This repository has been archived by the owner on Jul 9, 2022. It is now read-only.

[Snyk] Security upgrade plotly.js from 1.41.3 to 1.49.0 #9

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
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: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,6 @@ npm-debug.log

client/cypress/screenshots
client/cypress/videos

client/app/assets/less/**/*.css
client/app/visualizations/vega/vega.css
1 change: 1 addition & 0 deletions client/app/assets/less/inc/ant-variables.less
Original file line number Diff line number Diff line change
Expand Up @@ -82,5 +82,6 @@
/* --------------------------------------------------------
Notification
-----------------------------------------------------------*/
@notification-padding-vertical: 16px;
@notification-padding: @notification-padding-vertical 48px @notification-padding-vertical 17px;
@notification-width: auto;
2 changes: 2 additions & 0 deletions client/app/assets/less/redash/query.less
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,8 @@ edit-in-place p.editable:hover {
}

.query__vis {
height: 100%;

table {
border: 1px solid #f0f0f0;
}
Expand Down
28 changes: 1 addition & 27 deletions client/app/components/QueryEditor.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,9 @@ import Tooltip from 'antd/lib/tooltip';
import { react2angular } from 'react2angular';

import AceEditor from 'react-ace';
import ace from 'brace';
import { snippetManager, langTools } from '@/components/editor';
import notification from '@/services/notification';

import 'brace/ext/language_tools';
import 'brace/mode/json';
import 'brace/mode/python';
import 'brace/mode/sql';
import 'brace/mode/yaml';
import 'brace/theme/textmate';
import 'brace/ext/searchbox';

import { Query } from '@/services/query';
import { QuerySnippet } from '@/services/query-snippet';
import { KeyboardShortcuts } from '@/services/keyboard-shortcuts';
Expand All @@ -26,23 +18,6 @@ import { DataSource, Schema } from './proptypes';

import './QueryEditor.css';

const langTools = ace.acequire('ace/ext/language_tools');
const snippetsModule = ace.acequire('ace/snippets');

// By default Ace will try to load snippet files for the different modes and fail.
// We don't need them, so we use these placeholders until we define our own.
function defineDummySnippets(mode) {
ace.define(`ace/snippets/${mode}`, ['require', 'exports', 'module'], (require, exports) => {
exports.snippetText = '';
exports.scope = mode;
});
}

defineDummySnippets('python');
defineDummySnippets('sql');
defineDummySnippets('json');
defineDummySnippets('yaml');

class QueryEditor extends React.Component {
static propTypes = {
queryText: PropTypes.string.isRequired,
Expand Down Expand Up @@ -160,7 +135,6 @@ class QueryEditor extends React.Component {
});

QuerySnippet.query((snippets) => {
const snippetManager = snippetsModule.snippetManager;
const m = {
snippetText: '',
};
Expand Down
60 changes: 48 additions & 12 deletions client/app/components/dynamic-form/DynamicForm.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,37 @@ import Checkbox from 'antd/lib/checkbox';
import Button from 'antd/lib/button';
import Upload from 'antd/lib/upload';
import Icon from 'antd/lib/icon';
import { includes, isFunction } from 'lodash';
import { includes, isFunction, isPlainObject, isArray } from 'lodash';
import Select from 'antd/lib/select';
import notification from '@/services/notification';
import AceEditorInput from '@/components/AceEditorInput';
import { Field, Action, AntdForm } from '../proptypes';
import helper from './dynamicFormHelper';

const { TextArea } = Input;

const fieldRules = ({ type, required, minLength }) => {
const requiredRule = required;
const minLengthRule = minLength && includes(['text', 'email', 'password'], type);
const emailTypeRule = type === 'email';
const jsonRule = type === 'json';

return [
requiredRule && { required, message: 'This field is required.' },
minLengthRule && { min: minLength, message: 'This field is too short.' },
emailTypeRule && { type: 'email', message: 'This field must be a valid email.' },
minLengthRule && { min: minLength, message: 'This field is too short.' }, emailTypeRule && { type: 'email', message: 'This field must be a valid email.' },
jsonRule && {
type: 'object',
transform(x) {
if (x && x.trim()) {
try {
JSON.parse(x);
} catch {
return '';
}
}
},
message: 'This field must be a JSON string.',
},
].filter(rule => rule);
};

Expand Down Expand Up @@ -75,13 +90,27 @@ class DynamicForm extends React.Component {
}));
};

parseValues = (values) => {
this.props.fields.forEach((field) => {
if (field.type === 'json' && field.name in values) {
try {
values[field.name] = JSON.parse(values[field.name]);
} catch {
// Invalid JSON must be discarded
values[field.name] = null;
}
}
});
return values;
};

handleSubmit = (e) => {
this.setState({ isSubmitting: true });
e.preventDefault();
this.props.form.validateFieldsAndScroll((err, values) => {
if (!err) {
this.props.onSubmit(
values,
this.parseValues(values),
(msg) => {
const { setFieldsValue, getFieldsValue } = this.props.form;
this.setState({ isSubmitting: false });
Expand Down Expand Up @@ -156,8 +185,13 @@ class DynamicForm extends React.Component {

renderField(field, props) {
const { getFieldDecorator } = this.props.form;
const { name, type, initialValue } = field;
const { name, type } = field;
const fieldLabel = field.title || helper.toHuman(name);
let initialValue = field.initialValue;

if (isPlainObject(initialValue) || isArray(initialValue)) {
initialValue = JSON.stringify(field.initialValue);
}

const options = {
rules: fieldRules(field),
Expand All @@ -176,9 +210,11 @@ class DynamicForm extends React.Component {
} else if (type === 'number') {
return getFieldDecorator(name, options)(<InputNumber {...props} />);
} else if (type === 'textarea') {
return getFieldDecorator(name, options)(<Input.TextArea {...props} />);
return getFieldDecorator(name, options)(<TextArea {...props} />);
} else if (type === 'ace') {
return getFieldDecorator(name, options)(<AceEditorInput {...props} />);
} else if (type === 'json') {
return getFieldDecorator(name, options)(<TextArea {...props} />);
}
return getFieldDecorator(name, options)(<Input {...props} />);
}
Expand All @@ -190,12 +226,6 @@ class DynamicForm extends React.Component {
const fieldLabel = title || helper.toHuman(name);
const { feedbackIcons, form } = this.props;

const formItemProps = {
className: 'm-b-10',
hasFeedback: type !== 'checkbox' && type !== 'file' && feedbackIcons,
label: type === 'checkbox' ? '' : fieldLabel,
};

const fieldProps = {
...field.props,
className: 'w-100',
Expand All @@ -206,6 +236,12 @@ class DynamicForm extends React.Component {
placeholder: field.placeholder,
'data-test': fieldLabel,
};
const formItemProps = {
className: 'm-b-10',
hasFeedback: type !== 'checkbox' && type !== 'file' && feedbackIcons,
label: type === 'checkbox' ? '' : fieldLabel,
extra: fieldProps.extra,
};

return (
<React.Fragment key={name}>
Expand Down
14 changes: 10 additions & 4 deletions client/app/components/dynamic-form/dynamicFormHelper.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ function orderedInputs(properties, order, targetOptions) {
const inputs = new Array(order.length);
Object.keys(properties).forEach((key) => {
const position = order.indexOf(key);
const field = properties[key];
const input = {
name: key,
title: properties[key].title,
type: properties[key].type,
placeholder: properties[key].default && properties[key].default.toString(),
required: properties[key].required,
title: field.title,
type: field.type,
placeholder: field.default && field.default.toString(),
required: field.required,
initialValue: targetOptions[key],
props: field.props,
};

if (position > -1) {
Expand Down Expand Up @@ -41,6 +43,10 @@ function normalizeSchema(configurationSchema) {
prop.type = 'text';
}

if (prop.type === 'object') {
prop.type = 'json';
}

prop.required = includes(configurationSchema.required, name);
});

Expand Down
35 changes: 35 additions & 0 deletions client/app/components/editor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/**
* Common definition of the rich text editor
* (currently using Ace Editor, potentially migrating to Monaco)
*/
import ace from 'brace';

import 'brace/ext/language_tools';
import 'brace/mode/json';
import 'brace/mode/python';
import 'brace/mode/sql';
import 'brace/mode/yaml';
import 'brace/theme/textmate';
import 'brace/ext/searchbox';

defineDummySnippets('python');
defineDummySnippets('sql');
defineDummySnippets('json');
defineDummySnippets('yaml');

// By default Ace will try to load snippet files for the different modes and fail.
// We don't need them, so we use these placeholders until we define our own.
export function defineDummySnippets(mode, fn) {
ace.define(
`ace/snippets/${mode}`,
['require', 'exports', 'module'],
fn ||
((require, exports) => {
exports.snippetText = '';
exports.scope = mode;
}),
);
}

export const langTools = ace.acequire('ace/ext/language_tools');
export const snippetManager = ace.acequire('ace/snippets').snippetManager;
2 changes: 2 additions & 0 deletions client/app/components/proptypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,13 @@ export const Field = PropTypes.shape({
'file',
'select',
'content',
'json',
]).isRequired,
initialValue: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
PropTypes.bool,
PropTypes.object,
PropTypes.arrayOf(PropTypes.string),
PropTypes.arrayOf(PropTypes.number),
]),
Expand Down
6 changes: 4 additions & 2 deletions client/app/components/queries/api-key-dialog.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { getQueryDataUrl } from './index';

const ApiKeyDialog = {
template: `<div class="modal-header">
<button type="button" class="close" aria-label="Close" ng-click="$ctrl.close()"><span aria-hidden="true">&times;</span></button>
Expand Down Expand Up @@ -29,8 +31,8 @@ const ApiKeyDialog = {
this.canEdit = currentUser.id === this.resolve.query.user.id || currentUser.hasPermission('admin');
this.disableRegenerateApiKeyButton = false;
this.query = this.resolve.query;
this.csvUrlBase = `${clientConfig.basePath}api/queries/${this.resolve.query.id}/results.csv?api_key=`;
this.jsonUrlBase = `${clientConfig.basePath}api/queries/${this.resolve.query.id}/results.json?api_key=`;
this.csvUrlBase = getQueryDataUrl(this.resolve.query.id, 'csv');
this.jsonUrlBase = getQueryDataUrl(this.resolve.query.id, 'json');

this.regenerateQueryApiKey = () => {
this.disableRegenerateApiKeyButton = true;
Expand Down
16 changes: 16 additions & 0 deletions client/app/components/queries/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { clientConfig } from '@/services/auth';
import qs from 'qs';

export default {};

export const getQueryDataUrl = (queryId, format, apiKey = '', download = true) => {
const params = {};
if (apiKey) {
params.api_key = apiKey;
}
if (download === false) {
params.download = 'false';
}
const paramStr = qs.stringify(params);
return `${clientConfig.basePath}api/queries/${queryId}/results.${format}${paramStr ? '?' + paramStr : ''}`;
};
2 changes: 1 addition & 1 deletion client/app/services/query.js
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ export class Parameter {
}

if (isArray(value) && (value.length === 2)) {
value = [moment(value[0]), moment(value[1])];
value = [moment(value[0], 'YYYY-MM-DD'), moment(value[1], 'YYYY-MM-DD')];
if (value[0].isValid() && value[1].isValid()) {
this.value = {
start: value[0].format(DATETIME_FORMATS[this.type]),
Expand Down
9 changes: 5 additions & 4 deletions client/app/visualizations/EditVisualizationDialog.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ function EditVisualizationDialog({ dialog, visualization, query, queryResult })

const [saveInProgress, setSaveInProgress] = useState(false);

const { Renderer, Editor } = registeredVisualizations[type];

function onTypeChanged(newType) {
setType(newType);

Expand Down Expand Up @@ -129,8 +131,6 @@ function EditVisualizationDialog({ dialog, visualization, query, queryResult })
confirmDialogClose(nameChanged || optionsChanged).then(dialog.dismiss);
}

const { Renderer, Editor } = registeredVisualizations[type];

return (
<Modal
{...dialog.props}
Expand Down Expand Up @@ -187,11 +187,13 @@ function EditVisualizationDialog({ dialog, visualization, query, queryResult })
<Filters filters={filters} onChange={setFilters} />
<div className="scrollbox" data-test="VisualizationPreview">
<Renderer
fromEditor
data={filteredData}
options={options}
visualizationName={name}
onOptionsChange={onOptionsChanged}
context="query"
fromEditor
/>
</div>
</Grid.Col>
Expand All @@ -201,9 +203,8 @@ function EditVisualizationDialog({ dialog, visualization, query, queryResult })
}

EditVisualizationDialog.propTypes = {
dialog: DialogPropType.isRequired,
query: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
visualization: VisualizationType,
dialog: DialogPropType.isRequired,
queryResult: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
};

Expand Down
Loading