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

HACKATHON: Grafana Sharing Slack App #80263

Open
wants to merge 85 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
85 commits
Select commit Hold shift + click to select a range
494a0d5
Create new endpoint and return challenge
lucychen-grafana Jan 10, 2024
c580d83
Merge branch 'main' into lucychen/grafana-sharing-slack-app
juanicabanas Jan 10, 2024
9ed70e6
making slack unfurl endpoint public
evictorero Jan 10, 2024
d5a8576
fix lint issues
evictorero Jan 10, 2024
3144087
fix lint issues
evictorero Jan 10, 2024
bae3dba
any removed
juanicabanas Jan 10, 2024
efb3c0c
add support for event payload if not challenge
lucychen-grafana Jan 10, 2024
ecd0d6c
rtk query api in progress
juanicabanas Jan 10, 2024
4ae5f58
fix lint
lucychen-grafana Jan 10, 2024
c1c07fb
in progress dashboard image generation
evictorero Jan 10, 2024
8437125
Create workflow for link shared events
AgnesToulet Jan 11, 2024
d573632
fix image url
AgnesToulet Jan 11, 2024
07c859a
add config for slack token
AgnesToulet Jan 11, 2024
c1c466d
push forgotten change
AgnesToulet Jan 11, 2024
36954fb
improve unfurl structure
AgnesToulet Jan 11, 2024
364be46
more improvements on the preview layout
AgnesToulet Jan 11, 2024
e3a893a
attempt to fix image url
AgnesToulet Jan 11, 2024
dae71a1
interface added
juanicabanas Jan 11, 2024
d061546
Merge branch 'lucychen/grafana-sharing-slack-app' into juanicabanas/s…
juanicabanas Jan 11, 2024
31eed82
fix preview layout and log
AgnesToulet Jan 11, 2024
590fddd
slack api in progress
juanicabanas Jan 11, 2024
5c0c06b
Merge branch 'lucychen/grafana-sharing-slack-app' of github.com:grafa…
juanicabanas Jan 11, 2024
c91b2a5
renamed to slack
juanicabanas Jan 11, 2024
e538e96
move everything into slack.go
AgnesToulet Jan 11, 2024
bf5eaff
fix attachments
AgnesToulet Jan 11, 2024
0dea950
channels returned
juanicabanas Jan 11, 2024
152ee79
channels merged
juanicabanas Jan 11, 2024
3eed28b
generate dashboard preview
evictorero Jan 11, 2024
979311d
retrieve dashboard title for preview
AgnesToulet Jan 11, 2024
2f0ee86
Merge branch 'lucychen/grafana-sharing-slack-app' of https://github.c…
AgnesToulet Jan 11, 2024
b9c77b5
preview in progress
juanicabanas Jan 11, 2024
65aa426
Merge branch 'lucychen/grafana-sharing-slack-app' of github.com:grafa…
juanicabanas Jan 11, 2024
2196390
preview generated
juanicabanas Jan 11, 2024
e293376
gif added
juanicabanas Jan 11, 2024
a4dfc18
removing unused endpoint and accepting only a specific event
evictorero Jan 11, 2024
1ec9995
log fixed. error response returned
juanicabanas Jan 11, 2024
60c6db5
Merge branch 'lucychen/grafana-sharing-slack-app' of github.com:grafa…
juanicabanas Jan 11, 2024
5babaa4
errors improved
juanicabanas Jan 11, 2024
fd6bcc7
add url value to view dashboard button
evictorero Jan 11, 2024
bcf31fa
fix lint issues
evictorero Jan 11, 2024
07a72a9
remove unused code
evictorero Jan 11, 2024
72ace86
share call added
juanicabanas Jan 11, 2024
6b53498
Merge branch 'lucychen/grafana-sharing-slack-app' of github.com:grafa…
juanicabanas Jan 11, 2024
04db2ad
panel share in progress
juanicabanas Jan 11, 2024
a4cde47
lint fixed
juanicabanas Jan 11, 2024
2d2679c
import removed
juanicabanas Jan 11, 2024
95448ef
add endpoint for sharin
lucychen-grafana Jan 11, 2024
888332f
update message block
lucychen-grafana Jan 12, 2024
ee8d42c
fix lint
lucychen-grafana Jan 12, 2024
0c007c2
Fix share to slack endpoint
AgnesToulet Jan 12, 2024
0b4a2d9
start moving slack functions into a service
AgnesToulet Jan 12, 2024
a85e9d5
move postMessage func to slack service
AgnesToulet Jan 12, 2024
fe97e36
move postUnfurl to slack service
AgnesToulet Jan 12, 2024
a67b870
some more cleanup
AgnesToulet Jan 12, 2024
b117fe7
gif and dropdown improvements
juanicabanas Jan 12, 2024
9bb4bbd
Merge branch 'lucychen/grafana-sharing-slack-app' of github.com:grafa…
juanicabanas Jan 12, 2024
cdcb14d
panel in progress
juanicabanas Jan 12, 2024
4f8160f
add support for share panel to slack
lucychen-grafana Jan 12, 2024
68f35a5
change panelid to paneltitle
lucychen-grafana Jan 12, 2024
34c55d5
improvement on modal
juanicabanas Jan 12, 2024
b56cd13
panel title added
juanicabanas Jan 12, 2024
75ee1f6
Merge branch 'lucychen/grafana-sharing-slack-app' into juanicabanas/p…
juanicabanas Jan 12, 2024
72e093d
panel added as notification
juanicabanas Jan 12, 2024
8e82cde
add slack request authentication
evictorero Jan 12, 2024
bca8bfa
slack api error correctly parsed
juanicabanas Feb 29, 2024
dd6db9e
Merge branch 'main' into lucychen/grafana-sharing-slack-app
evictorero Feb 29, 2024
16f0c65
add rendertype
evictorero Feb 29, 2024
d681390
delete old share button
evictorero Feb 29, 2024
5063aa8
fix new share button modal
evictorero Feb 29, 2024
aca3f98
add feature toggles
evictorero Feb 29, 2024
cd82471
change endpoints and panel or dashboard title logic
evictorero Mar 1, 2024
0380c95
add swagger docs
evictorero Mar 1, 2024
293d639
kiosk mode added as query param as workaround
juanicabanas Mar 4, 2024
b9d0635
slack refactor
juanicabanas Mar 5, 2024
c50ce16
snapshot with upload added
juanicabanas Mar 6, 2024
eb74e36
new service added
juanicabanas Mar 6, 2024
71c34aa
file restored
juanicabanas Mar 6, 2024
1c96729
Merge branch 'main' into lucychen/grafana-sharing-slack-app
juanicabanas Mar 6, 2024
b0c1ea9
option restored
juanicabanas Mar 6, 2024
9d52bce
service restored
juanicabanas Mar 6, 2024
e328822
authtentication removed
juanicabanas Mar 6, 2024
15792d1
dashboard service added in order to add title and not query twice
juanicabanas Mar 6, 2024
a9ad8f6
viewPanel instead of panelId in order to use /d url instead of /d-solo
juanicabanas Mar 6, 2024
e1481ea
improvements
juanicabanas Mar 6, 2024
12f70ab
unfurl fixed due to bad url parsing
juanicabanas Mar 7, 2024
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
5 changes: 5 additions & 0 deletions conf/defaults.ini
Original file line number Diff line number Diff line change
Expand Up @@ -1844,3 +1844,8 @@ read_only_toggles =
[public_dashboards]
# Set to false to disable public dashboards
enabled = true

