PublicDashboards: Paused or deleted public dashboard screen (#63970)

This commit is contained in:
juanicabanas 2023-03-03 10:12:29 -03:00 committed by GitHub
parent 92f47e72e1
commit c59682fad6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 172 additions and 10 deletions

View File

@ -144,7 +144,7 @@ e2e.scenario({
.clearCookies()
.request({ url: getPublicDashboardAPIUrl(String(url)), failOnStatusCode: false })
.then((resp) => {
expect(resp.status).to.eq(404);
expect(resp.status).to.eq(403);
});
});
},

View File

@ -208,6 +208,14 @@ export const Pages = {
},
},
},
PublicDashboard: {
page: 'public-dashboard-page',
NotAvailable: {
container: 'public-dashboard-not-available',
title: 'public-dashboard-title',
pausedDescription: 'public-dashboard-paused-description',
},
},
Explore: {
url: '/explore',
General: {

View File

@ -43,6 +43,7 @@ func (api *Api) ViewPublicDashboard(c *contextmodel.ReqContext) response.Respons
IsFolder: false,
FolderId: dash.FolderID,
PublicDashboardAccessToken: pubdash.AccessToken,
PublicDashboardEnabled: pubdash.IsEnabled,
}
dash.Data.Get("timepicker").Set("hidden", !pubdash.TimeSelectionEnabled)

View File

@ -190,11 +190,11 @@ func (d *PublicDashboardStoreImpl) ExistsEnabledByAccessToken(ctx context.Contex
return hasPublicDashboard, err
}
// GetOrgIdByAccessToken Returns the public dashboard OrgId if exists and is enabled.
// GetOrgIdByAccessToken Returns the public dashboard OrgId if exists.
func (d *PublicDashboardStoreImpl) GetOrgIdByAccessToken(ctx context.Context, accessToken string) (int64, error) {
var orgId int64
err := d.sqlStore.WithDbSession(ctx, func(dbSession *db.Session) error {
sql := "SELECT org_id FROM dashboard_public WHERE access_token=? AND is_enabled=true"
sql := "SELECT org_id FROM dashboard_public WHERE access_token=?"
_, err := dbSession.SQL(sql, accessToken).Get(&orgId)
if err != nil {

View File

@ -597,7 +597,7 @@ func TestIntegrationGetOrgIdByAccessToken(t *testing.T) {
assert.Equal(t, savedDashboard.OrgID, orgId)
})
t.Run("GetOrgIdByAccessToken will return 0 when IsEnabled=false", func(t *testing.T) {
t.Run("GetOrgIdByAccessToken will return current OrgId when IsEnabled=false", func(t *testing.T) {
setup()
cmd := SavePublicDashboardCommand{
PublicDashboard: PublicDashboard{
@ -616,7 +616,7 @@ func TestIntegrationGetOrgIdByAccessToken(t *testing.T) {
orgId, err := publicdashboardStore.GetOrgIdByAccessToken(context.Background(), "accessToken")
require.NoError(t, err)
assert.NotEqual(t, savedDashboard.OrgID, orgId)
assert.Equal(t, savedDashboard.OrgID, orgId)
})
t.Run("GetOrgIdByAccessToken will return 0 when no public dashboard has matching access token", func(t *testing.T) {

View File

@ -20,4 +20,6 @@ var (
ErrInvalidMaxDataPoints = errutil.NewBase(errutil.StatusBadRequest, "publicdashboards.maxDataPoints", errutil.WithPublicMessage("maxDataPoints should be greater than 0"))
ErrInvalidTimeRange = errutil.NewBase(errutil.StatusBadRequest, "publicdashboards.invalidTimeRange", errutil.WithPublicMessage("Invalid time range"))
ErrInvalidShareType = errutil.NewBase(errutil.StatusBadRequest, "publicdashboards.invalidShareType", errutil.WithPublicMessage("Invalid share type"))
ErrPublicDashboardNotEnabled = errutil.NewBase(errutil.StatusForbidden, "publicdashboards.notEnabled", errutil.WithPublicMessage("Public dashboard paused"))
)

View File

@ -113,7 +113,7 @@ func (pd *PublicDashboardServiceImpl) FindPublicDashboardAndDashboardByAccessTok
}
if !pubdash.IsEnabled {
return nil, nil, ErrPublicDashboardNotFound.Errorf("FindPublicDashboardAndDashboardByAccessToken: Public dashboard is disabled accessToken: %s", accessToken)
return nil, nil, ErrPublicDashboardNotEnabled.Errorf("FindPublicDashboardAndDashboardByAccessToken: Public dashboard is paused accessToken: %s", accessToken)
}
dash, err := pd.store.FindDashboard(ctx, pubdash.OrgId, pubdash.DashboardUid)

View File

@ -0,0 +1,68 @@
import { css, cx } from '@emotion/css';
import React from 'react';
import { GrafanaTheme2 } from '@grafana/data/src';
import { selectors as e2eSelectors } from '@grafana/e2e-selectors/src';
import { useStyles2 } from '@grafana/ui/src';
import { Branding } from '../../../../core/components/Branding/Branding';
import { getLoginStyles } from '../../../../core/components/Login/LoginLayout';
const selectors = e2eSelectors.pages.PublicDashboard.NotAvailable;
export const PublicDashboardNotAvailable = ({ paused }: { paused?: boolean }) => {
const styles = useStyles2(getStyles);
const loginStyles = useStyles2(getLoginStyles);
const loginBoxBackground = Branding.LoginBoxBackground();
return (
<Branding.LoginBackground className={styles.container} data-testid={selectors.container}>
<div className={cx(styles.box, loginBoxBackground)}>
<Branding.LoginLogo className={loginStyles.loginLogo} />
<p className={styles.title} data-testid={selectors.title}>
{paused
? 'The dashboard has been temporarily paused by the administrator.'
: 'The dashboard your are trying to access does not exist.'}
</p>
{paused && (
<p className={styles.description} data-testid={selectors.pausedDescription}>
Please check again soon.
</p>
)}
</div>
</Branding.LoginBackground>
);
};
const getStyles = (theme: GrafanaTheme2) => ({
container: css`
display: flex;
justify-content: center;
align-items: center;
height: 100%;
:before {
opacity: 1;
}
`,
box: css`
width: 608px;
display: flex;
align-items: center;
flex-direction: column;
gap: ${theme.spacing(4)};
z-index: 1;
border-radius: ${theme.shape.borderRadius(4)};
padding: ${theme.spacing(6, 8)};
opacity: 1;
`,
title: css`
font-size: ${theme.typography.h3.fontSize};
text-align: center;
margin: 0;
`,
description: css`
font-size: ${theme.typography.h5.fontSize};
margin: 0;
`,
});

View File

@ -82,6 +82,7 @@ const renderWithProvider = ({
};
const selectors = e2eSelectors.components;
const publicDashboardSelector = e2eSelectors.pages.PublicDashboard;
const getTestDashboard = (overrides?: Partial<Dashboard>, metaOverrides?: Partial<DashboardMeta>): DashboardModel => {
const data: Dashboard = Object.assign(
@ -214,6 +215,10 @@ describe('PublicDashboardPage', () => {
expect(screen.queryByTestId(selectors.RefreshPicker.runButtonV2)).not.toBeInTheDocument();
expect(screen.queryByTestId(selectors.RefreshPicker.intervalButtonV2)).not.toBeInTheDocument();
});
it('Should not render paused or deleted screen', () => {
expect(screen.queryByTestId(publicDashboardSelector.NotAvailable.container)).not.toBeInTheDocument();
});
});
dashboardPageScenario('Given a public dashboard with time range enabled', (ctx) => {
@ -240,4 +245,50 @@ describe('PublicDashboardPage', () => {
expect(screen.getByTestId(selectors.RefreshPicker.intervalButtonV2)).toBeInTheDocument();
});
});
dashboardPageScenario('Given paused public dashboard', (ctx) => {
ctx.setup(() => {
ctx.mount();
ctx.rerender({
newState: {
dashboard: {
getModel: () => getTestDashboard(undefined, { publicDashboardEnabled: false, dashboardNotFound: false }),
initError: null,
initPhase: DashboardInitPhase.Completed,
permissions: [],
},
},
});
});
it('Should render public dashboard paused screen', () => {
expect(screen.queryByTestId(publicDashboardSelector.page)).not.toBeInTheDocument();
expect(screen.getByTestId(publicDashboardSelector.NotAvailable.title)).toBeInTheDocument();
expect(screen.getByTestId(publicDashboardSelector.NotAvailable.pausedDescription)).toBeInTheDocument();
});
});
dashboardPageScenario('Given deleted public dashboard', (ctx) => {
ctx.setup(() => {
ctx.mount();
ctx.rerender({
newState: {
dashboard: {
getModel: () => getTestDashboard(undefined, { dashboardNotFound: true }),
initError: null,
initPhase: DashboardInitPhase.Completed,
permissions: [],
},
},
});
});
it('Should render public dashboard deleted screen', () => {
expect(screen.queryByTestId(publicDashboardSelector.page)).not.toBeInTheDocument();
expect(screen.getByTestId(publicDashboardSelector.NotAvailable.title)).toBeInTheDocument();
expect(screen.queryByTestId(publicDashboardSelector.NotAvailable.pausedDescription)).not.toBeInTheDocument();
});
});
});

View File

@ -3,6 +3,7 @@ import React, { useEffect } from 'react';
import { usePrevious } from 'react-use';
import { GrafanaTheme2, PageLayoutType, TimeZone } from '@grafana/data';
import { selectors as e2eSelectors } from '@grafana/e2e-selectors/src';
import { PageToolbar, useStyles2 } from '@grafana/ui';
import { Page } from 'app/core/components/Page/Page';
import { useGrafana } from 'app/core/context/GrafanaContext';
@ -14,6 +15,7 @@ import { DashNavTimeControls } from '../components/DashNav/DashNavTimeControls';
import { DashboardFailed } from '../components/DashboardLoading/DashboardFailed';
import { DashboardLoading } from '../components/DashboardLoading/DashboardLoading';
import { PublicDashboardFooter } from '../components/PublicDashboardFooter/PublicDashboardsFooter';
import { PublicDashboardNotAvailable } from '../components/PublicDashboardNotAvailable/PublicDashboardNotAvailable';
import { DashboardGrid } from '../dashgrid/DashboardGrid';
import { getTimeSrv } from '../services/TimeSrv';
import { DashboardModel } from '../state';
@ -31,6 +33,8 @@ interface PublicDashboardPageRouteSearchParams {
export type Props = GrafanaRouteComponentProps<PublicDashboardPageRouteParams, PublicDashboardPageRouteSearchParams>;
const selectors = e2eSelectors.pages.PublicDashboard;
const Toolbar = ({ dashboard }: { dashboard: DashboardModel }) => {
const dispatch = useDispatch();
@ -91,11 +95,20 @@ const PublicDashboardPage = (props: Props) => {
return <DashboardLoading initPhase={dashboardState.initPhase} />;
}
if (dashboard.meta.publicDashboardEnabled === false) {
return <PublicDashboardNotAvailable paused />;
}
if (dashboard.meta.dashboardNotFound) {
return <PublicDashboardNotAvailable />;
}
return (
<Page
pageNav={{ text: dashboard.title }}
layout={PageLayoutType.Custom}
toolbar={<Toolbar dashboard={dashboard} />}
data-testid={selectors.page}
>
{dashboardState.initError && <DashboardFailed initError={dashboardState.initError} />}
<div className={styles.gridContainer}>

View File

@ -9,7 +9,7 @@ import impressionSrv from 'app/core/services/impression_srv';
import kbn from 'app/core/utils/kbn';
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
import { getGrafanaStorage } from 'app/features/storage/storage';
import { DashboardDTO, DashboardRoutes } from 'app/types';
import { DashboardDataDTO, DashboardDTO, DashboardMeta, DashboardRoutes } from 'app/types';
import { appEvents } from '../../../core/core';
@ -17,7 +17,10 @@ import { getDashboardSrv } from './DashboardSrv';
export class DashboardLoaderSrv {
constructor() {}
_dashboardLoadFailed(title: string, snapshot?: boolean) {
_dashboardLoadFailed(
title: string,
snapshot?: boolean
): { meta: DashboardMeta; dashboard: Partial<DashboardDataDTO> } {
snapshot = snapshot || false;
return {
meta: {
@ -51,8 +54,24 @@ export class DashboardLoaderSrv {
.then((result: any) => {
return result;
})
.catch(() => {
return this._dashboardLoadFailed('Public Dashboard Not found', true);
.catch((e) => {
const isPublicDashboardPaused =
e.data.statusCode === 403 && e.data.messageId === 'publicdashboards.notEnabled';
const isPublicDashboardNotFound =
e.data.statusCode === 404 && e.data.messageId === 'publicdashboards.notFound';
const dashboardModel = this._dashboardLoadFailed(
isPublicDashboardPaused ? 'Public Dashboard paused' : 'Public Dashboard Not found',
true
);
return {
...dashboardModel,
meta: {
...dashboardModel.meta,
publicDashboardEnabled: isPublicDashboardNotFound ? undefined : !isPublicDashboardPaused,
dashboardNotFound: isPublicDashboardNotFound,
},
};
});
} else {
promise = backendSrv