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()
|
.clearCookies()
|
||||||
.request({ url: getPublicDashboardAPIUrl(String(url)), failOnStatusCode: false })
|
.request({ url: getPublicDashboardAPIUrl(String(url)), failOnStatusCode: false })
|
||||||
.then((resp) => {
|
.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: {
|
Explore: {
|
||||||
url: '/explore',
|
url: '/explore',
|
||||||
General: {
|
General: {
|
||||||
|
@ -43,6 +43,7 @@ func (api *Api) ViewPublicDashboard(c *contextmodel.ReqContext) response.Respons
|
|||||||
IsFolder: false,
|
IsFolder: false,
|
||||||
FolderId: dash.FolderID,
|
FolderId: dash.FolderID,
|
||||||
PublicDashboardAccessToken: pubdash.AccessToken,
|
PublicDashboardAccessToken: pubdash.AccessToken,
|
||||||
|
PublicDashboardEnabled: pubdash.IsEnabled,
|
||||||
}
|
}
|
||||||
dash.Data.Get("timepicker").Set("hidden", !pubdash.TimeSelectionEnabled)
|
dash.Data.Get("timepicker").Set("hidden", !pubdash.TimeSelectionEnabled)
|
||||||
|
|
||||||
|
@ -190,11 +190,11 @@ func (d *PublicDashboardStoreImpl) ExistsEnabledByAccessToken(ctx context.Contex
|
|||||||
return hasPublicDashboard, err
|
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) {
|
func (d *PublicDashboardStoreImpl) GetOrgIdByAccessToken(ctx context.Context, accessToken string) (int64, error) {
|
||||||
var orgId int64
|
var orgId int64
|
||||||
err := d.sqlStore.WithDbSession(ctx, func(dbSession *db.Session) error {
|
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)
|
_, err := dbSession.SQL(sql, accessToken).Get(&orgId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -597,7 +597,7 @@ func TestIntegrationGetOrgIdByAccessToken(t *testing.T) {
|
|||||||
assert.Equal(t, savedDashboard.OrgID, orgId)
|
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()
|
setup()
|
||||||
cmd := SavePublicDashboardCommand{
|
cmd := SavePublicDashboardCommand{
|
||||||
PublicDashboard: PublicDashboard{
|
PublicDashboard: PublicDashboard{
|
||||||
@ -616,7 +616,7 @@ func TestIntegrationGetOrgIdByAccessToken(t *testing.T) {
|
|||||||
|
|
||||||
orgId, err := publicdashboardStore.GetOrgIdByAccessToken(context.Background(), "accessToken")
|
orgId, err := publicdashboardStore.GetOrgIdByAccessToken(context.Background(), "accessToken")
|
||||||
require.NoError(t, err)
|
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) {
|
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"))
|
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"))
|
ErrInvalidTimeRange = errutil.NewBase(errutil.StatusBadRequest, "publicdashboards.invalidTimeRange", errutil.WithPublicMessage("Invalid time range"))
|
||||||
ErrInvalidShareType = errutil.NewBase(errutil.StatusBadRequest, "publicdashboards.invalidShareType", errutil.WithPublicMessage("Invalid share type"))
|
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 {
|
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)
|
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 selectors = e2eSelectors.components;
|
||||||
|
const publicDashboardSelector = e2eSelectors.pages.PublicDashboard;
|
||||||
|
|
||||||
const getTestDashboard = (overrides?: Partial<Dashboard>, metaOverrides?: Partial<DashboardMeta>): DashboardModel => {
|
const getTestDashboard = (overrides?: Partial<Dashboard>, metaOverrides?: Partial<DashboardMeta>): DashboardModel => {
|
||||||
const data: Dashboard = Object.assign(
|
const data: Dashboard = Object.assign(
|
||||||
@ -214,6 +215,10 @@ describe('PublicDashboardPage', () => {
|
|||||||
expect(screen.queryByTestId(selectors.RefreshPicker.runButtonV2)).not.toBeInTheDocument();
|
expect(screen.queryByTestId(selectors.RefreshPicker.runButtonV2)).not.toBeInTheDocument();
|
||||||
expect(screen.queryByTestId(selectors.RefreshPicker.intervalButtonV2)).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) => {
|
dashboardPageScenario('Given a public dashboard with time range enabled', (ctx) => {
|
||||||
@ -240,4 +245,50 @@ describe('PublicDashboardPage', () => {
|
|||||||
expect(screen.getByTestId(selectors.RefreshPicker.intervalButtonV2)).toBeInTheDocument();
|
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 { usePrevious } from 'react-use';
|
||||||
|
|
||||||
import { GrafanaTheme2, PageLayoutType, TimeZone } from '@grafana/data';
|
import { GrafanaTheme2, PageLayoutType, TimeZone } from '@grafana/data';
|
||||||
|
import { selectors as e2eSelectors } from '@grafana/e2e-selectors/src';
|
||||||
import { PageToolbar, useStyles2 } from '@grafana/ui';
|
import { PageToolbar, useStyles2 } from '@grafana/ui';
|
||||||
import { Page } from 'app/core/components/Page/Page';
|
import { Page } from 'app/core/components/Page/Page';
|
||||||
import { useGrafana } from 'app/core/context/GrafanaContext';
|
import { useGrafana } from 'app/core/context/GrafanaContext';
|
||||||
@ -14,6 +15,7 @@ import { DashNavTimeControls } from '../components/DashNav/DashNavTimeControls';
|
|||||||
import { DashboardFailed } from '../components/DashboardLoading/DashboardFailed';
|
import { DashboardFailed } from '../components/DashboardLoading/DashboardFailed';
|
||||||
import { DashboardLoading } from '../components/DashboardLoading/DashboardLoading';
|
import { DashboardLoading } from '../components/DashboardLoading/DashboardLoading';
|
||||||
import { PublicDashboardFooter } from '../components/PublicDashboardFooter/PublicDashboardsFooter';
|
import { PublicDashboardFooter } from '../components/PublicDashboardFooter/PublicDashboardsFooter';
|
||||||
|
import { PublicDashboardNotAvailable } from '../components/PublicDashboardNotAvailable/PublicDashboardNotAvailable';
|
||||||
import { DashboardGrid } from '../dashgrid/DashboardGrid';
|
import { DashboardGrid } from '../dashgrid/DashboardGrid';
|
||||||
import { getTimeSrv } from '../services/TimeSrv';
|
import { getTimeSrv } from '../services/TimeSrv';
|
||||||
import { DashboardModel } from '../state';
|
import { DashboardModel } from '../state';
|
||||||
@ -31,6 +33,8 @@ interface PublicDashboardPageRouteSearchParams {
|
|||||||
|
|
||||||
export type Props = GrafanaRouteComponentProps<PublicDashboardPageRouteParams, PublicDashboardPageRouteSearchParams>;
|
export type Props = GrafanaRouteComponentProps<PublicDashboardPageRouteParams, PublicDashboardPageRouteSearchParams>;
|
||||||
|
|
||||||
|
const selectors = e2eSelectors.pages.PublicDashboard;
|
||||||
|
|
||||||
const Toolbar = ({ dashboard }: { dashboard: DashboardModel }) => {
|
const Toolbar = ({ dashboard }: { dashboard: DashboardModel }) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
@ -91,11 +95,20 @@ const PublicDashboardPage = (props: Props) => {
|
|||||||
return <DashboardLoading initPhase={dashboardState.initPhase} />;
|
return <DashboardLoading initPhase={dashboardState.initPhase} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (dashboard.meta.publicDashboardEnabled === false) {
|
||||||
|
return <PublicDashboardNotAvailable paused />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dashboard.meta.dashboardNotFound) {
|
||||||
|
return <PublicDashboardNotAvailable />;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Page
|
<Page
|
||||||
pageNav={{ text: dashboard.title }}
|
pageNav={{ text: dashboard.title }}
|
||||||
layout={PageLayoutType.Custom}
|
layout={PageLayoutType.Custom}
|
||||||
toolbar={<Toolbar dashboard={dashboard} />}
|
toolbar={<Toolbar dashboard={dashboard} />}
|
||||||
|
data-testid={selectors.page}
|
||||||
>
|
>
|
||||||
{dashboardState.initError && <DashboardFailed initError={dashboardState.initError} />}
|
{dashboardState.initError && <DashboardFailed initError={dashboardState.initError} />}
|
||||||
<div className={styles.gridContainer}>
|
<div className={styles.gridContainer}>
|
||||||
|
@ -9,7 +9,7 @@ import impressionSrv from 'app/core/services/impression_srv';
|
|||||||
import kbn from 'app/core/utils/kbn';
|
import kbn from 'app/core/utils/kbn';
|
||||||
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
|
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
|
||||||
import { getGrafanaStorage } from 'app/features/storage/storage';
|
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';
|
import { appEvents } from '../../../core/core';
|
||||||
|
|
||||||
@ -17,7 +17,10 @@ import { getDashboardSrv } from './DashboardSrv';
|
|||||||
|
|
||||||
export class DashboardLoaderSrv {
|
export class DashboardLoaderSrv {
|
||||||
constructor() {}
|
constructor() {}
|
||||||
_dashboardLoadFailed(title: string, snapshot?: boolean) {
|
_dashboardLoadFailed(
|
||||||
|
title: string,
|
||||||
|
snapshot?: boolean
|
||||||
|
): { meta: DashboardMeta; dashboard: Partial<DashboardDataDTO> } {
|
||||||
snapshot = snapshot || false;
|
snapshot = snapshot || false;
|
||||||
return {
|
return {
|
||||||
meta: {
|
meta: {
|
||||||
@ -51,8 +54,24 @@ export class DashboardLoaderSrv {
|
|||||||
.then((result: any) => {
|
.then((result: any) => {
|
||||||
return result;
|
return result;
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch((e) => {
|
||||||
return this._dashboardLoadFailed('Public Dashboard Not found', true);
|
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 {
|
} else {
|
||||||
promise = backendSrv
|
promise = backendSrv
|
||||||
|
Loading…
Reference in New Issue
Block a user