fix: show api url in main settings (#4896)

# What this PR does

- show api_url from GET /status endpoint on settings page
- refactor MainSettings to be functional component

## Which issue(s) this PR closes

https://raintank-corp.slack.com/archives/C0713BYQB0W/p1724249719392329

<!--
*Note*: If you want the issue to be auto-closed once the PR is merged,
change "Related to" to "Closes" in the line above.
If you have more than one GitHub issue that this PR closes, be sure to
preface
each issue link with a [closing
keyword](https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/using-keywords-in-issues-and-pull-requests#linking-a-pull-request-to-an-issue).
This ensures that the issue(s) are auto-closed once the PR has been
merged.
-->

## Checklist

- [ ] Unit, integration, and e2e (if applicable) tests updated
- [x] Documentation added (or `pr:no public docs` PR label added if not
required)
- [x] Added the relevant release notes label (see labels prefixed w/
`release:`). These labels dictate how your PR will
    show up in the autogenerated release notes.
This commit is contained in:
Dominik Broj 2024-08-22 10:02:05 +02:00 committed by GitHub
parent 070abb9d4f
commit b5c52255d5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 72 additions and 107 deletions

View file

@ -120,7 +120,7 @@ export const ApiTokenForm = observer((props: TokenCreationModalProps) => {
<VerticalGroup>
<Label>Curl command example</Label>
<SourceCode noMinHeight showClipboardIconOnly>
{getCurlExample(token, store.onCallApiUrl)}
{getCurlExample(token, store.pluginStore.apiUrlFromStatus)}
</SourceCode>
</VerticalGroup>
);

View file

@ -5,7 +5,7 @@ import { OnCallPluginMetaJSONData } from 'types';
import { ActionKey } from 'models/loader/action-keys';
import { GrafanaApiClient } from 'network/grafana-api/http-client';
import { makeRequest } from 'network/network';
import { PluginConnection, PostStatusResponse } from 'network/oncall-api/api.types';
import { PluginConnection, StatusResponse } from 'network/oncall-api/api.types';
import { RootBaseStore } from 'state/rootBaseStore/RootBaseStore';
import { waitInMs } from 'utils/async';
import { AutoLoadingState } from 'utils/decorators';
@ -31,6 +31,7 @@ On Cloud:
export class PluginStore {
rootStore: RootBaseStore;
connectionStatus?: PluginConnection;
apiUrlFromStatus?: string;
isPluginConnected = false;
appliedOnCallApiUrl = '';
@ -53,9 +54,10 @@ export class PluginStore {
@AutoLoadingState(ActionKey.PLUGIN_VERIFY_CONNECTION)
async verifyPluginConnection() {
const { pluginConnection } = await makeRequest<PostStatusResponse>(`/plugin/status`, {});
const { pluginConnection, api_url } = await makeRequest<StatusResponse>(`/plugin/status`, {});
runInAction(() => {
this.connectionStatus = pluginConnection;
this.apiUrlFromStatus = api_url;
this.isPluginConnected = Object.keys(pluginConnection).every(
(key) => pluginConnection[key as keyof PluginConnection]?.ok
);

View file

@ -16,15 +16,10 @@ type PluginConnection = {
grafana_url_from_engine: PluginConnectionCheck;
};
export type PostStatusResponse = {
export type StatusResponse = {
pluginConnection: PluginConnection;
allow_signup: boolean;
api_url: string;
currently_undergoing_maintenance_message: string | null;
is_installed: boolean;
is_user_anonymous: boolean;
license: string;
recaptcha_site_key: string;
token_ok: boolean;
version: string;
};

View file

@ -1,7 +0,0 @@
.title {
margin-bottom: 20px;
}
.settings {
width: fit-content;
}

View file

@ -1,7 +1,7 @@
import React from 'react';
import { Field, Input, Switch } from '@grafana/ui';
import cn from 'classnames/bind';
import { css } from '@emotion/css';
import { Field, Input, Switch, useStyles2 } from '@grafana/ui';
import { observer } from 'mobx-react';
import { LegacyNavHeading } from 'navbar/LegacyNavHeading';
@ -10,86 +10,71 @@ import { ApiTokenSettings } from 'containers/ApiTokenSettings/ApiTokenSettings';
import { WithPermissionControlTooltip } from 'containers/WithPermissionControl/WithPermissionControlTooltip';
import { TeamsSettings } from 'pages/settings/tabs/TeamsSettings/TeamsSettings';
import { isTopNavbar } from 'plugin/GrafanaPluginRootPage.helpers';
import { WithStoreProps } from 'state/types';
import { withMobXProviderContext } from 'state/withStore';
import { useStore } from 'state/useStore';
import { UserActions } from 'utils/authorization/authorization';
import styles from './MainSettings.module.css';
export const MainSettings = observer(() => {
const styles = useStyles2(getStyles);
const {
organizationStore: { currentOrganization, saveCurrentOrganization },
pluginStore: { apiUrlFromStatus },
} = useStore();
const cx = cn.bind(styles);
interface SettingsPageProps extends WithStoreProps {}
interface SettingsPageState {
apiUrl?: string;
}
@observer
class Settings extends React.Component<SettingsPageProps, SettingsPageState> {
state: SettingsPageState = {
apiUrl: '',
};
async componentDidMount() {
const { store } = this.props;
const url = await store.getApiUrlForSettings();
this.setState({ apiUrl: url });
}
render() {
const { organizationStore } = this.props.store;
const { currentOrganization } = organizationStore;
const { apiUrl } = this.state;
return (
<div className={cx('root')}>
<LegacyNavHeading>
<Text.Title level={3} className={cx('title')}>
Organization settings
</Text.Title>
</LegacyNavHeading>
<div className={cx('settings')}>
<Text.Title level={3} className={cx('title')}>
Resolution Note
</Text.Title>
<Field
loading={!currentOrganization}
label="Require a resolution note when resolving Alert Groups"
description={`Once user clicks "Resolve" for an Alert Group, they will be required to fill in a resolution note about the Alert Group`}
>
<WithPermissionControlTooltip userAction={UserActions.OtherSettingsWrite}>
<Switch
value={currentOrganization?.is_resolution_note_required}
onChange={(event) => {
organizationStore.saveCurrentOrganization({
is_resolution_note_required: event.currentTarget.checked,
});
}}
/>
</WithPermissionControlTooltip>
</Field>
</div>
{!isTopNavbar() && (
<div style={{ marginBottom: '20px' }}>
<Text.Title level={3} className={cx('title')}>
Teams and Access Settings
</Text.Title>
<TeamsSettings />
</div>
)}
<Text.Title level={3} className={cx('title')}>
API URL
return (
<div>
<LegacyNavHeading>
<Text.Title level={3} className={styles.title}>
Organization settings
</Text.Title>
<div>
<Field>
<Input value={apiUrl} disabled />
</Field>
</div>
<ApiTokenSettings />
</div>
);
}
}
</LegacyNavHeading>
export const MainSettings = withMobXProviderContext(Settings);
<div className={styles.settings}>
<Text.Title level={3} className={styles.title}>
Resolution Note
</Text.Title>
<Field
loading={!currentOrganization}
label="Require a resolution note when resolving Alert Groups"
description={`Once user clicks "Resolve" for an Alert Group, they will be required to fill in a resolution note about the Alert Group`}
>
<WithPermissionControlTooltip userAction={UserActions.OtherSettingsWrite}>
<Switch
value={currentOrganization?.is_resolution_note_required}
onChange={(event) => {
saveCurrentOrganization({
is_resolution_note_required: event.currentTarget.checked,
});
}}
/>
</WithPermissionControlTooltip>
</Field>
</div>
{!isTopNavbar() && (
<div style={{ marginBottom: '20px' }}>
<Text.Title level={3} className={styles.title}>
Teams and Access Settings
</Text.Title>
<TeamsSettings />
</div>
)}
<Text.Title level={3} className={styles.title}>
API URL
</Text.Title>
<div>
<Field>
<Input value={apiUrlFromStatus} disabled />
</Field>
</div>
<ApiTokenSettings />
</div>
);
});
const getStyles = () => ({
settings: css`
width: fit-content;
`,
title: css`
margin-bottom: 20px;
`,
});

View file

@ -69,9 +69,6 @@ export class RootBaseStore {
@observable
pageTitle = '';
@observable
onCallApiUrl: string;
@observable
insightsDatasource = 'grafanacloud-usage';
@ -186,11 +183,6 @@ export class RootBaseStore {
this.pageTitle = title;
}
@action.bound
async getApiUrlForSettings() {
return this.onCallApiUrl;
}
@action.bound
async loadRecaptcha() {
const { recaptcha_site_key } = await makeRequest<{ recaptcha_site_key: string }>('/plugin/recaptcha');

View file

@ -1,4 +1,4 @@
import { AppRootProps as BaseAppRootProps, AppPluginMeta, PluginConfigPageProps, BootData } from '@grafana/data';
import { AppRootProps as BaseAppRootProps, AppPluginMeta, PluginConfigPageProps } from '@grafana/data';
import { getPluginId } from 'utils/consts';
@ -30,8 +30,6 @@ export type OnCallPluginExtensionPoints =
declare global {
export interface Window {
// https://github.com/grafana/grafana/blob/78bef7a26a799209b5307d6bde8e25fcb4fbde7d/public/views/index-template.html#L251-L258
grafanaBootData?: BootData;
RECAPTCHA_SITE_KEY: string;
grecaptcha: any;
dataLayer: any;