mirror of
https://github.com/grafana/grafana.git
synced 2024-11-23 01:16:31 -06:00
PublicDashboards: Paused or deleted public dashboard screen (#63970)
This commit is contained in:
parent
92f47e72e1
commit
c59682fad6
@ -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);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
@ -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: {
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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) {
|
||||
|
@ -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"))
|
||||
)
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
`,
|
||||
});
|
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -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}>
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user