oncall-engine/grafana-plugin/src/models/plugin/plugin.ts
Dominik Broj b5c52255d5
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.
2024-08-22 08:02:05 +00:00

98 lines
4.1 KiB
TypeScript

import { isEqual } from 'lodash-es';
import { makeAutoObservable, runInAction } from 'mobx';
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, StatusResponse } from 'network/oncall-api/api.types';
import { RootBaseStore } from 'state/rootBaseStore/RootBaseStore';
import { waitInMs } from 'utils/async';
import { AutoLoadingState } from 'utils/decorators';
import { PluginHelper } from './plugin.helper';
/*
High-level OnCall initialization process:
On OSS:
- On OnCall page / OnCall extension mount POST /status is called and it has pluginConfiguration object with different flags.
If all of them have `ok: true` , we consider plugin to be successfully configured and application loading is being continued.
Otherwise, we show error page with the option to go to plugin config (for Admin user) or to contact administrator (for nonAdmin user)
- On plugin config page frontend sends another POST /status. If every flag has `ok: true`, it shows that plugin is connected.
Otherwise, it shows more detailed information of what is misconfigured / missing. User can update onCallApiUrl and try to reconnect plugin.
- If Grafana version >= 10.3 AND externalServiceAccount feature flag is `true`, then grafana token is autoprovisioned and there is no need to create it
- Otherwise, user is given the option to manually create service account as Admin and then reconnect the plugin
On Cloud:
- On OnCall page / OnCall extension mount POST /status is called. If plugin is configured correctly, application loads as usual.
If it's not, we show error page with the button to contact support
- On plugin config page we show info if plugin is connected. If it's not we show detailed information of the errors and the button to contact support
*/
export class PluginStore {
rootStore: RootBaseStore;
connectionStatus?: PluginConnection;
apiUrlFromStatus?: string;
isPluginConnected = false;
appliedOnCallApiUrl = '';
constructor(rootStore: RootBaseStore) {
makeAutoObservable(this, undefined, { autoBind: true });
this.rootStore = rootStore;
}
private resetConnectionStatus() {
this.connectionStatus = undefined;
this.isPluginConnected = false;
}
async refreshAppliedOnCallApiUrl() {
const { jsonData } = await GrafanaApiClient.getGrafanaPluginSettings();
runInAction(() => {
this.appliedOnCallApiUrl = jsonData.onCallApiUrl;
});
}
@AutoLoadingState(ActionKey.PLUGIN_VERIFY_CONNECTION)
async verifyPluginConnection() {
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
);
});
}
@AutoLoadingState(ActionKey.PLUGIN_UPDATE_SETTINGS_AND_REINITIALIZE)
async updatePluginSettingsAndReinitializePlugin({
currentJsonData,
newJsonData,
}: {
currentJsonData: OnCallPluginMetaJSONData;
newJsonData: Partial<OnCallPluginMetaJSONData>;
}) {
this.resetConnectionStatus();
const saveJsonDataCandidate = { ...currentJsonData, ...newJsonData };
if (!isEqual(currentJsonData, saveJsonDataCandidate) || !this.connectionStatus?.oncall_api_url?.ok) {
await GrafanaApiClient.updateGrafanaPluginSettings({ jsonData: saveJsonDataCandidate });
await waitInMs(1000); // It's required for backend proxy to pick up new settings
}
try {
await PluginHelper.install();
} finally {
await this.verifyPluginConnection();
}
}
@AutoLoadingState(ActionKey.PLUGIN_RECREATE_SERVICE_ACCOUNT)
async recreateServiceAccountAndRecheckPluginStatus() {
await GrafanaApiClient.recreateGrafanaTokenAndSaveInPluginSettings();
await this.verifyPluginConnection();
}
async enablePlugin() {
await GrafanaApiClient.updateGrafanaPluginSettings({}, true);
location.reload();
}
}