123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228 |
- import {browserHistory} from 'react-router';
- import {Location} from 'history';
- import isEmpty from 'lodash/isEmpty';
- import isEqual from 'lodash/isEqual';
- import {Client} from 'sentry/api';
- import DeprecatedAsyncComponent from 'sentry/components/deprecatedAsyncComponent';
- import NotFound from 'sentry/components/errors/notFound';
- import * as Layout from 'sentry/components/layouts/thirds';
- import LoadingIndicator from 'sentry/components/loadingIndicator';
- import SentryDocumentTitle from 'sentry/components/sentryDocumentTitle';
- import {t} from 'sentry/locale';
- import {Organization} from 'sentry/types';
- import withRouteAnalytics, {
- WithRouteAnalyticsProps,
- } from 'sentry/utils/routeAnalytics/withRouteAnalytics';
- import {normalizeUrl} from 'sentry/utils/withDomainRequired';
- import {assignTempId} from './layoutUtils';
- import {DashboardDetails, DashboardListItem} from './types';
- import {hasSavedPageFilters} from './utils';
- type OrgDashboardsChildrenProps = {
- dashboard: DashboardDetails | null;
- dashboards: DashboardListItem[];
- error: boolean;
- onDashboardUpdate: (updatedDashboard: DashboardDetails) => void;
- };
- type Props = WithRouteAnalyticsProps & {
- api: Client;
- children: (props: OrgDashboardsChildrenProps) => React.ReactNode;
- location: Location;
- organization: Organization;
- params: {orgId: string; dashboardId?: string};
- };
- type State = {
- // endpoint response
- dashboards: DashboardListItem[] | null;
- /**
- * The currently selected dashboard.
- */
- selectedDashboard: DashboardDetails | null;
- } & DeprecatedAsyncComponent['state'];
- class OrgDashboards extends DeprecatedAsyncComponent<Props, State> {
- state: State = {
- // AsyncComponent state
- loading: true,
- reloading: false,
- error: false,
- errors: {},
- dashboards: [],
- selectedDashboard: null,
- };
- componentDidUpdate(prevProps: Props) {
- if (!isEqual(prevProps.params.dashboardId, this.props.params.dashboardId)) {
- this.remountComponent();
- }
- }
- getEndpoints(): ReturnType<DeprecatedAsyncComponent['getEndpoints']> {
- const {organization, params} = this.props;
- const url = `/organizations/${organization.slug}/dashboards/`;
- const endpoints: ReturnType<DeprecatedAsyncComponent['getEndpoints']> = [
- ['dashboards', url],
- ];
- if (params.dashboardId) {
- endpoints.push(['selectedDashboard', `${url}${params.dashboardId}/`]);
- this.props.setEventNames('dashboards2.view', 'Dashboards2: View dashboard');
- this.props.setRouteAnalyticsParams({
- dashboard_id: params.dashboardId,
- });
- }
- return endpoints;
- }
- onDashboardUpdate(updatedDashboard: DashboardDetails) {
- this.setState({selectedDashboard: updatedDashboard});
- }
- getDashboards(): DashboardListItem[] {
- const {dashboards} = this.state;
- return Array.isArray(dashboards) ? dashboards : [];
- }
- onRequestSuccess({stateKey, data}) {
- const {params, organization, location} = this.props;
- if (params.dashboardId || stateKey === 'selectedDashboard') {
- const queryParamFilters = new Set([
- 'project',
- 'environment',
- 'statsPeriod',
- 'start',
- 'end',
- 'utc',
- 'release',
- ]);
- if (
- stateKey === 'selectedDashboard' &&
- // Only redirect if there are saved filters and none of the filters
- // appear in the query params
- hasSavedPageFilters(data) &&
- isEmpty(
- Object.keys(location.query).filter(unsavedQueryParam =>
- queryParamFilters.has(unsavedQueryParam)
- )
- )
- ) {
- browserHistory.replace({
- ...location,
- query: {
- ...location.query,
- project: data.projects,
- environment: data.environment,
- statsPeriod: data.period,
- start: data.start,
- end: data.end,
- utc: data.utc,
- },
- });
- }
- return;
- }
- // If we don't have a selected dashboard, and one isn't going to arrive
- // we can redirect to the first dashboard in the list.
- const dashboardId = data.length ? data[0].id : 'default-overview';
- browserHistory.replace(
- normalizeUrl({
- pathname: `/organizations/${organization.slug}/dashboard/${dashboardId}/`,
- query: {
- ...location.query,
- },
- })
- );
- }
- renderLoading() {
- return (
- <Layout.Page withPadding>
- <LoadingIndicator />
- </Layout.Page>
- );
- }
- renderBody() {
- const {children} = this.props;
- const {selectedDashboard, error} = this.state;
- let dashboard = selectedDashboard;
- // Ensure there are always tempIds for grid layout
- // This is needed because there are cases where the dashboard
- // renders before the onRequestSuccess setState is processed
- // and will caused stacked widgets because of missing tempIds
- dashboard = selectedDashboard
- ? {
- ...selectedDashboard,
- widgets: selectedDashboard.widgets.map(assignTempId),
- }
- : null;
- return children({
- error,
- dashboard,
- dashboards: this.getDashboards(),
- onDashboardUpdate: (updatedDashboard: DashboardDetails) =>
- this.onDashboardUpdate(updatedDashboard),
- });
- }
- renderError(error: Error) {
- const notFound = Object.values(this.state.errors).find(
- resp => resp && resp.status === 404
- );
- if (notFound) {
- return <NotFound />;
- }
- return super.renderError(error, true);
- }
- renderComponent() {
- const {organization, location} = this.props;
- const {loading, selectedDashboard} = this.state;
- if (!organization.features.includes('dashboards-basic')) {
- // Redirect to Dashboards v1
- browserHistory.replace(
- normalizeUrl({
- pathname: `/organizations/${organization.slug}/dashboards/`,
- query: {
- ...location.query,
- },
- })
- );
- return null;
- }
- if (
- loading &&
- selectedDashboard &&
- hasSavedPageFilters(selectedDashboard) &&
- isEmpty(location.query)
- ) {
- // Block dashboard from rendering if the dashboard has filters and
- // the URL does not contain filters yet. The filters can either match the
- // saved filters, or can be different (i.e. sharing an unsaved state)
- return this.renderLoading();
- }
- return (
- <SentryDocumentTitle title={t('Dashboards')} orgSlug={organization.slug}>
- {super.renderComponent() as React.ReactChild}
- </SentryDocumentTitle>
- );
- }
- }
- export default withRouteAnalytics(OrgDashboards);
|