├── .ci ├── Dockerfile.cypress ├── docker-compose.ci.yml ├── docker-compose.cypress.yml ├── docker_build ├── pack └── update_version ├── .coveragerc ├── .dockerignore ├── .editorconfig ├── .github ├── ISSUE_TEMPLATE │ ├── ---bug_report.md │ └── --anything_else.md ├── PULL_REQUEST_TEMPLATE.md ├── config.yml ├── weekly-digest.yml └── workflows │ └── ci.yml ├── .gitignore ├── .nvmrc ├── .restyled.yaml ├── .yarn └── .gitignore ├── .yarnrc ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── SECURITY.md ├── bin ├── docker-entrypoint ├── flake8_tests.sh ├── get_changes.py ├── release_manager.py ├── run └── upgrade ├── client ├── .babelrc ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── app │ ├── .eslintrc.js │ ├── __tests__ │ │ ├── enzyme_setup.js │ │ └── mocks.js │ ├── assets │ │ ├── css │ │ │ └── login.css │ │ ├── fonts │ │ │ └── roboto │ │ │ │ ├── Roboto-Bold-webfont.eot │ │ │ │ ├── Roboto-Bold-webfont.svg │ │ │ │ ├── Roboto-Bold-webfont.ttf │ │ │ │ ├── Roboto-Bold-webfont.woff │ │ │ │ ├── Roboto-Light-webfont.eot │ │ │ │ ├── Roboto-Light-webfont.svg │ │ │ │ ├── Roboto-Light-webfont.ttf │ │ │ │ ├── Roboto-Light-webfont.woff │ │ │ │ ├── Roboto-Medium-webfont.eot │ │ │ │ ├── Roboto-Medium-webfont.svg │ │ │ │ ├── Roboto-Medium-webfont.ttf │ │ │ │ ├── Roboto-Medium-webfont.woff │ │ │ │ ├── Roboto-Regular-webfont.eot │ │ │ │ ├── Roboto-Regular-webfont.svg │ │ │ │ ├── Roboto-Regular-webfont.ttf │ │ │ │ ├── Roboto-Regular-webfont.woff │ │ │ │ ├── Roboto-Thin-webfont.eot │ │ │ │ ├── Roboto-Thin-webfont.svg │ │ │ │ ├── Roboto-Thin-webfont.ttf │ │ │ │ └── Roboto-Thin-webfont.woff │ │ ├── images │ │ │ ├── avatar.svg │ │ │ ├── db-logos │ │ │ │ ├── Cassandra.png │ │ │ │ ├── arangodb.png │ │ │ │ ├── athena.png │ │ │ │ ├── aws_es.png │ │ │ │ ├── axibasetsd.png │ │ │ │ ├── azure_kusto.png │ │ │ │ ├── bigquery.png │ │ │ │ ├── bigquery_gce.png │ │ │ │ ├── clickhouse.png │ │ │ │ ├── cloudwatch.png │ │ │ │ ├── cloudwatch_insights.png │ │ │ │ ├── cockroach.png │ │ │ │ ├── corporate_memory.png │ │ │ │ ├── couchbase.png │ │ │ │ ├── csv.png │ │ │ │ ├── databend.png │ │ │ │ ├── databricks.png │ │ │ │ ├── db2.png │ │ │ │ ├── dgraph.png │ │ │ │ ├── drill.png │ │ │ │ ├── druid.png │ │ │ │ ├── dynamodb_sql.png │ │ │ │ ├── elasticsearch.png │ │ │ │ ├── elasticsearch2.png │ │ │ │ ├── elasticsearch2_OpenDistroSQLElasticSearch.png │ │ │ │ ├── elasticsearch2_XPackSQLElasticSearch.png │ │ │ │ ├── exasol.png │ │ │ │ ├── excel.png │ │ │ │ ├── firebolt.png │ │ │ │ ├── google_analytics.png │ │ │ │ ├── google_spreadsheets.png │ │ │ │ ├── graphite.png │ │ │ │ ├── hive.png │ │ │ │ ├── hive_http.png │ │ │ │ ├── impala.png │ │ │ │ ├── influxdb.png │ │ │ │ ├── jirajql.png │ │ │ │ ├── json.png │ │ │ │ ├── kibana.png │ │ │ │ ├── kylin.png │ │ │ │ ├── mapd.png │ │ │ │ ├── memsql.png │ │ │ │ ├── mongodb.png │ │ │ │ ├── mssql.png │ │ │ │ ├── mssql_odbc.png │ │ │ │ ├── mysql.png │ │ │ │ ├── nz.png │ │ │ │ ├── oracle.png │ │ │ │ ├── pg.png │ │ │ │ ├── phoenix.png │ │ │ │ ├── pinot.png │ │ │ │ ├── presto.png │ │ │ │ ├── prometheus.png │ │ │ │ ├── python.png │ │ │ │ ├── qubole.png │ │ │ │ ├── rds_mysql.png │ │ │ │ ├── redshift.png │ │ │ │ ├── redshift_iam.png │ │ │ │ ├── results.png │ │ │ │ ├── rockset.png │ │ │ │ ├── salesforce.png │ │ │ │ ├── scylla.png │ │ │ │ ├── snowflake.png │ │ │ │ ├── sparql_endpoint.png │ │ │ │ ├── sqlite.png │ │ │ │ ├── treasuredata.png │ │ │ │ ├── trino.png │ │ │ │ ├── uptycs.png │ │ │ │ ├── url.png │ │ │ │ ├── vertica.png │ │ │ │ ├── yandex_appmetrika.png │ │ │ │ └── yandex_metrika.png │ │ │ ├── destinations │ │ │ │ ├── chatwork.png │ │ │ │ ├── email.png │ │ │ │ ├── hangouts_chat.png │ │ │ │ ├── hipchat.png │ │ │ │ ├── mattermost.png │ │ │ │ ├── microsoft_teams_webhook.png │ │ │ │ ├── pagerduty.png │ │ │ │ ├── slack.png │ │ │ │ └── webhook.png │ │ │ ├── favicon-16x16.png │ │ │ ├── favicon-32x32.png │ │ │ ├── favicon-96x96.png │ │ │ ├── fixtures │ │ │ │ └── map-tile.png │ │ │ ├── google_logo.svg │ │ │ ├── gravatar.png │ │ │ ├── illustrations │ │ │ │ ├── alert.svg │ │ │ │ ├── dashboard.svg │ │ │ │ ├── no-query-results.svg │ │ │ │ ├── query.svg │ │ │ │ └── readme.md │ │ │ ├── logo.png │ │ │ ├── logo_white.png │ │ │ └── redash_icon_small.png │ │ ├── less │ │ │ ├── STYLING-README.md │ │ │ ├── ant.less │ │ │ ├── inc │ │ │ │ ├── 404.less │ │ │ │ ├── ace-editor.less │ │ │ │ ├── alert.less │ │ │ │ ├── ant-variables.less │ │ │ │ ├── base.less │ │ │ │ ├── bootstrap-overrides.less │ │ │ │ ├── breadcrumb.less │ │ │ │ ├── button.less │ │ │ │ ├── carousel.less │ │ │ │ ├── chart.less │ │ │ │ ├── dropdown.less │ │ │ │ ├── edit-in-place.less │ │ │ │ ├── flex.less │ │ │ │ ├── font.less │ │ │ │ ├── form.less │ │ │ │ ├── generics.less │ │ │ │ ├── header.less │ │ │ │ ├── ie-warning.less │ │ │ │ ├── jumbotron.less │ │ │ │ ├── label.less │ │ │ │ ├── less-plugins │ │ │ │ │ └── for.less │ │ │ │ ├── list-group.less │ │ │ │ ├── list.less │ │ │ │ ├── login.less │ │ │ │ ├── media.less │ │ │ │ ├── messages.less │ │ │ │ ├── misc.less │ │ │ │ ├── mixins.less │ │ │ │ ├── modal.less │ │ │ │ ├── panel.less │ │ │ │ ├── photos.less │ │ │ │ ├── popover.less │ │ │ │ ├── pricing-table.less │ │ │ │ ├── print.less │ │ │ │ ├── profile.less │ │ │ │ ├── progress-bar.less │ │ │ │ ├── schema-browser.less │ │ │ │ ├── sidebar.less │ │ │ │ ├── table.less │ │ │ │ ├── tile.less │ │ │ │ ├── tooltips.less │ │ │ │ ├── variables.less │ │ │ │ ├── visualizations │ │ │ │ │ ├── box.less │ │ │ │ │ ├── map.less │ │ │ │ │ ├── misc.less │ │ │ │ │ └── pivot-table.less │ │ │ │ ├── well.less │ │ │ │ └── widgets.less │ │ │ ├── main.less │ │ │ ├── redash │ │ │ │ ├── css-logo.less │ │ │ │ ├── loading-indicator.less │ │ │ │ ├── query.less │ │ │ │ ├── redash-table.less │ │ │ │ └── tags-control.less │ │ │ └── server.less │ │ └── robots.txt │ ├── components │ │ ├── AceEditorInput.jsx │ │ ├── AceEditorInput.less │ │ ├── ApplicationArea │ │ │ ├── ApplicationLayout │ │ │ │ ├── DesktopNavbar.jsx │ │ │ │ ├── DesktopNavbar.less │ │ │ │ ├── MobileNavbar.jsx │ │ │ │ ├── MobileNavbar.less │ │ │ │ ├── VersionInfo.jsx │ │ │ │ ├── index.jsx │ │ │ │ └── index.less │ │ │ ├── ErrorMessage.jsx │ │ │ ├── ErrorMessage.less │ │ │ ├── ErrorMessage.test.js │ │ │ ├── ErrorMessageDetails.jsx │ │ │ ├── Router.jsx │ │ │ ├── handleNavigationIntent.js │ │ │ ├── index.jsx │ │ │ ├── navigateTo.js │ │ │ ├── routeWithApiKeySession.jsx │ │ │ └── routeWithUserSession.tsx │ │ ├── BeaconConsent.jsx │ │ ├── BigMessage.jsx │ │ ├── CodeBlock.jsx │ │ ├── CodeBlock.less │ │ ├── Collapse.jsx │ │ ├── CreateSourceDialog.jsx │ │ ├── DateInput.jsx │ │ ├── DateRangeInput.jsx │ │ ├── DateTimeInput.jsx │ │ ├── DateTimeRangeInput.jsx │ │ ├── DialogWrapper.d.ts │ │ ├── DialogWrapper.jsx │ │ ├── DynamicComponent.jsx │ │ ├── EditInPlace.jsx │ │ ├── EditParameterSettingsDialog.jsx │ │ ├── EditVisualizationButton │ │ │ ├── QueryControlDropdown.jsx │ │ │ ├── QueryResultsLink.jsx │ │ │ └── index.jsx │ │ ├── EmailSettingsWarning.jsx │ │ ├── FavoritesControl.jsx │ │ ├── Filters.jsx │ │ ├── HelpTrigger.jsx │ │ ├── HelpTrigger.less │ │ ├── InputWithCopy.jsx │ │ ├── Link.tsx │ │ ├── NoTaggedObjectsFound.jsx │ │ ├── PageHeader │ │ │ ├── index.jsx │ │ │ └── index.less │ │ ├── Paginator.jsx │ │ ├── ParameterApplyButton.jsx │ │ ├── ParameterMappingInput.jsx │ │ ├── ParameterMappingInput.less │ │ ├── ParameterValueInput.jsx │ │ ├── ParameterValueInput.less │ │ ├── Parameters.jsx │ │ ├── Parameters.less │ │ ├── PermissionsEditorDialog │ │ │ ├── index.jsx │ │ │ └── index.less │ │ ├── PlainButton.less │ │ ├── PlainButton.tsx │ │ ├── PreviewCard.jsx │ │ ├── QueryBasedParameterInput.jsx │ │ ├── QueryLink.jsx │ │ ├── QueryLink.less │ │ ├── QuerySelector.jsx │ │ ├── Resizable │ │ │ ├── index.jsx │ │ │ └── index.less │ │ ├── SelectItemsDialog.jsx │ │ ├── SelectItemsDialog.less │ │ ├── SelectWithVirtualScroll.tsx │ │ ├── SettingsWrapper.jsx │ │ ├── TagsList.less │ │ ├── TagsList.tsx │ │ ├── TimeAgo.jsx │ │ ├── Timer.jsx │ │ ├── Tooltip.tsx │ │ ├── UserGroups.jsx │ │ ├── UserGroups.less │ │ ├── admin │ │ │ ├── Layout.jsx │ │ │ ├── RQStatus.jsx │ │ │ ├── StatusBlock.jsx │ │ │ └── layout.less │ │ ├── cards-list │ │ │ ├── CardsList.less │ │ │ └── CardsList.tsx │ │ ├── dashboards │ │ │ ├── AddWidgetDialog.jsx │ │ │ ├── AutoHeightController.js │ │ │ ├── CreateDashboardDialog.jsx │ │ │ ├── DashboardGrid.jsx │ │ │ ├── EditParameterMappingsDialog.jsx │ │ │ ├── ExpandedWidgetDialog.jsx │ │ │ ├── TextboxDialog.jsx │ │ │ ├── TextboxDialog.less │ │ │ ├── dashboard-grid.less │ │ │ └── dashboard-widget │ │ │ │ ├── RestrictedWidget.jsx │ │ │ │ ├── TextboxWidget.jsx │ │ │ │ ├── VisualizationWidget.jsx │ │ │ │ ├── Widget.jsx │ │ │ │ ├── Widget.less │ │ │ │ └── index.js │ │ ├── dynamic-form │ │ │ ├── DynamicForm.jsx │ │ │ ├── DynamicForm.less │ │ │ ├── DynamicFormField.jsx │ │ │ ├── dynamicFormHelper.js │ │ │ ├── fields │ │ │ │ ├── AceEditorField.jsx │ │ │ │ ├── CheckboxField.jsx │ │ │ │ ├── ContentField.jsx │ │ │ │ ├── FileField.jsx │ │ │ │ ├── InputField.jsx │ │ │ │ ├── NumberField.jsx │ │ │ │ ├── SelectField.jsx │ │ │ │ ├── TextAreaField.jsx │ │ │ │ └── index.js │ │ │ └── getFieldLabel.js │ │ ├── dynamic-parameters │ │ │ ├── DateParameter.jsx │ │ │ ├── DateRangeParameter.jsx │ │ │ ├── DynamicButton.jsx │ │ │ ├── DynamicButton.less │ │ │ ├── DynamicDatePicker.jsx │ │ │ ├── DynamicDateRangePicker.jsx │ │ │ └── DynamicParameters.less │ │ ├── empty-state │ │ │ ├── EmptyState.d.ts │ │ │ ├── EmptyState.jsx │ │ │ └── empty-state.less │ │ ├── groups │ │ │ ├── CreateGroupDialog.jsx │ │ │ ├── DeleteGroupButton.jsx │ │ │ ├── DetailsPageSidebar.jsx │ │ │ ├── GroupName.jsx │ │ │ └── ListItemAddon.jsx │ │ ├── items-list │ │ │ ├── ItemsList.tsx │ │ │ ├── classes │ │ │ │ ├── ItemsFetcher.js │ │ │ │ ├── ItemsSource.d.ts │ │ │ │ ├── ItemsSource.js │ │ │ │ ├── Paginator.js │ │ │ │ ├── Sorter.js │ │ │ │ └── StateStorage.js │ │ │ ├── components │ │ │ │ ├── EmptyState.jsx │ │ │ │ ├── ItemsTable.jsx │ │ │ │ ├── LoadingState.jsx │ │ │ │ └── Sidebar.jsx │ │ │ └── hooks │ │ │ │ └── useItemsListExtraActions.js │ │ ├── layouts │ │ │ ├── ContentWithSidebar.jsx │ │ │ └── content-with-sidebar.less │ │ ├── proptypes.js │ │ ├── queries │ │ │ ├── AddToDashboardDialog.jsx │ │ │ ├── ApiKeyDialog │ │ │ │ ├── index.jsx │ │ │ │ └── index.less │ │ │ ├── EmbedQueryDialog.jsx │ │ │ ├── EmbedQueryDialog.less │ │ │ ├── QueryEditor │ │ │ │ ├── AutoLimitCheckbox.jsx │ │ │ │ ├── AutocompleteToggle.jsx │ │ │ │ ├── QueryEditorControls.jsx │ │ │ │ ├── QueryEditorControls.less │ │ │ │ ├── ace.js │ │ │ │ ├── index.jsx │ │ │ │ └── index.less │ │ │ ├── ScheduleDialog.css │ │ │ ├── ScheduleDialog.jsx │ │ │ ├── ScheduleDialog.test.js │ │ │ ├── SchedulePhrase.jsx │ │ │ ├── SchemaBrowser.jsx │ │ │ ├── __snapshots__ │ │ │ │ └── ScheduleDialog.test.js.snap │ │ │ ├── add-to-dashboard-dialog.less │ │ │ └── editor-components │ │ │ │ ├── databricks │ │ │ │ ├── DatabricksSchemaBrowser.jsx │ │ │ │ ├── DatabricksSchemaBrowser.less │ │ │ │ └── useDatabricksSchema.js │ │ │ │ ├── editorComponents.js │ │ │ │ └── index.js │ │ ├── query-snippets │ │ │ └── QuerySnippetDialog.jsx │ │ ├── tags-control │ │ │ ├── EditTagsDialog.jsx │ │ │ └── TagsControl.jsx │ │ └── visualizations │ │ │ ├── EditVisualizationDialog.jsx │ │ │ ├── EditVisualizationDialog.less │ │ │ ├── VisualizationName.jsx │ │ │ ├── VisualizationName.less │ │ │ ├── VisualizationRenderer.jsx │ │ │ └── visualizationComponents.jsx │ ├── config │ │ ├── antd-spinner.jsx │ │ ├── dashboard-grid-options.js │ │ └── index.js │ ├── extensions │ │ └── .gitkeep │ ├── index.html │ ├── index.js │ ├── lib │ │ ├── accessibility.ts │ │ ├── calculateTextWidth.ts │ │ ├── hooks │ │ │ ├── useFullscreenHandler.js │ │ │ ├── useImmutableCallback.js │ │ │ ├── useLazyRef.ts │ │ │ ├── useSearchResults.js │ │ │ └── useUniqueId.ts │ │ ├── localOptions.js │ │ ├── pagination │ │ │ ├── index.js │ │ │ └── paginator.js │ │ ├── queryFormat.test.js │ │ ├── queryFormat.ts │ │ ├── useQueryResultData.js │ │ └── utils.js │ ├── multi_org.html │ ├── pages │ │ ├── admin │ │ │ ├── Jobs.jsx │ │ │ ├── OutdatedQueries.jsx │ │ │ ├── SystemStatus.jsx │ │ │ └── system-status.less │ │ ├── alert │ │ │ ├── Alert.jsx │ │ │ ├── AlertEdit.jsx │ │ │ ├── AlertNew.jsx │ │ │ ├── AlertView.jsx │ │ │ └── components │ │ │ │ ├── AlertDestinations.jsx │ │ │ │ ├── AlertDestinations.less │ │ │ │ ├── Criteria.jsx │ │ │ │ ├── Criteria.less │ │ │ │ ├── HorizontalFormItem.jsx │ │ │ │ ├── MenuButton.jsx │ │ │ │ ├── NotificationTemplate.jsx │ │ │ │ ├── NotificationTemplate.less │ │ │ │ ├── Query.jsx │ │ │ │ ├── Query.less │ │ │ │ ├── Rearm.jsx │ │ │ │ ├── Rearm.less │ │ │ │ ├── Title.jsx │ │ │ │ └── Title.less │ │ ├── alerts │ │ │ └── AlertsList.jsx │ │ ├── dashboards │ │ │ ├── DashboardList.jsx │ │ │ ├── DashboardPage.jsx │ │ │ ├── DashboardPage.less │ │ │ ├── PublicDashboardPage.jsx │ │ │ ├── PublicDashboardPage.less │ │ │ ├── components │ │ │ │ ├── DashboardHeader.jsx │ │ │ │ ├── DashboardHeader.less │ │ │ │ ├── DashboardListEmptyState.tsx │ │ │ │ └── ShareDashboardDialog.jsx │ │ │ ├── dashboard-list.css │ │ │ └── hooks │ │ │ │ ├── useDashboard.js │ │ │ │ ├── useDataSources.js │ │ │ │ ├── useEditModeHandler.js │ │ │ │ └── useRefreshRateHandler.js │ │ ├── data-sources │ │ │ ├── DataSourcesList.jsx │ │ │ └── EditDataSource.jsx │ │ ├── destinations │ │ │ ├── DestinationsList.jsx │ │ │ └── EditDestination.jsx │ │ ├── groups │ │ │ ├── GroupDataSources.jsx │ │ │ ├── GroupMembers.jsx │ │ │ └── GroupsList.jsx │ │ ├── home │ │ │ ├── Home.jsx │ │ │ ├── Home.less │ │ │ └── components │ │ │ │ └── FavoritesList.jsx │ │ ├── index.js │ │ ├── queries-list │ │ │ ├── QueriesList.jsx │ │ │ ├── QueriesListEmptyState.jsx │ │ │ └── queries-list.css │ │ ├── queries │ │ │ ├── QuerySource.jsx │ │ │ ├── QuerySource.less │ │ │ ├── QueryView.jsx │ │ │ ├── QueryView.less │ │ │ ├── VisualizationEmbed.jsx │ │ │ ├── components │ │ │ │ ├── QueryExecutionMetadata.jsx │ │ │ │ ├── QueryExecutionMetadata.less │ │ │ │ ├── QueryExecutionStatus.jsx │ │ │ │ ├── QueryMetadata.jsx │ │ │ │ ├── QueryMetadata.less │ │ │ │ ├── QueryPageHeader.jsx │ │ │ │ ├── QueryPageHeader.less │ │ │ │ ├── QuerySourceAlerts.jsx │ │ │ │ ├── QuerySourceAlerts.less │ │ │ │ ├── QuerySourceDropdown.jsx │ │ │ │ ├── QuerySourceDropdownItem.jsx │ │ │ │ ├── QuerySourceTypeIcon.jsx │ │ │ │ ├── QueryViewButton.jsx │ │ │ │ ├── QueryVisualizationTabs.jsx │ │ │ │ ├── QueryVisualizationTabs.less │ │ │ │ └── wrapQueryPage.jsx │ │ │ └── hooks │ │ │ │ ├── useAddNewParameterDialog.js │ │ │ │ ├── useAddToDashboardDialog.js │ │ │ │ ├── useAddVisualizationDialog.js │ │ │ │ ├── useApiKeyDialog.js │ │ │ │ ├── useArchiveQuery.jsx │ │ │ │ ├── useAutoLimitFlags.js │ │ │ │ ├── useAutocompleteFlags.js │ │ │ │ ├── useDataSourceSchema.js │ │ │ │ ├── useDeleteVisualization.js │ │ │ │ ├── useDuplicateQuery.js │ │ │ │ ├── useEditScheduleDialog.js │ │ │ │ ├── useEditVisualizationDialog.js │ │ │ │ ├── useEmbedDialog.js │ │ │ │ ├── usePermissionsEditorDialog.js │ │ │ │ ├── usePublishQuery.js │ │ │ │ ├── useQuery.js │ │ │ │ ├── useQueryDataSources.js │ │ │ │ ├── useQueryExecute.js │ │ │ │ ├── useQueryFlags.js │ │ │ │ ├── useQueryParameters.js │ │ │ │ ├── useRenameQuery.js │ │ │ │ ├── useUnpublishQuery.js │ │ │ │ ├── useUnsavedChangesAlert.js │ │ │ │ ├── useUpdateQuery.jsx │ │ │ │ ├── useUpdateQueryDescription.js │ │ │ │ ├── useUpdateQueryTags.js │ │ │ │ └── useVisualizationTabHandler.js │ │ ├── query-snippets │ │ │ ├── QuerySnippetsList.jsx │ │ │ └── QuerySnippetsList.less │ │ ├── settings │ │ │ ├── OrganizationSettings.jsx │ │ │ ├── components │ │ │ │ ├── AuthSettings │ │ │ │ │ ├── GoogleLoginSettings.jsx │ │ │ │ │ ├── PasswordLoginSettings.jsx │ │ │ │ │ ├── SAMLSettings.jsx │ │ │ │ │ └── index.jsx │ │ │ │ ├── GeneralSettings │ │ │ │ │ ├── BeaconConsentSettings.jsx │ │ │ │ │ ├── FeatureFlagsSettings.jsx │ │ │ │ │ ├── FormatSettings.jsx │ │ │ │ │ ├── PlotlySettings.jsx │ │ │ │ │ └── index.jsx │ │ │ │ └── prop-types.js │ │ │ └── hooks │ │ │ │ └── useOrganizationSettings.js │ │ └── users │ │ │ ├── UserProfile.jsx │ │ │ ├── UsersList.jsx │ │ │ ├── components │ │ │ ├── ApiKeyForm.jsx │ │ │ ├── CreateUserDialog.jsx │ │ │ ├── EditableUserProfile.jsx │ │ │ ├── PasswordForm │ │ │ │ ├── ChangePasswordDialog.jsx │ │ │ │ ├── PasswordLinkAlert.jsx │ │ │ │ ├── PasswordResetForm.jsx │ │ │ │ ├── ResendInvitationForm.jsx │ │ │ │ └── index.jsx │ │ │ ├── ReadOnlyUserProfile.jsx │ │ │ ├── ReadOnlyUserProfile.test.js │ │ │ ├── ToggleUserForm.jsx │ │ │ ├── UserInfoForm.jsx │ │ │ └── __snapshots__ │ │ │ │ └── ReadOnlyUserProfile.test.js.snap │ │ │ ├── hooks │ │ │ └── useUserGroups.js │ │ │ └── settings.less │ ├── redash-font │ │ ├── fonts │ │ │ ├── redash-icons.eot │ │ │ ├── redash-icons.svg │ │ │ ├── redash-icons.ttf │ │ │ └── redash-icons.woff │ │ ├── style.less │ │ └── variables.less │ ├── services │ │ ├── KeyboardShortcuts.js │ │ ├── alert-subscription.js │ │ ├── alert.js │ │ ├── auth.js │ │ ├── auth.test.js │ │ ├── axios.js │ │ ├── dashboard.js │ │ ├── data-source.js │ │ ├── databricks-data-source.js │ │ ├── destination.js │ │ ├── getTags.js │ │ ├── group.js │ │ ├── location.js │ │ ├── notification.d.ts │ │ ├── notification.js │ │ ├── notifications.js │ │ ├── offline-listener.js │ │ ├── organizationSettings.js │ │ ├── organizationStatus.js │ │ ├── parameters │ │ │ ├── DateParameter.js │ │ │ ├── DateRangeParameter.js │ │ │ ├── EnumParameter.js │ │ │ ├── NumberParameter.js │ │ │ ├── Parameter.js │ │ │ ├── QueryBasedDropdownParameter.js │ │ │ ├── TextParameter.js │ │ │ ├── index.js │ │ │ └── tests │ │ │ │ ├── DateParameter.test.js │ │ │ │ ├── DateRangeParameter.test.js │ │ │ │ ├── EnumParameter.test.js │ │ │ │ ├── NumberParameter.test.js │ │ │ │ ├── Parameter.test.js │ │ │ │ ├── QueryBasedDropdownParameter.test.js │ │ │ │ └── TextParameter.test.js │ │ ├── policy │ │ │ ├── DefaultPolicy.js │ │ │ └── index.js │ │ ├── query-result.js │ │ ├── query-snippet.js │ │ ├── query.js │ │ ├── recordEvent.js │ │ ├── resizeObserver.js │ │ ├── restoreSession.jsx │ │ ├── routes.ts │ │ ├── sanitize.js │ │ ├── settingsMenu.js │ │ ├── settingsMenu.test.js │ │ ├── url.js │ │ ├── user.js │ │ ├── utils.js │ │ ├── visualization.js │ │ └── widget.js │ ├── styles │ │ ├── formStyle.less │ │ └── formStyle.ts │ ├── unsupported.html │ ├── unsupportedRedirect.js │ └── version.json ├── cypress │ ├── .eslintrc.js │ ├── cypress.js │ ├── integration │ │ ├── alert │ │ │ ├── create_alert_spec.js │ │ │ ├── edit_alert_spec.js │ │ │ └── view_alert_spec.js │ │ ├── dashboard │ │ │ ├── dashboard_spec.js │ │ │ ├── dashboard_tags_spec.js │ │ │ ├── filters_spec.js │ │ │ ├── grid_compliant_widgets_spec.js │ │ │ ├── parameter_spec.js │ │ │ ├── sharing_spec.js │ │ │ ├── textbox_spec.js │ │ │ └── widget_spec.js │ │ ├── data-source │ │ │ ├── create_data_source_spec.js │ │ │ └── edit_data_source_spec.js │ │ ├── destination │ │ │ └── create_destination_spec.js │ │ ├── embed │ │ │ └── share_embed_spec.js │ │ ├── group │ │ │ ├── edit_group_spec.js │ │ │ └── group_list_spec.js │ │ ├── query-snippets │ │ │ └── create_query_snippet_spec.js │ │ ├── query │ │ │ ├── create_query_spec.js │ │ │ ├── filters_spec.js │ │ │ ├── parameter_spec.js │ │ │ └── query_tags_spec.js │ │ ├── settings │ │ │ ├── organization_settings_spec.js │ │ │ └── settings_tabs_spec.js │ │ ├── user │ │ │ ├── create_user_spec.js │ │ │ ├── edit_profile_spec.js │ │ │ ├── login_spec.js │ │ │ ├── logout_spec.js │ │ │ └── user_list_spec.js │ │ └── visualizations │ │ │ ├── box_plot_spec.js │ │ │ ├── chart_spec.js │ │ │ ├── choropleth_spec.js │ │ │ ├── cohort_spec.js │ │ │ ├── counter_spec.js │ │ │ ├── edit_visualization_dialog_spec.js │ │ │ ├── funnel_spec.js │ │ │ ├── map_spec.js │ │ │ ├── pivot_spec.js │ │ │ ├── sankey_sunburst_spec.js │ │ │ ├── table │ │ │ ├── .mocks │ │ │ │ ├── all-cell-types.js │ │ │ │ ├── large-dataset.js │ │ │ │ ├── multi-column-sort.js │ │ │ │ └── search-in-data.js │ │ │ └── table_spec.js │ │ │ └── word_cloud_spec.js │ ├── plugins │ │ └── index.js │ ├── seed-data.js │ ├── support │ │ ├── commands.js │ │ ├── dashboard │ │ │ └── index.js │ │ ├── index.js │ │ ├── parameters.js │ │ ├── redash-api │ │ │ └── index.js │ │ ├── tags │ │ │ └── index.js │ │ └── visualizations │ │ │ ├── chart.js │ │ │ └── table.js │ └── tsconfig.json ├── prettier.config.js └── tsconfig.json ├── cypress.json ├── docker-compose.yml ├── manage.py ├── migrations ├── 0001_warning.py ├── README ├── alembic.ini ├── env.py ├── script.py.mako └── versions │ ├── 0ec979123ba4_.py │ ├── 0f740a081d20_inline_tags.py │ ├── 1daa601d3ae5_add_columns_for_disabled_users.py │ ├── 5ec5c84ba61e_.py │ ├── 640888ce445d_.py │ ├── 65fc9ede4746_add_is_draft_status_to_queries_and_.py │ ├── 6b5be7e0a0ef_.py │ ├── 71477dadd6ef_favorites_unique_constraint.py │ ├── 73beceabb948_bring_back_null_schedule.py │ ├── 7671dca4e604_.py │ ├── 89bc7873a3e0_fix_multiple_heads.py │ ├── 969126bd800f_.py │ ├── 98af61feea92_add_encrypted_options_to_data_sources.py │ ├── a92d92aa678e_inline_tags.py │ ├── d1eae8b9893e_.py │ ├── d4c798575877_create_favorites.py │ ├── d7d747033183_encrypt_alert_destinations.py │ ├── e5c7a4e2df4d_remove_query_tracker_keys.py │ ├── e7004224f284_add_org_id_to_favorites.py │ ├── e7f8a917aa8e_add_user_details_json_column.py │ └── fd4fc850d7ea_.py ├── netlify.toml ├── package.json ├── pip.conf ├── pytest.ini ├── redash ├── __init__.py ├── app.py ├── authentication │ ├── __init__.py │ ├── account.py │ ├── google_oauth.py │ ├── jwt_auth.py │ ├── ldap_auth.py │ ├── org_resolving.py │ ├── remote_user_auth.py │ └── saml_auth.py ├── cli │ ├── __init__.py │ ├── data_sources.py │ ├── database.py │ ├── groups.py │ ├── organization.py │ ├── queries.py │ ├── rq.py │ └── users.py ├── destinations │ ├── __init__.py │ ├── chatwork.py │ ├── email.py │ ├── hangoutschat.py │ ├── hipchat.py │ ├── mattermost.py │ ├── microsoft_teams_webhook.py │ ├── pagerduty.py │ ├── slack.py │ └── webhook.py ├── handlers │ ├── __init__.py │ ├── admin.py │ ├── alerts.py │ ├── api.py │ ├── authentication.py │ ├── base.py │ ├── dashboards.py │ ├── data_sources.py │ ├── databricks.py │ ├── destinations.py │ ├── embed.py │ ├── events.py │ ├── favorites.py │ ├── groups.py │ ├── organization.py │ ├── permissions.py │ ├── queries.py │ ├── query_results.py │ ├── query_snippets.py │ ├── settings.py │ ├── setup.py │ ├── static.py │ ├── users.py │ ├── visualizations.py │ ├── webpack.py │ └── widgets.py ├── metrics │ ├── __init__.py │ ├── database.py │ └── request.py ├── models │ ├── __init__.py │ ├── base.py │ ├── changes.py │ ├── mixins.py │ ├── organizations.py │ ├── parameterized_query.py │ ├── types.py │ └── users.py ├── monitor.py ├── permissions.py ├── query_runner │ ├── __init__.py │ ├── amazon_elasticsearch.py │ ├── arango.py │ ├── athena.py │ ├── axibase_tsd.py │ ├── azure_kusto.py │ ├── big_query.py │ ├── big_query_gce.py │ ├── cass.py │ ├── clickhouse.py │ ├── cloudwatch.py │ ├── cloudwatch_insights.py │ ├── corporate_memory.py │ ├── couchbase.py │ ├── csv.py │ ├── databend.py │ ├── databricks.py │ ├── db2.py │ ├── dgraph.py │ ├── drill.py │ ├── druid.py │ ├── dynamodb_sql.py │ ├── elasticsearch.py │ ├── elasticsearch2.py │ ├── exasol.py │ ├── excel.py │ ├── files │ │ ├── rds-combined-ca-bundle.pem │ │ └── redshift-ca-bundle.crt │ ├── firebolt.py │ ├── google_analytics.py │ ├── google_spanner.py │ ├── google_spreadsheets.py │ ├── graphite.py │ ├── hive_ds.py │ ├── impala_ds.py │ ├── influx_db.py │ ├── jql.py │ ├── json_ds.py │ ├── kylin.py │ ├── mapd.py │ ├── memsql_ds.py │ ├── mongodb.py │ ├── mssql.py │ ├── mssql_odbc.py │ ├── mysql.py │ ├── nz.py │ ├── oracle.py │ ├── pg.py │ ├── phoenix.py │ ├── pinot.py │ ├── presto.py │ ├── prometheus.py │ ├── python.py │ ├── qubole.py │ ├── query_results.py │ ├── rockset.py │ ├── salesforce.py │ ├── script.py │ ├── snowflake.py │ ├── sparql_endpoint.py │ ├── sqlite.py │ ├── treasuredata.py │ ├── trino.py │ ├── uptycs.py │ ├── url.py │ ├── vertica.py │ └── yandex_metrica.py ├── security.py ├── serializers │ ├── __init__.py │ └── query_result.py ├── settings │ ├── __init__.py │ ├── dynamic_settings.py │ ├── helpers.py │ └── organization.py ├── tasks │ ├── __init__.py │ ├── alerts.py │ ├── databricks.py │ ├── failure_report.py │ ├── general.py │ ├── queries │ │ ├── __init__.py │ │ ├── execution.py │ │ └── maintenance.py │ ├── schedule.py │ └── worker.py ├── templates │ ├── _includes │ │ ├── signed_out_tail.html │ │ └── tail.html │ ├── emails │ │ ├── failures.html │ │ ├── failures.txt │ │ ├── invite.html │ │ ├── invite.txt │ │ ├── layout.html │ │ ├── reset.html │ │ ├── reset.txt │ │ ├── reset_disabled.html │ │ ├── reset_disabled.txt │ │ ├── verify.html │ │ └── verify.txt │ ├── error.html │ ├── forgot.html │ ├── invite.html │ ├── layouts │ │ └── signed_out.html │ ├── login.html │ ├── reset.html │ ├── setup.html │ └── verify.html ├── utils │ ├── __init__.py │ ├── configuration.py │ ├── human_time.py │ ├── requests_session.py │ └── sentry.py ├── version_check.py ├── worker.py └── wsgi.py ├── requirements.txt ├── requirements_all_ds.txt ├── requirements_dev.txt ├── requirements_oracle_ds.txt ├── scripts └── README.md ├── setup.cfg ├── setup └── README.md ├── tests ├── __init__.py ├── factories.py ├── handlers │ ├── __init__.py │ ├── test_alerts.py │ ├── test_authentication.py │ ├── test_dashboards.py │ ├── test_data_sources.py │ ├── test_destinations.py │ ├── test_embed.py │ ├── test_favorites.py │ ├── test_groups.py │ ├── test_paginate.py │ ├── test_permissions.py │ ├── test_queries.py │ ├── test_query_results.py │ ├── test_query_snippets.py │ ├── test_settings.py │ ├── test_users.py │ ├── test_visualizations.py │ └── test_widgets.py ├── metrics │ ├── __init__.py │ ├── test_database.py │ └── test_request.py ├── models │ ├── __init__.py │ ├── test_alerts.py │ ├── test_api_keys.py │ ├── test_changes.py │ ├── test_dashboards.py │ ├── test_data_sources.py │ ├── test_parameterized_query.py │ ├── test_permissions.py │ ├── test_queries.py │ ├── test_query_results.py │ └── test_users.py ├── query_runner │ ├── __init__.py │ ├── test_athena.py │ ├── test_basesql_queryrunner.py │ ├── test_cass.py │ ├── test_clickhouse.py │ ├── test_databricks.py │ ├── test_drill.py │ ├── test_elasticsearch2.py │ ├── test_google_spreadsheets.py │ ├── test_http.py │ ├── test_jql.py │ ├── test_mongodb.py │ ├── test_oracle.py │ ├── test_pg.py │ ├── test_prometheus.py │ ├── test_python.py │ ├── test_query_results.py │ ├── test_script.py │ └── test_utils.py ├── serializers │ ├── __init__.py │ └── test_query_results.py ├── tasks │ ├── __init__.py │ ├── test_alerts.py │ ├── test_empty_schedule.py │ ├── test_failure_report.py │ ├── test_queries.py │ ├── test_refresh_queries.py │ ├── test_refresh_schemas.py │ ├── test_schedule.py │ └── test_worker.py ├── test_authentication.py ├── test_cli.py ├── test_configuration.py ├── test_handlers.py ├── test_migrations.py ├── test_models.py ├── test_permissions.py ├── test_utils.py └── utils │ └── __init__.py ├── viz-lib ├── .babelrc ├── .gitignore ├── CHANGELOG.md ├── README.md ├── __tests__ │ ├── enzyme_setup.js │ └── mocks.js ├── package.json ├── prettier.config.js ├── src │ ├── components │ │ ├── ColorPicker │ │ │ ├── Input.tsx │ │ │ ├── Label.tsx │ │ │ ├── Swatch.tsx │ │ │ ├── index.less │ │ │ ├── index.tsx │ │ │ ├── input.less │ │ │ ├── label.less │ │ │ ├── swatch.less │ │ │ └── utils.ts │ │ ├── ErrorBoundary.tsx │ │ ├── HtmlContent.tsx │ │ ├── TextAlignmentSelect │ │ │ ├── index.less │ │ │ └── index.tsx │ │ ├── json-view-interactive │ │ │ ├── JsonViewInteractive.tsx │ │ │ └── json-view-interactive.less │ │ ├── sortable │ │ │ ├── index.tsx │ │ │ └── style.less │ │ └── visualizations │ │ │ └── editor │ │ │ ├── ContextHelp.tsx │ │ │ ├── Section.less │ │ │ ├── Section.tsx │ │ │ ├── Switch.less │ │ │ ├── Switch.tsx │ │ │ ├── TextArea.less │ │ │ ├── TextArea.tsx │ │ │ ├── context-help.less │ │ │ ├── control-label.less │ │ │ ├── createTabbedEditor.tsx │ │ │ ├── index.ts │ │ │ └── withControlLabel.tsx │ ├── index.ts │ ├── lib │ │ ├── chooseTextColorForBackground.ts │ │ ├── hooks │ │ │ └── useMemoWithDeepCompare.ts │ │ ├── referenceCountingCache.ts │ │ ├── utils.ts │ │ └── value-format.tsx │ ├── services │ │ ├── resizeObserver.ts │ │ └── sanitize.ts │ └── visualizations │ │ ├── ColorPalette.ts │ │ ├── Editor.tsx │ │ ├── Renderer.tsx │ │ ├── box-plot │ │ ├── Editor.tsx │ │ ├── Renderer.tsx │ │ ├── d3box.ts │ │ ├── index.ts │ │ └── renderer.less │ │ ├── chart │ │ ├── Editor │ │ │ ├── AxisSettings.tsx │ │ │ ├── ChartTypeSelect.tsx │ │ │ ├── ColorsSettings.test.tsx │ │ │ ├── ColorsSettings.tsx │ │ │ ├── ColumnMappingSelect.tsx │ │ │ ├── CustomChartSettings.tsx │ │ │ ├── DataLabelsSettings.test.tsx │ │ │ ├── DataLabelsSettings.tsx │ │ │ ├── DefaultColorsSettings.tsx │ │ │ ├── GeneralSettings.test.tsx │ │ │ ├── GeneralSettings.tsx │ │ │ ├── HeatmapColorsSettings.tsx │ │ │ ├── PieColorsSettings.tsx │ │ │ ├── SeriesSettings.test.tsx │ │ │ ├── SeriesSettings.tsx │ │ │ ├── XAxisSettings.test.tsx │ │ │ ├── XAxisSettings.tsx │ │ │ ├── YAxisSettings.test.tsx │ │ │ ├── YAxisSettings.tsx │ │ │ ├── __snapshots__ │ │ │ │ ├── ColorsSettings.test.tsx.snap │ │ │ │ ├── DataLabelsSettings.test.tsx.snap │ │ │ │ ├── GeneralSettings.test.tsx.snap │ │ │ │ ├── SeriesSettings.test.tsx.snap │ │ │ │ ├── XAxisSettings.test.tsx.snap │ │ │ │ └── YAxisSettings.test.tsx.snap │ │ │ ├── editor.less │ │ │ ├── index.test.tsx │ │ │ └── index.tsx │ │ ├── Renderer │ │ │ ├── CustomPlotlyChart.tsx │ │ │ ├── PlotlyChart.tsx │ │ │ ├── index.tsx │ │ │ ├── initChart.ts │ │ │ └── renderer.less │ │ ├── fixtures │ │ │ └── getChartData │ │ │ │ ├── multiple-series-grouped.json │ │ │ │ ├── multiple-series-multiple-y.json │ │ │ │ ├── multiple-series-sorted.json │ │ │ │ └── single-series.json │ │ ├── getChartData.test.ts │ │ ├── getChartData.ts │ │ ├── getOptions.ts │ │ ├── index.ts │ │ └── plotly │ │ │ ├── customChartUtils.ts │ │ │ ├── fixtures │ │ │ ├── prepareData │ │ │ │ ├── bar │ │ │ │ │ ├── default.json │ │ │ │ │ ├── normalized.json │ │ │ │ │ └── stacked.json │ │ │ │ ├── box │ │ │ │ │ ├── default.json │ │ │ │ │ └── with-points.json │ │ │ │ ├── bubble │ │ │ │ │ └── default.json │ │ │ │ ├── heatmap │ │ │ │ │ ├── default.json │ │ │ │ │ ├── reversed.json │ │ │ │ │ ├── sorted-reversed.json │ │ │ │ │ ├── sorted.json │ │ │ │ │ └── with-labels.json │ │ │ │ ├── line-area │ │ │ │ │ ├── default.json │ │ │ │ │ ├── keep-missing-values.json │ │ │ │ │ ├── missing-values-0.json │ │ │ │ │ ├── normalized-stacked.json │ │ │ │ │ ├── normalized.json │ │ │ │ │ └── stacked.json │ │ │ │ ├── pie │ │ │ │ │ ├── custom-tooltip.json │ │ │ │ │ ├── default.json │ │ │ │ │ ├── without-labels.json │ │ │ │ │ └── without-x.json │ │ │ │ └── scatter │ │ │ │ │ ├── default.json │ │ │ │ │ └── without-labels.json │ │ │ └── prepareLayout │ │ │ │ ├── box-single-axis.json │ │ │ │ ├── box-with-second-axis.json │ │ │ │ ├── default-single-axis.json │ │ │ │ ├── default-with-second-axis.json │ │ │ │ ├── default-with-stacking.json │ │ │ │ ├── default-without-legend.json │ │ │ │ ├── pie-multiple-series.json │ │ │ │ ├── pie-without-annotations.json │ │ │ │ └── pie.json │ │ │ ├── index.ts │ │ │ ├── prepareData.test.ts │ │ │ ├── prepareData.ts │ │ │ ├── prepareDefaultData.ts │ │ │ ├── prepareHeatmapData.ts │ │ │ ├── prepareLayout.test.ts │ │ │ ├── prepareLayout.ts │ │ │ ├── preparePieData.ts │ │ │ ├── updateAxes.ts │ │ │ ├── updateChartSize.ts │ │ │ ├── updateData.ts │ │ │ └── utils.ts │ │ ├── choropleth │ │ ├── ColorPalette.ts │ │ ├── Editor │ │ │ ├── BoundsSettings.tsx │ │ │ ├── ColorsSettings.tsx │ │ │ ├── FormatSettings.tsx │ │ │ ├── GeneralSettings.tsx │ │ │ ├── index.ts │ │ │ └── utils.ts │ │ ├── Renderer │ │ │ ├── Legend.tsx │ │ │ ├── index.tsx │ │ │ ├── initChoropleth.tsx │ │ │ ├── renderer.less │ │ │ └── utils.ts │ │ ├── getOptions.ts │ │ ├── hooks │ │ │ └── useLoadGeoJson.ts │ │ ├── index.ts │ │ └── maps │ │ │ ├── convert-projection.ts │ │ │ ├── countries.geo.json │ │ │ ├── japan.prefectures.geo.json │ │ │ ├── usa-albers.geo.json │ │ │ └── usa.geo.json │ │ ├── cohort │ │ ├── Cornelius.tsx │ │ ├── Editor │ │ │ ├── AppearanceSettings.tsx │ │ │ ├── ColorsSettings.tsx │ │ │ ├── ColumnsSettings.tsx │ │ │ ├── OptionsSettings.tsx │ │ │ └── index.ts │ │ ├── Renderer.tsx │ │ ├── cornelius.less │ │ ├── getOptions.ts │ │ ├── index.ts │ │ ├── prepareData.ts │ │ └── renderer.less │ │ ├── counter │ │ ├── Editor │ │ │ ├── FormatSettings.tsx │ │ │ ├── GeneralSettings.tsx │ │ │ └── index.ts │ │ ├── Renderer.tsx │ │ ├── index.ts │ │ ├── render.less │ │ ├── utils.test.ts │ │ └── utils.ts │ │ ├── details │ │ ├── DetailsRenderer.tsx │ │ ├── details.less │ │ └── index.ts │ │ ├── funnel │ │ ├── Editor │ │ │ ├── AppearanceSettings.tsx │ │ │ ├── GeneralSettings.tsx │ │ │ └── index.ts │ │ ├── Renderer │ │ │ ├── FunnelBar.tsx │ │ │ ├── funnel-bar.less │ │ │ ├── index.less │ │ │ ├── index.tsx │ │ │ └── prepareData.ts │ │ ├── getOptions.ts │ │ └── index.ts │ │ ├── index.ts │ │ ├── map │ │ ├── Editor │ │ │ ├── FormatSettings.tsx │ │ │ ├── GeneralSettings.tsx │ │ │ ├── GroupsSettings.tsx │ │ │ ├── StyleSettings.tsx │ │ │ └── index.ts │ │ ├── Renderer.tsx │ │ ├── getOptions.ts │ │ ├── index.ts │ │ ├── initMap.ts │ │ └── prepareData.ts │ │ ├── pivot │ │ ├── Editor.tsx │ │ ├── Renderer.tsx │ │ ├── index.ts │ │ └── renderer.less │ │ ├── prop-types.ts │ │ ├── registeredVisualizations.ts │ │ ├── sankey │ │ ├── Editor.tsx │ │ ├── Renderer.tsx │ │ ├── d3sankey.ts │ │ ├── index.ts │ │ ├── initSankey.ts │ │ └── renderer.less │ │ ├── sunburst │ │ ├── Editor.tsx │ │ ├── Renderer.tsx │ │ ├── index.ts │ │ ├── initSunburst.ts │ │ └── renderer.less │ │ ├── table │ │ ├── Editor │ │ │ ├── ColumnEditor.tsx │ │ │ ├── ColumnsSettings.test.tsx │ │ │ ├── ColumnsSettings.tsx │ │ │ ├── GridSettings.test.tsx │ │ │ ├── GridSettings.tsx │ │ │ ├── __snapshots__ │ │ │ │ ├── ColumnsSettings.test.tsx.snap │ │ │ │ └── GridSettings.test.tsx.snap │ │ │ ├── editor.less │ │ │ └── index.tsx │ │ ├── Renderer.tsx │ │ ├── columns │ │ │ ├── __snapshots__ │ │ │ │ ├── boolean.test.tsx.snap │ │ │ │ ├── datetime.test.tsx.snap │ │ │ │ ├── image.test.tsx.snap │ │ │ │ ├── link.test.tsx.snap │ │ │ │ ├── number.test.tsx.snap │ │ │ │ └── text.test.tsx.snap │ │ │ ├── boolean.test.tsx │ │ │ ├── boolean.tsx │ │ │ ├── datetime.test.tsx │ │ │ ├── datetime.tsx │ │ │ ├── image.test.tsx │ │ │ ├── image.tsx │ │ │ ├── index.ts │ │ │ ├── json.tsx │ │ │ ├── link.test.tsx │ │ │ ├── link.tsx │ │ │ ├── number.test.tsx │ │ │ ├── number.tsx │ │ │ ├── text.test.tsx │ │ │ └── text.tsx │ │ ├── getOptions.ts │ │ ├── index.ts │ │ ├── renderer.less │ │ └── utils.tsx │ │ ├── variables.less │ │ ├── visualizationsSettings.tsx │ │ └── word-cloud │ │ ├── Editor.tsx │ │ ├── Renderer.tsx │ │ ├── index.ts │ │ └── renderer.less ├── tsconfig.json ├── webpack.config.js └── yarn.lock ├── webpack.config.js ├── worker.conf └── yarn.lock /.ci/Dockerfile.cypress: -------------------------------------------------------------------------------- 1 | FROM cypress/browsers:node14.17.0-chrome91-ff89 2 | 3 | ENV APP /usr/src/app 4 | WORKDIR $APP 5 | 6 | COPY package.json yarn.lock .yarnrc $APP/ 7 | COPY viz-lib $APP/viz-lib 8 | RUN npm install yarn@1.22.10 -g && yarn --frozen-lockfile --network-concurrency 1 > /dev/null 9 | 10 | COPY . $APP 11 | 12 | RUN ./node_modules/.bin/cypress verify 13 | -------------------------------------------------------------------------------- /.ci/docker-compose.ci.yml: -------------------------------------------------------------------------------- 1 | version: '2.2' 2 | services: 3 | redash: 4 | build: ../ 5 | command: manage version 6 | depends_on: 7 | - postgres 8 | - redis 9 | ports: 10 | - "5000:5000" 11 | environment: 12 | PYTHONUNBUFFERED: 0 13 | REDASH_LOG_LEVEL: "INFO" 14 | REDASH_REDIS_URL: "redis://redis:6379/0" 15 | REDASH_DATABASE_URL: "postgresql://postgres@postgres/postgres" 16 | REDASH_COOKIE_SECRET: "2H9gNG9obnAQ9qnR9BDTQUph6CbXKCzF" 17 | redis: 18 | image: redis:3.0-alpine 19 | restart: unless-stopped 20 | postgres: 21 | image: postgres:9.5.6-alpine 22 | command: "postgres -c fsync=off -c full_page_writes=off -c synchronous_commit=OFF" 23 | restart: unless-stopped 24 | -------------------------------------------------------------------------------- /.ci/docker_build: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | VERSION=$(jq -r .version package.json) 4 | VERSION_TAG=$VERSION.b$CIRCLE_BUILD_NUM 5 | 6 | export DOCKER_BUILDKIT=1 7 | export COMPOSE_DOCKER_CLI_BUILD=1 8 | 9 | docker login -u $DOCKER_USER -p $DOCKER_PASS 10 | 11 | if [ $CIRCLE_BRANCH = master ] || [ $CIRCLE_BRANCH = preview-image ] 12 | then 13 | docker build --build-arg skip_dev_deps=true -t redash/redash:preview -t redash/preview:$VERSION_TAG . 14 | docker push redash/redash:preview 15 | docker push redash/preview:$VERSION_TAG 16 | else 17 | docker build --build-arg skip_dev_deps=true -t redash/redash:$VERSION_TAG . 18 | docker push redash/redash:$VERSION_TAG 19 | fi 20 | 21 | echo "Built: $VERSION_TAG" 22 | -------------------------------------------------------------------------------- /.ci/pack: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | NAME=redash 3 | VERSION=$(jq -r .version package.json) 4 | FULL_VERSION=$VERSION+b$CIRCLE_BUILD_NUM 5 | FILENAME=$NAME.$FULL_VERSION.tar.gz 6 | 7 | mkdir -p /tmp/artifacts/ 8 | 9 | tar -zcv -f /tmp/artifacts/$FILENAME --exclude=".git" --exclude="optipng*" --exclude="cypress" --exclude="*.pyc" --exclude="*.pyo" --exclude="venv" * 10 | -------------------------------------------------------------------------------- /.ci/update_version: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | VERSION=$(jq -r .version package.json) 3 | FULL_VERSION=$VERSION+b$CIRCLE_BUILD_NUM 4 | 5 | sed -ri "s/^__version__ = '([A-Za-z0-9.-]*)'/__version__ = '$FULL_VERSION'/" redash/__init__.py 6 | sed -i "s/dev/$CIRCLE_SHA1/" client/app/version.json 7 | -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | branch = True 3 | source = redash 4 | 5 | [report] 6 | omit = 7 | */settings.py 8 | */python?.?/* 9 | show_missing = True 10 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | client/.tmp/ 2 | client/dist/ 3 | node_modules/ 4 | viz-lib/node_modules/ 5 | .tmp/ 6 | .venv/ 7 | venv/ 8 | .git/ 9 | /.codeclimate.yml 10 | /.coverage 11 | /coverage.xml 12 | /.circleci/ 13 | /.github/ 14 | /netlify.toml 15 | /setup/ 16 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | trim_trailing_whitespace = true 7 | 8 | [*.py] 9 | indent_style = space 10 | indent_size = 4 11 | 12 | [*.{js,jsx,css,less,html}] 13 | indent_style = space 14 | indent_size = 2 15 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/--anything_else.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F4A1Anything else" 3 | about: "For help, support, features & ideas - please use Discussions \U0001F46B " 4 | labels: "Support Question" 5 | --- 6 | 7 | We use GitHub only for bug reports 🐛 8 | 9 | Anything else should be a discussion: https://github.com/getredash/redash/discussions/ 👫 10 | 11 | 🚨For support, help & questions use https://github.com/getredash/redash/discussions/categories/q-a 12 | 💡For feature requests & ideas use https://github.com/getredash/redash/discussions/categories/ideas 13 | 14 | Alternatively, check out these resources below. Thanks! 😁. 15 | 16 | - [Discussions](https://github.com/getredash/redash/discussions/) 17 | - [Knowledge Base](https://redash.io/help) 18 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## What type of PR is this? 2 | 3 | 4 | - [ ] Refactor 5 | - [ ] Feature 6 | - [ ] Bug Fix 7 | - [ ] New Query Runner (Data Source) 8 | - [ ] New Alert Destination 9 | - [ ] Other 10 | 11 | ## Description 12 | 13 | 14 | ## How is this tested? 15 | 16 | - [ ] Unit tests (pytest, jest) 17 | - [ ] E2E Tests (Cypress) 18 | - [ ] Manually 19 | - [ ] N/A 20 | 21 | 22 | 23 | ## Related Tickets & Documents 24 | 25 | 26 | ## Mobile & Desktop Screenshots/Recordings (if there are UI changes) 27 | -------------------------------------------------------------------------------- /.github/config.yml: -------------------------------------------------------------------------------- 1 | # https://github.com/behaviorbot/request-info?installation_id=189571 2 | requestInfoLabelToAdd: needs-more-info 3 | requestInfoReplyComment: > 4 | We would appreciate it if you could provide us with more info about this issue/pr! 5 | 6 | -------------------------------------------------------------------------------- /.github/weekly-digest.yml: -------------------------------------------------------------------------------- 1 | # Configuration for weekly-digest - https://github.com/apps/weekly-digest 2 | publishDay: mon 3 | canPublishIssues: true 4 | canPublishPullRequests: true 5 | canPublishContributors: true 6 | canPublishStargazers: true 7 | canPublishCommits: true 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .venv 2 | venv/ 3 | .cache 4 | .coverage.* 5 | .coveralls.yml 6 | .idea 7 | *.pyc 8 | .nyc_output 9 | coverage 10 | .coverage 11 | coverage.xml 12 | client/dist 13 | .DS_Store 14 | .#* 15 | \#*# 16 | *~ 17 | _build 18 | .vscode 19 | .env 20 | 21 | dump.rdb 22 | 23 | node_modules 24 | .tmp 25 | .sass-cache 26 | npm-debug.log 27 | 28 | client/cypress/screenshots 29 | client/cypress/videos 30 | supervisord.conf 31 | uwsgi.ini 32 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v14.16.1 2 | -------------------------------------------------------------------------------- /.yarn/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /.yarnrc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/.yarnrc -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Reporting a Vulnerability 4 | 5 | Please email security@redash.io to report any security vulnerabilities. We will acknowledge receipt of your vulnerability and strive to send you regular updates about our progress. If you're curious about the status of your disclosure please feel free to email us again. If you want to encrypt your disclosure email, you can use [this PGP key](https://keybase.io/arikfr/key.asc). 6 | -------------------------------------------------------------------------------- /bin/flake8_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -o errexit # fail the build if any task fails 4 | 5 | flake8 --version ; pip --version 6 | # stop the build if there are Python syntax errors or undefined names 7 | flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics 8 | # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide 9 | flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics 10 | -------------------------------------------------------------------------------- /bin/run: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Ideally I would use stdin with source, but in older bash versions this 4 | # wasn't supported properly. 5 | TEMP_ENV_FILE=`mktemp /tmp/redash_env.XXXXXX` 6 | sed 's/^REDASH/export REDASH/' .env > $TEMP_ENV_FILE 7 | source $TEMP_ENV_FILE 8 | rm $TEMP_ENV_FILE 9 | 10 | exec "$@" 11 | -------------------------------------------------------------------------------- /client/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | "exclude": ["@babel/plugin-transform-async-to-generator", "@babel/plugin-transform-arrow-functions"], 7 | "corejs": "2", 8 | "useBuiltIns": "usage" 9 | } 10 | ], 11 | "@babel/preset-react", 12 | "@babel/preset-typescript" 13 | ], 14 | "plugins": [ 15 | "@babel/plugin-proposal-class-properties", 16 | "@babel/plugin-transform-object-assign", 17 | [ 18 | "babel-plugin-transform-builtin-extend", 19 | { 20 | "globals": ["Error"] 21 | } 22 | ] 23 | ], 24 | "env": { 25 | "test": { 26 | "plugins": ["istanbul"] 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /client/.eslintignore: -------------------------------------------------------------------------------- 1 | build/*.js 2 | dist 3 | config/*.js 4 | client/dist 5 | -------------------------------------------------------------------------------- /client/.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | -------------------------------------------------------------------------------- /client/app/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ["plugin:jest/recommended"], 3 | plugins: ["jest"], 4 | env: { 5 | "jest/globals": true, 6 | }, 7 | rules: { 8 | "jest/no-focused-tests": "off", 9 | }, 10 | }; 11 | -------------------------------------------------------------------------------- /client/app/__tests__/enzyme_setup.js: -------------------------------------------------------------------------------- 1 | import { configure } from "enzyme"; 2 | import Adapter from "enzyme-adapter-react-16"; 3 | 4 | configure({ adapter: new Adapter() }); 5 | -------------------------------------------------------------------------------- /client/app/__tests__/mocks.js: -------------------------------------------------------------------------------- 1 | import MockDate from "mockdate"; 2 | 3 | const date = new Date("2000-01-01T02:00:00.000"); 4 | 5 | MockDate.set(date); 6 | -------------------------------------------------------------------------------- /client/app/assets/fonts/roboto/Roboto-Bold-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/fonts/roboto/Roboto-Bold-webfont.eot -------------------------------------------------------------------------------- /client/app/assets/fonts/roboto/Roboto-Bold-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/fonts/roboto/Roboto-Bold-webfont.ttf -------------------------------------------------------------------------------- /client/app/assets/fonts/roboto/Roboto-Bold-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/fonts/roboto/Roboto-Bold-webfont.woff -------------------------------------------------------------------------------- /client/app/assets/fonts/roboto/Roboto-Light-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/fonts/roboto/Roboto-Light-webfont.eot -------------------------------------------------------------------------------- /client/app/assets/fonts/roboto/Roboto-Light-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/fonts/roboto/Roboto-Light-webfont.ttf -------------------------------------------------------------------------------- /client/app/assets/fonts/roboto/Roboto-Light-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/fonts/roboto/Roboto-Light-webfont.woff -------------------------------------------------------------------------------- /client/app/assets/fonts/roboto/Roboto-Medium-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/fonts/roboto/Roboto-Medium-webfont.eot -------------------------------------------------------------------------------- /client/app/assets/fonts/roboto/Roboto-Medium-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/fonts/roboto/Roboto-Medium-webfont.ttf -------------------------------------------------------------------------------- /client/app/assets/fonts/roboto/Roboto-Medium-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/fonts/roboto/Roboto-Medium-webfont.woff -------------------------------------------------------------------------------- /client/app/assets/fonts/roboto/Roboto-Regular-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/fonts/roboto/Roboto-Regular-webfont.eot -------------------------------------------------------------------------------- /client/app/assets/fonts/roboto/Roboto-Regular-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/fonts/roboto/Roboto-Regular-webfont.ttf -------------------------------------------------------------------------------- /client/app/assets/fonts/roboto/Roboto-Regular-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/fonts/roboto/Roboto-Regular-webfont.woff -------------------------------------------------------------------------------- /client/app/assets/fonts/roboto/Roboto-Thin-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/fonts/roboto/Roboto-Thin-webfont.eot -------------------------------------------------------------------------------- /client/app/assets/fonts/roboto/Roboto-Thin-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/fonts/roboto/Roboto-Thin-webfont.ttf -------------------------------------------------------------------------------- /client/app/assets/fonts/roboto/Roboto-Thin-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/fonts/roboto/Roboto-Thin-webfont.woff -------------------------------------------------------------------------------- /client/app/assets/images/db-logos/Cassandra.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/images/db-logos/Cassandra.png -------------------------------------------------------------------------------- /client/app/assets/images/db-logos/arangodb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/images/db-logos/arangodb.png -------------------------------------------------------------------------------- /client/app/assets/images/db-logos/athena.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/images/db-logos/athena.png -------------------------------------------------------------------------------- /client/app/assets/images/db-logos/aws_es.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/images/db-logos/aws_es.png -------------------------------------------------------------------------------- /client/app/assets/images/db-logos/axibasetsd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/images/db-logos/axibasetsd.png -------------------------------------------------------------------------------- /client/app/assets/images/db-logos/azure_kusto.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/images/db-logos/azure_kusto.png -------------------------------------------------------------------------------- /client/app/assets/images/db-logos/bigquery.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/images/db-logos/bigquery.png -------------------------------------------------------------------------------- /client/app/assets/images/db-logos/bigquery_gce.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/images/db-logos/bigquery_gce.png -------------------------------------------------------------------------------- /client/app/assets/images/db-logos/clickhouse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/images/db-logos/clickhouse.png -------------------------------------------------------------------------------- /client/app/assets/images/db-logos/cloudwatch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/images/db-logos/cloudwatch.png -------------------------------------------------------------------------------- /client/app/assets/images/db-logos/cloudwatch_insights.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/images/db-logos/cloudwatch_insights.png -------------------------------------------------------------------------------- /client/app/assets/images/db-logos/cockroach.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/images/db-logos/cockroach.png -------------------------------------------------------------------------------- /client/app/assets/images/db-logos/corporate_memory.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/images/db-logos/corporate_memory.png -------------------------------------------------------------------------------- /client/app/assets/images/db-logos/couchbase.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/images/db-logos/couchbase.png -------------------------------------------------------------------------------- /client/app/assets/images/db-logos/csv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/images/db-logos/csv.png -------------------------------------------------------------------------------- /client/app/assets/images/db-logos/databend.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/images/db-logos/databend.png -------------------------------------------------------------------------------- /client/app/assets/images/db-logos/databricks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/images/db-logos/databricks.png -------------------------------------------------------------------------------- /client/app/assets/images/db-logos/db2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/images/db-logos/db2.png -------------------------------------------------------------------------------- /client/app/assets/images/db-logos/dgraph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/images/db-logos/dgraph.png -------------------------------------------------------------------------------- /client/app/assets/images/db-logos/drill.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/images/db-logos/drill.png -------------------------------------------------------------------------------- /client/app/assets/images/db-logos/druid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/images/db-logos/druid.png -------------------------------------------------------------------------------- /client/app/assets/images/db-logos/dynamodb_sql.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/images/db-logos/dynamodb_sql.png -------------------------------------------------------------------------------- /client/app/assets/images/db-logos/elasticsearch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/images/db-logos/elasticsearch.png -------------------------------------------------------------------------------- /client/app/assets/images/db-logos/elasticsearch2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/images/db-logos/elasticsearch2.png -------------------------------------------------------------------------------- /client/app/assets/images/db-logos/elasticsearch2_OpenDistroSQLElasticSearch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/images/db-logos/elasticsearch2_OpenDistroSQLElasticSearch.png -------------------------------------------------------------------------------- /client/app/assets/images/db-logos/elasticsearch2_XPackSQLElasticSearch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/images/db-logos/elasticsearch2_XPackSQLElasticSearch.png -------------------------------------------------------------------------------- /client/app/assets/images/db-logos/exasol.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/images/db-logos/exasol.png -------------------------------------------------------------------------------- /client/app/assets/images/db-logos/excel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/images/db-logos/excel.png -------------------------------------------------------------------------------- /client/app/assets/images/db-logos/firebolt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/images/db-logos/firebolt.png -------------------------------------------------------------------------------- /client/app/assets/images/db-logos/google_analytics.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/images/db-logos/google_analytics.png -------------------------------------------------------------------------------- /client/app/assets/images/db-logos/google_spreadsheets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/images/db-logos/google_spreadsheets.png -------------------------------------------------------------------------------- /client/app/assets/images/db-logos/graphite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/images/db-logos/graphite.png -------------------------------------------------------------------------------- /client/app/assets/images/db-logos/hive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/images/db-logos/hive.png -------------------------------------------------------------------------------- /client/app/assets/images/db-logos/hive_http.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/images/db-logos/hive_http.png -------------------------------------------------------------------------------- /client/app/assets/images/db-logos/impala.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/images/db-logos/impala.png -------------------------------------------------------------------------------- /client/app/assets/images/db-logos/influxdb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/images/db-logos/influxdb.png -------------------------------------------------------------------------------- /client/app/assets/images/db-logos/jirajql.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/images/db-logos/jirajql.png -------------------------------------------------------------------------------- /client/app/assets/images/db-logos/json.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/images/db-logos/json.png -------------------------------------------------------------------------------- /client/app/assets/images/db-logos/kibana.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/images/db-logos/kibana.png -------------------------------------------------------------------------------- /client/app/assets/images/db-logos/kylin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/images/db-logos/kylin.png -------------------------------------------------------------------------------- /client/app/assets/images/db-logos/mapd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/images/db-logos/mapd.png -------------------------------------------------------------------------------- /client/app/assets/images/db-logos/memsql.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/images/db-logos/memsql.png -------------------------------------------------------------------------------- /client/app/assets/images/db-logos/mongodb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/images/db-logos/mongodb.png -------------------------------------------------------------------------------- /client/app/assets/images/db-logos/mssql.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/images/db-logos/mssql.png -------------------------------------------------------------------------------- /client/app/assets/images/db-logos/mssql_odbc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/images/db-logos/mssql_odbc.png -------------------------------------------------------------------------------- /client/app/assets/images/db-logos/mysql.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/images/db-logos/mysql.png -------------------------------------------------------------------------------- /client/app/assets/images/db-logos/nz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/images/db-logos/nz.png -------------------------------------------------------------------------------- /client/app/assets/images/db-logos/oracle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/images/db-logos/oracle.png -------------------------------------------------------------------------------- /client/app/assets/images/db-logos/pg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/images/db-logos/pg.png -------------------------------------------------------------------------------- /client/app/assets/images/db-logos/phoenix.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/images/db-logos/phoenix.png -------------------------------------------------------------------------------- /client/app/assets/images/db-logos/pinot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/images/db-logos/pinot.png -------------------------------------------------------------------------------- /client/app/assets/images/db-logos/presto.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/images/db-logos/presto.png -------------------------------------------------------------------------------- /client/app/assets/images/db-logos/prometheus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/images/db-logos/prometheus.png -------------------------------------------------------------------------------- /client/app/assets/images/db-logos/python.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/images/db-logos/python.png -------------------------------------------------------------------------------- /client/app/assets/images/db-logos/qubole.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/images/db-logos/qubole.png -------------------------------------------------------------------------------- /client/app/assets/images/db-logos/rds_mysql.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/images/db-logos/rds_mysql.png -------------------------------------------------------------------------------- /client/app/assets/images/db-logos/redshift.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/images/db-logos/redshift.png -------------------------------------------------------------------------------- /client/app/assets/images/db-logos/redshift_iam.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/images/db-logos/redshift_iam.png -------------------------------------------------------------------------------- /client/app/assets/images/db-logos/results.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/images/db-logos/results.png -------------------------------------------------------------------------------- /client/app/assets/images/db-logos/rockset.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/images/db-logos/rockset.png -------------------------------------------------------------------------------- /client/app/assets/images/db-logos/salesforce.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/images/db-logos/salesforce.png -------------------------------------------------------------------------------- /client/app/assets/images/db-logos/scylla.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/images/db-logos/scylla.png -------------------------------------------------------------------------------- /client/app/assets/images/db-logos/snowflake.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/images/db-logos/snowflake.png -------------------------------------------------------------------------------- /client/app/assets/images/db-logos/sparql_endpoint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/images/db-logos/sparql_endpoint.png -------------------------------------------------------------------------------- /client/app/assets/images/db-logos/sqlite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/images/db-logos/sqlite.png -------------------------------------------------------------------------------- /client/app/assets/images/db-logos/treasuredata.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/images/db-logos/treasuredata.png -------------------------------------------------------------------------------- /client/app/assets/images/db-logos/trino.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/images/db-logos/trino.png -------------------------------------------------------------------------------- /client/app/assets/images/db-logos/uptycs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/images/db-logos/uptycs.png -------------------------------------------------------------------------------- /client/app/assets/images/db-logos/url.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/images/db-logos/url.png -------------------------------------------------------------------------------- /client/app/assets/images/db-logos/vertica.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/images/db-logos/vertica.png -------------------------------------------------------------------------------- /client/app/assets/images/db-logos/yandex_appmetrika.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/images/db-logos/yandex_appmetrika.png -------------------------------------------------------------------------------- /client/app/assets/images/db-logos/yandex_metrika.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/images/db-logos/yandex_metrika.png -------------------------------------------------------------------------------- /client/app/assets/images/destinations/chatwork.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/images/destinations/chatwork.png -------------------------------------------------------------------------------- /client/app/assets/images/destinations/email.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/images/destinations/email.png -------------------------------------------------------------------------------- /client/app/assets/images/destinations/hangouts_chat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/images/destinations/hangouts_chat.png -------------------------------------------------------------------------------- /client/app/assets/images/destinations/hipchat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/images/destinations/hipchat.png -------------------------------------------------------------------------------- /client/app/assets/images/destinations/mattermost.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/images/destinations/mattermost.png -------------------------------------------------------------------------------- /client/app/assets/images/destinations/microsoft_teams_webhook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/images/destinations/microsoft_teams_webhook.png -------------------------------------------------------------------------------- /client/app/assets/images/destinations/pagerduty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/images/destinations/pagerduty.png -------------------------------------------------------------------------------- /client/app/assets/images/destinations/slack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/images/destinations/slack.png -------------------------------------------------------------------------------- /client/app/assets/images/destinations/webhook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/images/destinations/webhook.png -------------------------------------------------------------------------------- /client/app/assets/images/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/images/favicon-16x16.png -------------------------------------------------------------------------------- /client/app/assets/images/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/images/favicon-32x32.png -------------------------------------------------------------------------------- /client/app/assets/images/favicon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/images/favicon-96x96.png -------------------------------------------------------------------------------- /client/app/assets/images/fixtures/map-tile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/images/fixtures/map-tile.png -------------------------------------------------------------------------------- /client/app/assets/images/google_logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/app/assets/images/gravatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/images/gravatar.png -------------------------------------------------------------------------------- /client/app/assets/images/illustrations/readme.md: -------------------------------------------------------------------------------- 1 | The illustrations shared in this folder are covered by the CC-BY-SA-NC-ND License v4.0 (or newer). You are allowed to use them when using unmodified versions of the Redash source code for non-commercial purposes (meaning for yourself), but you are not allowed to use for any other use or to distribute them as part of your software or fork of Redash. 2 | 3 | For any questions, please contact us. 4 | -------------------------------------------------------------------------------- /client/app/assets/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/images/logo.png -------------------------------------------------------------------------------- /client/app/assets/images/logo_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/images/logo_white.png -------------------------------------------------------------------------------- /client/app/assets/images/redash_icon_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/client/app/assets/images/redash_icon_small.png -------------------------------------------------------------------------------- /client/app/assets/less/STYLING-README.md: -------------------------------------------------------------------------------- 1 | # Styling readme 2 | 3 | Some general rules before you add stuff. 4 | 5 | - Avoid using inline css 6 | - If possible, use classes that are already in place instead of adding new 7 | - Keep less/inc folder untouched, rewrite things in less/redash respectively 8 | - Try following BEM naming conventions: http://getbem.com/naming/ 9 | -------------------------------------------------------------------------------- /client/app/assets/less/inc/ace-editor.less: -------------------------------------------------------------------------------- 1 | .ace_editor { 2 | border: 1px solid fade(@redash-gray, 15%); 3 | height: 100%; 4 | margin-bottom: 10px; 5 | 6 | &.ace_autocomplete .ace_completion-highlight { 7 | text-shadow: none !important; 8 | background: #ffff005e; 9 | font-weight: 600; 10 | } 11 | 12 | &.ace-tm { 13 | .ace_gutter { 14 | background: #fff !important; 15 | } 16 | 17 | .ace_gutter-active-line { 18 | background-color: fade(@redash-gray, 20%) !important; 19 | } 20 | 21 | .ace_marker-layer .ace_active-line { 22 | background: fade(@redash-gray, 9%) !important; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /client/app/assets/less/inc/breadcrumb.less: -------------------------------------------------------------------------------- 1 | .breadcrumb { 2 | border-bottom: 1px solid #E5E5E5; 3 | border-radius: 0; 4 | padding-top: 10px; 5 | padding-right: 33px; 6 | padding-bottom: 11px; 7 | 8 | @media (min-width: (@screen-lg-min + 80px)) { 9 | padding-left: (@sidebar-left-width + @grid-gutter-width); 10 | } 11 | 12 | @media (min-width: @screen-sm-min) and (max-width: (@screen-md-max + 80px)) { 13 | padding-left: (@sidebar-left-mid-width + @grid-gutter-width); 14 | } 15 | 16 | @media (max-width: (@screen-sm-min)) { 17 | padding-left: @grid-gutter-width/2; 18 | } 19 | 20 | & > li { 21 | & > a { 22 | color: #A9A9A9; 23 | 24 | &:hover { 25 | color: @breadcrumb-active-color; 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /client/app/assets/less/inc/carousel.less: -------------------------------------------------------------------------------- 1 | .carousel-caption { 2 | left: 0; 3 | right: 0; 4 | bottom: 0; 5 | background: rgba(0,0,0,0.6); 6 | 7 | h3 { 8 | margin-top: 0; 9 | margin-bottom: 3px; 10 | color: #fff; 11 | } 12 | } 13 | 14 | .carousel-indicators { 15 | bottom: 10px; 16 | 17 | & > li:not(.active) { 18 | border: 0; 19 | background: #000; 20 | } 21 | } 22 | 23 | .carousel-control { 24 | width: 50px; 25 | background: none; 26 | 27 | .fa { 28 | font-size: 50px; 29 | height: 52px; 30 | margin-top: -26px; 31 | position: absolute; 32 | top: 50%; 33 | .margin-left(-9px); 34 | } 35 | } 36 | 37 | @media @max-768 { 38 | .carousel-indicators, .carousel-caption { 39 | display: none; 40 | } 41 | } -------------------------------------------------------------------------------- /client/app/assets/less/inc/edit-in-place.less: -------------------------------------------------------------------------------- 1 | .edit-in-place { 2 | white-space: pre-line; 3 | display: inline-block; 4 | 5 | p { 6 | margin-bottom: 0; 7 | } 8 | 9 | .editable { 10 | display: inline-block; 11 | cursor: pointer; 12 | 13 | &:hover { 14 | background: @redash-yellow; 15 | border-radius: @redash-radius; 16 | } 17 | } 18 | 19 | &.active input, 20 | &.active textarea { 21 | display: inline-block; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /client/app/assets/less/inc/jumbotron.less: -------------------------------------------------------------------------------- 1 | .jumbotron { 2 | padding-left: 60px; 3 | padding-right: 60px; 4 | } -------------------------------------------------------------------------------- /client/app/assets/less/inc/label.less: -------------------------------------------------------------------------------- 1 | .label { 2 | border-radius: 2px; 3 | padding: 3px 6px 4px; 4 | font-weight: 500; 5 | font-size: 11px; 6 | } 7 | 8 | .badge { 9 | border-radius: 1px; 10 | } 11 | 12 | .label-default { 13 | background: fade(@redash-gray, 85%); 14 | } 15 | 16 | .label-tag-unpublished { 17 | background: fade(@redash-gray, 85%); 18 | } 19 | 20 | .label-tag-archived { 21 | .label-warning(); 22 | } 23 | 24 | .label-tag { 25 | background: fade(@redash-gray, 10%); 26 | color: fade(@redash-gray, 75%); 27 | } 28 | 29 | .label-tag-unpublished, 30 | .label-tag-archived, 31 | .label-tag { 32 | margin-right: 3px; 33 | display: inline; 34 | margin-top: 2px; 35 | max-width: 24ch; 36 | .text-overflow(); 37 | } -------------------------------------------------------------------------------- /client/app/assets/less/inc/less-plugins/for.less: -------------------------------------------------------------------------------- 1 | 2 | .for(@i, @n) {.-each(@i)} 3 | .for(@n) when (isnumber(@n)) {.for(1, @n)} 4 | .for(@i, @n) when not (@i = @n) { 5 | .for((@i + (@n - @i) / abs(@n - @i)), @n); 6 | } 7 | 8 | .for(@array) when (default()) {.for-impl_(length(@array))} 9 | .for-impl_(@i) when (@i > 1) {.for-impl_((@i - 1))} 10 | .for-impl_(@i) when (@i > 0) {.-each(extract(@array, @i))} 11 | -------------------------------------------------------------------------------- /client/app/assets/less/inc/list.less: -------------------------------------------------------------------------------- 1 | .clist { 2 | list-style: none; 3 | 4 | & > li { 5 | &:before { 6 | font-family: @font-icon; 7 | margin: 0 10px 0 -20px; 8 | vertical-align: middle; 9 | } 10 | } 11 | 12 | &.clist-angle > li:before { 13 | content: "\f2fb"; 14 | } 15 | 16 | &.clist-check > li:before { 17 | content: "\f26b"; 18 | } 19 | 20 | &.clist-star > li:before { 21 | content: "\f27d"; 22 | } 23 | } -------------------------------------------------------------------------------- /client/app/assets/less/inc/popover.less: -------------------------------------------------------------------------------- 1 | .popover { 2 | box-shadow: fade(@redash-gray, 25%) 0px 0px 15px 0px; 3 | } 4 | 5 | .popover-title { 6 | border-bottom: 0; 7 | padding: 15px; 8 | font-size: 12px; 9 | text-transform: uppercase; 10 | 11 | & + .popover-content { 12 | padding-top: 0; 13 | } 14 | } 15 | 16 | .popover-content { 17 | padding: 15px; 18 | 19 | p { 20 | margin-bottom: 0; 21 | } 22 | } -------------------------------------------------------------------------------- /client/app/assets/less/inc/progress-bar.less: -------------------------------------------------------------------------------- 1 | .progress { 2 | box-shadow: none; 3 | border-radius: 0; 4 | height: 5px; 5 | margin-bottom: 0; 6 | 7 | .progress-bar { 8 | box-shadow: none; 9 | } 10 | } -------------------------------------------------------------------------------- /client/app/assets/less/inc/tooltips.less: -------------------------------------------------------------------------------- 1 | .tooltip-inner { 2 | border-radius: 1px; 3 | padding: 5px 10px; 4 | font-size: 12px; 5 | } -------------------------------------------------------------------------------- /client/app/assets/less/inc/visualizations/box.less: -------------------------------------------------------------------------------- 1 | .box { 2 | font: 10px sans-serif; 3 | line, rect, circle { 4 | fill: #fff; 5 | stroke: #000; 6 | stroke-width: 1.5px; 7 | } 8 | .center { 9 | stroke-dasharray: 3, 3; 10 | } 11 | .outlier { 12 | fill: none; 13 | stroke: #000; 14 | } 15 | } 16 | 17 | .axis text { 18 | font: 10px sans-serif; 19 | } 20 | 21 | .axis path, 22 | .axis line { 23 | fill: none; 24 | stroke: #000; 25 | shape-rendering: crispEdges; 26 | } 27 | 28 | .grid-background { 29 | fill: #ddd; 30 | } 31 | 32 | .grid path, 33 | .grid line { 34 | fill: none; 35 | stroke: #fff; 36 | shape-rendering: crispEdges; 37 | } 38 | 39 | .grid .minor line { 40 | stroke-opacity: .5; 41 | } 42 | 43 | .grid text { 44 | display: none; 45 | } 46 | -------------------------------------------------------------------------------- /client/app/assets/less/inc/visualizations/map.less: -------------------------------------------------------------------------------- 1 | .map-visualization-container { 2 | height: 500px; 3 | 4 | > div:first-child { 5 | width: 100%; 6 | height: 100%; 7 | z-index: 0; 8 | } 9 | } 10 | 11 | .leaflet-popup-content img { 12 | max-width: 100%; 13 | height: auto; 14 | } 15 | -------------------------------------------------------------------------------- /client/app/assets/less/inc/visualizations/misc.less: -------------------------------------------------------------------------------- 1 | .visualization-renderer { 2 | display: block; 3 | 4 | .pagination, 5 | .ant-pagination { 6 | margin: 0; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /client/app/assets/less/inc/visualizations/pivot-table.less: -------------------------------------------------------------------------------- 1 | .pivot-table-visualization-container > table, 2 | .visualization-renderer > .visualization-renderer-wrapper { 3 | overflow: auto; 4 | } 5 | -------------------------------------------------------------------------------- /client/app/assets/less/inc/well.less: -------------------------------------------------------------------------------- 1 | .well { 2 | border-radius: 0; 3 | background: #fff; 4 | box-shadow: none; 5 | } -------------------------------------------------------------------------------- /client/app/assets/less/inc/widgets.less: -------------------------------------------------------------------------------- 1 | /* -------------------------------------------------------- 2 | User Signups 3 | -----------------------------------------------------------*/ 4 | .rounded-thumbs { 5 | padding: 15px 25px 0; 6 | } 7 | 8 | .rt-item { 9 | display: block; 10 | padding-top: 10px; 11 | padding-bottom: 10px; 12 | 13 | img { 14 | width: 100%; 15 | height: 100%; 16 | border-radius: 50%; 17 | } 18 | 19 | small { 20 | .text-overflow(); 21 | text-align: center; 22 | display: block; 23 | color: #777; 24 | margin-top: 3px; 25 | } 26 | 27 | &:hover { 28 | background-color: @light-gray; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /client/app/assets/less/redash/tags-control.less: -------------------------------------------------------------------------------- 1 | .tags-control { 2 | display: flex; 3 | flex-direction: row; 4 | flex-wrap: wrap; 5 | align-items: stretch; 6 | justify-content: flex-start; 7 | line-height: 1em; 8 | 9 | &.inline-tags-control { 10 | display: inline-block; 11 | } 12 | 13 | .tag-separator { 14 | margin: 4px 3px 0 0; 15 | } 16 | 17 | &.disabled { 18 | opacity: 0.4; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /client/app/assets/robots.txt: -------------------------------------------------------------------------------- 1 | # robotstxt.org 2 | 3 | User-agent: * 4 | -------------------------------------------------------------------------------- /client/app/components/AceEditorInput.jsx: -------------------------------------------------------------------------------- 1 | import React, { forwardRef } from "react"; 2 | import AceEditor from "react-ace"; 3 | 4 | import "./AceEditorInput.less"; 5 | 6 | function AceEditorInput(props, ref) { 7 | return ( 8 |
9 | 18 |
19 | ); 20 | } 21 | 22 | export default forwardRef(AceEditorInput); 23 | -------------------------------------------------------------------------------- /client/app/components/AceEditorInput.less: -------------------------------------------------------------------------------- 1 | .ace-editor-input { 2 | // hide ghost cursor when not focused 3 | .ace_hidden-cursors { 4 | opacity: 0; 5 | } 6 | 7 | // allow Ant Form feedback icon to hover scrollbar 8 | .ace_scrollbar { 9 | z-index: auto; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /client/app/components/ApplicationArea/ApplicationLayout/MobileNavbar.less: -------------------------------------------------------------------------------- 1 | @backgroundColor: #001529; 2 | @dividerColor: rgba(255, 255, 255, 0.5); 3 | @textColor: rgba(255, 255, 255, 0.75); 4 | 5 | .mobile-navbar { 6 | display: flex; 7 | justify-content: space-between; 8 | align-items: center; 9 | background: @backgroundColor; 10 | box-shadow: 0 4px 9px -3px rgba(102, 136, 153, 0.15); 11 | padding: 0 15px; 12 | height: 100%; 13 | 14 | &-logo { 15 | img { 16 | height: 40px; 17 | width: 40px; 18 | } 19 | } 20 | 21 | .ant-btn.mobile-navbar-toggle-button { 22 | padding: 0 10px; 23 | } 24 | } 25 | 26 | .mobile-navbar-menu { 27 | .ant-dropdown-menu-item { 28 | font-weight: 500; 29 | color: @textColor; 30 | } 31 | 32 | .ant-dropdown-menu-item-divider { 33 | background: @dividerColor; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /client/app/components/ApplicationArea/ErrorMessage.less: -------------------------------------------------------------------------------- 1 | .error-message-container { 2 | width: 100%; 3 | padding: 0 15px; 4 | display: flex; 5 | flex-direction: column; 6 | align-items: center; 7 | justify-content: flex-start; 8 | 9 | .error-state { 10 | max-width: 1200px; 11 | width: 100%; 12 | 13 | @media (min-width: 768px) { 14 | width: 65%; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /client/app/components/ApplicationArea/ErrorMessageDetails.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | 4 | export function ErrorMessageDetails(props) { 5 | return

{props.message}

; 6 | } 7 | 8 | ErrorMessageDetails.propTypes = { 9 | error: PropTypes.instanceOf(Error).isRequired, 10 | message: PropTypes.string.isRequired, 11 | }; 12 | -------------------------------------------------------------------------------- /client/app/components/ApplicationArea/handleNavigationIntent.js: -------------------------------------------------------------------------------- 1 | import { isString } from "lodash"; 2 | import navigateTo from "./navigateTo"; 3 | 4 | export default function handleNavigationIntent(event) { 5 | let element = event.target; 6 | while (element) { 7 | if (element.tagName === "A") { 8 | break; 9 | } 10 | element = element.parentNode; 11 | } 12 | if (!element || !element.hasAttribute("href") || element.hasAttribute("download") || element.dataset.skipRouter) { 13 | return; 14 | } 15 | 16 | // Keep some default behaviour 17 | if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) { 18 | return; 19 | } 20 | 21 | const target = element.getAttribute("target"); 22 | if (isString(target) && target.toLowerCase() === "_blank") { 23 | return; 24 | } 25 | 26 | event.preventDefault(); 27 | 28 | navigateTo(element.href); 29 | } 30 | -------------------------------------------------------------------------------- /client/app/components/ApplicationArea/navigateTo.js: -------------------------------------------------------------------------------- 1 | import location from "@/services/location"; 2 | import url from "@/services/url"; 3 | import { stripBase } from "./Router"; 4 | 5 | // When `replace` is set to `true` - it will just replace current URL 6 | // without reloading current page (router will skip this location change) 7 | export default function navigateTo(href, replace = false) { 8 | // Allow calling chain to roll up, and then navigate 9 | setTimeout(() => { 10 | const isExternal = stripBase(href) === false; 11 | if (isExternal) { 12 | window.location = href; 13 | return; 14 | } 15 | href = url.parse(href); 16 | location.update( 17 | { 18 | path: href.pathname, 19 | search: href.search, 20 | hash: href.hash, 21 | }, 22 | replace 23 | ); 24 | }, 10); 25 | } 26 | -------------------------------------------------------------------------------- /client/app/components/CodeBlock.less: -------------------------------------------------------------------------------- 1 | @import (reference, less) "~@/assets/less/ant"; 2 | 3 | .code-block { 4 | background: rgba(0, 0, 0, 0.06); 5 | border: 1px solid rgba(0, 0, 0, 0.06); 6 | border-radius: 2px; 7 | padding: 3px 27px 3px 3px; 8 | position: relative; 9 | min-height: 32px; 10 | 11 | code { 12 | padding: 0; 13 | font-size: 85%; 14 | } 15 | 16 | .@{btn-prefix-cls} { 17 | position: absolute; 18 | right: 3px; 19 | bottom: 3px; 20 | padding-left: 3px !important; 21 | padding-right: 3px !important; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /client/app/components/Collapse.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | import cx from "classnames"; 4 | import AntCollapse from "antd/lib/collapse"; 5 | 6 | export default function Collapse({ collapsed, children, className, ...props }) { 7 | return ( 8 | 12 | 13 | {children} 14 | 15 | 16 | ); 17 | } 18 | 19 | Collapse.propTypes = { 20 | collapsed: PropTypes.bool, 21 | children: PropTypes.node, 22 | className: PropTypes.string, 23 | }; 24 | 25 | Collapse.defaultProps = { 26 | collapsed: true, 27 | children: null, 28 | className: "", 29 | }; 30 | -------------------------------------------------------------------------------- /client/app/components/EditVisualizationButton/index.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | import Button from "antd/lib/button"; 4 | import FormOutlinedIcon from "@ant-design/icons/FormOutlined"; 5 | 6 | export default function EditVisualizationButton(props) { 7 | return ( 8 | 15 | ); 16 | } 17 | 18 | EditVisualizationButton.propTypes = { 19 | openVisualizationEditor: PropTypes.func.isRequired, 20 | selectedTab: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), 21 | }; 22 | 23 | EditVisualizationButton.defaultProps = { 24 | selectedTab: "", 25 | }; 26 | -------------------------------------------------------------------------------- /client/app/components/NoTaggedObjectsFound.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | import BigMessage from "@/components/BigMessage"; 4 | import { TagsControl } from "@/components/tags-control/TagsControl"; 5 | 6 | export default function NoTaggedObjectsFound({ objectType, tags }) { 7 | return ( 8 | 9 | No {objectType} found tagged with  10 | . 11 | 12 | ); 13 | } 14 | 15 | NoTaggedObjectsFound.propTypes = { 16 | objectType: PropTypes.string.isRequired, 17 | tags: PropTypes.oneOfType([PropTypes.array, PropTypes.objectOf(Set)]).isRequired, 18 | }; 19 | -------------------------------------------------------------------------------- /client/app/components/PageHeader/index.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | 4 | import "./index.less"; 5 | 6 | export default function PageHeader({ title, actions }) { 7 | return ( 8 |
9 |

{title}

10 | {actions &&
{actions}
} 11 |
12 | ); 13 | } 14 | 15 | PageHeader.propTypes = { 16 | title: PropTypes.string, 17 | actions: PropTypes.node, 18 | }; 19 | 20 | PageHeader.defaultProps = { 21 | title: "", 22 | actions: null, 23 | }; 24 | -------------------------------------------------------------------------------- /client/app/components/PageHeader/index.less: -------------------------------------------------------------------------------- 1 | .page-header-wrapper { 2 | margin: 15px 0 10px 0; 3 | display: flex; 4 | flex-direction: row; 5 | flex-wrap: nowrap; 6 | align-items: center; 7 | justify-content: stretch; 8 | 9 | h3 { 10 | margin: 0; 11 | line-height: 1.3; 12 | font-weight: 500; 13 | flex: 1 1 auto; 14 | } 15 | 16 | .page-header-actions { 17 | flex: 0 0 auto; 18 | padding: 0 0 0 15px; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /client/app/components/ParameterValueInput.less: -------------------------------------------------------------------------------- 1 | @import (reference, less) "~@/assets/less/ant"; // for ant @vars 2 | 3 | @input-dirty: #fffce1; 4 | 5 | .parameter-input { 6 | display: inline-block; 7 | position: relative; 8 | width: 100%; 9 | 10 | .@{ant-prefix}-input, 11 | .@{ant-prefix}-input-number { 12 | min-width: 100% !important; 13 | } 14 | 15 | .@{ant-prefix}-select { 16 | width: 100%; 17 | } 18 | 19 | &[data-dirty] { 20 | .@{ant-prefix}-input, 21 | .@{ant-prefix}-input-number, 22 | .@{ant-prefix}-select-selector, 23 | .@{ant-prefix}-picker { 24 | background-color: @input-dirty; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /client/app/components/PermissionsEditorDialog/index.less: -------------------------------------------------------------------------------- 1 | .permissions-editor-dialog { 2 | .ant-select-dropdown-menu-item-disabled { 3 | // make sure .text-muted has the disabled color 4 | &, .text-muted { 5 | color: rgba(0, 0, 0, 0.25); 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /client/app/components/PlainButton.less: -------------------------------------------------------------------------------- 1 | @import (reference, less) "~@/assets/less/ant"; 2 | 3 | .plain-button { 4 | all: unset; 5 | transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1); 6 | 7 | .@{dropdown-prefix-cls}-menu-item > & { 8 | width: 100%; 9 | margin: -5px -12px; 10 | padding: 5px 12px; 11 | } 12 | 13 | .@{menu-prefix-cls}-item > & { 14 | width: 100%; 15 | margin: 0 -16px; 16 | padding: 0 16px; 17 | } 18 | } 19 | 20 | .plain-button-link { 21 | .btn-link(); 22 | } 23 | -------------------------------------------------------------------------------- /client/app/components/PlainButton.tsx: -------------------------------------------------------------------------------- 1 | import classNames from "classnames"; 2 | import React from "react"; 3 | 4 | import "./PlainButton.less"; 5 | 6 | export interface PlainButtonProps extends Omit, "type"> { 7 | type?: "link" | "button"; 8 | } 9 | 10 | function PlainButton({ className, type, ...rest }: PlainButtonProps) { 11 | return ( 12 | 16 | 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /client/app/components/dynamic-form/fields/InputField.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Input from "antd/lib/input"; 3 | 4 | export default function InputField({ form, field, ...otherProps }) { 5 | return ; 6 | } 7 | -------------------------------------------------------------------------------- /client/app/components/dynamic-form/fields/NumberField.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import InputNumber from "antd/lib/input-number"; 3 | 4 | export default function NumberField({ form, field, ...otherProps }) { 5 | return ; 6 | } 7 | -------------------------------------------------------------------------------- /client/app/components/dynamic-form/fields/SelectField.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Select from "antd/lib/select"; 3 | 4 | export default function SelectField({ form, field, ...otherProps }) { 5 | const { readOnly } = field; 6 | return ( 7 | 20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /client/app/components/dynamic-form/fields/TextAreaField.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Input from "antd/lib/input"; 3 | 4 | export default function TextAreaField({ form, field, ...otherProps }) { 5 | return ; 6 | } 7 | -------------------------------------------------------------------------------- /client/app/components/dynamic-form/fields/index.js: -------------------------------------------------------------------------------- 1 | export { default as AceEditorField } from "./AceEditorField"; 2 | export { default as CheckboxField } from "./CheckboxField"; 3 | export { default as ContentField } from "./ContentField"; 4 | export { default as FileField } from "./FileField"; 5 | export { default as InputField } from "./InputField"; 6 | export { default as NumberField } from "./NumberField"; 7 | export { default as SelectField } from "./SelectField"; 8 | export { default as TextAreaField } from "./TextAreaField"; 9 | -------------------------------------------------------------------------------- /client/app/components/dynamic-form/getFieldLabel.js: -------------------------------------------------------------------------------- 1 | import { toHuman } from "@/lib/utils"; 2 | 3 | export default function getFieldLabel(field) { 4 | const { title, name } = field; 5 | return title || toHuman(name); 6 | } 7 | -------------------------------------------------------------------------------- /client/app/components/dynamic-parameters/DynamicButton.less: -------------------------------------------------------------------------------- 1 | .dynamic-button { 2 | height: 100%; 3 | position: absolute !important; 4 | right: 1px; 5 | top: 0; 6 | 7 | .ant-dropdown-trigger { 8 | height: 100%; 9 | } 10 | 11 | button { 12 | border: none; 13 | padding: 0; 14 | box-shadow: none; 15 | background-color: transparent !important; 16 | } 17 | 18 | &:after { 19 | content: ""; 20 | position: absolute; 21 | width: 1px; 22 | height: 19px; 23 | left: 0; 24 | top: 8px; 25 | border-left: 1px dotted rgba(0, 0, 0, 0.12); 26 | } 27 | } 28 | 29 | .dynamic-menu { 30 | width: 187px; 31 | 32 | em { 33 | color: #ccc; 34 | font-size: 11px; 35 | } 36 | } 37 | 38 | .dynamic-icon { 39 | display: flex !important; 40 | align-items: center; 41 | justify-content: center; 42 | } 43 | -------------------------------------------------------------------------------- /client/app/components/items-list/components/EmptyState.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import BigMessage from "@/components/BigMessage"; 3 | 4 | // Default "list empty" message for list pages 5 | export default function EmptyState(props) { 6 | return ( 7 |
8 | 9 |
10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /client/app/components/items-list/components/LoadingState.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import BigMessage from "@/components/BigMessage"; 3 | 4 | // Default "loading" message for list pages 5 | export default function LoadingState(props) { 6 | return ( 7 |
8 | 9 |
10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /client/app/components/layouts/content-with-sidebar.less: -------------------------------------------------------------------------------- 1 | .layout-with-sidebar { 2 | @spacing: 15px; 3 | position: relative; 4 | 5 | display: flex; 6 | align-items: stretch; 7 | justify-content: stretch; 8 | flex-direction: row; 9 | margin: 0; 10 | 11 | > .layout-content { 12 | flex: 1 0 auto; 13 | width: 75%; 14 | order: 1; 15 | margin: 0; 16 | padding: 0 0 0 @spacing 17 | } 18 | 19 | > .layout-sidebar { 20 | flex: 0 0 auto; 21 | width: 25%; 22 | max-width: 350px; 23 | order: 0; 24 | margin: 0; 25 | } 26 | 27 | @media (max-width: 990px) { 28 | flex-direction: column; 29 | 30 | > .layout-content { 31 | width: 100%; 32 | order: 1; 33 | margin: 0; 34 | padding: 0; 35 | } 36 | 37 | > .layout-sidebar { 38 | width: 100%; 39 | max-width: none; 40 | order: 0; 41 | margin: 0 0 @spacing 0; 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /client/app/components/queries/ApiKeyDialog/index.less: -------------------------------------------------------------------------------- 1 | .query-api-key-dialog-wrapper { 2 | .ant-input-group.ant-input-group-compact { 3 | display: flex; 4 | flex-wrap: nowrap; 5 | 6 | .ant-input { 7 | flex-grow: 1; 8 | flex-shrink: 1; 9 | } 10 | 11 | .ant-btn { 12 | flex-grow: 0; 13 | flex-shrink: 0; 14 | height: auto; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /client/app/components/queries/EmbedQueryDialog.less: -------------------------------------------------------------------------------- 1 | @import (reference, less) "~@/assets/less/ant"; 2 | 3 | .embed-query-dialog { 4 | label { 5 | font-weight: normal; 6 | } 7 | 8 | .size-input { 9 | width: 72px; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /client/app/components/queries/QueryEditor/QueryEditorControls.less: -------------------------------------------------------------------------------- 1 | .query-editor-controls { 2 | display: flex; 3 | flex-wrap: nowrap; 4 | align-items: stretch; 5 | justify-content: stretch; 6 | 7 | // Styles for a wrapper that `Tooltip` adds for disabled `Button`s 8 | span.query-editor-controls-button { 9 | display: flex !important; 10 | align-items: stretch; 11 | justify-content: stretch; 12 | } 13 | 14 | .ant-btn { 15 | height: auto; 16 | 17 | .fa + span, 18 | .zmdi + span { 19 | // if button has icon and label - add some space between them 20 | margin-left: 5px; 21 | } 22 | } 23 | 24 | .query-editor-controls-checkbox { 25 | display: inline-block; 26 | white-space: nowrap; 27 | margin: auto 5px; 28 | } 29 | 30 | .query-editor-controls-spacer { 31 | flex: 1 1 auto; 32 | height: 35px; // same as Antd 13 |
14 | 15 | 16 |
17 | 18 | 19 | {% endif %} 20 | 21 | 22 | {% endblock %} 23 | -------------------------------------------------------------------------------- /redash/templates/verify.html: -------------------------------------------------------------------------------- 1 | {% extends "layouts/signed_out.html" %} 2 | {% block title %}电子邮箱验证{% endblock %} 3 | {% block head %} 4 | 5 | {% endblock %} 6 | {% block content %} 7 |
8 |
9 |
10 | 感谢验证电子邮箱,几秒后将返回应用。如果没有返回,请点击这里。 11 |
12 |
13 |
14 | {% endblock %} 15 | -------------------------------------------------------------------------------- /redash/utils/human_time.py: -------------------------------------------------------------------------------- 1 | import parsedatetime 2 | from time import mktime 3 | from datetime import datetime 4 | 5 | cal = parsedatetime.Calendar() 6 | 7 | 8 | def parse_human_time(s): 9 | time_struct, _ = cal.parse(s) 10 | return datetime.fromtimestamp(mktime(time_struct)) 11 | -------------------------------------------------------------------------------- /redash/utils/requests_session.py: -------------------------------------------------------------------------------- 1 | from redash import settings 2 | 3 | from advocate.exceptions import UnacceptableAddressException 4 | if settings.ENFORCE_PRIVATE_ADDRESS_BLOCK: 5 | import advocate as requests_or_advocate 6 | else: 7 | import requests as requests_or_advocate 8 | 9 | 10 | 11 | class ConfiguredSession(requests_or_advocate.Session): 12 | def request(self, *args, **kwargs): 13 | if not settings.REQUESTS_ALLOW_REDIRECTS: 14 | kwargs.update({"allow_redirects": False}) 15 | return super().request(*args, **kwargs) 16 | 17 | 18 | requests_session = ConfiguredSession() 19 | -------------------------------------------------------------------------------- /redash/wsgi.py: -------------------------------------------------------------------------------- 1 | from redash import create_app 2 | 3 | app = create_app() 4 | -------------------------------------------------------------------------------- /requirements_dev.txt: -------------------------------------------------------------------------------- 1 | pytest==5.2.1 2 | pytest-cov==2.8.1 3 | coverage==4.5.4 4 | mock==3.0.5 5 | 6 | # PyMongo and Athena dependencies are needed for some of the unit tests: 7 | # (this is not perfect and we should resolve this in a different way) 8 | pymongo[srv,tls]==3.9.0 9 | boto3>=1.10.0,<1.11.0 10 | botocore>=1.13,<1.14.0 11 | PyAthena>=1.5.0,<=1.11.5 12 | ptvsd==4.3.2 13 | freezegun==0.3.12 14 | watchdog==0.9.0 15 | ptpython==3.0.17 16 | -------------------------------------------------------------------------------- /requirements_oracle_ds.txt: -------------------------------------------------------------------------------- 1 | # Requires installation of, or similar versions of: 2 | # oracle-instantclient12.2-basic_12.2.0.1.0-1_x86_64.rpm 3 | # oracle-instantclient12.2-devel_12.2.0.1.0-1_x64_64.rpm 4 | cx_Oracle==5.3 5 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [pep8] 2 | max-line-length = 120 3 | 4 | [flake8] 5 | ignore = E501 6 | exclude = .git 7 | max-complexity = 10 8 | -------------------------------------------------------------------------------- /setup/README.md: -------------------------------------------------------------------------------- 1 | # Setup script for Redash with Docker on Ubuntu 18.04. 2 | 3 | The setup script moved to its own repository: 4 | 5 | [https://github.com/getredash/setup](https://github.com/getredash/setup) 6 | -------------------------------------------------------------------------------- /tests/handlers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/tests/handlers/__init__.py -------------------------------------------------------------------------------- /tests/metrics/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/tests/metrics/__init__.py -------------------------------------------------------------------------------- /tests/metrics/test_database.py: -------------------------------------------------------------------------------- 1 | from mock import patch, ANY 2 | from tests import BaseTestCase 3 | 4 | 5 | @patch("statsd.StatsClient.timing") 6 | class TestDatabaseMetrics(BaseTestCase): 7 | def test_db_request_records_statsd_metrics(self, timing): 8 | self.factory.create_query() 9 | timing.assert_called_with("db.changes.insert", ANY) 10 | -------------------------------------------------------------------------------- /tests/metrics/test_request.py: -------------------------------------------------------------------------------- 1 | from mock import patch, ANY 2 | from tests import BaseTestCase 3 | 4 | 5 | @patch("statsd.StatsClient.timing") 6 | class TestRequestMetrics(BaseTestCase): 7 | def test_flask_request_records_statsd_metrics(self, timing): 8 | self.client.get("/ping") 9 | timing.assert_called_once_with("requests.redash_ping.get", ANY) 10 | -------------------------------------------------------------------------------- /tests/models/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/tests/models/__init__.py -------------------------------------------------------------------------------- /tests/models/test_api_keys.py: -------------------------------------------------------------------------------- 1 | from tests import BaseTestCase 2 | from redash.models import ApiKey 3 | 4 | 5 | class TestApiKeyGetByObject(BaseTestCase): 6 | def test_returns_none_if_not_exists(self): 7 | dashboard = self.factory.create_dashboard() 8 | self.assertIsNone(ApiKey.get_by_object(dashboard)) 9 | 10 | def test_returns_only_active_key(self): 11 | dashboard = self.factory.create_dashboard() 12 | api_key = self.factory.create_api_key(object=dashboard, active=False) 13 | self.assertIsNone(ApiKey.get_by_object(dashboard)) 14 | 15 | api_key = self.factory.create_api_key(object=dashboard) 16 | self.assertEqual(api_key, ApiKey.get_by_object(dashboard)) 17 | -------------------------------------------------------------------------------- /tests/query_runner/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/tests/query_runner/__init__.py -------------------------------------------------------------------------------- /tests/query_runner/test_cass.py: -------------------------------------------------------------------------------- 1 | import shutil 2 | import ssl 3 | from unittest import TestCase 4 | 5 | from redash.query_runner.cass import generate_ssl_options_dict 6 | 7 | 8 | class TestCassandra(TestCase): 9 | 10 | def test_generate_ssl_options_dict_creates_plain_protocol_dict(self): 11 | expected = {'ssl_version': ssl.PROTOCOL_TLSv1_2} 12 | actual = generate_ssl_options_dict("PROTOCOL_TLSv1_2") 13 | self.assertDictEqual(expected, actual) 14 | 15 | def test_generate_ssl_options_dict_creates_certificate_dict(self): 16 | expected = { 17 | 'ssl_version': ssl.PROTOCOL_TLSv1_2, 18 | 'ca_certs': 'some/path', 19 | 'cert_reqs': ssl.CERT_REQUIRED, 20 | } 21 | actual = generate_ssl_options_dict("PROTOCOL_TLSv1_2", "some/path") 22 | self.assertDictEqual(expected, actual) 23 | -------------------------------------------------------------------------------- /tests/query_runner/test_python.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | 3 | from redash.query_runner.python import Python 4 | 5 | 6 | class TestPython(TestCase): 7 | def test_sorted_safe_builtins(self): 8 | src = list(Python.safe_builtins) 9 | assert src == sorted(src), 'Python safe_builtins package not sorted.' 10 | -------------------------------------------------------------------------------- /tests/serializers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/tests/serializers/__init__.py -------------------------------------------------------------------------------- /tests/tasks/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/tasks/test_empty_schedule.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | from mock import patch 3 | from tests import BaseTestCase 4 | from redash.tasks import empty_schedules 5 | from redash.models import Query 6 | from redash.utils import utcnow 7 | 8 | 9 | class TestEmptyScheduleQuery(BaseTestCase): 10 | def test_empty_schedules(self): 11 | one_day_ago = (utcnow() - datetime.timedelta(days=1)).strftime("%Y-%m-%d") 12 | query = self.factory.create_query( 13 | schedule={"interval": "3600", "until": one_day_ago} 14 | ) 15 | oq = staticmethod(lambda: [query]) 16 | with patch.object(Query, "past_scheduled_queries", oq): 17 | empty_schedules() 18 | self.assertEqual(query.schedule, None) 19 | -------------------------------------------------------------------------------- /tests/test_migrations.py: -------------------------------------------------------------------------------- 1 | import os 2 | from alembic.config import Config 3 | from alembic.script import ScriptDirectory 4 | 5 | 6 | def test_only_single_head_revision_in_migrations(): 7 | """ 8 | If multiple developers are working on migrations and one of them is merged before the 9 | other you might end up with multiple heads (multiple revisions with the same down_revision). 10 | 11 | This makes sure that there is only a single head revision in the migrations directory. 12 | 13 | Adopted from https://blog.jerrycodes.com/multiple-heads-in-alembic-migrations/. 14 | """ 15 | config = Config(os.path.join("migrations", 'alembic.ini')) 16 | config.set_main_option('script_location', "migrations") 17 | script = ScriptDirectory.from_config(config) 18 | 19 | # This will raise if there are multiple heads 20 | script.get_current_head() -------------------------------------------------------------------------------- /tests/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dazdata/redash/e8b4c30d801c863c23a4237a5cae8088866da35a/tests/utils/__init__.py -------------------------------------------------------------------------------- /viz-lib/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env", "@babel/preset-react", "@babel/preset-typescript"], 3 | "plugins": [ 4 | "@babel/plugin-proposal-class-properties", 5 | [ 6 | "module-resolver", 7 | { 8 | "root": ["./src"], 9 | "alias": { 10 | "@": "./src" 11 | } 12 | } 13 | ] 14 | ], 15 | "env": { 16 | "test": { 17 | "plugins": ["istanbul"] 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /viz-lib/.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | node_modules 3 | 4 | # builds 5 | /build 6 | /dist 7 | /lib 8 | .rpt2_cache 9 | 10 | # misc 11 | .DS_Store 12 | .env 13 | .env.local 14 | .env.development.local 15 | .env.test.local 16 | .env.production.local 17 | 18 | npm-debug.log* 19 | yarn-debug.log* 20 | yarn-error.log* -------------------------------------------------------------------------------- /viz-lib/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## v0.1.0 - 2020-05-05 4 | 5 | - Created the library from Redash codebase 6 | -------------------------------------------------------------------------------- /viz-lib/__tests__/enzyme_setup.js: -------------------------------------------------------------------------------- 1 | import { configure } from "enzyme"; 2 | import Adapter from "enzyme-adapter-react-16"; 3 | 4 | configure({ adapter: new Adapter() }); 5 | -------------------------------------------------------------------------------- /viz-lib/__tests__/mocks.js: -------------------------------------------------------------------------------- 1 | import MockDate from "mockdate"; 2 | 3 | const date = new Date("2000-01-01T02:00:00.000"); 4 | 5 | MockDate.set(date); 6 | 7 | Object.defineProperty(window, "matchMedia", { 8 | writable: true, 9 | value: jest.fn().mockImplementation(query => ({ 10 | matches: false, 11 | media: query, 12 | onchange: null, 13 | addListener: jest.fn(), // deprecated 14 | removeListener: jest.fn(), // deprecated 15 | addEventListener: jest.fn(), 16 | removeEventListener: jest.fn(), 17 | dispatchEvent: jest.fn(), 18 | })), 19 | }); 20 | -------------------------------------------------------------------------------- /viz-lib/prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | printWidth: 120, 3 | jsxBracketSameLine: true, 4 | tabWidth: 2, 5 | trailingComma: 'es5', 6 | }; 7 | -------------------------------------------------------------------------------- /viz-lib/src/components/ColorPicker/input.less: -------------------------------------------------------------------------------- 1 | .color-picker-input-swatches { 2 | margin: 0 0 10px 0; 3 | text-align: left; 4 | white-space: nowrap; 5 | 6 | .color-swatch { 7 | cursor: pointer; 8 | margin: 0 10px 0 0; 9 | 10 | &:last-child { 11 | margin-right: 0; 12 | } 13 | } 14 | } 15 | 16 | .color-picker-input { 17 | text-align: left; 18 | white-space: nowrap; 19 | } 20 | -------------------------------------------------------------------------------- /viz-lib/src/components/ColorPicker/label.less: -------------------------------------------------------------------------------- 1 | .color-label { 2 | vertical-align: middle; 3 | 4 | .color-swatch + & { 5 | margin-left: 7px; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /viz-lib/src/components/ColorPicker/utils.ts: -------------------------------------------------------------------------------- 1 | import { isArray, findKey } from "lodash"; 2 | import tinycolor from "tinycolor2"; 3 | 4 | export function validateColor(value: any, fallback = null) { 5 | value = tinycolor(value); 6 | return value.isValid() ? "#" + value.toHex().toUpperCase() : fallback; 7 | } 8 | 9 | export function getColorName(color: any, presetColors: any) { 10 | if (isArray(presetColors)) { 11 | return color; 12 | } 13 | return findKey(presetColors, v => validateColor(v) === color) || color; 14 | } 15 | -------------------------------------------------------------------------------- /viz-lib/src/components/TextAlignmentSelect/index.less: -------------------------------------------------------------------------------- 1 | .ant-radio-group.text-alignment-select { 2 | display: flex; 3 | align-items: stretch; 4 | justify-content: stretch; 5 | 6 | .ant-radio-button-wrapper { 7 | flex-grow: 1; 8 | text-align: center; 9 | // fit height 10 | height: 35px; 11 | line-height: 33px; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /viz-lib/src/components/sortable/style.less: -------------------------------------------------------------------------------- 1 | .drag-handle { 2 | vertical-align: bottom; 3 | cursor: move; 4 | 5 | display: inline-flex; 6 | align-items: stretch; 7 | justify-content: center; 8 | 9 | &:before { 10 | content: ''; 11 | display: block; 12 | width: 6px; 13 | 14 | background: 15 | linear-gradient(90deg, transparent 0px, white 1px, white 2px) center, 16 | linear-gradient(transparent 0px, white 1px, white 2px) center, 17 | #111111; 18 | background-size: 2px 2px; 19 | } 20 | } 21 | 22 | .sortable-container { 23 | transition: background-color 200ms ease-out; 24 | transition-delay: 300ms; // short pause before returning to original bgcolor 25 | 26 | &.sortable-container-dragging { 27 | transition-delay: 0s; 28 | background-color: #f6f8f9; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /viz-lib/src/components/visualizations/editor/Section.less: -------------------------------------------------------------------------------- 1 | .visualization-editor-section-title { 2 | margin-top: 0px; 3 | margin-bottom: 15px; 4 | } 5 | 6 | .visualization-editor-section { 7 | margin-bottom: 15px; 8 | } 9 | -------------------------------------------------------------------------------- /viz-lib/src/components/visualizations/editor/Switch.less: -------------------------------------------------------------------------------- 1 | .switch-with-label { 2 | display: flex; 3 | align-items: center; 4 | 5 | .switch-text { 6 | margin-left: 10px; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /viz-lib/src/components/visualizations/editor/TextArea.less: -------------------------------------------------------------------------------- 1 | .visualization-editor-text-area { 2 | resize: vertical; 3 | } 4 | -------------------------------------------------------------------------------- /viz-lib/src/components/visualizations/editor/TextArea.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import cx from "classnames"; 3 | import AntInput from "antd/lib/input"; 4 | import withControlLabel from "./withControlLabel"; 5 | 6 | import "./TextArea.less"; 7 | 8 | function TextArea({ className, ...otherProps }: any) { 9 | return ; 10 | } 11 | 12 | export default withControlLabel(TextArea); 13 | -------------------------------------------------------------------------------- /viz-lib/src/components/visualizations/editor/context-help.less: -------------------------------------------------------------------------------- 1 | a.visualization-editor-context-help { 2 | &, 3 | .ant-typography & { 4 | font: inherit; 5 | color: inherit; 6 | 7 | &:hover, 8 | &:active { 9 | color: #0a6ebd; 10 | } 11 | } 12 | } 13 | 14 | .context-help-default-icon { 15 | margin-left: 5px; 16 | margin-right: 5px; 17 | } 18 | -------------------------------------------------------------------------------- /viz-lib/src/components/visualizations/editor/control-label.less: -------------------------------------------------------------------------------- 1 | .visualization-editor-control-label { 2 | &.visualization-editor-control-label-horizontal { 3 | label { 4 | margin-bottom: 0; 5 | } 6 | } 7 | } 8 | 9 | .visualization-editor-input { 10 | width: 100% !important; 11 | } 12 | -------------------------------------------------------------------------------- /viz-lib/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./visualizations"; 2 | export * from "./visualizations/visualizationsSettings"; 3 | export { VisualizationType } from "./visualizations/prop-types"; 4 | export { 5 | default as registeredVisualizations, 6 | getDefaultVisualization, 7 | newVisualization, 8 | } from "./visualizations/registeredVisualizations"; 9 | -------------------------------------------------------------------------------- /viz-lib/src/lib/chooseTextColorForBackground.ts: -------------------------------------------------------------------------------- 1 | import { maxBy } from "lodash"; 2 | import chroma from "chroma-js"; 3 | 4 | export default function chooseTextColorForBackground(backgroundColor: any, textColors = ["#ffffff", "#333333"]) { 5 | try { 6 | backgroundColor = chroma(backgroundColor); 7 | return maxBy(textColors, color => chroma.contrast(backgroundColor, color)); 8 | } catch (e) { 9 | return null; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /viz-lib/src/lib/hooks/useMemoWithDeepCompare.ts: -------------------------------------------------------------------------------- 1 | import { isEqual } from "lodash"; 2 | import { useMemo, useRef } from "react"; 3 | 4 | export default function useMemoWithDeepCompare(create: any, inputs: any) { 5 | const valueRef = useRef(); 6 | const value = useMemo(create, inputs); 7 | if (!isEqual(value, valueRef.current)) { 8 | // @ts-expect-error ts-migrate(2322) FIXME: Type 'unknown' is not assignable to type 'undefine... Remove this comment to see the full error message 9 | valueRef.current = value; 10 | } 11 | return valueRef.current; 12 | } 13 | -------------------------------------------------------------------------------- /viz-lib/src/services/sanitize.ts: -------------------------------------------------------------------------------- 1 | import { isString } from "lodash"; 2 | import DOMPurify from "dompurify"; 3 | 4 | DOMPurify.setConfig({ 5 | ADD_ATTR: ["target"], 6 | }); 7 | 8 | DOMPurify.addHook("afterSanitizeAttributes", function(node) { 9 | // Fix elements with `target` attribute: 10 | // - allow only `target="_blank" 11 | // - add `rel="noopener noreferrer"` to prevent https://www.owasp.org/index.php/Reverse_Tabnabbing 12 | 13 | const target = node.getAttribute("target"); 14 | if (isString(target) && target.toLowerCase() === "_blank") { 15 | node.setAttribute("rel", "noopener noreferrer"); 16 | } else { 17 | node.removeAttribute("target"); 18 | } 19 | }); 20 | 21 | export { DOMPurify }; 22 | 23 | export default DOMPurify.sanitize; 24 | -------------------------------------------------------------------------------- /viz-lib/src/visualizations/Editor.tsx: -------------------------------------------------------------------------------- 1 | import React, { useMemo } from "react"; 2 | import { EditorPropTypes } from "@/visualizations/prop-types"; 3 | import registeredVisualizations from "@/visualizations/registeredVisualizations"; 4 | 5 | /* 6 | (ts-migrate) TODO: Migrate the remaining prop types 7 | ...EditorPropTypes 8 | */ 9 | type Props = { 10 | type: string; 11 | } & typeof EditorPropTypes; 12 | 13 | export default function Editor({ type, options: optionsProp, data, ...otherProps }: Props) { 14 | // @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message 15 | const { Editor, getOptions } = registeredVisualizations[type]; 16 | const options = useMemo(() => getOptions(optionsProp, data), [optionsProp, data]); 17 | 18 | return ; 19 | } 20 | -------------------------------------------------------------------------------- /viz-lib/src/visualizations/box-plot/index.ts: -------------------------------------------------------------------------------- 1 | import Renderer from "./Renderer"; 2 | import Editor from "./Editor"; 3 | 4 | export default { 5 | type: "BOXPLOT", 6 | name: "箱线图(Boxplot)", 7 | isDeprecated: true, 8 | getOptions: (options: any) => ({ 9 | ...options, 10 | }), 11 | Renderer, 12 | Editor, 13 | 14 | defaultRows: 8, 15 | minRows: 5, 16 | }; 17 | -------------------------------------------------------------------------------- /viz-lib/src/visualizations/box-plot/renderer.less: -------------------------------------------------------------------------------- 1 | .box-plot-deprecated-visualization-container { 2 | overflow: hidden; 3 | height: 500px; 4 | } 5 | -------------------------------------------------------------------------------- /viz-lib/src/visualizations/chart/Editor/ColorsSettings.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { EditorPropTypes } from "@/visualizations/prop-types"; 3 | 4 | import PieColorsSettings from "./PieColorsSettings"; 5 | import HeatmapColorsSettings from "./HeatmapColorsSettings"; 6 | import DefaultColorsSettings from "./DefaultColorsSettings"; 7 | 8 | const components = { 9 | pie: PieColorsSettings, 10 | heatmap: HeatmapColorsSettings, 11 | }; 12 | 13 | export default function ColorsSettings({ options, ...props }: any) { 14 | // @ts-expect-error ts-migrate(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message 15 | const Component = components[options.globalSeriesType] || DefaultColorsSettings; 16 | return ; 17 | } 18 | 19 | ColorsSettings.propTypes = EditorPropTypes; 20 | -------------------------------------------------------------------------------- /viz-lib/src/visualizations/chart/Editor/__snapshots__/SeriesSettings.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Visualizations -> Chart -> Editor -> Series Settings Changes series axis 1`] = ` 4 | Object { 5 | "seriesOptions": Object { 6 | "a": Object { 7 | "yAxis": 1, 8 | }, 9 | }, 10 | } 11 | `; 12 | 13 | exports[`Visualizations -> Chart -> Editor -> Series Settings Changes series label 1`] = ` 14 | Object { 15 | "seriesOptions": Object { 16 | "a": Object { 17 | "name": "test", 18 | }, 19 | }, 20 | } 21 | `; 22 | 23 | exports[`Visualizations -> Chart -> Editor -> Series Settings Changes series type 1`] = ` 24 | Object { 25 | "seriesOptions": Object { 26 | "a": Object { 27 | "type": "area", 28 | }, 29 | }, 30 | } 31 | `; 32 | -------------------------------------------------------------------------------- /viz-lib/src/visualizations/chart/Renderer/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { RendererPropTypes } from "@/visualizations/prop-types"; 3 | 4 | import PlotlyChart from "./PlotlyChart"; 5 | import CustomPlotlyChart from "./CustomPlotlyChart"; 6 | import { visualizationsSettings } from "@/visualizations/visualizationsSettings"; 7 | 8 | import "./renderer.less"; 9 | 10 | export default function Renderer({ options, ...props }: any) { 11 | if (options.globalSeriesType === "custom" && visualizationsSettings.allowCustomJSVisualizations) { 12 | return ; 13 | } 14 | return ; 15 | } 16 | 17 | Renderer.propTypes = RendererPropTypes; 18 | -------------------------------------------------------------------------------- /viz-lib/src/visualizations/chart/Renderer/renderer.less: -------------------------------------------------------------------------------- 1 | .chart-visualization-container { 2 | height: 400px; 3 | overflow: hidden; 4 | } 5 | -------------------------------------------------------------------------------- /viz-lib/src/visualizations/chart/fixtures/getChartData/single-series.json: -------------------------------------------------------------------------------- 1 | { 2 | "input": { 3 | "data": [ 4 | { "a": 42, "b": 10, "c": 41, "d": 92 }, 5 | { "a": 62, "b": 73 }, 6 | { "a": 21, "b": null }, 7 | { "a": 85, "b": 50 }, 8 | { "a": 95 } 9 | ], 10 | "options": { 11 | "columnMapping": { 12 | "a": "x", 13 | "b": "y" 14 | }, 15 | "seriesOptions": {} 16 | } 17 | }, 18 | "output": { 19 | "data": [ 20 | { 21 | "name": "b", 22 | "type": "column", 23 | "data": [ 24 | { "x": 42, "y": 10, "$raw": { "a": 42, "b": 10, "c": 41, "d": 92 } }, 25 | { "x": 62, "y": 73, "$raw": { "a": 62, "b": 73 } }, 26 | { "x": 21, "y": null, "$raw": { "a": 21, "b": null } }, 27 | { "x": 85, "y": 50, "$raw": { "a": 85, "b": 50 } } 28 | ] 29 | } 30 | ] 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /viz-lib/src/visualizations/chart/index.ts: -------------------------------------------------------------------------------- 1 | import getOptions from "./getOptions"; 2 | import Renderer from "./Renderer"; 3 | import Editor from "./Editor"; 4 | 5 | export default { 6 | type: "CHART", 7 | name: "图表(Chart)", 8 | isDefault: true, 9 | getOptions, 10 | Renderer, 11 | Editor, 12 | 13 | defaultColumns: 3, 14 | defaultRows: 8, 15 | minColumns: 1, 16 | minRows: 5, 17 | }; 18 | -------------------------------------------------------------------------------- /viz-lib/src/visualizations/chart/plotly/fixtures/prepareData/heatmap/default.json: -------------------------------------------------------------------------------- 1 | { 2 | "input": { 3 | "options": { 4 | "globalSeriesType": "heatmap", 5 | "colorScheme": "Bluered", 6 | "seriesOptions": {}, 7 | "showDataLabels": false 8 | }, 9 | "data": [ 10 | { 11 | "name": "a", 12 | "data": [ 13 | { "x": 12, "y": 21, "zVal": 3 }, 14 | { "x": 11, "y": 22, "zVal": 2 }, 15 | { "x": 11, "y": 21, "zVal": 1 }, 16 | { "x": 12, "y": 22, "zVal": 4 } 17 | ] 18 | } 19 | ] 20 | }, 21 | "output": { 22 | "series": [ 23 | { 24 | "x": [12, 11], 25 | "y": [21, 22], 26 | "z": [[3, 1], [4, 2]], 27 | "type": "heatmap", 28 | "name": "", 29 | "colorscale": "Bluered" 30 | } 31 | ] 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /viz-lib/src/visualizations/chart/plotly/fixtures/prepareData/heatmap/reversed.json: -------------------------------------------------------------------------------- 1 | { 2 | "input": { 3 | "options": { 4 | "globalSeriesType": "heatmap", 5 | "colorScheme": "Bluered", 6 | "seriesOptions": {}, 7 | "showDataLabels": false, 8 | "reverseX": true, 9 | "reverseY": true 10 | }, 11 | "data": [ 12 | { 13 | "name": "a", 14 | "data": [ 15 | { "x": 12, "y": 21, "zVal": 3 }, 16 | { "x": 11, "y": 22, "zVal": 2 }, 17 | { "x": 11, "y": 21, "zVal": 1 }, 18 | { "x": 12, "y": 22, "zVal": 4 } 19 | ] 20 | } 21 | ] 22 | }, 23 | "output": { 24 | "series": [ 25 | { 26 | "x": [11, 12], 27 | "y": [22, 21], 28 | "z": [[2, 4], [1, 3]], 29 | "type": "heatmap", 30 | "name": "", 31 | "colorscale": "Bluered" 32 | } 33 | ] 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /viz-lib/src/visualizations/chart/plotly/fixtures/prepareData/heatmap/sorted.json: -------------------------------------------------------------------------------- 1 | { 2 | "input": { 3 | "options": { 4 | "globalSeriesType": "heatmap", 5 | "colorScheme": "Bluered", 6 | "seriesOptions": {}, 7 | "showDataLabels": false, 8 | "sortX": true, 9 | "sortY": true 10 | }, 11 | "data": [ 12 | { 13 | "name": "a", 14 | "data": [ 15 | { "x": 12, "y": 21, "zVal": 3 }, 16 | { "x": 11, "y": 22, "zVal": 2 }, 17 | { "x": 11, "y": 21, "zVal": 1 }, 18 | { "x": 12, "y": 22, "zVal": 4 } 19 | ] 20 | } 21 | ] 22 | }, 23 | "output": { 24 | "series": [ 25 | { 26 | "x": [11, 12], 27 | "y": [21, 22], 28 | "z": [[1, 3], [2, 4]], 29 | "type": "heatmap", 30 | "name": "", 31 | "colorscale": "Bluered" 32 | } 33 | ] 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /viz-lib/src/visualizations/chart/plotly/fixtures/prepareLayout/pie-without-annotations.json: -------------------------------------------------------------------------------- 1 | { 2 | "input": { 3 | "options": { 4 | "globalSeriesType": "pie", 5 | "textFormat": "{{ @@name }}", 6 | "legend": { 7 | "traceorder": "normal" 8 | } 9 | }, 10 | "series": [{ "name": "a" }] 11 | }, 12 | "output": { 13 | "layout": { 14 | "margin": { "l": 10, "r": 10, "b": 5, "t": 20, "pad": 4 }, 15 | "width": 400, 16 | "height": 300, 17 | "autosize": false, 18 | "showlegend": true, 19 | "legend": { 20 | "traceorder": "normal" 21 | }, 22 | "hoverlabel": { 23 | "namelength": -1 24 | }, 25 | "annotations": [] 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /viz-lib/src/visualizations/chart/plotly/fixtures/prepareLayout/pie.json: -------------------------------------------------------------------------------- 1 | { 2 | "input": { 3 | "options": { 4 | "globalSeriesType": "pie", 5 | "textFormat": "", 6 | "legend": { 7 | "traceorder": "normal" 8 | } 9 | }, 10 | "series": [{ "name": "a" }] 11 | }, 12 | "output": { 13 | "layout": { 14 | "margin": { "l": 10, "r": 10, "b": 5, "t": 20, "pad": 4 }, 15 | "width": 400, 16 | "height": 300, 17 | "autosize": false, 18 | "showlegend": true, 19 | "legend": { 20 | "traceorder": "normal" 21 | }, 22 | "hoverlabel": { 23 | "namelength": -1 24 | }, 25 | "annotations": [ 26 | { 27 | "x": 0.49, 28 | "y": 0.985, 29 | "xanchor": "center", 30 | "yanchor": "top", 31 | "text": "a", 32 | "showarrow": false 33 | } 34 | ] 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /viz-lib/src/visualizations/chart/plotly/prepareData.ts: -------------------------------------------------------------------------------- 1 | import preparePieData from "./preparePieData"; 2 | import prepareHeatmapData from "./prepareHeatmapData"; 3 | import prepareDefaultData from "./prepareDefaultData"; 4 | import updateData from "./updateData"; 5 | 6 | export default function prepareData(seriesList: any, options: any) { 7 | switch (options.globalSeriesType) { 8 | case "pie": 9 | return updateData(preparePieData(seriesList, options), options); 10 | case "heatmap": 11 | // @ts-expect-error ts-migrate(2554) FIXME: Expected 2 arguments, but got 1. 12 | return updateData(prepareHeatmapData(seriesList, options, options)); 13 | default: 14 | return updateData(prepareDefaultData(seriesList, options), options); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /viz-lib/src/visualizations/choropleth/ColorPalette.ts: -------------------------------------------------------------------------------- 1 | import { extend } from "lodash"; 2 | import ColorPalette from "@/visualizations/ColorPalette"; 3 | 4 | export default extend( 5 | { 6 | White: "#ffffff", 7 | Black: "#000000", 8 | "Light Gray": "#dddddd", 9 | }, 10 | ColorPalette 11 | ); 12 | -------------------------------------------------------------------------------- /viz-lib/src/visualizations/choropleth/Editor/index.ts: -------------------------------------------------------------------------------- 1 | import createTabbedEditor from "@/components/visualizations/editor/createTabbedEditor"; 2 | 3 | import GeneralSettings from "./GeneralSettings"; 4 | import ColorsSettings from "./ColorsSettings"; 5 | import FormatSettings from "./FormatSettings"; 6 | import BoundsSettings from "./BoundsSettings"; 7 | 8 | export default createTabbedEditor([ 9 | { key: "General", title: "通用", component: GeneralSettings }, 10 | { key: "Colors", title: "颜色", component: ColorsSettings }, 11 | { key: "Format", title: "格式化", component: FormatSettings }, 12 | { key: "Bounds", title: "边界", component: BoundsSettings }, 13 | ]); 14 | -------------------------------------------------------------------------------- /viz-lib/src/visualizations/choropleth/Renderer/renderer.less: -------------------------------------------------------------------------------- 1 | .choropleth-visualization-legend { 2 | padding: 3px; 3 | cursor: default; 4 | 5 | > div { 6 | line-height: 1; 7 | margin: 5px; 8 | } 9 | 10 | .legend-item { 11 | display: flex; 12 | align-items: center; 13 | 14 | .color-swatch { 15 | margin-right: 5px; 16 | } 17 | 18 | .legend-item-text { 19 | flex: 1 1 auto; 20 | 21 | &.text-left { 22 | text-align: left; 23 | } 24 | &.text-center { 25 | text-align: center; 26 | } 27 | &.text-right { 28 | text-align: right; 29 | } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /viz-lib/src/visualizations/choropleth/index.ts: -------------------------------------------------------------------------------- 1 | import getOptions from "./getOptions"; 2 | import Renderer from "./Renderer"; 3 | import Editor from "./Editor"; 4 | 5 | export default { 6 | type: "CHOROPLETH", 7 | name: "地理分布图(Choropleth Map)", 8 | getOptions, 9 | Renderer, 10 | Editor, 11 | 12 | defaultColumns: 3, 13 | defaultRows: 8, 14 | minColumns: 2, 15 | }; 16 | -------------------------------------------------------------------------------- /viz-lib/src/visualizations/cohort/Editor/index.ts: -------------------------------------------------------------------------------- 1 | import createTabbedEditor from "@/components/visualizations/editor/createTabbedEditor"; 2 | 3 | import ColumnsSettings from "./ColumnsSettings"; 4 | import OptionsSettings from "./OptionsSettings"; 5 | import ColorsSettings from "./ColorsSettings"; 6 | import AppearanceSettings from "./AppearanceSettings"; 7 | 8 | export default createTabbedEditor([ 9 | { key: "Columns", title: "列", component: ColumnsSettings }, 10 | { key: "Options", title: "选项", component: OptionsSettings }, 11 | { key: "Colors", title: "颜色", component: ColorsSettings }, 12 | { key: "Appearance", title: "外观", component: AppearanceSettings }, 13 | ]); 14 | -------------------------------------------------------------------------------- /viz-lib/src/visualizations/cohort/getOptions.ts: -------------------------------------------------------------------------------- 1 | import { merge } from "lodash"; 2 | import ColorPalette from "@/visualizations/ColorPalette"; 3 | 4 | const DEFAULT_OPTIONS = { 5 | timeInterval: "daily", 6 | mode: "diagonal", 7 | dateColumn: "date", 8 | stageColumn: "day_number", 9 | totalColumn: "total", 10 | valueColumn: "value", 11 | 12 | showTooltips: true, 13 | percentValues: true, 14 | 15 | timeColumnTitle: "Time", 16 | peopleColumnTitle: "Users", 17 | stageColumnTitle: "{{ @ }}", 18 | 19 | numberFormat: "0,0[.]00", 20 | percentFormat: "0.00%", 21 | noValuePlaceholder: "-", 22 | 23 | colors: { 24 | min: "#ffffff", 25 | max: ColorPalette["Dark Blue"], 26 | steps: 7, 27 | }, 28 | }; 29 | 30 | export default function getOptions(options: any) { 31 | return merge({}, DEFAULT_OPTIONS, options); 32 | } 33 | -------------------------------------------------------------------------------- /viz-lib/src/visualizations/cohort/index.ts: -------------------------------------------------------------------------------- 1 | import getOptions from "./getOptions"; 2 | import Renderer from "./Renderer"; 3 | import Editor from "./Editor"; 4 | 5 | export default { 6 | type: "COHORT", 7 | name: "同期群分析(Cohort)", 8 | getOptions, 9 | Renderer, 10 | Editor, 11 | 12 | autoHeight: true, 13 | defaultRows: 8, 14 | }; 15 | -------------------------------------------------------------------------------- /viz-lib/src/visualizations/cohort/renderer.less: -------------------------------------------------------------------------------- 1 | @import "../variables"; 2 | 3 | .cohort-visualization-container { 4 | .cornelius-table { 5 | width: 100%; 6 | 7 | &, 8 | tr, 9 | th, 10 | td { 11 | border-color: #f0f0f0; 12 | } 13 | 14 | .cornelius-time, 15 | .cornelius-label, 16 | .cornelius-stage, 17 | .cornelius-people { 18 | background-color: fade(@visualizations-gray, 3%); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /viz-lib/src/visualizations/counter/Editor/index.ts: -------------------------------------------------------------------------------- 1 | import createTabbedEditor from "@/components/visualizations/editor/createTabbedEditor"; 2 | 3 | import GeneralSettings from "./GeneralSettings"; 4 | import FormatSettings from "./FormatSettings"; 5 | 6 | export default createTabbedEditor([ 7 | { key: "General", title: "通用", component: GeneralSettings }, 8 | { key: "Format", title: "格式化", component: FormatSettings }, 9 | ]); 10 | -------------------------------------------------------------------------------- /viz-lib/src/visualizations/counter/index.ts: -------------------------------------------------------------------------------- 1 | import Renderer from "./Renderer"; 2 | import Editor from "./Editor"; 3 | 4 | const DEFAULT_OPTIONS = { 5 | counterLabel: "", 6 | counterColName: "counter", 7 | rowNumber: 1, 8 | targetRowNumber: 1, 9 | stringDecimal: 0, 10 | stringDecChar: ".", 11 | stringThouSep: ",", 12 | tooltipFormat: "0,0.000", // TODO: Show in editor 13 | }; 14 | 15 | export default { 16 | type: "COUNTER", 17 | name: "计数器(Counter)", 18 | getOptions: (options: any) => ({ 19 | ...DEFAULT_OPTIONS, 20 | ...options, 21 | }), 22 | Renderer, 23 | Editor, 24 | 25 | defaultColumns: 2, 26 | defaultRows: 5, 27 | }; 28 | -------------------------------------------------------------------------------- /viz-lib/src/visualizations/details/details.less: -------------------------------------------------------------------------------- 1 | .details-viz { 2 | .ant-descriptions-item-label { 3 | width: 1px; 4 | white-space: nowrap; 5 | } 6 | 7 | .paginator-container { 8 | margin-top: 10px; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /viz-lib/src/visualizations/details/index.ts: -------------------------------------------------------------------------------- 1 | import DetailsRenderer from "./DetailsRenderer"; 2 | 3 | const DEFAULT_OPTIONS = {}; 4 | 5 | export default { 6 | type: "DETAILS", 7 | name: "<记录明细显示>", 8 | getOptions: (options: any) => ({ 9 | ...DEFAULT_OPTIONS, 10 | ...options, 11 | }), 12 | Renderer: DetailsRenderer, 13 | defaultColumns: 2, 14 | defaultRows: 2, 15 | }; 16 | -------------------------------------------------------------------------------- /viz-lib/src/visualizations/funnel/Editor/index.ts: -------------------------------------------------------------------------------- 1 | import createTabbedEditor from "@/components/visualizations/editor/createTabbedEditor"; 2 | 3 | import GeneralSettings from "./GeneralSettings"; 4 | import AppearanceSettings from "./AppearanceSettings"; 5 | 6 | export default createTabbedEditor([ 7 | { key: "General", title: "通用", component: GeneralSettings }, 8 | { key: "Appearance", title: "外观", component: AppearanceSettings }, 9 | ]); 10 | -------------------------------------------------------------------------------- /viz-lib/src/visualizations/funnel/Renderer/FunnelBar.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import cx from "classnames"; 3 | 4 | import "./funnel-bar.less"; 5 | 6 | type OwnProps = { 7 | color?: string; 8 | value?: number; 9 | align?: "left" | "center" | "right"; 10 | className?: string; 11 | children?: React.ReactNode; 12 | }; 13 | 14 | type Props = OwnProps & typeof FunnelBar.defaultProps; 15 | 16 | export default function FunnelBar({ color, value, align, className, children }: Props) { 17 | return ( 18 |
19 |
20 |
{children}
21 |
22 | ); 23 | } 24 | 25 | FunnelBar.defaultProps = { 26 | color: "#dadada", 27 | value: 0.0, 28 | align: "left", 29 | className: null, 30 | children: null, 31 | }; 32 | -------------------------------------------------------------------------------- /viz-lib/src/visualizations/funnel/Renderer/funnel-bar.less: -------------------------------------------------------------------------------- 1 | .funnel-bar { 2 | @height: 30px; 3 | 4 | position: relative; 5 | height: @height; 6 | line-height: @height; 7 | 8 | &-left { 9 | text-align: left; 10 | } 11 | &-center { 12 | text-align: center; 13 | } 14 | &-right { 15 | text-align: right; 16 | } 17 | 18 | .funnel-bar-value { 19 | display: inline-block; 20 | vertical-align: top; 21 | height: @height; 22 | } 23 | 24 | .funnel-bar-label { 25 | display: inline-block; 26 | text-align: center; 27 | vertical-align: middle; 28 | position: absolute; 29 | left: 0; 30 | right: 0; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /viz-lib/src/visualizations/funnel/Renderer/index.less: -------------------------------------------------------------------------------- 1 | .funnel-visualization-container { 2 | table { 3 | min-width: 450px; 4 | table-layout: fixed; 5 | 6 | tbody tr td { 7 | border: none; 8 | 9 | &.text-ellipsis { 10 | white-space: nowrap; 11 | overflow: hidden; 12 | text-overflow: ellipsis; 13 | } 14 | } 15 | } 16 | .step { 17 | max-width: 0; 18 | white-space: nowrap; 19 | overflow: hidden; 20 | text-overflow: ellipsis; 21 | } 22 | .step .step-name { 23 | visibility: hidden; 24 | width: inherit; 25 | padding: 3px 5px; 26 | background-color: white; 27 | border: 1px solid; 28 | border-radius: 3px; 29 | position: absolute; 30 | z-index: 1; 31 | white-space: initial; 32 | word-wrap: break-word; 33 | } 34 | 35 | .step:hover .step-name { 36 | visibility: visible; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /viz-lib/src/visualizations/funnel/index.ts: -------------------------------------------------------------------------------- 1 | import getOptions from "./getOptions"; 2 | import Renderer from "./Renderer"; 3 | import Editor from "./Editor"; 4 | 5 | export default { 6 | type: "FUNNEL", 7 | name: "漏斗分析(Funnel)", 8 | getOptions, 9 | Renderer, 10 | Editor, 11 | 12 | defaultRows: 10, 13 | }; 14 | -------------------------------------------------------------------------------- /viz-lib/src/visualizations/index.ts: -------------------------------------------------------------------------------- 1 | import Renderer from "./Renderer"; 2 | import Editor from "./Editor"; 3 | 4 | export { Renderer, Editor }; 5 | -------------------------------------------------------------------------------- /viz-lib/src/visualizations/map/Editor/index.ts: -------------------------------------------------------------------------------- 1 | import createTabbedEditor from "@/components/visualizations/editor/createTabbedEditor"; 2 | 3 | import GeneralSettings from "./GeneralSettings"; 4 | import GroupsSettings from "./GroupsSettings"; 5 | import FormatSettings from "./FormatSettings"; 6 | import StyleSettings from "./StyleSettings"; 7 | 8 | export default createTabbedEditor([ 9 | { key: "General", title: "通用", component: GeneralSettings }, 10 | { key: "Groups", title: "角色", component: GroupsSettings }, 11 | { key: "Format", title: "格式化", component: FormatSettings }, 12 | { key: "Style", title: "样式", component: StyleSettings }, 13 | ]); 14 | -------------------------------------------------------------------------------- /viz-lib/src/visualizations/map/index.ts: -------------------------------------------------------------------------------- 1 | import getOptions from "./getOptions"; 2 | import Renderer from "./Renderer"; 3 | import Editor from "./Editor"; 4 | 5 | export default { 6 | type: "MAP", 7 | name: "地理标记(Map Markers)", 8 | getOptions, 9 | Renderer, 10 | Editor, 11 | 12 | defaultColumns: 3, 13 | defaultRows: 8, 14 | minColumns: 2, 15 | }; 16 | -------------------------------------------------------------------------------- /viz-lib/src/visualizations/pivot/index.ts: -------------------------------------------------------------------------------- 1 | import { merge } from "lodash"; 2 | 3 | import Renderer from "./Renderer"; 4 | import Editor from "./Editor"; 5 | 6 | const DEFAULT_OPTIONS = { 7 | controls: { 8 | enabled: false, // `false` means "show controls" o_O 9 | }, 10 | rendererOptions: { 11 | table: { 12 | colTotals: true, 13 | rowTotals: true, 14 | }, 15 | }, 16 | }; 17 | 18 | export default { 19 | type: "PIVOT", 20 | name: "数据透视表(Pivot Table)", 21 | getOptions: (options: any) => merge({}, DEFAULT_OPTIONS, options), 22 | Renderer, 23 | Editor, 24 | 25 | defaultRows: 10, 26 | defaultColumns: 3, 27 | minColumns: 2, 28 | }; 29 | -------------------------------------------------------------------------------- /viz-lib/src/visualizations/sankey/Editor.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export default function Editor() { 4 | return ( 5 | 6 |

该视图要求查询结果包含下列行格式:

7 |
    8 |
  • 9 | stage1 - 层级1的值 10 |
  • 11 |
  • 12 | stage2 - 层级2的值(可空) 13 |
  • 14 |
  • 15 | stage3 - 层级3的值(可空) 16 |
  • 17 |
  • 18 | stage4 - 层级4的值(可空) 19 |
  • 20 |
  • 21 | stage5 - 层级5的值(可空) 22 |
  • 23 |
  • 24 | value - 数值 25 |
  • 26 |
27 |
28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /viz-lib/src/visualizations/sankey/index.ts: -------------------------------------------------------------------------------- 1 | import Renderer from "./Renderer"; 2 | import Editor from "./Editor"; 3 | export interface SankeyDataType { 4 | columns: { 5 | name: string; 6 | friendly_name: string; 7 | type: "integer"; 8 | }[]; 9 | 10 | rows: { 11 | value: number; 12 | [name: string]: number | string | null; 13 | }[]; 14 | } 15 | 16 | export default { 17 | type: "SANKEY", 18 | name: "桑基图(Sankey)", 19 | getOptions: (options: {}) => ({ 20 | ...options, 21 | }), 22 | Renderer, 23 | Editor, 24 | 25 | defaultRows: 7, 26 | }; 27 | -------------------------------------------------------------------------------- /viz-lib/src/visualizations/sankey/renderer.less: -------------------------------------------------------------------------------- 1 | /* Sankey Visualization */ 2 | .sankey .node rect { 3 | fill-opacity: .9; 4 | shape-rendering: crispEdges; 5 | stroke-width: 0; 6 | } 7 | .sankey .node text { 8 | text-shadow: 0 1px 0 #fff; 9 | } 10 | .sankey .link { 11 | fill: none; 12 | stroke: #000; 13 | stroke-opacity: .2; 14 | } 15 | 16 | .sankey-visualization-container { 17 | height: 500px; 18 | overflow: hidden; 19 | } 20 | -------------------------------------------------------------------------------- /viz-lib/src/visualizations/sunburst/index.ts: -------------------------------------------------------------------------------- 1 | import Renderer from "./Renderer"; 2 | import Editor from "./Editor"; 3 | 4 | export default { 5 | type: "SUNBURST_SEQUENCE", 6 | name: "旭辉图(Sunburst Sequence)", 7 | getOptions: (options: any) => ({ 8 | ...options, 9 | }), 10 | Renderer, 11 | Editor, 12 | 13 | defaultRows: 7, 14 | }; 15 | -------------------------------------------------------------------------------- /viz-lib/src/visualizations/sunburst/renderer.less: -------------------------------------------------------------------------------- 1 | .sunburst-visualization-container { 2 | height: 400px; 3 | display: flex; 4 | flex-direction: column; 5 | 6 | > div { 7 | position: relative; 8 | 9 | &:first-child { 10 | flex-grow: 0; 11 | } 12 | &:last-child { 13 | flex-grow: 1; 14 | } 15 | } 16 | 17 | .sunburst-container, 18 | .summary-container { 19 | position: absolute; 20 | left: 0; 21 | top: 0; 22 | right: 0; 23 | bottom: 0; 24 | width: auto; 25 | height: auto; 26 | display: flex; 27 | flex-direction: column; 28 | align-items: center; 29 | justify-content: center; 30 | } 31 | 32 | .summary-container { 33 | font-size: 11px; 34 | color: #666; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /viz-lib/src/visualizations/table/Editor/__snapshots__/GridSettings.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Visualizations -> Table -> Editor -> Grid Settings Changes items per page 1`] = ` 4 | Object { 5 | "itemsPerPage": 100, 6 | } 7 | `; 8 | -------------------------------------------------------------------------------- /viz-lib/src/visualizations/table/Editor/editor.less: -------------------------------------------------------------------------------- 1 | .table-visualization-editor-columns { 2 | .ant-collapse { 3 | background: transparent; 4 | } 5 | 6 | .ant-collapse-item { 7 | background: #ffffff; 8 | 9 | .drag-handle { 10 | height: 20px; 11 | margin-left: -16px; 12 | padding: 0 16px; 13 | } 14 | } 15 | 16 | .table-editor-columns-dragged-item { 17 | z-index: 1; 18 | } 19 | } 20 | 21 | .table-visualization-editor-column { 22 | padding-left: 6px; 23 | 24 | .image-dimension-selector { 25 | display: flex; 26 | align-items: center; 27 | 28 | .image-dimension-selector-spacer { 29 | padding-left: 5px; 30 | padding-right: 5px; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /viz-lib/src/visualizations/table/Editor/index.tsx: -------------------------------------------------------------------------------- 1 | import createTabbedEditor from "@/components/visualizations/editor/createTabbedEditor"; 2 | 3 | import ColumnsSettings from "./ColumnsSettings"; 4 | import GridSettings from "./GridSettings"; 5 | 6 | import "./editor.less"; 7 | 8 | export default createTabbedEditor([ 9 | { key: "Columns", title: "列设置", component: ColumnsSettings }, 10 | { key: "Grid", title: "表格设置", component: GridSettings }, 11 | ]); 12 | -------------------------------------------------------------------------------- /viz-lib/src/visualizations/table/columns/__snapshots__/boolean.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Visualizations -> Table -> Columns -> Boolean Editor Changes value for FALSE 1`] = ` 4 | Object { 5 | "booleanValues": Array [ 6 | "no", 7 | "true", 8 | ], 9 | } 10 | `; 11 | 12 | exports[`Visualizations -> Table -> Columns -> Boolean Editor Changes value for TRUE 1`] = ` 13 | Object { 14 | "booleanValues": Array [ 15 | "false", 16 | "yes", 17 | ], 18 | } 19 | `; 20 | -------------------------------------------------------------------------------- /viz-lib/src/visualizations/table/columns/__snapshots__/datetime.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Visualizations -> Table -> Columns -> Date/Time Editor Changes format 1`] = ` 4 | Object { 5 | "dateTimeFormat": "YYYY/MM/DD HH:ss", 6 | } 7 | `; 8 | -------------------------------------------------------------------------------- /viz-lib/src/visualizations/table/columns/__snapshots__/image.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Visualizations -> Table -> Columns -> Image Editor Changes URL template 1`] = ` 4 | Object { 5 | "imageUrlTemplate": "http://{{ @ }}.jpeg", 6 | } 7 | `; 8 | 9 | exports[`Visualizations -> Table -> Columns -> Image Editor Changes height 1`] = ` 10 | Object { 11 | "imageHeight": "300", 12 | } 13 | `; 14 | 15 | exports[`Visualizations -> Table -> Columns -> Image Editor Changes title template 1`] = ` 16 | Object { 17 | "imageTitleTemplate": "Image {{ @ }}", 18 | } 19 | `; 20 | 21 | exports[`Visualizations -> Table -> Columns -> Image Editor Changes width 1`] = ` 22 | Object { 23 | "imageWidth": "400", 24 | } 25 | `; 26 | -------------------------------------------------------------------------------- /viz-lib/src/visualizations/table/columns/__snapshots__/link.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Visualizations -> Table -> Columns -> Link Editor Changes URL template 1`] = ` 4 | Object { 5 | "linkUrlTemplate": "http://{{ @ }}/index.html", 6 | } 7 | `; 8 | 9 | exports[`Visualizations -> Table -> Columns -> Link Editor Changes text template 1`] = ` 10 | Object { 11 | "linkTextTemplate": "Text of {{ @ }}", 12 | } 13 | `; 14 | 15 | exports[`Visualizations -> Table -> Columns -> Link Editor Changes title template 1`] = ` 16 | Object { 17 | "linkTitleTemplate": "Title of {{ @ }}", 18 | } 19 | `; 20 | 21 | exports[`Visualizations -> Table -> Columns -> Link Editor Makes link open in new tab 1`] = ` 22 | Object { 23 | "linkOpenInNewTab": true, 24 | } 25 | `; 26 | -------------------------------------------------------------------------------- /viz-lib/src/visualizations/table/columns/__snapshots__/number.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Visualizations -> Table -> Columns -> Number Editor Changes format 1`] = ` 4 | Object { 5 | "numberFormat": "0.00%", 6 | } 7 | `; 8 | -------------------------------------------------------------------------------- /viz-lib/src/visualizations/table/columns/__snapshots__/text.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Visualizations -> Table -> Columns -> Text Editor Enables HTML content 1`] = ` 4 | Object { 5 | "allowHTML": true, 6 | } 7 | `; 8 | 9 | exports[`Visualizations -> Table -> Columns -> Text Editor Enables highlight links option 1`] = ` 10 | Object { 11 | "highlightLinks": true, 12 | } 13 | `; 14 | -------------------------------------------------------------------------------- /viz-lib/src/visualizations/table/columns/index.ts: -------------------------------------------------------------------------------- 1 | import initTextColumn from "./text"; 2 | import initNumberColumn from "./number"; 3 | import initDateTimeColumn from "./datetime"; 4 | import initBooleanColumn from "./boolean"; 5 | import initLinkColumn from "./link"; 6 | import initImageColumn from "./image"; 7 | import initJsonColumn from "./json"; 8 | 9 | // this map should contain all possible values for `column.displayAs` property 10 | export default { 11 | string: initTextColumn, 12 | number: initNumberColumn, 13 | datetime: initDateTimeColumn, 14 | boolean: initBooleanColumn, 15 | link: initLinkColumn, 16 | image: initImageColumn, 17 | json: initJsonColumn, 18 | }; 19 | -------------------------------------------------------------------------------- /viz-lib/src/visualizations/table/index.ts: -------------------------------------------------------------------------------- 1 | import getOptions from "./getOptions"; 2 | import Renderer from "./Renderer"; 3 | import Editor from "./Editor"; 4 | 5 | export default { 6 | type: "TABLE", 7 | name: "表格(Table)", 8 | getOptions, 9 | Renderer, 10 | Editor, 11 | 12 | autoHeight: true, 13 | defaultRows: 14, 14 | defaultColumns: 3, 15 | minColumns: 2, 16 | }; 17 | -------------------------------------------------------------------------------- /viz-lib/src/visualizations/word-cloud/index.ts: -------------------------------------------------------------------------------- 1 | import { merge } from "lodash"; 2 | 3 | import Renderer from "./Renderer"; 4 | import Editor from "./Editor"; 5 | 6 | const DEFAULT_OPTIONS = { 7 | column: "", 8 | frequenciesColumn: "", 9 | wordLengthLimit: { min: null, max: null }, 10 | wordCountLimit: { min: null, max: null }, 11 | }; 12 | 13 | export default { 14 | type: "WORD_CLOUD", 15 | name: "词云图(Word Cloud)", 16 | getOptions: (options: any) => merge({}, DEFAULT_OPTIONS, options), 17 | Renderer, 18 | Editor, 19 | 20 | defaultRows: 8, 21 | }; 22 | -------------------------------------------------------------------------------- /viz-lib/src/visualizations/word-cloud/renderer.less: -------------------------------------------------------------------------------- 1 | .word-cloud-visualization-container { 2 | overflow: hidden; 3 | height: 400px; 4 | display: flex; 5 | align-items: center; 6 | justify-content: center; 7 | 8 | svg { 9 | transform-origin: center center; 10 | flex: 0 0 auto; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /viz-lib/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2019", 4 | "module": "commonjs", 5 | "moduleResolution": "node", 6 | "lib": ["dom", "dom.iterable", "esnext"], 7 | "jsx": "react", 8 | "types": ["node", "jest"], 9 | "outDir": "lib", 10 | "declaration": true, 11 | "strict": true, 12 | "esModuleInterop": true, 13 | "allowSyntheticDefaultImports": true, 14 | "skipLibCheck": true, 15 | "forceConsistentCasingInFileNames": true, 16 | "baseUrl": "./", 17 | "paths": { 18 | "@/*": ["./src/*"] 19 | } 20 | }, 21 | "include": ["src/**/*"] 22 | } 23 | -------------------------------------------------------------------------------- /worker.conf: -------------------------------------------------------------------------------- 1 | [supervisord] 2 | logfile=/dev/null 3 | pidfile=/tmp/supervisord.pid 4 | nodaemon=true 5 | 6 | [unix_http_server] 7 | file = /tmp/supervisor.sock 8 | 9 | [rpcinterface:supervisor] 10 | supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface 11 | 12 | [program:worker] 13 | command=./manage.py rq worker %(ENV_QUEUES)s 14 | process_name=%(program_name)s-%(process_num)s 15 | numprocs=%(ENV_WORKERS_COUNT)s 16 | directory=/app 17 | stopsignal=TERM 18 | autostart=true 19 | autorestart=true 20 | startsecs=300 21 | stdout_logfile=/dev/stdout 22 | stdout_logfile_maxbytes=0 23 | stderr_logfile=/dev/stderr 24 | stderr_logfile_maxbytes=0 25 | 26 | [eventlistener:worker_healthcheck] 27 | serverurl=AUTO 28 | command=./manage.py rq healthcheck 29 | stdout_logfile=/dev/stdout 30 | stdout_logfile_maxbytes=0 31 | stderr_logfile=/dev/stderr 32 | stderr_logfile_maxbytes=0 33 | events=TICK_60 --------------------------------------------------------------------------------