Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[IRM] - changes to get incident working in IRM plugin #4987

Merged
merged 6 commits into from
Sep 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 1 addition & 6 deletions grafana-plugin/.bra.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,14 @@
[run]
init_cmds = [
["mage", "-v", "build:debug"],
["mage", "-v" , "reloadPlugin"],
]
watch_all = true
follow_symlinks = false
ignore = [".git", "node_modules", "dist"]
ignore_files = ["mage_output_file.go"]
watch_dirs = [
"pkg",
# "src",
]
watch_dirs = ["pkg"]
watch_exts = [".go", ".json"]
build_delay = 2000
cmds = [
["mage", "-v", "build:debug"],
["mage", "-v" , "reloadPlugin"],
]
2 changes: 1 addition & 1 deletion grafana-plugin/go.mod
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module github.com/grafana-labs/grafana-oncall-app
module github.com/grafana/grafana-oncall-app

go 1.21.5

Expand Down
4 changes: 2 additions & 2 deletions grafana-plugin/pkg/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package main
import (
"os"

"github.com/grafana-labs/grafana-oncall-app/pkg/plugin"
"github.com/grafana/grafana-oncall-app/pkg/plugin"
"github.com/grafana/grafana-plugin-sdk-go/backend/app"
"github.com/grafana/grafana-plugin-sdk-go/backend/log"
)
Expand All @@ -16,7 +16,7 @@ func main() {
// argument. This factory will be automatically called on incoming request
// from Grafana to create different instances of `App` (per plugin
// ID).
if err := app.Manage("grafana-oncall-app", plugin.NewApp, app.ManageOpts{}); err != nil {
if err := app.Manage("grafana-oncall-app", plugin.NewInstance, app.ManageOpts{}); err != nil {
log.DefaultLogger.Error(err.Error())
os.Exit(1)
}
Expand Down
27 changes: 20 additions & 7 deletions grafana-plugin/pkg/plugin/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,9 @@ type App struct {
}

// NewApp creates a new example *App instance.
func NewApp(ctx context.Context, settings backend.AppInstanceSettings) (instancemgmt.Instance, error) {
func NewApp(ctx context.Context, settings backend.AppInstanceSettings) (*App, error) {
var app App

// Use a httpadapter (provided by the SDK) for resource calls. This allows us
// to use a *http.ServeMux for resource calls, so we can map multiple routes
// to CallResource without having to implement extra logic.
mux := http.NewServeMux()
app.registerRoutes(mux)
app.CallResourceHandler = httpadapter.New(mux)
app.OnCallSyncCache = &OnCallSyncCache{}
app.OnCallSettingsCache = &OnCallSettingsCache{}
app.OnCallUserCache = NewOnCallUserCache()
Expand All @@ -66,6 +60,25 @@ func NewApp(ctx context.Context, settings backend.AppInstanceSettings) (instance
return &app, nil
}

// NewInstance creates a new example *Instance instance.
func NewInstance(ctx context.Context, settings backend.AppInstanceSettings) (instancemgmt.Instance, error) {
app, err := NewApp(ctx, settings)

if err != nil {
log.DefaultLogger.Error("Error creating new app", "error", err)
return nil, err
}

// Use a httpadapter (provided by the SDK) for resource calls. This allows us
// to use a *http.ServeMux for resource calls, so we can map multiple routes
// to CallResource without having to implement extra logic.
mux := http.NewServeMux()
app.registerRoutes(mux)
app.CallResourceHandler = httpadapter.New(mux)

return app, nil
}

// Dispose here tells plugin SDK that plugin wants to clean up resources when a new instance
// created.
func (a *App) Dispose() {
Expand Down
5 changes: 3 additions & 2 deletions grafana-plugin/pkg/plugin/debug.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ package plugin

import (
"encoding/json"
"net/http"

"github.com/grafana/grafana-plugin-sdk-go/backend/log"
"github.com/grafana/grafana-plugin-sdk-go/backend/resource/httpadapter"
"net/http"
)

type OnCallDebugStats struct {
Expand Down Expand Up @@ -47,7 +48,7 @@ func (a *App) handleDebugSync(w http.ResponseWriter, req *http.Request) {
return
}

onCallSync, err := a.GetSyncData(req.Context(), onCallPluginSettings)
onCallSync, err := a.GetSyncData(onCallPluginSettings)
if err != nil {
log.DefaultLogger.Error("Error getting sync data", "error", err)
return
Expand Down
2 changes: 1 addition & 1 deletion grafana-plugin/pkg/plugin/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func (a *App) handleInstall(w http.ResponseWriter, req *http.Request) {
return
}

onCallSync, err := a.GetSyncData(req.Context(), onCallPluginSettings)
onCallSync, err := a.GetSyncData(onCallPluginSettings)
if err != nil {
log.DefaultLogger.Error("Error getting sync data", "error", err)
return
Expand Down
10 changes: 5 additions & 5 deletions grafana-plugin/pkg/plugin/resources.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ func afterRequest(handler http.Handler, afterFunc func(*responseWriter, *http.Re
})
}

func (a *App) handleInternalApi(w http.ResponseWriter, req *http.Request) {
func (a *App) HandleInternalApi(w http.ResponseWriter, req *http.Request) {
a.ProxyRequestToOnCall(w, req, "api/internal/v1/")
}

Expand Down Expand Up @@ -121,10 +121,10 @@ func (a *App) handleLegacyInstall(w *responseWriter, req *http.Request) {
// registerRoutes takes a *http.ServeMux and registers some HTTP handlers.
func (a *App) registerRoutes(mux *http.ServeMux) {
mux.HandleFunc("/plugin/install", a.handleInstall)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IRM won't need to route to /plugin/install (it has it's own install handler)

mux.HandleFunc("/plugin/status", a.handleStatus)
mux.HandleFunc("/plugin/sync", a.handleSync)
mux.HandleFunc("/plugin/status", a.HandleStatus)
mux.HandleFunc("/plugin/sync", a.HandleSync)

mux.Handle("/plugin/self-hosted/install", afterRequest(http.HandlerFunc(a.handleInternalApi), a.handleLegacyInstall))
mux.Handle("/plugin/self-hosted/install", afterRequest(http.HandlerFunc(a.HandleInternalApi), a.handleLegacyInstall))

// Disable debug endpoints
//mux.HandleFunc("/debug/user", a.handleDebugUser)
Expand All @@ -134,5 +134,5 @@ func (a *App) registerRoutes(mux *http.ServeMux) {
//mux.HandleFunc("/debug/stats", a.handleDebugStats)
//mux.HandleFunc("/debug/unlock", a.handleDebugUnlock)

mux.HandleFunc("/", a.handleInternalApi)
mux.HandleFunc("/", a.HandleInternalApi)
}
5 changes: 3 additions & 2 deletions grafana-plugin/pkg/plugin/resources_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ package plugin
import (
"bytes"
"context"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"testing"

"github.com/grafana/grafana-plugin-sdk-go/backend"
)

// mockCallResourceResponseSender implements backend.CallResourceResponseSender
Expand All @@ -23,7 +24,7 @@ func (s *mockCallResourceResponseSender) Send(response *backend.CallResourceResp
// This ensures the httpadapter for CallResource works correctly.
func TestCallResource(t *testing.T) {
// Initialize app
inst, err := NewApp(context.Background(), backend.AppInstanceSettings{})
inst, err := NewInstance(context.Background(), backend.AppInstanceSettings{})
if err != nil {
t.Fatalf("new app: %s", err)
}
Expand Down
17 changes: 7 additions & 10 deletions grafana-plugin/pkg/plugin/settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -317,32 +317,29 @@ func (a *App) SaveOnCallSettings(settings *OnCallPluginSettings) error {
return nil
}

func (a *App) GetSyncData(ctx context.Context, settings *OnCallPluginSettings) (*OnCallSync, error) {
func (a *App) GetSyncData(pluginSettings *OnCallPluginSettings) (*OnCallSync, error) {
var err error

startGetSyncData := time.Now()
defer func() {
elapsed := time.Since(startGetSyncData)
log.DefaultLogger.Info("GetSyncData", "time", elapsed.Milliseconds())
}()

onCallPluginSettings, err := a.OnCallSettingsFromContext(ctx)
if err != nil {
return nil, fmt.Errorf("error getting settings from context = %v", err)
}
Comment on lines -327 to -330
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need to do:

onCallPluginSettings, err := a.OnCallSettingsFromContext(ctx)

when settings *OnCallPluginSettings is already being passed into GetSyncData 🤔

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see your point. At a quick glance, it seems the passed value is already being got from the context every time? I think the original idea was to sync settings from context with previously saved settings, in case there were any changes. Maybe @mderynck can confirm.

Copy link
Contributor

@mderynck mderynck Sep 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 good cleanup to make. This parameter+call was leftover from when there was some back and forth about what was coming from where.


onCallSync := OnCallSync{
Settings: *settings,
Settings: *pluginSettings,
}
onCallSync.Users, err = a.GetAllUsersWithPermissions(onCallPluginSettings)
onCallSync.Users, err = a.GetAllUsersWithPermissions(pluginSettings)
if err != nil {
return nil, fmt.Errorf("error getting users = %v", err)
}

onCallSync.Teams, err = a.GetAllTeams(onCallPluginSettings)
onCallSync.Teams, err = a.GetAllTeams(pluginSettings)
if err != nil {
return nil, fmt.Errorf("error getting teams = %v", err)
}

teamMembers, err := a.GetAllTeamMembers(onCallPluginSettings, onCallSync.Teams)
teamMembers, err := a.GetAllTeamMembers(pluginSettings, onCallSync.Teams)
if err != nil {
return nil, fmt.Errorf("error getting team members = %v", err)
}
Expand Down
2 changes: 1 addition & 1 deletion grafana-plugin/pkg/plugin/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ func (a *App) ValidateOnCallStatus(ctx context.Context, settings *OnCallPluginSe
return &status, nil
}

func (a *App) handleStatus(w http.ResponseWriter, req *http.Request) {
func (a *App) HandleStatus(w http.ResponseWriter, req *http.Request) {
if req.Method != http.MethodGet {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
Expand Down
7 changes: 4 additions & 3 deletions grafana-plugin/pkg/plugin/sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@ import (
"encoding/json"
"errors"
"fmt"
"github.com/grafana/grafana-plugin-sdk-go/backend/log"
"io"
"net/http"
"net/url"
"strconv"
"sync"
"time"

"github.com/grafana/grafana-plugin-sdk-go/backend/log"
)

type OnCallSyncCache struct {
Expand All @@ -38,7 +39,7 @@ func (oc *OnCallSyncCache) UnlockAfterDelay(delay time.Duration) {
})
}

func (a *App) handleSync(w http.ResponseWriter, req *http.Request) {
func (a *App) HandleSync(w http.ResponseWriter, req *http.Request) {
if req.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
Expand Down Expand Up @@ -122,7 +123,7 @@ func (a *App) makeSyncRequest(ctx context.Context, forceSend bool) error {
return fmt.Errorf("error getting settings from context: %v ", err)
}

onCallSync, err := a.GetSyncData(ctx, onCallPluginSettings)
onCallSync, err := a.GetSyncData(onCallPluginSettings)
if err != nil {
return fmt.Errorf("error getting sync data: %v", err)
}
Expand Down
26 changes: 17 additions & 9 deletions grafana-plugin/src/network/grafana-api/http-client.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { getBackendSrv } from '@grafana/runtime';
import { OnCallPluginMetaJSONData } from 'app-types';
import { getPluginId } from 'helpers/consts';
import { getPluginId, PluginId } from 'helpers/consts';

import {
ApiAuthKeyDTO,
Expand All @@ -11,18 +11,26 @@ import {
UpdateGrafanaPluginSettingsProps,
} from './api.types';

const pluginId = getPluginId();
const KEY_NAME = {
[PluginId.OnCall]: 'OnCall',
[PluginId.Irm]: 'IRM',
}[pluginId];
const SERVICE_ACCOUNT_NAME = {
[PluginId.OnCall]: 'sa-autogen-OnCall',
[PluginId.Irm]: 'sa-autogen-IRM',
}[pluginId];

const KEYS_BASE_URL = '/api/auth/keys';
const SERVICE_ACCOUNTS_BASE_URL = '/api/serviceaccounts';
const ONCALL_KEY_NAME = 'OnCall';
const ONCALL_SERVICE_ACCOUNT_NAME = 'sa-autogen-OnCall';
const GRAFANA_PLUGIN_SETTINGS_URL = `/api/plugins/${getPluginId()}/settings`;
const GRAFANA_PLUGIN_SETTINGS_URL = `/api/plugins/${pluginId}/settings`;

export class GrafanaApiClient {
static grafanaBackend = getBackendSrv();

private static getServiceAccount = async () => {
const serviceAccounts = await this.grafanaBackend.get<PaginatedServiceAccounts>(
`${SERVICE_ACCOUNTS_BASE_URL}/search?query=${ONCALL_SERVICE_ACCOUNT_NAME}`
`${SERVICE_ACCOUNTS_BASE_URL}/search?query=${SERVICE_ACCOUNT_NAME}`
);
return serviceAccounts.serviceAccounts.length > 0 ? serviceAccounts.serviceAccounts[0] : null;
};
Expand All @@ -34,7 +42,7 @@ export class GrafanaApiClient {
}

return await this.grafanaBackend.post<ServiceAccountDTO>(SERVICE_ACCOUNTS_BASE_URL, {
name: ONCALL_SERVICE_ACCOUNT_NAME,
name: SERVICE_ACCOUNT_NAME,
role: 'Admin',
isDisabled: false,
});
Expand All @@ -44,7 +52,7 @@ export class GrafanaApiClient {
const tokens = await this.grafanaBackend.get<TokenDTO[]>(
`${SERVICE_ACCOUNTS_BASE_URL}/${serviceAccount.id}/tokens`
);
return tokens.find(({ name }) => name === ONCALL_KEY_NAME);
return tokens.find(({ name }) => name === KEY_NAME);
};

private static getGrafanaToken = async () => {
Expand All @@ -54,7 +62,7 @@ export class GrafanaApiClient {
}

const keys = await this.grafanaBackend.get<ApiAuthKeyDTO[]>(KEYS_BASE_URL);
return keys.find(({ name }) => name === ONCALL_KEY_NAME);
return keys.find(({ name }) => name === KEY_NAME);
};

static updateGrafanaPluginSettings = async (data: UpdateGrafanaPluginSettingsProps, enabled = true) =>
Expand All @@ -79,7 +87,7 @@ export class GrafanaApiClient {
const { key: grafanaToken } = await this.grafanaBackend.post<NewApiKeyResult>(
`${SERVICE_ACCOUNTS_BASE_URL}/${serviceAccount.id}/tokens`,
{
name: ONCALL_KEY_NAME,
name: KEY_NAME,
role: 'Admin',
}
);
Expand Down
Loading