diff --git a/client/app/pages/admin/Jobs.jsx b/client/app/pages/admin/Jobs.jsx
index 4516986c77..fa6e0a31c3 100644
--- a/client/app/pages/admin/Jobs.jsx
+++ b/client/app/pages/admin/Jobs.jsx
@@ -1,22 +1,22 @@
import { flatMap, values } from "lodash";
import React from "react";
import { axios } from "@/services/axios";
-import { react2angular } from "react2angular";
import Alert from "antd/lib/alert";
import Tabs from "antd/lib/tabs";
import * as Grid from "antd/lib/grid";
+import AuthenticatedPageWrapper from "@/components/ApplicationArea/AuthenticatedPageWrapper";
import Layout from "@/components/admin/Layout";
+import { ErrorBoundaryContext } from "@/components/ErrorBoundary";
import { CounterCard, WorkersTable, QueuesTable, OtherJobsTable } from "@/components/admin/RQStatus";
-import { $location, $rootScope } from "@/services/ng";
+import location from "@/services/location";
import recordEvent from "@/services/recordEvent";
-import { routesToAngularRoutes } from "@/lib/utils";
import moment from "moment";
class Jobs extends React.Component {
state = {
- activeTab: $location.hash(),
+ activeTab: location.hash,
isLoading: true,
error: null,
@@ -82,8 +82,7 @@ class Jobs extends React.Component {
const { isLoading, error, queueCounters, startedJobs, overallCounters, workers, activeTab } = this.state;
const changeTab = newTab => {
- $location.hash(newTab);
- $rootScope.$applyAsync();
+ location.setHash(newTab);
this.setState({ activeTab: newTab });
};
@@ -122,21 +121,14 @@ class Jobs extends React.Component {
}
}
-export default function init(ngModule) {
- ngModule.component("pageJobs", react2angular(Jobs));
-
- return routesToAngularRoutes(
- [
- {
- path: "/admin/queries/jobs",
- title: "RQ Status",
- key: "jobs",
- },
- ],
- {
- template: "
",
- }
- );
-}
-
-init.init = true;
+export default {
+ path: "/admin/queries/jobs",
+ title: "RQ Status",
+ render: currentRoute => (
+
+
+ {({ handleError }) => }
+
+
+ ),
+};
diff --git a/client/app/pages/admin/OutdatedQueries.jsx b/client/app/pages/admin/OutdatedQueries.jsx
index d04c8aa2e9..a955700e46 100644
--- a/client/app/pages/admin/OutdatedQueries.jsx
+++ b/client/app/pages/admin/OutdatedQueries.jsx
@@ -1,15 +1,16 @@
import { map } from "lodash";
import React from "react";
import { axios } from "@/services/axios";
-import { react2angular } from "react2angular";
import Switch from "antd/lib/switch";
import * as Grid from "antd/lib/grid";
+import AuthenticatedPageWrapper from "@/components/ApplicationArea/AuthenticatedPageWrapper";
import Paginator from "@/components/Paginator";
import { QueryTagsControl } from "@/components/tags-control/TagsControl";
import SchedulePhrase from "@/components/queries/SchedulePhrase";
import TimeAgo from "@/components/TimeAgo";
import Layout from "@/components/admin/Layout";
+import { ErrorBoundaryContext } from "@/components/ErrorBoundary";
import { wrap as itemsList, ControllerType } from "@/components/items-list/ItemsList";
import { ItemsSource } from "@/components/items-list/classes/ItemsSource";
@@ -21,7 +22,6 @@ import ItemsTable, { Columns } from "@/components/items-list/components/ItemsTab
import { Query } from "@/services/query";
import recordEvent from "@/services/recordEvent";
-import { routesToAngularRoutes } from "@/lib/utils";
class OutdatedQueries extends React.Component {
static propTypes = {
@@ -147,51 +147,43 @@ class OutdatedQueries extends React.Component {
}
}
-export default function init(ngModule) {
- ngModule.component(
- "pageOutdatedQueries",
- react2angular(
- itemsList(
- OutdatedQueries,
- new ItemsSource({
- doRequest(request, context) {
- return (
- axios
- .get("/api/admin/queries/outdated")
- // eslint-disable-next-line camelcase
- .then(({ queries, updated_at }) => {
- context.setCustomParams({ lastUpdatedAt: parseFloat(updated_at) });
- return queries;
- })
- );
- },
- processResults(items) {
- return map(items, item => new Query(item));
- },
- isPlainList: true,
- }),
- new StateStorage({ orderByField: "created_at", orderByReverse: true })
- )
- )
- );
-
- return routesToAngularRoutes(
- [
- {
- path: "/admin/queries/outdated",
- title: "Outdated Queries",
- key: "outdated_queries",
+const OutdatedQueriesPage = itemsList(
+ OutdatedQueries,
+ () =>
+ new ItemsSource({
+ doRequest(request, context) {
+ return (
+ axios
+ .get("/api/admin/queries/outdated")
+ // eslint-disable-next-line camelcase
+ .then(({ queries, updated_at }) => {
+ context.setCustomParams({ lastUpdatedAt: parseFloat(updated_at) });
+ return queries;
+ })
+ );
},
- ],
- {
- template: '
',
- controller($scope, $exceptionHandler) {
- "ngInject";
-
- $scope.handleError = $exceptionHandler;
+ processResults(items) {
+ return map(items, item => new Query(item));
},
- }
- );
-}
-
-init.init = true;
+ isPlainList: true,
+ }),
+ () => new StateStorage({ orderByField: "created_at", orderByReverse: true })
+);
+
+export default {
+ path: "/admin/queries/outdated",
+ title: "Outdated Queries",
+ render: currentRoute => (
+
+
+ {({ handleError }) => (
+
+ )}
+
+
+ ),
+};
diff --git a/client/app/pages/admin/SystemStatus.jsx b/client/app/pages/admin/SystemStatus.jsx
index 8b2cd1dd52..6b023ca89e 100644
--- a/client/app/pages/admin/SystemStatus.jsx
+++ b/client/app/pages/admin/SystemStatus.jsx
@@ -2,14 +2,14 @@ import { omit } from "lodash";
import React from "react";
import { axios } from "@/services/axios";
import PropTypes from "prop-types";
-import { react2angular } from "react2angular";
+import AuthenticatedPageWrapper from "@/components/ApplicationArea/AuthenticatedPageWrapper";
import Layout from "@/components/admin/Layout";
+import { ErrorBoundaryContext } from "@/components/ErrorBoundary";
import * as StatusBlock from "@/components/admin/StatusBlock";
import recordEvent from "@/services/recordEvent";
import PromiseRejectionError from "@/lib/promise-rejection-error";
-import { routesToAngularRoutes } from "@/lib/utils";
import "./system-status.less";
@@ -56,11 +56,7 @@ class SystemStatus extends React.Component {
});
})
.catch(error => {
- // ANGULAR_REMOVE_ME This code is related to Angular's HTTP services
- if (error.status && error.data) {
- error = new PromiseRejectionError(error);
- }
- this.props.onError(error);
+ this.props.onError(new PromiseRejectionError(error));
});
this._refreshTimer = setTimeout(this.refresh, 60 * 1000);
};
@@ -89,26 +85,14 @@ class SystemStatus extends React.Component {
}
}
-export default function init(ngModule) {
- ngModule.component("pageSystemStatus", react2angular(SystemStatus));
-
- return routesToAngularRoutes(
- [
- {
- path: "/admin/status",
- title: "System Status",
- key: "system_status",
- },
- ],
- {
- template: '
',
- controller($scope, $exceptionHandler) {
- "ngInject";
-
- $scope.handleError = $exceptionHandler;
- },
- }
- );
-}
-
-init.init = true;
+export default {
+ path: "/admin/status",
+ title: "System Status",
+ render: currentRoute => (
+
+
+ {({ handleError }) => }
+
+
+ ),
+};
diff --git a/client/app/pages/alert/Alert.jsx b/client/app/pages/alert/Alert.jsx
index dcdc8ff82c..9fbac72a21 100644
--- a/client/app/pages/alert/Alert.jsx
+++ b/client/app/pages/alert/Alert.jsx
@@ -1,10 +1,11 @@
+import { head, includes, trim, template, values } from "lodash";
import React from "react";
-import { react2angular } from "react2angular";
-import { head, includes, trim, template } from "lodash";
+import PropTypes from "prop-types";
-import { $route } from "@/services/ng";
import { currentUser } from "@/services/auth";
-import navigateTo from "@/services/navigateTo";
+import AuthenticatedPageWrapper from "@/components/ApplicationArea/AuthenticatedPageWrapper";
+import navigateTo from "@/components/ApplicationArea/navigateTo";
+import { ErrorBoundaryContext } from "@/components/ErrorBoundary";
import notification from "@/services/notification";
import AlertService from "@/services/alert";
import { Query as QueryService } from "@/services/query";
@@ -15,7 +16,6 @@ import AlertView from "./AlertView";
import AlertEdit from "./AlertEdit";
import AlertNew from "./AlertNew";
-import { routesToAngularRoutes } from "@/lib/utils";
import PromiseRejectionError from "@/lib/promise-rejection-error";
const MODES = {
@@ -34,6 +34,18 @@ export function getDefaultName(alert) {
}
class AlertPage extends React.Component {
+ static propTypes = {
+ mode: PropTypes.oneOf(values(MODES)),
+ alertId: PropTypes.string,
+ onError: PropTypes.func,
+ };
+
+ static defaultProps = {
+ mode: null,
+ alertId: null,
+ onError: () => {},
+ };
+
_isMounted = false;
state = {
@@ -46,7 +58,7 @@ class AlertPage extends React.Component {
componentDidMount() {
this._isMounted = true;
- const { mode } = $route.current.locals;
+ const { mode } = this.props;
this.setState({ mode });
if (mode === MODES.NEW) {
@@ -62,7 +74,7 @@ class AlertPage extends React.Component {
canEdit: true,
});
} else {
- const { alertId } = $route.current.params;
+ const { alertId } = this.props;
AlertService.get({ id: alertId })
.then(alert => {
if (this._isMounted) {
@@ -84,7 +96,7 @@ class AlertPage extends React.Component {
})
.catch(err => {
if (this._isMounted) {
- throw new PromiseRejectionError(err);
+ this.props.onError(new PromiseRejectionError(err));
}
});
}
@@ -103,7 +115,7 @@ class AlertPage extends React.Component {
return AlertService.save(alert)
.then(alert => {
notification.success("Saved.");
- navigateTo(`/alerts/${alert.id}`, true, false);
+ navigateTo(`alerts/${alert.id}`, true);
this.setState({ alert, mode: MODES.VIEW });
})
.catch(() => {
@@ -159,7 +171,7 @@ class AlertPage extends React.Component {
return AlertService.delete(alert)
.then(() => {
notification.success("Alert deleted successfully.");
- navigateTo("/alerts");
+ navigateTo("alerts");
})
.catch(() => {
notification.error("Failed deleting alert.");
@@ -192,13 +204,13 @@ class AlertPage extends React.Component {
edit = () => {
const { id } = this.state.alert;
- navigateTo(`/alerts/${id}/edit`, true, false);
+ navigateTo(`alerts/${id}/edit`, true);
this.setState({ mode: MODES.EDIT });
};
cancel = () => {
const { id } = this.state.alert;
- navigateTo(`/alerts/${id}`, true, false);
+ navigateTo(`alerts/${id}`, true);
this.setState({ mode: MODES.VIEW });
};
@@ -229,47 +241,51 @@ class AlertPage extends React.Component {
};
return (
-
- {mode === MODES.NEW &&
}
- {mode === MODES.VIEW && (
-
- )}
- {mode === MODES.EDIT &&
}
+
+
+ {mode === MODES.NEW &&
}
+ {mode === MODES.VIEW && (
+
+ )}
+ {mode === MODES.EDIT &&
}
+
);
}
}
-export default function init(ngModule) {
- ngModule.component("alertPage", react2angular(AlertPage));
-
- return routesToAngularRoutes(
- [
- {
- path: "/alerts/new",
- title: "New Alert",
- mode: MODES.NEW,
- },
- {
- path: "/alerts/:alertId",
- title: "Alert",
- mode: MODES.VIEW,
- },
- {
- path: "/alerts/:alertId/edit",
- title: "Alert",
- mode: MODES.EDIT,
- },
- ],
- {
- template: "
",
- controller($scope, $exceptionHandler) {
- "ngInject";
-
- $scope.handleError = $exceptionHandler;
- },
- }
- );
-}
-
-init.init = true;
+export default [
+ {
+ path: "/alerts/new",
+ title: "New Alert",
+ render: currentRoute => (
+
+
+ {({ handleError }) => }
+
+
+ ),
+ },
+ {
+ path: "/alerts/:alertId([0-9]+)",
+ title: "Alert",
+ render: currentRoute => (
+
+
+ {({ handleError }) => }
+
+
+ ),
+ },
+ {
+ path: "/alerts/:alertId([0-9]+)/edit",
+ title: "Alert",
+ render: currentRoute => (
+
+
+ {({ handleError }) => }
+
+
+ ),
+ },
+];
diff --git a/client/app/pages/alert/components/AlertDestinations.jsx b/client/app/pages/alert/components/AlertDestinations.jsx
index 2fa581aac4..de5cffee0f 100644
--- a/client/app/pages/alert/components/AlertDestinations.jsx
+++ b/client/app/pages/alert/components/AlertDestinations.jsx
@@ -127,7 +127,7 @@ export default class AlertDestinations extends React.Component {
notification.error("Failed saving subscription.");
});
},
- });
+ }).result.catch(() => {}); // ignore dismiss
};
onUserEmailToggle = sub => {
diff --git a/client/app/pages/alerts/AlertsList.jsx b/client/app/pages/alerts/AlertsList.jsx
index 92065ecb5a..620a2aa3ea 100644
--- a/client/app/pages/alerts/AlertsList.jsx
+++ b/client/app/pages/alerts/AlertsList.jsx
@@ -1,11 +1,10 @@
-import React from "react";
-import { react2angular } from "react2angular";
-
import { toUpper } from "lodash";
+import React from "react";
+import AuthenticatedPageWrapper from "@/components/ApplicationArea/AuthenticatedPageWrapper";
import PageHeader from "@/components/PageHeader";
import Paginator from "@/components/Paginator";
import EmptyState from "@/components/empty-state/EmptyState";
-
+import { ErrorBoundaryContext } from "@/components/ErrorBoundary";
import { wrap as liveItemsList, ControllerType } from "@/components/items-list/ItemsList";
import { ResourceItemsSource } from "@/components/items-list/classes/ItemsSource";
import { StateStorage } from "@/components/items-list/classes/StateStorage";
@@ -14,7 +13,6 @@ import LoadingState from "@/components/items-list/components/LoadingState";
import ItemsTable, { Columns } from "@/components/items-list/components/ItemsTable";
import Alert from "@/services/alert";
-import { routesToAngularRoutes } from "@/lib/utils";
export const STATE_CLASS = {
unknown: "label-warning",
@@ -70,79 +68,73 @@ class AlertsList extends React.Component {
const { controller } = this.props;
return (
-
-
-
- {!controller.isLoaded &&
}
- {controller.isLoaded && controller.isEmpty && (
-
- )}
- {controller.isLoaded && !controller.isEmpty && (
-
-
-
controller.updatePagination({ page })}
+
+
+
+
+ {!controller.isLoaded && }
+ {controller.isLoaded && controller.isEmpty && (
+
-
- )}
+ )}
+ {controller.isLoaded && !controller.isEmpty && (
+
+
+
controller.updatePagination({ page })}
+ />
+
+ )}
+
);
}
}
-export default function init(ngModule) {
- ngModule.component(
- "pageAlertsList",
- react2angular(
- liveItemsList(
- AlertsList,
- new ResourceItemsSource({
- isPlainList: true,
- getRequest() {
- return {};
- },
- getResource() {
- return Alert.query.bind(Alert);
- },
- }),
- new StateStorage({ orderByField: "created_at", orderByReverse: true, itemsPerPage: 20 })
- )
- )
- );
- return routesToAngularRoutes(
- [
- {
- path: "/alerts",
- title: "Alerts",
- key: "alerts",
+const AlertsListPage = liveItemsList(
+ AlertsList,
+ () =>
+ new ResourceItemsSource({
+ isPlainList: true,
+ getRequest() {
+ return {};
},
- ],
- {
- reloadOnSearch: false,
- template: '
',
- controller($scope, $exceptionHandler) {
- "ngInject";
-
- $scope.handleError = $exceptionHandler;
+ getResource() {
+ return Alert.query.bind(Alert);
},
- }
- );
-}
+ }),
+ () => new StateStorage({ orderByField: "created_at", orderByReverse: true, itemsPerPage: 20 })
+);
-init.init = true;
+export default {
+ path: "/alerts",
+ title: "Alerts",
+ render: currentRoute => (
+
+
+ {({ handleError }) => (
+
+ )}
+
+
+ ),
+};
diff --git a/client/app/pages/dashboards/DashboardList.jsx b/client/app/pages/dashboards/DashboardList.jsx
index 967acb23c2..5c84d7f593 100644
--- a/client/app/pages/dashboards/DashboardList.jsx
+++ b/client/app/pages/dashboards/DashboardList.jsx
@@ -1,6 +1,6 @@
import React from "react";
-import { react2angular } from "react2angular";
+import AuthenticatedPageWrapper from "@/components/ApplicationArea/AuthenticatedPageWrapper";
import PageHeader from "@/components/PageHeader";
import Paginator from "@/components/Paginator";
import { DashboardTagsControl } from "@/components/tags-control/TagsControl";
@@ -8,7 +8,7 @@ import { DashboardTagsControl } from "@/components/tags-control/TagsControl";
import { wrap as itemsList, ControllerType } from "@/components/items-list/ItemsList";
import { ResourceItemsSource } from "@/components/items-list/classes/ItemsSource";
import { UrlStateStorage } from "@/components/items-list/classes/StateStorage";
-
+import { ErrorBoundaryContext } from "@/components/ErrorBoundary";
import LoadingState from "@/components/items-list/components/LoadingState";
import * as Sidebar from "@/components/items-list/components/Sidebar";
import ItemsTable, { Columns } from "@/components/items-list/components/ItemsTable";
@@ -16,7 +16,6 @@ import ItemsTable, { Columns } from "@/components/items-list/components/ItemsTab
import Layout from "@/components/layouts/ContentWithSidebar";
import { Dashboard } from "@/services/dashboard";
-import { routesToAngularRoutes } from "@/lib/utils";
import DashboardListEmptyState from "./DashboardListEmptyState";
@@ -75,106 +74,113 @@ class DashboardList extends React.Component {
render() {
const { controller } = this.props;
return (
-
-
-
-
-
-
-
- controller.updatePagination({ itemsPerPage })}
- />
-
-
- {controller.isLoaded ? (
-
- {controller.isEmpty ? (
-
- ) : (
-
-
-
controller.updatePagination({ page })}
+
+
+
+
+
+
+
+
+ controller.updatePagination({ itemsPerPage })}
+ />
+
+
+ {controller.isLoaded ? (
+
+ {controller.isEmpty ? (
+
-
- )}
-
- ) : (
-
- )}
-
-
+ ) : (
+
+
+
controller.updatePagination({ page })}
+ />
+
+ )}
+
+ ) : (
+
+ )}
+
+
+
);
}
}
-export default function init(ngModule) {
- ngModule.component(
- "pageDashboardList",
- react2angular(
- itemsList(
- DashboardList,
- new ResourceItemsSource({
- getResource({ params: { currentPage } }) {
- return {
- all: Dashboard.query.bind(Dashboard),
- favorites: Dashboard.favorites.bind(Dashboard),
- }[currentPage];
- },
- getItemProcessor() {
- return item => new Dashboard(item);
- },
- }),
- new UrlStateStorage({ orderByField: "created_at", orderByReverse: true })
- )
- )
- );
-
- return routesToAngularRoutes(
- [
- {
- path: "/dashboards",
- title: "Dashboards",
- key: "all",
- },
- {
- path: "/dashboards/favorites",
- title: "Favorite Dashboards",
- key: "favorites",
+const DashboardListPage = itemsList(
+ DashboardList,
+ () =>
+ new ResourceItemsSource({
+ getResource({ params: { currentPage } }) {
+ return {
+ all: Dashboard.query.bind(Dashboard),
+ favorites: Dashboard.favorites.bind(Dashboard),
+ }[currentPage];
},
- ],
- {
- reloadOnSearch: false,
- template: '',
- controller($scope, $exceptionHandler) {
- "ngInject";
-
- $scope.handleError = $exceptionHandler;
+ getItemProcessor() {
+ return item => new Dashboard(item);
},
- }
- );
-}
+ }),
+ () => new UrlStateStorage({ orderByField: "created_at", orderByReverse: true })
+);
-init.init = true;
+export default [
+ {
+ path: "/dashboards",
+ title: "Dashboards",
+ render: currentRoute => (
+
+
+ {({ handleError }) => (
+
+ )}
+
+
+ ),
+ },
+ {
+ path: "/dashboards/favorites",
+ title: "Favorite Dashboards",
+ render: currentRoute => (
+
+
+ {({ handleError }) => (
+
+ )}
+
+
+ ),
+ },
+];
diff --git a/client/app/pages/dashboards/DashboardPage.jsx b/client/app/pages/dashboards/DashboardPage.jsx
index aa72e03127..0d8bf30c73 100644
--- a/client/app/pages/dashboards/DashboardPage.jsx
+++ b/client/app/pages/dashboards/DashboardPage.jsx
@@ -1,8 +1,7 @@
-import React, { useState, useEffect } from "react";
+import React, { useState, useEffect, useRef } from "react";
import PropTypes from "prop-types";
import cx from "classnames";
import { map, isEmpty, includes } from "lodash";
-import { react2angular } from "react2angular";
import Button from "antd/lib/button";
import Checkbox from "antd/lib/checkbox";
import Dropdown from "antd/lib/dropdown";
@@ -10,15 +9,16 @@ import Menu from "antd/lib/menu";
import Icon from "antd/lib/icon";
import Modal from "antd/lib/modal";
import Tooltip from "antd/lib/tooltip";
+import AuthenticatedPageWrapper from "@/components/ApplicationArea/AuthenticatedPageWrapper";
import DashboardGrid from "@/components/dashboards/DashboardGrid";
import FavoritesControl from "@/components/FavoritesControl";
import EditInPlace from "@/components/EditInPlace";
import { DashboardTagsControl } from "@/components/tags-control/TagsControl";
import Parameters from "@/components/Parameters";
import Filters from "@/components/Filters";
+import { ErrorBoundaryContext } from "@/components/ErrorBoundary";
import { Dashboard } from "@/services/dashboard";
import recordEvent from "@/services/recordEvent";
-import { $route } from "@/services/ng";
import getTags from "@/services/getTags";
import { clientConfig } from "@/services/auth";
import { policy } from "@/services/policy";
@@ -378,32 +378,45 @@ DashboardComponent.propTypes = {
dashboard: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
};
-function DashboardPage() {
+function DashboardPage({ dashboardSlug, onError }) {
const [dashboard, setDashboard] = useState(null);
+ const onErrorRef = useRef();
+ onErrorRef.current = onError;
useEffect(() => {
- Dashboard.get({ slug: $route.current.params.dashboardSlug })
+ Dashboard.get({ slug: dashboardSlug })
.then(dashboardData => {
recordEvent("view", "dashboard", dashboardData.id);
setDashboard(dashboardData);
})
.catch(error => {
- throw new PromiseRejectionError(error);
+ onErrorRef.current(new PromiseRejectionError(error));
});
- }, []);
+ }, [dashboardSlug]);
- return {dashboard && }
;
+ return (
+
+ );
}
-export default function init(ngModule) {
- ngModule.component("dashboardPage", react2angular(DashboardPage));
+DashboardPage.propTypes = {
+ dashboardSlug: PropTypes.string.isRequired,
+ onError: PropTypes.func,
+};
- return {
- "/dashboard/:dashboardSlug": {
- template: "",
- reloadOnSearch: false,
- },
- };
-}
+DashboardPage.defaultProps = {
+ onError: PropTypes.func,
+};
-init.init = true;
+export default {
+ path: "/dashboard/:dashboardSlug",
+ render: currentRoute => (
+
+
+ {({ handleError }) => }
+
+
+ ),
+};
diff --git a/client/app/pages/dashboards/DashboardPage.less b/client/app/pages/dashboards/DashboardPage.less
index 8dcbc4fcfb..c3d1c1d422 100644
--- a/client/app/pages/dashboards/DashboardPage.less
+++ b/client/app/pages/dashboards/DashboardPage.less
@@ -1,12 +1,13 @@
-@import '../../assets/less/inc/variables';
-@import '../../components/app-header/AppHeader.less';
+@import "~@/assets/less/inc/variables";
+@import "~@/components/ApplicationArea/ApplicationHeader/index.less";
/****
grid bg - based on 6 cols, 35px rows and 15px spacing
****/
// let the bg go all the way to the bottom
-dashboard-page, dashboard-page .container {
+.dashboard-page,
+.dashboard-page .container {
display: flex;
flex-grow: 1;
flex-direction: column;
@@ -22,7 +23,7 @@ dashboard-page, dashboard-page .container {
.dashboard-header {
padding: 0 15px !important;
margin: 0 0 10px !important;
- position: -webkit-sticky; // required for Safari
+ position: -webkit-sticky; // required for Safari
position: sticky;
background: #f6f7f9;
z-index: 99;
@@ -81,26 +82,27 @@ dashboard-page, dashboard-page .container {
width: 45px;
&:after {
- content: '';
+ content: "";
animation: saving 2s linear infinite;
}
}
&[data-error] {
- color: #F44336;
+ color: #f44336;
}
}
}
@keyframes saving {
- 0%, 100% {
- content: '.';
+ 0%,
+ 100% {
+ content: ".";
}
33% {
- content: '..';
+ content: "..";
}
66% {
- content: '...';
+ content: "...";
}
}
@@ -111,7 +113,7 @@ dashboard-page, dashboard-page .container {
position: fixed;
left: 15px;
bottom: 20px;
- width: calc(~'100% - 30px');
+ width: calc(~"100% - 30px");
z-index: 99;
box-shadow: fade(@redash-gray, 50%) 0px 7px 29px -3px;
display: flex;
diff --git a/client/app/pages/dashboards/PublicDashboardPage.jsx b/client/app/pages/dashboards/PublicDashboardPage.jsx
index 67ac220ab5..996cf546b1 100644
--- a/client/app/pages/dashboards/PublicDashboardPage.jsx
+++ b/client/app/pages/dashboards/PublicDashboardPage.jsx
@@ -1,14 +1,14 @@
-import React from "react";
import { isEmpty } from "lodash";
+import React from "react";
import PropTypes from "prop-types";
-import { react2angular } from "react2angular";
+import SignedOutPageWrapper from "@/components/ApplicationArea/SignedOutPageWrapper";
import BigMessage from "@/components/BigMessage";
import PageHeader from "@/components/PageHeader";
import Parameters from "@/components/Parameters";
import DashboardGrid from "@/components/dashboards/DashboardGrid";
import Filters from "@/components/Filters";
+import { ErrorBoundaryContext } from "@/components/ErrorBoundary";
import { Dashboard } from "@/services/dashboard";
-import { $route as ngRoute } from "@/services/ng";
import PromiseRejectionError from "@/lib/promise-rejection-error";
import logoUrl from "@/assets/images/redash_icon_small.png";
import useDashboard from "./useDashboard";
@@ -53,16 +53,25 @@ PublicDashboard.propTypes = {
};
class PublicDashboardPage extends React.Component {
+ static propTypes = {
+ token: PropTypes.string.isRequired,
+ onError: PropTypes.func,
+ };
+
+ static defaultProps = {
+ onError: () => {},
+ };
+
state = {
loading: true,
dashboard: null,
};
componentDidMount() {
- Dashboard.getByToken({ token: ngRoute.current.params.token })
+ Dashboard.getByToken({ token: this.props.token })
.then(dashboard => this.setState({ dashboard, loading: false }))
.catch(error => {
- throw new PromiseRejectionError(error);
+ this.props.onError(new PromiseRejectionError(error));
});
}
@@ -90,24 +99,14 @@ class PublicDashboardPage extends React.Component {
}
}
-export default function init(ngModule) {
- ngModule.component("publicDashboardPage", react2angular(PublicDashboardPage));
-
- return {
- "/public/dashboards/:token": {
- authenticated: false,
- template: "",
- reloadOnSearch: false,
- resolve: {
- session: ($route, Auth) => {
- "ngInject";
- const token = $route.current.params.token;
- Auth.setApiKey(token);
- return Auth.loadConfig();
- },
- },
- },
- };
-}
-
-init.init = true;
+export default {
+ path: "/public/dashboards/:token",
+ authenticated: false,
+ render: currentRoute => (
+
+
+ {({ handleError }) => }
+
+
+ ),
+};
diff --git a/client/app/pages/dashboards/useDashboard.js b/client/app/pages/dashboards/useDashboard.js
index c6ef41a82b..49e6f7ca28 100644
--- a/client/app/pages/dashboards/useDashboard.js
+++ b/client/app/pages/dashboards/useDashboard.js
@@ -18,7 +18,7 @@ import {
min,
} from "lodash";
import notification from "@/services/notification";
-import { $location, $rootScope } from "@/services/ng";
+import location from "@/services/location";
import { Dashboard, collectDashboardFilters } from "@/services/dashboard";
import { currentUser } from "@/services/auth";
import recordEvent from "@/services/recordEvent";
@@ -35,11 +35,6 @@ export const DashboardStatusEnum = {
SAVING_FAILED: "saving_failed",
};
-function updateUrlSearch(...params) {
- $location.search(...params);
- $rootScope.$applyAsync();
-}
-
function getAffectedWidgets(widgets, updatedParameters = []) {
return !isEmpty(updatedParameters)
? widgets.filter(widget =>
@@ -69,15 +64,15 @@ function getLimitedRefreshRate(refreshRate) {
}
function getRefreshRateFromUrl() {
- const refreshRate = parseFloat($location.search().refresh);
+ const refreshRate = parseFloat(location.search.refresh);
return isNaN(refreshRate) ? null : getLimitedRefreshRate(refreshRate);
}
function useFullscreenHandler() {
- const [fullscreen, setFullscreen] = useState(has($location.search(), "fullscreen"));
+ const [fullscreen, setFullscreen] = useState(has(location.search, "fullscreen"));
useEffect(() => {
- document.querySelector("body").classList.toggle("headless", fullscreen);
- updateUrlSearch("fullscreen", fullscreen ? true : null);
+ document.body.classList.toggle("headless", fullscreen);
+ location.setSearch({ fullscreen: fullscreen ? true : null }, true);
}, [fullscreen]);
const toggleFullscreen = () => setFullscreen(!fullscreen);
@@ -88,7 +83,7 @@ function useRefreshRateHandler(refreshDashboard) {
const [refreshRate, setRefreshRate] = useState(getRefreshRateFromUrl());
useEffect(() => {
- updateUrlSearch("refresh", refreshRate || null);
+ location.setSearch({ refresh: refreshRate || null }, true);
if (refreshRate) {
const refreshTimer = setInterval(refreshDashboard, refreshRate * 1000);
return () => clearInterval(refreshTimer);
@@ -99,13 +94,13 @@ function useRefreshRateHandler(refreshDashboard) {
}
function useEditModeHandler(canEditDashboard, widgets) {
- const [editingLayout, setEditingLayout] = useState(canEditDashboard && has($location.search(), "edit"));
+ const [editingLayout, setEditingLayout] = useState(canEditDashboard && has(location.search, "edit"));
const [dashboardStatus, setDashboardStatus] = useState(DashboardStatusEnum.SAVED);
const [recentPositions, setRecentPositions] = useState([]);
const [doneBtnClickedWhileSaving, setDoneBtnClickedWhileSaving] = useState(false);
useEffect(() => {
- updateUrlSearch("edit", editingLayout ? true : null);
+ location.setSearch({ edit: editingLayout ? true : null }, true);
}, [editingLayout]);
useEffect(() => {
@@ -206,7 +201,7 @@ function useDashboard(dashboardData) {
aclUrl,
context: "dashboard",
author: dashboard.user,
- });
+ }).result.catch(() => {}); // ignore dismiss
}, [dashboard]);
const updateDashboard = useCallback(
@@ -266,7 +261,7 @@ function useDashboard(dashboardData) {
return Promise.all(loadWidgetPromises).then(() => {
const queryResults = compact(map(dashboard.widgets, widget => widget.getQueryResult()));
- const updatedFilters = collectDashboardFilters(dashboard, queryResults, $location.search());
+ const updatedFilters = collectDashboardFilters(dashboard, queryResults, location.search);
setFilters(updatedFilters);
});
},
@@ -292,7 +287,9 @@ function useDashboard(dashboardData) {
ShareDashboardDialog.showModal({
dashboard,
hasOnlySafeQueries,
- }).result.finally(() => setDashboard(currentDashboard => extend({}, currentDashboard)));
+ })
+ .result.catch(() => {}) // ignore dismiss
+ .finally(() => setDashboard(currentDashboard => extend({}, currentDashboard)));
}, [dashboard, hasOnlySafeQueries]);
const showAddTextboxDialog = useCallback(() => {
@@ -300,7 +297,7 @@ function useDashboard(dashboardData) {
dashboard,
onConfirm: text =>
dashboard.addWidget(text).then(() => setDashboard(currentDashboard => extend({}, currentDashboard))),
- });
+ }).result.catch(() => {}); // ignore dismiss
}, [dashboard]);
const showAddWidgetDialog = useCallback(() => {
@@ -320,7 +317,7 @@ function useDashboard(dashboardData) {
setDashboard(currentDashboard => extend({}, currentDashboard))
);
}),
- });
+ }).result.catch(() => {}); // ignore dismiss
}, [dashboard]);
const [refreshRate, setRefreshRate, disableRefreshRate] = useRefreshRateHandler(refreshDashboard);
diff --git a/client/app/pages/data-sources/DataSourcesList.jsx b/client/app/pages/data-sources/DataSourcesList.jsx
index 5f47605101..7bd32c284c 100644
--- a/client/app/pages/data-sources/DataSourcesList.jsx
+++ b/client/app/pages/data-sources/DataSourcesList.jsx
@@ -1,21 +1,32 @@
import React from "react";
+import PropTypes from "prop-types";
import Button from "antd/lib/button";
-import { react2angular } from "react2angular";
-import { isEmpty, get } from "lodash";
+import { isEmpty } from "lodash";
import DataSource, { IMG_ROOT } from "@/services/data-source";
import { policy } from "@/services/policy";
-import navigateTo from "@/services/navigateTo";
-import { $route } from "@/services/ng";
-import { routesToAngularRoutes } from "@/lib/utils";
+import AuthenticatedPageWrapper from "@/components/ApplicationArea/AuthenticatedPageWrapper";
+import navigateTo from "@/components/ApplicationArea/navigateTo";
import CardsList from "@/components/cards-list/CardsList";
import LoadingState from "@/components/items-list/components/LoadingState";
import CreateSourceDialog from "@/components/CreateSourceDialog";
import DynamicComponent from "@/components/DynamicComponent";
import helper from "@/components/dynamic-form/dynamicFormHelper";
import wrapSettingsTab from "@/components/SettingsWrapper";
+import { ErrorBoundaryContext } from "@/components/ErrorBoundary";
import recordEvent from "@/services/recordEvent";
+import PromiseRejectionError from "@/lib/promise-rejection-error";
class DataSourcesList extends React.Component {
+ static propTypes = {
+ isNewDataSourcePage: PropTypes.bool,
+ onError: PropTypes.func,
+ };
+
+ static defaultProps = {
+ isNewDataSourcePage: false,
+ onError: () => {},
+ };
+
state = {
dataSourceTypes: [],
dataSources: [],
@@ -25,25 +36,27 @@ class DataSourcesList extends React.Component {
newDataSourceDialog = null;
componentDidMount() {
- Promise.all([DataSource.query(), DataSource.types()]).then(values =>
- this.setState(
- {
- dataSources: values[0],
- dataSourceTypes: values[1],
- loading: false,
- },
- () => {
- // all resources are loaded in state
- if ($route.current.locals.isNewDataSourcePage) {
- if (policy.canCreateDataSource()) {
- this.showCreateSourceDialog();
- } else {
- navigateTo("/data_sources");
+ Promise.all([DataSource.query(), DataSource.types()])
+ .then(values =>
+ this.setState(
+ {
+ dataSources: values[0],
+ dataSourceTypes: values[1],
+ loading: false,
+ },
+ () => {
+ // all resources are loaded in state
+ if (this.props.isNewDataSourcePage) {
+ if (policy.canCreateDataSource()) {
+ this.showCreateSourceDialog();
+ } else {
+ navigateTo("data_sources", true);
+ }
}
}
- }
+ )
)
- );
+ .catch(error => this.props.onError(new PromiseRejectionError(error)));
}
componentWillUnmount() {
@@ -62,12 +75,7 @@ class DataSourcesList extends React.Component {
DataSource.query().then(dataSources => this.setState({ dataSources, loading: false }));
return dataSource;
})
- .catch(error => {
- if (!(error instanceof Error)) {
- error = new Error(get(error, "data.message", "Failed saving."));
- }
- return Promise.reject(error);
- });
+ .catch(error => Promise.reject(new PromiseRejectionError(error)));
};
showCreateSourceDialog = () => {
@@ -88,6 +96,7 @@ class DataSourcesList extends React.Component {
}
})
.catch(() => {
+ navigateTo("data_sources", true);
this.newDataSourceDialog = null;
});
};
@@ -139,45 +148,39 @@ class DataSourcesList extends React.Component {
}
}
-export default function init(ngModule) {
- ngModule.component(
- "pageDataSourcesList",
- react2angular(
- wrapSettingsTab(
- {
- permission: "admin",
- title: "Data Sources",
- path: "data_sources",
- order: 1,
- },
- DataSourcesList
- )
- )
- );
-
- return routesToAngularRoutes(
- [
- {
- path: "/data_sources",
- title: "Data Sources",
- key: "data_sources",
- },
- {
- path: "/data_sources/new",
- title: "Data Sources",
- key: "data_sources",
- isNewDataSourcePage: true,
- },
- ],
- {
- template: "",
- controller($scope, $exceptionHandler) {
- "ngInject";
-
- $scope.handleError = $exceptionHandler;
- },
- }
- );
-}
-
-init.init = true;
+const DataSourcesListPage = wrapSettingsTab(
+ {
+ permission: "admin",
+ title: "Data Sources",
+ path: "data_sources",
+ order: 1,
+ },
+ DataSourcesList
+);
+
+export default [
+ {
+ path: "/data_sources",
+ title: "Data Sources",
+ render: currentRoute => (
+
+
+ {({ handleError }) => }
+
+
+ ),
+ },
+ {
+ path: "/data_sources/new",
+ title: "Data Sources",
+ render: currentRoute => (
+
+
+ {({ handleError }) => (
+
+ )}
+
+
+ ),
+ },
+];
diff --git a/client/app/pages/data-sources/EditDataSource.jsx b/client/app/pages/data-sources/EditDataSource.jsx
index f96b6e8422..5df0d067ba 100644
--- a/client/app/pages/data-sources/EditDataSource.jsx
+++ b/client/app/pages/data-sources/EditDataSource.jsx
@@ -1,11 +1,10 @@
import React from "react";
import PropTypes from "prop-types";
import { get, find, toUpper } from "lodash";
-import { react2angular } from "react2angular";
import Modal from "antd/lib/modal";
import DataSource, { IMG_ROOT } from "@/services/data-source";
-import navigateTo from "@/services/navigateTo";
-import { $route } from "@/services/ng";
+import AuthenticatedPageWrapper from "@/components/ApplicationArea/AuthenticatedPageWrapper";
+import navigateTo from "@/components/ApplicationArea/navigateTo";
import notification from "@/services/notification";
import PromiseRejectionError from "@/lib/promise-rejection-error";
import LoadingState from "@/components/items-list/components/LoadingState";
@@ -13,9 +12,11 @@ import DynamicForm from "@/components/dynamic-form/DynamicForm";
import helper from "@/components/dynamic-form/dynamicFormHelper";
import HelpTrigger, { TYPES as HELP_TRIGGER_TYPES } from "@/components/HelpTrigger";
import wrapSettingsTab from "@/components/SettingsWrapper";
+import { ErrorBoundaryContext } from "@/components/ErrorBoundary";
class EditDataSource extends React.Component {
static propTypes = {
+ dataSourceId: PropTypes.string.isRequired,
onError: PropTypes.func,
};
@@ -30,18 +31,14 @@ class EditDataSource extends React.Component {
};
componentDidMount() {
- DataSource.get({ id: $route.current.params.dataSourceId })
+ DataSource.get({ id: this.props.dataSourceId })
.then(dataSource => {
const { type } = dataSource;
this.setState({ dataSource });
DataSource.types().then(types => this.setState({ type: find(types, { type }), loading: false }));
})
.catch(error => {
- // ANGULAR_REMOVE_ME This code is related to Angular's HTTP services
- if (error.status && error.data) {
- error = new PromiseRejectionError(error);
- }
- this.props.onError(error);
+ this.props.onError(new PromiseRejectionError(error));
});
}
@@ -63,7 +60,7 @@ class EditDataSource extends React.Component {
DataSource.delete(dataSource)
.then(() => {
notification.success("Data source deleted successfully.");
- navigateTo("/data_sources", true);
+ navigateTo("data_sources");
})
.catch(() => {
callback();
@@ -143,20 +140,16 @@ class EditDataSource extends React.Component {
}
}
-export default function init(ngModule) {
- ngModule.component("pageEditDataSource", react2angular(wrapSettingsTab(null, EditDataSource)));
-
- return {
- "/data_sources/:dataSourceId": {
- template: '',
- title: "Data Sources",
- controller($scope, $exceptionHandler) {
- "ngInject";
-
- $scope.handleError = $exceptionHandler;
- },
- },
- };
-}
-
-init.init = true;
+const EditDataSourcePage = wrapSettingsTab(null, EditDataSource);
+
+export default {
+ path: "/data_sources/:dataSourceId([0-9]+)",
+ title: "Data Sources",
+ render: currentRoute => (
+
+
+ {({ handleError }) => }
+
+
+ ),
+};
diff --git a/client/app/pages/destinations/DestinationsList.jsx b/client/app/pages/destinations/DestinationsList.jsx
index 2f6b5ed54b..fef0a5c267 100644
--- a/client/app/pages/destinations/DestinationsList.jsx
+++ b/client/app/pages/destinations/DestinationsList.jsx
@@ -1,19 +1,30 @@
import React from "react";
+import PropTypes from "prop-types";
import Button from "antd/lib/button";
-import { react2angular } from "react2angular";
import { isEmpty, get } from "lodash";
import Destination, { IMG_ROOT } from "@/services/destination";
import { policy } from "@/services/policy";
-import navigateTo from "@/services/navigateTo";
-import { $route } from "@/services/ng";
-import { routesToAngularRoutes } from "@/lib/utils";
+import AuthenticatedPageWrapper from "@/components/ApplicationArea/AuthenticatedPageWrapper";
+import navigateTo from "@/components/ApplicationArea/navigateTo";
import CardsList from "@/components/cards-list/CardsList";
import LoadingState from "@/components/items-list/components/LoadingState";
import CreateSourceDialog from "@/components/CreateSourceDialog";
import helper from "@/components/dynamic-form/dynamicFormHelper";
import wrapSettingsTab from "@/components/SettingsWrapper";
+import { ErrorBoundaryContext } from "@/components/ErrorBoundary";
+import PromiseRejectionError from "@/lib/promise-rejection-error";
class DestinationsList extends React.Component {
+ static propTypes = {
+ isNewDestinationPage: PropTypes.bool,
+ onError: PropTypes.func,
+ };
+
+ static defaultProps = {
+ isNewDestinationPage: false,
+ onError: () => {},
+ };
+
state = {
destinationTypes: [],
destinations: [],
@@ -21,25 +32,27 @@ class DestinationsList extends React.Component {
};
componentDidMount() {
- Promise.all([Destination.query(), Destination.types()]).then(values =>
- this.setState(
- {
- destinations: values[0],
- destinationTypes: values[1],
- loading: false,
- },
- () => {
- // all resources are loaded in state
- if ($route.current.locals.isNewDestinationPage) {
- if (policy.canCreateDestination()) {
- this.showCreateSourceDialog();
- } else {
- navigateTo("/destinations");
+ Promise.all([Destination.query(), Destination.types()])
+ .then(values =>
+ this.setState(
+ {
+ destinations: values[0],
+ destinationTypes: values[1],
+ loading: false,
+ },
+ () => {
+ // all resources are loaded in state
+ if (this.props.isNewDestinationPage) {
+ if (policy.canCreateDestination()) {
+ this.showCreateSourceDialog();
+ } else {
+ navigateTo("destinations", true);
+ }
}
}
- }
+ )
)
- );
+ .catch(error => this.props.onError(new PromiseRejectionError(error)));
}
createDestination = (selectedType, values) => {
@@ -66,11 +79,15 @@ class DestinationsList extends React.Component {
sourceType: "Alert Destination",
imageFolder: IMG_ROOT,
onCreate: this.createDestination,
- }).result.then((result = {}) => {
- if (result.success) {
- navigateTo(`destinations/${result.data.id}`);
- }
- });
+ })
+ .result.then((result = {}) => {
+ if (result.success) {
+ navigateTo(`destinations/${result.data.id}`);
+ }
+ })
+ .catch(() => {
+ navigateTo("destinations", true);
+ });
};
renderDestinations() {
@@ -119,45 +136,39 @@ class DestinationsList extends React.Component {
}
}
-export default function init(ngModule) {
- ngModule.component(
- "pageDestinationsList",
- react2angular(
- wrapSettingsTab(
- {
- permission: "admin",
- title: "Alert Destinations",
- path: "destinations",
- order: 4,
- },
- DestinationsList
- )
- )
- );
-
- return routesToAngularRoutes(
- [
- {
- path: "/destinations",
- title: "Alert Destinations",
- key: "destinations",
- },
- {
- path: "/destinations/new",
- title: "Alert Destinations",
- key: "destinations",
- isNewDestinationPage: true,
- },
- ],
- {
- template: "",
- controller($scope, $exceptionHandler) {
- "ngInject";
-
- $scope.handleError = $exceptionHandler;
- },
- }
- );
-}
+const DestinationsListPage = wrapSettingsTab(
+ {
+ permission: "admin",
+ title: "Alert Destinations",
+ path: "destinations",
+ order: 4,
+ },
+ DestinationsList
+);
-init.init = true;
+export default [
+ {
+ path: "/destinations",
+ title: "Alert Destinations",
+ render: currentRoute => (
+
+
+ {({ handleError }) => }
+
+
+ ),
+ },
+ {
+ path: "/destinations/new",
+ title: "Alert Destinations",
+ render: currentRoute => (
+
+
+ {({ handleError }) => (
+
+ )}
+
+
+ ),
+ },
+];
diff --git a/client/app/pages/destinations/EditDestination.jsx b/client/app/pages/destinations/EditDestination.jsx
index 0502945749..22c4461e10 100644
--- a/client/app/pages/destinations/EditDestination.jsx
+++ b/client/app/pages/destinations/EditDestination.jsx
@@ -1,20 +1,21 @@
import React from "react";
import PropTypes from "prop-types";
import { get, find } from "lodash";
-import { react2angular } from "react2angular";
import Modal from "antd/lib/modal";
import Destination, { IMG_ROOT } from "@/services/destination";
-import navigateTo from "@/services/navigateTo";
-import { $route } from "@/services/ng";
+import AuthenticatedPageWrapper from "@/components/ApplicationArea/AuthenticatedPageWrapper";
+import navigateTo from "@/components/ApplicationArea/navigateTo";
import notification from "@/services/notification";
import PromiseRejectionError from "@/lib/promise-rejection-error";
import LoadingState from "@/components/items-list/components/LoadingState";
import DynamicForm from "@/components/dynamic-form/DynamicForm";
import helper from "@/components/dynamic-form/dynamicFormHelper";
import wrapSettingsTab from "@/components/SettingsWrapper";
+import { ErrorBoundaryContext } from "@/components/ErrorBoundary";
class EditDestination extends React.Component {
static propTypes = {
+ destinationId: PropTypes.string.isRequired,
onError: PropTypes.func,
};
@@ -29,18 +30,14 @@ class EditDestination extends React.Component {
};
componentDidMount() {
- Destination.get({ id: $route.current.params.destinationId })
+ Destination.get({ id: this.props.destinationId })
.then(destination => {
const { type } = destination;
this.setState({ destination });
Destination.types().then(types => this.setState({ type: find(types, { type }), loading: false }));
})
.catch(error => {
- // ANGULAR_REMOVE_ME This code is related to Angular's HTTP services
- if (error.status && error.data) {
- error = new PromiseRejectionError(error);
- }
- this.props.onError(error);
+ this.props.onError(new PromiseRejectionError(error));
});
}
@@ -62,7 +59,7 @@ class EditDestination extends React.Component {
Destination.delete(destination)
.then(() => {
notification.success("Alert destination deleted successfully.");
- navigateTo("/destinations", true);
+ navigateTo("destinations");
})
.catch(() => {
callback();
@@ -110,20 +107,16 @@ class EditDestination extends React.Component {
}
}
-export default function init(ngModule) {
- ngModule.component("pageEditDestination", react2angular(wrapSettingsTab(null, EditDestination)));
+const EditDestinationPage = wrapSettingsTab(null, EditDestination);
- return {
- "/destinations/:destinationId": {
- template: '',
- title: "Alert Destinations",
- controller($scope, $exceptionHandler) {
- "ngInject";
-
- $scope.handleError = $exceptionHandler;
- },
- },
- };
-}
-
-init.init = true;
+export default {
+ path: "/destinations/:destinationId([0-9]+)",
+ title: "Alert Destinations",
+ render: currentRoute => (
+
+
+ {({ handleError }) => }
+
+
+ ),
+};
diff --git a/client/app/pages/groups/GroupDataSources.jsx b/client/app/pages/groups/GroupDataSources.jsx
index 8f91f7d00b..360936f226 100644
--- a/client/app/pages/groups/GroupDataSources.jsx
+++ b/client/app/pages/groups/GroupDataSources.jsx
@@ -1,11 +1,12 @@
import { filter, map, includes } from "lodash";
import React from "react";
-import { react2angular } from "react2angular";
import Button from "antd/lib/button";
import Dropdown from "antd/lib/dropdown";
import Menu from "antd/lib/menu";
import Icon from "antd/lib/icon";
+import AuthenticatedPageWrapper from "@/components/ApplicationArea/AuthenticatedPageWrapper";
+import navigateTo from "@/components/ApplicationArea/navigateTo";
import Paginator from "@/components/Paginator";
import { wrap as liveItemsList, ControllerType } from "@/components/items-list/ItemsList";
@@ -22,13 +23,12 @@ import ListItemAddon from "@/components/groups/ListItemAddon";
import Sidebar from "@/components/groups/DetailsPageSidebar";
import Layout from "@/components/layouts/ContentWithSidebar";
import wrapSettingsTab from "@/components/SettingsWrapper";
+import { ErrorBoundaryContext } from "@/components/ErrorBoundary";
import notification from "@/services/notification";
import { currentUser } from "@/services/auth";
import Group from "@/services/group";
import DataSource from "@/services/data-source";
-import navigateTo from "@/services/navigateTo";
-import { routesToAngularRoutes } from "@/lib/utils";
class GroupDataSources extends React.Component {
static propTypes = {
@@ -167,9 +167,11 @@ class GroupDataSources extends React.Component {
const promises = map(items, ds => Group.addDataSource({ id: this.groupId }, { data_source_id: ds.id }));
return Promise.all(promises);
},
- }).result.finally(() => {
- this.props.controller.update();
- });
+ })
+ .result.catch(() => {}) // ignore dismiss
+ .finally(() => {
+ this.props.controller.update();
+ });
};
render() {
@@ -185,7 +187,7 @@ class GroupDataSources extends React.Component {
items={this.sidebarMenu}
canAddDataSources={currentUser.isAdmin}
onAddDataSourcesClick={this.addDataSources}
- onGroupDeleted={() => navigateTo("/groups", true)}
+ onGroupDeleted={() => navigateTo("groups")}
/>
@@ -227,47 +229,38 @@ class GroupDataSources extends React.Component {
}
}
-export default function init(ngModule) {
- ngModule.component(
- "pageGroupDataSources",
- react2angular(
- wrapSettingsTab(
- null,
- liveItemsList(
- GroupDataSources,
- new ResourceItemsSource({
- isPlainList: true,
- getRequest(unused, { params: { groupId } }) {
- return { id: groupId };
- },
- getResource() {
- return Group.dataSources.bind(Group);
- },
- }),
- new StateStorage({ orderByField: "name" })
- )
- )
- )
- );
-
- return routesToAngularRoutes(
- [
- {
- path: "/groups/:groupId/data_sources",
- title: "Group Data Sources",
- key: "datasources",
- },
- ],
- {
- reloadOnSearch: false,
- template: '',
- controller($scope, $exceptionHandler) {
- "ngInject";
-
- $scope.handleError = $exceptionHandler;
- },
- }
- );
-}
+const GroupDataSourcesPage = wrapSettingsTab(
+ null,
+ liveItemsList(
+ GroupDataSources,
+ () =>
+ new ResourceItemsSource({
+ isPlainList: true,
+ getRequest(unused, { params: { groupId } }) {
+ return { id: groupId };
+ },
+ getResource() {
+ return Group.dataSources.bind(Group);
+ },
+ }),
+ () => new StateStorage({ orderByField: "name" })
+ )
+);
-init.init = true;
+export default {
+ path: "/groups/:groupId([0-9]+)/data_sources",
+ title: "Group Data Sources",
+ render: currentRoute => (
+
+
+ {({ handleError }) => (
+
+ )}
+
+
+ ),
+};
diff --git a/client/app/pages/groups/GroupMembers.jsx b/client/app/pages/groups/GroupMembers.jsx
index 4ea8d5bac6..eebe42c941 100644
--- a/client/app/pages/groups/GroupMembers.jsx
+++ b/client/app/pages/groups/GroupMembers.jsx
@@ -1,8 +1,9 @@
import { includes, map } from "lodash";
import React from "react";
-import { react2angular } from "react2angular";
import Button from "antd/lib/button";
+import AuthenticatedPageWrapper from "@/components/ApplicationArea/AuthenticatedPageWrapper";
+import navigateTo from "@/components/ApplicationArea/navigateTo";
import Paginator from "@/components/Paginator";
import { wrap as liveItemsList, ControllerType } from "@/components/items-list/ItemsList";
@@ -19,13 +20,12 @@ import ListItemAddon from "@/components/groups/ListItemAddon";
import Sidebar from "@/components/groups/DetailsPageSidebar";
import Layout from "@/components/layouts/ContentWithSidebar";
import wrapSettingsTab from "@/components/SettingsWrapper";
+import { ErrorBoundaryContext } from "@/components/ErrorBoundary";
import notification from "@/services/notification";
import { currentUser } from "@/services/auth";
import Group from "@/services/group";
import User from "@/services/user";
-import navigateTo from "@/services/navigateTo";
-import { routesToAngularRoutes } from "@/lib/utils";
class GroupMembers extends React.Component {
static propTypes = {
@@ -130,9 +130,11 @@ class GroupMembers extends React.Component {
const promises = map(items, u => Group.addMember({ id: this.groupId }, { user_id: u.id }));
return Promise.all(promises);
},
- }).result.finally(() => {
- this.props.controller.update();
- });
+ })
+ .result.catch(() => {}) // ignore dismiss
+ .finally(() => {
+ this.props.controller.update();
+ });
};
render() {
@@ -148,7 +150,7 @@ class GroupMembers extends React.Component {
items={this.sidebarMenu}
canAddMembers={currentUser.isAdmin}
onAddMembersClick={this.addMembers}
- onGroupDeleted={() => navigateTo("/groups", true)}
+ onGroupDeleted={() => navigateTo("groups")}
/>
@@ -190,47 +192,38 @@ class GroupMembers extends React.Component {
}
}
-export default function init(ngModule) {
- ngModule.component(
- "pageGroupMembers",
- react2angular(
- wrapSettingsTab(
- null,
- liveItemsList(
- GroupMembers,
- new ResourceItemsSource({
- isPlainList: true,
- getRequest(unused, { params: { groupId } }) {
- return { id: groupId };
- },
- getResource() {
- return Group.members.bind(Group);
- },
- }),
- new StateStorage({ orderByField: "name" })
- )
- )
- )
- );
-
- return routesToAngularRoutes(
- [
- {
- path: "/groups/:groupId",
- title: "Group Members",
- key: "users",
- },
- ],
- {
- reloadOnSearch: false,
- template: '',
- controller($scope, $exceptionHandler) {
- "ngInject";
-
- $scope.handleError = $exceptionHandler;
- },
- }
- );
-}
-
-init.init = true;
+const GroupMembersPage = wrapSettingsTab(
+ null,
+ liveItemsList(
+ GroupMembers,
+ () =>
+ new ResourceItemsSource({
+ isPlainList: true,
+ getRequest(unused, { params: { groupId } }) {
+ return { id: groupId };
+ },
+ getResource() {
+ return Group.members.bind(Group);
+ },
+ }),
+ () => new StateStorage({ orderByField: "name" })
+ )
+);
+
+export default {
+ path: "/groups/:groupId([0-9]+)",
+ title: "Group Members",
+ render: currentRoute => (
+
+
+ {({ handleError }) => (
+
+ )}
+
+
+ ),
+};
diff --git a/client/app/pages/groups/GroupsList.jsx b/client/app/pages/groups/GroupsList.jsx
index 82bebc7b2a..8f5406eae5 100644
--- a/client/app/pages/groups/GroupsList.jsx
+++ b/client/app/pages/groups/GroupsList.jsx
@@ -1,7 +1,8 @@
import React from "react";
-import { react2angular } from "react2angular";
import Button from "antd/lib/button";
+import AuthenticatedPageWrapper from "@/components/ApplicationArea/AuthenticatedPageWrapper";
+import navigateTo from "@/components/ApplicationArea/navigateTo";
import Paginator from "@/components/Paginator";
import { wrap as liveItemsList, ControllerType } from "@/components/items-list/ItemsList";
@@ -15,11 +16,10 @@ import ItemsTable, { Columns } from "@/components/items-list/components/ItemsTab
import CreateGroupDialog from "@/components/groups/CreateGroupDialog";
import DeleteGroupButton from "@/components/groups/DeleteGroupButton";
import wrapSettingsTab from "@/components/SettingsWrapper";
+import { ErrorBoundaryContext } from "@/components/ErrorBoundary";
import Group from "@/services/group";
import { currentUser } from "@/services/auth";
-import navigateTo from "@/services/navigateTo";
-import { routesToAngularRoutes } from "@/lib/utils";
class GroupsList extends React.Component {
static propTypes = {
@@ -74,9 +74,11 @@ class GroupsList extends React.Component {
];
createGroup = () => {
- CreateGroupDialog.showModal().result.then(group => {
- Group.create(group).then(newGroup => navigateTo(`/groups/${newGroup.id}`));
- });
+ CreateGroupDialog.showModal()
+ .result.then(group => {
+ Group.create(group).then(newGroup => navigateTo(`groups/${newGroup.id}`));
+ })
+ .catch(() => {}); // ignore dismiss
};
onGroupDeleted = () => {
@@ -124,52 +126,43 @@ class GroupsList extends React.Component {
}
}
-export default function init(ngModule) {
- ngModule.component(
- "pageGroupsList",
- react2angular(
- wrapSettingsTab(
- {
- permission: "list_users",
- title: "Groups",
- path: "groups",
- order: 3,
+const GroupsListPage = wrapSettingsTab(
+ {
+ permission: "list_users",
+ title: "Groups",
+ path: "groups",
+ order: 3,
+ },
+ liveItemsList(
+ GroupsList,
+ () =>
+ new ResourceItemsSource({
+ isPlainList: true,
+ getRequest() {
+ return {};
},
- liveItemsList(
- GroupsList,
- new ResourceItemsSource({
- isPlainList: true,
- getRequest() {
- return {};
- },
- getResource() {
- return Group.query.bind(Group);
- },
- }),
- new StateStorage({ orderByField: "name", itemsPerPage: 10 })
- )
- )
- )
- );
-
- return routesToAngularRoutes(
- [
- {
- path: "/groups",
- title: "Groups",
- key: "groups",
- },
- ],
- {
- reloadOnSearch: false,
- template: '',
- controller($scope, $exceptionHandler) {
- "ngInject";
-
- $scope.handleError = $exceptionHandler;
- },
- }
- );
-}
-
-init.init = true;
+ getResource() {
+ return Group.query.bind(Group);
+ },
+ }),
+ () => new StateStorage({ orderByField: "name", itemsPerPage: 10 })
+ )
+);
+
+export default {
+ path: "/groups",
+ title: "Groups",
+ render: currentRoute => (
+
+
+ {({ handleError }) => (
+
+ )}
+
+
+ ),
+};
diff --git a/client/app/pages/home/Home.jsx b/client/app/pages/home/Home.jsx
index 24f4e77643..2ef9abfdfd 100644
--- a/client/app/pages/home/Home.jsx
+++ b/client/app/pages/home/Home.jsx
@@ -2,9 +2,9 @@ import React, { useEffect, useState } from "react";
import { axios } from "@/services/axios";
import PropTypes from "prop-types";
import { includes, isEmpty } from "lodash";
-import { react2angular } from "react2angular";
import Alert from "antd/lib/alert";
import Icon from "antd/lib/icon";
+import AuthenticatedPageWrapper from "@/components/ApplicationArea/AuthenticatedPageWrapper";
import EmptyState from "@/components/empty-state/EmptyState";
import DynamicComponent from "@/components/DynamicComponent";
import BeaconConsent from "@/components/BeaconConsent";
@@ -173,15 +173,12 @@ function Home() {
);
}
-export default function init(ngModule) {
- ngModule.component("homePage", react2angular(Home));
-
- return {
- "/": {
- template: "",
- title: "Redash",
- },
- };
-}
-
-init.init = true;
+export default {
+ path: "/",
+ title: "Redash",
+ render: currentRoute => (
+
+
+
+ ),
+};
diff --git a/client/app/pages/index.js b/client/app/pages/index.js
new file mode 100644
index 0000000000..0640cf4da4
--- /dev/null
+++ b/client/app/pages/index.js
@@ -0,0 +1,53 @@
+import { flatten } from "lodash";
+
+import adminJobsRoutes from "./admin/Jobs";
+import adminOutdatedQueriesRoutes from "./admin/OutdatedQueries";
+import adminSystemStatusRoutes from "./admin/SystemStatus";
+import alertRoutes from "./alert/Alert";
+import alertsListRoutes from "./alerts/AlertsList";
+import dashboardListRoutes from "./dashboards/DashboardList";
+import dashboardRoutes from "./dashboards/DashboardPage";
+import publicDashboardRoutes from "./dashboards/PublicDashboardPage";
+import dataSourcesListRoutes from "./data-sources/DataSourcesList";
+import editDataSourceRoutes from "./data-sources/EditDataSource";
+import destinationsListRoutes from "./destinations/DestinationsList";
+import editDestinationRoutes from "./destinations/EditDestination";
+import groupsListRoutes from "./groups/GroupsList";
+import groupsDataSourcesRoutes from "./groups/GroupDataSources";
+import groupsMembersRoutes from "./groups/GroupMembers";
+import homeRoutes from "./home/Home";
+import querySourceRoutes from "./queries/QuerySource";
+import queryViewRoutes from "./queries/QueryView";
+import visualizationEmbedRoutes from "./queries/VisualizationEmbed";
+import queriesListRoutes from "./queries-list/QueriesList";
+import querySnippetsRoutes from "./query-snippets/QuerySnippetsList";
+import organizationSettingsRoutes from "./settings/OrganizationSettings";
+import userProfileRoutes from "./users/UserProfile";
+import usersListRoutes from "./users/UsersList";
+
+export default flatten([
+ adminJobsRoutes,
+ adminOutdatedQueriesRoutes,
+ adminSystemStatusRoutes,
+ alertRoutes,
+ alertsListRoutes,
+ dashboardListRoutes,
+ dashboardRoutes,
+ publicDashboardRoutes,
+ dataSourcesListRoutes,
+ editDataSourceRoutes,
+ destinationsListRoutes,
+ editDestinationRoutes,
+ groupsListRoutes,
+ groupsDataSourcesRoutes,
+ groupsMembersRoutes,
+ homeRoutes,
+ queriesListRoutes,
+ queryViewRoutes,
+ querySourceRoutes,
+ visualizationEmbedRoutes,
+ querySnippetsRoutes,
+ organizationSettingsRoutes,
+ usersListRoutes,
+ userProfileRoutes,
+]);
diff --git a/client/app/pages/queries-list/QueriesList.jsx b/client/app/pages/queries-list/QueriesList.jsx
index 17229ae8a4..287973978c 100644
--- a/client/app/pages/queries-list/QueriesList.jsx
+++ b/client/app/pages/queries-list/QueriesList.jsx
@@ -1,6 +1,6 @@
import React from "react";
-import { react2angular } from "react2angular";
+import AuthenticatedPageWrapper from "@/components/ApplicationArea/AuthenticatedPageWrapper";
import PageHeader from "@/components/PageHeader";
import Paginator from "@/components/Paginator";
import { QueryTagsControl } from "@/components/tags-control/TagsControl";
@@ -15,10 +15,10 @@ import * as Sidebar from "@/components/items-list/components/Sidebar";
import ItemsTable, { Columns } from "@/components/items-list/components/ItemsTable";
import Layout from "@/components/layouts/ContentWithSidebar";
+import { ErrorBoundaryContext } from "@/components/ErrorBoundary";
import { Query } from "@/services/query";
import { currentUser } from "@/services/auth";
-import { routesToAngularRoutes } from "@/lib/utils";
import QueriesListEmptyState from "./QueriesListEmptyState";
@@ -91,114 +91,145 @@ class QueriesList extends React.Component {
render() {
const { controller } = this.props;
return (
-
-
-
-
-
-
-
- controller.updatePagination({ itemsPerPage })}
- />
-
-
- {!controller.isLoaded && }
- {controller.isLoaded && controller.isEmpty && (
-
+
+
+
+
+
- )}
- {controller.isLoaded && !controller.isEmpty && (
-
-
-
controller.updatePagination({ page })}
+
+
+ controller.updatePagination({ itemsPerPage })}
+ />
+
+
+ {!controller.isLoaded && }
+ {controller.isLoaded && controller.isEmpty && (
+
-
- )}
-
-
+ )}
+ {controller.isLoaded && !controller.isEmpty && (
+
+
+
controller.updatePagination({ page })}
+ />
+
+ )}
+
+
+
);
}
}
-export default function init(ngModule) {
- ngModule.component(
- "pageQueriesList",
- react2angular(
- itemsList(
- QueriesList,
- new ResourceItemsSource({
- getResource({ params: { currentPage } }) {
- return {
- all: Query.query.bind(Query),
- my: Query.myQueries.bind(Query),
- favorites: Query.favorites.bind(Query),
- archive: Query.archive.bind(Query),
- }[currentPage];
- },
- getItemProcessor() {
- return item => new Query(item);
- },
- }),
- new UrlStateStorage({ orderByField: "created_at", orderByReverse: true })
- )
- )
- );
-
- return routesToAngularRoutes(
- [
- {
- path: "/queries",
- title: "Queries",
- key: "all",
- },
- {
- path: "/queries/favorites",
- title: "Favorite Queries",
- key: "favorites",
- },
- {
- path: "/queries/archive",
- title: "Archived Queries",
- key: "archive",
- },
- {
- path: "/queries/my",
- title: "My Queries",
- key: "my",
+const QueriesListPage = itemsList(
+ QueriesList,
+ () =>
+ new ResourceItemsSource({
+ getResource({ params: { currentPage } }) {
+ return {
+ all: Query.query.bind(Query),
+ my: Query.myQueries.bind(Query),
+ favorites: Query.favorites.bind(Query),
+ archive: Query.archive.bind(Query),
+ }[currentPage];
},
- ],
- {
- reloadOnSearch: false,
- template: '',
- controller($scope, $exceptionHandler) {
- "ngInject";
-
- $scope.handleError = $exceptionHandler;
+ getItemProcessor() {
+ return item => new Query(item);
},
- }
- );
-}
+ }),
+ () => new UrlStateStorage({ orderByField: "created_at", orderByReverse: true })
+);
-init.init = true;
+export default [
+ {
+ path: "/queries",
+ title: "Queries",
+ render: currentRoute => (
+
+
+ {({ handleError }) => (
+
+ )}
+
+
+ ),
+ },
+ {
+ path: "/queries/favorites",
+ title: "Favorite Queries",
+ render: currentRoute => (
+
+
+ {({ handleError }) => (
+
+ )}
+
+
+ ),
+ },
+ {
+ path: "/queries/archive",
+ title: "Archived Queries",
+ render: currentRoute => (
+
+
+ {({ handleError }) => (
+
+ )}
+
+
+ ),
+ },
+ {
+ path: "/queries/my",
+ title: "My Queries",
+ render: currentRoute => (
+
+
+ {({ handleError }) => (
+
+ )}
+
+
+ ),
+ },
+];
diff --git a/client/app/pages/queries/QuerySource.jsx b/client/app/pages/queries/QuerySource.jsx
index ae5951ed61..84b1c6e10a 100644
--- a/client/app/pages/queries/QuerySource.jsx
+++ b/client/app/pages/queries/QuerySource.jsx
@@ -1,9 +1,9 @@
import { isEmpty, find, map, extend, includes } from "lodash";
import React, { useState, useRef, useEffect, useCallback } from "react";
import PropTypes from "prop-types";
-import { react2angular } from "react2angular";
import { useDebouncedCallback } from "use-debounce";
import Select from "antd/lib/select";
+import AuthenticatedPageWrapper from "@/components/ApplicationArea/AuthenticatedPageWrapper";
import Resizable from "@/components/Resizable";
import Parameters from "@/components/Parameters";
import EditInPlace from "@/components/EditInPlace";
@@ -11,7 +11,7 @@ import EditVisualizationButton from "@/components/EditVisualizationButton";
import QueryControlDropdown from "@/components/EditVisualizationButton/QueryControlDropdown";
import QueryEditor from "@/components/queries/QueryEditor";
import TimeAgo from "@/components/TimeAgo";
-import { routesToAngularRoutes } from "@/lib/utils";
+import { ErrorBoundaryContext } from "@/components/ErrorBoundary";
import { durationHumanize, prettySize } from "@/lib/utils";
import { Query } from "@/services/query";
import recordEvent from "@/services/recordEvent";
@@ -443,45 +443,31 @@ QuerySource.propTypes = {
query: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
};
-export default function init(ngModule) {
- ngModule.component("pageQuerySource", react2angular(QuerySource));
-
- return {
- ...routesToAngularRoutes(
- [
- {
- path: "/queries/new",
- },
- ],
- {
- layout: "fixed",
- reloadOnSearch: false,
- template: '',
- resolve: {
- query: () => Query.newQuery(),
- },
- }
+export default [
+ {
+ path: "/queries/new",
+ render: currentRoute => (
+
+
+ {({ handleError }) => }
+
+
),
- ...routesToAngularRoutes(
- [
- {
- path: "/queries/:queryId/source",
- },
- ],
- {
- layout: "fixed",
- reloadOnSearch: false,
- template: '',
- resolve: {
- query: $route => {
- "ngInject";
-
- return Query.get({ id: $route.current.params.queryId });
- },
- },
- }
+ resolve: {
+ query: () => Query.newQuery(),
+ },
+ },
+ {
+ path: "/queries/:queryId([0-9]+)/source",
+ render: currentRoute => (
+
+
+ {({ handleError }) => }
+
+
),
- };
-}
-
-init.init = true;
+ resolve: {
+ query: ({ queryId }) => Query.get({ id: queryId }),
+ },
+ },
+];
diff --git a/client/app/pages/queries/QuerySource.less b/client/app/pages/queries/QuerySource.less
index 1d414fa67c..e70d49087b 100644
--- a/client/app/pages/queries/QuerySource.less
+++ b/client/app/pages/queries/QuerySource.less
@@ -1,8 +1,3 @@
-page-query-source {
- display: flex;
- flex-grow: 1;
-}
-
.query-fullscreen {
.query-editor-wrapper {
padding: 15px;
diff --git a/client/app/pages/queries/QueryView.jsx b/client/app/pages/queries/QueryView.jsx
index 8b6fb86e96..9ca87f070f 100644
--- a/client/app/pages/queries/QueryView.jsx
+++ b/client/app/pages/queries/QueryView.jsx
@@ -1,16 +1,17 @@
import React, { useState, useEffect, useCallback } from "react";
import PropTypes from "prop-types";
-import { react2angular } from "react2angular";
import Divider from "antd/lib/divider";
+import AuthenticatedPageWrapper from "@/components/ApplicationArea/AuthenticatedPageWrapper";
import EditInPlace from "@/components/EditInPlace";
import Parameters from "@/components/Parameters";
import TimeAgo from "@/components/TimeAgo";
import QueryControlDropdown from "@/components/EditVisualizationButton/QueryControlDropdown";
import EditVisualizationButton from "@/components/EditVisualizationButton";
+import { ErrorBoundaryContext } from "@/components/ErrorBoundary";
-import DataSource from "@/services/data-source";
import { Query } from "@/services/query";
+import DataSource from "@/services/data-source";
import { pluralize, durationHumanize } from "@/lib/utils";
import QueryPageHeader from "./components/QueryPageHeader";
@@ -184,22 +185,16 @@ function QueryView(props) {
QueryView.propTypes = { query: PropTypes.object.isRequired }; // eslint-disable-line react/forbid-prop-types
-export default function init(ngModule) {
- ngModule.component("pageQueryView", react2angular(QueryView));
-
- return {
- "/queries/:queryId": {
- template: '',
- reloadOnSearch: false,
- resolve: {
- query: $route => {
- "ngInject";
-
- return Query.get({ id: $route.current.params.queryId });
- },
- },
- },
- };
-}
-
-init.init = true;
+export default {
+ path: "/queries/:queryId([0-9]+)",
+ render: currentRoute => (
+
+
+ {({ handleError }) => }
+
+
+ ),
+ resolve: {
+ query: ({ queryId }) => Query.get({ id: queryId }),
+ },
+};
diff --git a/client/app/pages/queries/VisualizationEmbed.jsx b/client/app/pages/queries/VisualizationEmbed.jsx
index c81bc5b0c2..d0e8ce9baf 100644
--- a/client/app/pages/queries/VisualizationEmbed.jsx
+++ b/client/app/pages/queries/VisualizationEmbed.jsx
@@ -1,7 +1,6 @@
-import React, { useState, useEffect, useCallback } from "react";
+import React, { useState, useEffect, useCallback, useRef } from "react";
import PropTypes from "prop-types";
import { find, has } from "lodash";
-import { react2angular } from "react2angular";
import moment from "moment";
import { markdown } from "markdown";
import Button from "antd/lib/button";
@@ -9,7 +8,9 @@ import Dropdown from "antd/lib/dropdown";
import Icon from "antd/lib/icon";
import Menu from "antd/lib/menu";
import Tooltip from "antd/lib/tooltip";
-import { $location, $routeParams } from "@/services/ng";
+import SignedOutPageWrapper from "@/components/ApplicationArea/SignedOutPageWrapper";
+import { Query } from "@/services/query";
+import location from "@/services/location";
import { formatDateTime } from "@/lib/utils";
import HtmlContent from "@/components/HtmlContent";
import Parameters from "@/components/Parameters";
@@ -17,12 +18,13 @@ import { Moment } from "@/components/proptypes";
import TimeAgo from "@/components/TimeAgo";
import Timer from "@/components/Timer";
import QueryResultsLink from "@/components/EditVisualizationButton/QueryResultsLink";
+import { ErrorBoundaryContext } from "@/components/ErrorBoundary";
import VisualizationName from "@/visualizations/VisualizationName";
import VisualizationRenderer from "@/visualizations/VisualizationRenderer";
import { VisualizationType } from "@/visualizations";
-import { Query } from "@/services/query";
import logoUrl from "@/assets/images/redash_icon_small.png";
+import PromiseRejectionError from "@/lib/promise-rejection-error";
function VisualizationEmbedHeader({ queryName, queryDescription, visualization }) {
return (
@@ -48,7 +50,15 @@ VisualizationEmbedHeader.propTypes = {
VisualizationEmbedHeader.defaultProps = { queryDescription: "" };
-function VisualizationEmbedFooter({ query, queryResults, updatedAt, refreshStartedAt, queryUrl, hideTimestamp }) {
+function VisualizationEmbedFooter({
+ query,
+ queryResults,
+ updatedAt,
+ refreshStartedAt,
+ queryUrl,
+ hideTimestamp,
+ apiKey,
+}) {
const downloadMenu = (
-
-
{% block scripts %}{% endblock %}
{% include '_includes/signed_out_tail.html' %}
diff --git a/webpack.config.js b/webpack.config.js
index 3a3b20ba3f..ecd3240347 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -1,11 +1,10 @@
/* eslint-disable */
-const fs = require("fs");
const webpack = require("webpack");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const WebpackBuildNotifierPlugin = require("webpack-build-notifier");
const ManifestPlugin = require("webpack-manifest-plugin");
-const MiniCssExtractPlugin = require('mini-css-extract-plugin');
+const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CopyWebpackPlugin = require("copy-webpack-plugin");
const LessPluginAutoPrefix = require("less-plugin-autoprefix");
const BundleAnalyzerPlugin = require("webpack-bundle-analyzer")
@@ -20,8 +19,8 @@ const redashBackend = process.env.REDASH_BACKEND || "http://localhost:5000";
const basePath = path.join(__dirname, "client");
const appPath = path.join(__dirname, "client", "app");
-const extensionsRelativePath = process.env.EXTENSIONS_DIRECTORY ||
- path.join("client", "app", "extensions");
+const extensionsRelativePath =
+ process.env.EXTENSIONS_DIRECTORY || path.join("client", "app", "extensions");
const extensionPath = path.join(__dirname, extensionsRelativePath);
const config = {
@@ -41,16 +40,14 @@ const config = {
},
resolve: {
symlinks: false,
- extensions: ['.js', '.jsx'],
+ extensions: [".js", ".jsx"],
alias: {
"@": appPath,
- "extensions": extensionPath
- },
+ extensions: extensionPath
+ }
},
plugins: [
new WebpackBuildNotifierPlugin({ title: "Redash" }),
- // Enforce angular to use jQuery instead of jqLite
- new webpack.ProvidePlugin({ "window.jQuery": "jquery" }),
// bundle only default `moment` locale (`en`)
new webpack.ContextReplacementPlugin(/moment[\/\\]locale$/, /en/),
new HtmlWebpackPlugin({
@@ -68,20 +65,19 @@ const config = {
}),
new ManifestPlugin({
fileName: "asset-manifest.json",
- publicPath: "",
+ publicPath: ""
}),
new CopyWebpackPlugin([
{ from: "client/app/assets/robots.txt" },
{ from: "client/app/unsupported.html" },
{ from: "client/app/unsupportedRedirect.js" },
{ from: "client/app/assets/css/*.css", to: "styles/", flatten: true },
- { from: "node_modules/jquery/dist/jquery.min.js", to: "js/jquery.min.js" },
- { from: "client/app/assets/fonts", to: "fonts/" },
+ { from: "client/app/assets/fonts", to: "fonts/" }
])
],
optimization: {
splitChunks: {
- chunks: (chunk) => {
+ chunks: chunk => {
return chunk.name != "server";
}
}
@@ -154,7 +150,7 @@ const config = {
},
{
test: /\.geo\.json$/,
- type: 'javascript/auto',
+ type: "javascript/auto",
use: [
{
loader: "file-loader",