#################################### Slack integration #####################################
[slack]
token =
signing_secret =
2 changes: 2 additions & 0 deletions packages/grafana-data/src/types/featureToggles.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,4 +180,6 @@ export interface FeatureToggles {
groupByVariable?: boolean;
alertingUpgradeDryrunOnStart?: boolean;
scopeFilters?: boolean;
slackSharePreview?: boolean;
slackUnfurling?: boolean;
}
2 changes: 2 additions & 0 deletions pkg/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,7 @@ func (hs *HTTPServer) registerRoutes() {
dashboardPermissionRoute.Get("/", authorize(ac.EvalPermission(dashboards.ActionDashboardsPermissionsRead)), routing.Wrap(hs.GetDashboardPermissionList))
dashboardPermissionRoute.Post("/", authorize(ac.EvalPermission(dashboards.ActionDashboardsPermissionsWrite)), routing.Wrap(hs.UpdateDashboardPermissions))
})
dashUidRoute.Post("/preview", authorize(ac.EvalPermission(dashboards.ActionDashboardsRead)), routing.Wrap(hs.GeneratePreview))
})

dashboardRoute.Post("/calculate-diff", authorize(ac.EvalPermission(dashboards.ActionDashboardsWrite)), routing.Wrap(hs.CalculateDashboardDiff))
Expand Down Expand Up @@ -575,6 +576,7 @@ func (hs *HTTPServer) registerRoutes() {

// short urls
apiRoute.Post("/short-urls", routing.Wrap(hs.createShortURL))

}, reqSignedIn)

// admin api
Expand Down
66 changes: 66 additions & 0 deletions pkg/api/dashboard.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,16 @@ import (
"encoding/json"
"errors"
"fmt"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/dashboardimage"
"github.com/grafana/grafana/pkg/services/rendering"
"net/http"
"net/url"
"os"
"path/filepath"
"strconv"
"strings"
"time"

"github.com/grafana/grafana/pkg/api/apierrors"
"github.com/grafana/grafana/pkg/api/dtos"
Expand Down Expand Up @@ -1030,6 +1035,67 @@ func (hs *HTTPServer) GetDashboardUIDs(c *contextmodel.ReqContext) {
c.JSON(http.StatusOK, uids)
}

func (hs *HTTPServer) GeneratePreview(c *contextmodel.ReqContext) response.Response {
var previewRequest PreviewRequest
if err := web.Bind(c.Req, &previewRequest); err != nil {
return response.Error(http.StatusBadRequest, "error parsing body", err)
}
previewUrl, err := url.Parse(previewRequest.ResourcePath)
if err != nil {
return response.Error(http.StatusInternalServerError, "error parsing resource path", err)
}
q := previewUrl.Query()
panelId, _ := strconv.ParseInt(q.Get("viewPanel"), 10, 64)

hs.log.Info("Generating preview", "resourcePath", previewRequest.ResourcePath)

dq := dashboards.GetDashboardQuery{OrgID: c.SignedInUser.GetOrgID(), UID: web.Params(c.Req)[":uid"]}
dashboard, err := hs.DashboardService.GetDashboard(c.Req.Context(), &dq)
if err != nil {
return response.Error(http.StatusBadRequest, "Retrieving dashboard failed", err)
}

opts := dashboardimage.ScreenshotOptions{
AuthOptions: rendering.AuthOpts{
OrgID: c.SignedInUser.GetOrgID(),
UserID: c.SignedInUser.UserID,
OrgRole: c.SignedInUser.OrgRole,
},
OrgID: c.SignedInUser.GetOrgID(),
DashboardUID: dashboard.UID,
DashboardSlug: dashboard.Slug,
PanelID: panelId,
From: q.Get("from"),
To: q.Get("to"),
Width: 1600,
Height: 800,
Theme: models.ThemeDark,
Timeout: time.Duration(60) * time.Second,
}

previewURL, err := hs.dashboardImageService.TakeScreenshotAndUpload(c.Req.Context(), opts)
if err != nil {
return response.Error(http.StatusInternalServerError, "Rendering failed", err)
}

return response.JSON(http.StatusOK, &PreviewResponse{
PreviewURL: previewURL,
})
}

func (hs *HTTPServer) getImageURL(imageName string) string {
grafanaURL := hs.getGrafanaURL()
return fmt.Sprintf("%s%s/%s", grafanaURL, "public/img/attachments", imageName)
}

type PreviewRequest struct {
ResourcePath string `json:"resourcePath"`
}

type PreviewResponse struct {
PreviewURL string `json:"previewUrl"`
}

// swagger:parameters restoreDashboardVersionByID
type RestoreDashboardVersionByIDParams struct {
// in:body
Expand Down
17 changes: 17 additions & 0 deletions pkg/api/dtos/slack.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package dtos

type SlackChannel struct {
Id string `json:"id"`
Name string `json:"name"`
}
type SlackChannels struct {
Channels []SlackChannel `json:"channels"`
}

type ShareRequest struct {
ChannelIds []string `json:"channelIds"`
ImagePreviewUrl string `json:"imagePreviewUrl"`
ResourcePath string `json:"resourcePath"`
Title string `json:"title"`
Message string `json:"message,omitempty"`
}
70 changes: 53 additions & 17 deletions pkg/api/http_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import (
"encoding/pem"
"errors"
"fmt"
"github.com/grafana/grafana/pkg/services/dashboardimage"
slackApi "github.com/grafana/grafana/pkg/services/slack/api"
"math/big"
"net"
"net/http"
Expand Down Expand Up @@ -200,23 +202,25 @@ type HTTPServer struct {
kvStore kvstore.KVStore
pluginsCDNService *pluginscdn.Service

userService user.Service
tempUserService tempUser.Service
loginAttemptService loginAttempt.Service
orgService org.Service
teamService team.Service
accesscontrolService accesscontrol.Service
annotationsRepo annotations.Repository
tagService tag.Service
oauthTokenService oauthtoken.OAuthTokenService
statsService stats.Service
authnService authn.Service
starApi *starApi.API
promRegister prometheus.Registerer
promGatherer prometheus.Gatherer
clientConfigProvider grafanaapiserver.DirectRestConfigProvider
namespacer request.NamespaceMapper
anonService anonymous.Service
userService user.Service
tempUserService tempUser.Service
loginAttemptService loginAttempt.Service
orgService org.Service
teamService team.Service
accesscontrolService accesscontrol.Service
annotationsRepo annotations.Repository
tagService tag.Service
oauthTokenService oauthtoken.OAuthTokenService
statsService stats.Service
authnService authn.Service
starApi *starApi.API
promRegister prometheus.Registerer
promGatherer prometheus.Gatherer
clientConfigProvider grafanaapiserver.DirectRestConfigProvider
namespacer request.NamespaceMapper
anonService anonymous.Service
slackApi *slackApi.Api
dashboardImageService dashboardimage.Service
}

type ServerOptions struct {
Expand Down Expand Up @@ -259,6 +263,8 @@ func ProvideHTTPServer(opts ServerOptions, cfg *setting.Cfg, routeRegister routi
annotationRepo annotations.Repository, tagService tag.Service, searchv2HTTPService searchV2.SearchHTTPService, oauthTokenService oauthtoken.OAuthTokenService,
statsService stats.Service, authnService authn.Service, pluginsCDNService *pluginscdn.Service, promGatherer prometheus.Gatherer,
starApi *starApi.API, promRegister prometheus.Registerer, clientConfigProvider grafanaapiserver.DirectRestConfigProvider, anonService anonymous.Service,
slackApi *slackApi.Api,
dashboardImageService dashboardimage.Service,
) (*HTTPServer, error) {
web.Env = cfg.Env
m := web.New()
Expand Down Expand Up @@ -361,6 +367,8 @@ func ProvideHTTPServer(opts ServerOptions, cfg *setting.Cfg, routeRegister routi
clientConfigProvider: clientConfigProvider,
namespacer: request.GetNamespaceMapper(cfg),
anonService: anonService,
slackApi: slackApi,
dashboardImageService: dashboardImageService,
}
if hs.Listener != nil {
hs.log.Debug("Using provided listener")
Expand Down Expand Up @@ -803,6 +811,34 @@ func (hs *HTTPServer) mapStatic(m *web.Mux, rootDir string, dir string, prefix s
))
}

// TODO: Duplicated from the rendering service - maybe we can do this in another way to not duplicate this
func (hs *HTTPServer) getGrafanaURL() string {
if hs.Cfg.RendererCallbackUrl != "" {
return hs.Cfg.RendererCallbackUrl
}

protocol := hs.Cfg.Protocol
switch protocol {
case setting.HTTPScheme:
protocol = "http"
case setting.HTTP2Scheme, setting.HTTPSScheme:
protocol = "https"
default:
// TODO: Handle other schemes?
}

subPath := ""
if hs.Cfg.ServeFromSubPath {
subPath = hs.Cfg.AppSubURL
}

domain := "localhost"
if hs.Cfg.HTTPAddr != "0.0.0.0" {
domain = hs.Cfg.HTTPAddr
}
return fmt.Sprintf("%s://%s:%s%s/", protocol, domain, hs.Cfg.HTTPPort, subPath)
}

func (hs *HTTPServer) metricsEndpointBasicAuthEnabled() bool {
return hs.Cfg.MetricsEndpointBasicAuthUsername != "" && hs.Cfg.MetricsEndpointBasicAuthPassword != ""
}
Expand Down
13 changes: 12 additions & 1 deletion pkg/server/wire.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ package server

import (
"github.com/google/wire"

sdkhttpclient "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient"
"github.com/grafana/grafana/pkg/services/dashboardimage"
"github.com/grafana/grafana/pkg/services/screenshot"

"github.com/grafana/grafana/pkg/api"
"github.com/grafana/grafana/pkg/api/avatar"
Expand Down Expand Up @@ -53,6 +54,7 @@ import (
"github.com/grafana/grafana/pkg/services/cloudmigration/cloudmigrationimpl"
"github.com/grafana/grafana/pkg/services/contexthandler"
"github.com/grafana/grafana/pkg/services/correlations"
dashboardImageService "github.com/grafana/grafana/pkg/services/dashboardimage/service"
"github.com/grafana/grafana/pkg/services/dashboardimport"
dashboardimportservice "github.com/grafana/grafana/pkg/services/dashboardimport/service"
dashboardstore "github.com/grafana/grafana/pkg/services/dashboards/database"
Expand Down Expand Up @@ -128,6 +130,9 @@ import (
"github.com/grafana/grafana/pkg/services/shorturls/shorturlimpl"
"github.com/grafana/grafana/pkg/services/signingkeys"
"github.com/grafana/grafana/pkg/services/signingkeys/signingkeysimpl"
"github.com/grafana/grafana/pkg/services/slack"
slackApi "github.com/grafana/grafana/pkg/services/slack/api"
slackService "github.com/grafana/grafana/pkg/services/slack/service"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/services/sqlstore/sqlutil"
"github.com/grafana/grafana/pkg/services/ssosettings"
Expand Down Expand Up @@ -183,6 +188,7 @@ var wireBasicSet = wire.NewSet(
alerting.ProvideAlertEngine,
wire.Bind(new(alerting.UsageStatsQuerier), new(*alerting.AlertEngine)),
New,
screenshot.NewHeadlessScreenshotService,
api.ProvideHTTPServer,
query.ProvideService,
wire.Bind(new(query.Service), new(*query.ServiceImpl)),
Expand Down Expand Up @@ -375,6 +381,7 @@ var wireBasicSet = wire.NewSet(
anonstore.ProvideAnonDBStore,
wire.Bind(new(anonstore.AnonStore), new(*anonstore.AnonDBStore)),
loggermw.Provide,
slackApi.ProvideApi,
signingkeysimpl.ProvideEmbeddedSigningKeysService,
wire.Bind(new(signingkeys.Service), new(*signingkeysimpl.Service)),
ssoSettingsImpl.ProvideService,
Expand All @@ -385,6 +392,10 @@ var wireBasicSet = wire.NewSet(
// Kubernetes API server
grafanaapiserver.WireSet,
apiregistry.WireSet,
dashboardImageService.ProvideService,
wire.Bind(new(dashboardimage.Service), new(*dashboardImageService.DashboardImageService)),
slackService.ProvideService,
wire.Bind(new(slack.Service), new(*slackService.SlackService)),
)

var wireSet = wire.NewSet(
Expand Down
7 changes: 7 additions & 0 deletions pkg/services/dashboardimage/dashboardimage.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package dashboardimage

import "context"

type Service interface {
TakeScreenshotAndUpload(ctx context.Context, opts ScreenshotOptions) (string, error)
}
23 changes: 23 additions & 0 deletions pkg/services/dashboardimage/models.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package dashboardimage

import (
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/rendering"
"time"
)

type ScreenshotOptions struct {
AuthOptions rendering.AuthOpts
OrgID int64
DashboardUID string
DashboardSlug string

// PanelID must be 0 or null if the screenshot is for the whole dashboard
PanelID int64
From string
To string
Width int
Height int
Theme models.Theme
Timeout time.Duration
}
Loading
Loading