Skip to content

Commit

Permalink
Merge pull request #4958 from grafana/dev
Browse files Browse the repository at this point in the history
v1.9.18
  • Loading branch information
mderynck authored Aug 29, 2024
2 parents 4285be9 + 962cc34 commit 8bdb124
Show file tree
Hide file tree
Showing 19 changed files with 160 additions and 28 deletions.
1 change: 1 addition & 0 deletions .github/workflows/expensive-e2e-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ jobs:
matrix:
grafana_version:
- 10.3.0
- 11.2.0
- latest
fail-fast: false
# Run one version at a time to avoid the issue when SMS notification are bundled together for multiple versions
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/linting-and-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,7 @@ jobs:
matrix:
grafana_version:
- 10.3.0
- 11.2.0
- latest
fail-fast: false
with:
Expand Down
2 changes: 1 addition & 1 deletion engine/apps/grafana_plugin/serializers/sync_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class SyncUserSerializer(serializers.Serializer):
login = serializers.CharField()
email = serializers.CharField()
role = serializers.CharField()
avatar_url = serializers.CharField()
avatar_url = serializers.CharField(allow_blank=True)
permissions = SyncPermissionSerializer(many=True, allow_empty=True, allow_null=True)
teams = serializers.ListField(child=serializers.IntegerField(), allow_empty=True, allow_null=True)

Expand Down
60 changes: 60 additions & 0 deletions engine/apps/grafana_plugin/tests/test_sync_v2.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import gzip
import json
from dataclasses import asdict
from unittest.mock import patch

import pytest
Expand All @@ -6,6 +9,7 @@
from rest_framework.test import APIClient

from apps.api.permissions import LegacyAccessControlRole
from apps.grafana_plugin.sync_data import SyncData, SyncSettings, SyncUser
from apps.grafana_plugin.tasks.sync_v2 import start_sync_organizations_v2


Expand Down Expand Up @@ -76,3 +80,59 @@ def test_skip_org_without_api_token(make_organization, api_token, sync_called):
) as mock_sync:
start_sync_organizations_v2()
assert mock_sync.called == sync_called


@pytest.mark.parametrize("format", [("json"), ("gzip")])
@pytest.mark.django_db
def test_sync_v2_content_encoding(
make_organization_and_user_with_plugin_token, make_user_auth_headers, settings, format
):
organization, user, token = make_organization_and_user_with_plugin_token()
settings.LICENSE = settings.CLOUD_LICENSE_NAME
client = APIClient()
headers = make_user_auth_headers(None, token, organization=organization)

data = SyncData(
users=[
SyncUser(
id=user.user_id,
name=user.username,
login=user.username,
email=user.email,
role="Admin",
avatar_url="",
permissions=[],
teams=[],
)
],
teams=[],
team_members={},
settings=SyncSettings(
stack_id=organization.stack_id,
org_id=organization.org_id,
license=settings.CLOUD_LICENSE_NAME,
oncall_api_url="http://localhost",
oncall_token="",
grafana_url="http://localhost",
grafana_token="fake_token",
rbac_enabled=False,
incident_enabled=False,
incident_backend_url="",
labels_enabled=False,
),
)

payload = asdict(data)
headers["HTTP_Content-Type"] = "application/json"
url = reverse("grafana-plugin:sync-v2")
with patch("apps.grafana_plugin.views.sync_v2.apply_sync_data") as mock_sync:
if format == "gzip":
headers["HTTP_Content-Encoding"] = "gzip"
json_data = json.dumps(payload)
payload = gzip.compress(json_data.encode("utf-8"))
response = client.generic("POST", url, data=payload, **headers)
else:
response = client.post(url, format=format, data=payload, **headers)

assert response.status_code == status.HTTP_200_OK
mock_sync.assert_called()
11 changes: 10 additions & 1 deletion engine/apps/grafana_plugin/views/sync_v2.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import gzip
import json
import logging
from dataclasses import asdict, is_dataclass

Expand Down Expand Up @@ -25,7 +27,14 @@ class SyncV2View(APIView):
authentication_classes = (BasePluginAuthentication,)

def do_sync(self, request: Request) -> Organization:
serializer = SyncDataSerializer(data=request.data)
if request.headers.get("Content-Encoding") == "gzip":
gzip_data = gzip.GzipFile(fileobj=request).read()
decoded_data = gzip_data.decode("utf-8")
data = json.loads(decoded_data)
else:
data = request.data

serializer = SyncDataSerializer(data=data)
if not serializer.is_valid():
raise SyncException(serializer.errors)

Expand Down
4 changes: 3 additions & 1 deletion engine/apps/public_api/views/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ def get_object(self):
permission_classes=(IsAuthenticated,),
)
def schedule_export(self, request, pk):
schedules = OnCallSchedule.objects.filter(organization=self.request.auth.organization)
schedules = OnCallSchedule.objects.filter(organization=self.request.auth.organization).related_to_user(
self.request.user
)
export = user_ical_export(self.request.user, schedules)
return Response(export)
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ test.skip(
'Above 10.3 labels need enterprise version to validate permissions'
);

test('New label keys and labels can be created @expensive', async ({ adminRolePage }) => {
// TODO: This test is flaky on CI. Undo skipping once we can test labels locally
test.skip('New label keys and labels can be created @expensive', async ({ adminRolePage }) => {
const { page } = adminRolePage;
await goToOnCallPage(page, 'integrations');
await openCreateIntegrationModal(page);
Expand Down
5 changes: 5 additions & 0 deletions grafana-plugin/pkg/plugin/debug.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,3 +95,8 @@ func (a *App) handleDebugStats(w http.ResponseWriter, req *http.Request) {
}
w.WriteHeader(http.StatusOK)
}

func (a *App) handleDebugUnlock(w http.ResponseWriter, req *http.Request) {
a.OnCallSyncCache.syncMutex.Unlock()
w.WriteHeader(http.StatusOK)
}
4 changes: 2 additions & 2 deletions grafana-plugin/pkg/plugin/permissions.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func (a *App) GetPermissions(settings *OnCallPluginSettings, onCallUser *OnCallU
var permissions []OnCallPermission
err = json.Unmarshal(body, &permissions)
if err != nil {
return nil, fmt.Errorf("failed to parse JSON response: %v body=%v", err, body)
return nil, fmt.Errorf("failed to parse JSON response: %v body=%v", err, string(body))
}

if res.StatusCode == 200 {
Expand Down Expand Up @@ -88,7 +88,7 @@ func (a *App) GetAllPermissions(settings *OnCallPluginSettings) (map[string]map[
var permissions map[string]map[string]interface{}
err = json.Unmarshal(body, &permissions)
if err != nil {
return nil, fmt.Errorf("failed to parse JSON response: %v body=%v", err, body)
return nil, fmt.Errorf("failed to parse JSON response: %v body=%v", err, string(body))
}

if res.StatusCode == 200 {
Expand Down
1 change: 1 addition & 0 deletions grafana-plugin/pkg/plugin/resources.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ func (a *App) registerRoutes(mux *http.ServeMux) {
//mux.HandleFunc("/debug/settings", a.handleDebugSettings)
//mux.HandleFunc("/debug/permissions", a.handleDebugPermissions)
//mux.HandleFunc("/debug/stats", a.handleDebugStats)
//mux.HandleFunc("/debug/unlock", a.handleDebugUnlock)

mux.HandleFunc("/", a.handleInternalApi)
}
2 changes: 1 addition & 1 deletion grafana-plugin/pkg/plugin/settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ func (a *App) GetOtherPluginSettings(settings *OnCallPluginSettings, pluginID st
var result map[string]interface{}
err = json.Unmarshal(body, &result)
if err != nil {
return nil, fmt.Errorf("failed to parse JSON response: %v body=%v", err, body)
return nil, fmt.Errorf("failed to parse JSON response: %v body=%v", err, string(body))
}

return result, nil
Expand Down
14 changes: 13 additions & 1 deletion grafana-plugin/pkg/plugin/sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package plugin

import (
"bytes"
"compress/gzip"
"context"
"encoding/json"
"errors"
Expand Down Expand Up @@ -136,6 +137,16 @@ func (a *App) makeSyncRequest(ctx context.Context, forceSend bool) error {
return fmt.Errorf("error marshalling JSON: %v", err)
}

var syncDataBuffer bytes.Buffer
gzipWriter := gzip.NewWriter(&syncDataBuffer)
_, err = gzipWriter.Write(onCallSyncJsonData)
if err != nil {
return fmt.Errorf("error writing sync data to gzip writer: %v", err)
}
if err := gzipWriter.Close(); err != nil {
return fmt.Errorf("error closing gzip writer: %v", err)
}

syncURL, err := url.JoinPath(onCallPluginSettings.OnCallAPIURL, "api/internal/v1/plugin/v2/sync")
if err != nil {
return fmt.Errorf("error joining path: %v", err)
Expand All @@ -146,7 +157,7 @@ func (a *App) makeSyncRequest(ctx context.Context, forceSend bool) error {
return fmt.Errorf("error parsing path: %v", err)
}

syncReq, err := http.NewRequest("POST", parsedSyncURL.String(), bytes.NewBuffer(onCallSyncJsonData))
syncReq, err := http.NewRequest("POST", parsedSyncURL.String(), &syncDataBuffer)
if err != nil {
return fmt.Errorf("error creating request: %v", err)
}
Expand All @@ -156,6 +167,7 @@ func (a *App) makeSyncRequest(ctx context.Context, forceSend bool) error {
return err
}
syncReq.Header.Set("Content-Type", "application/json")
syncReq.Header.Set("Content-Encoding", "gzip")

res, err := a.httpClient.Do(syncReq)
if err != nil {
Expand Down
6 changes: 3 additions & 3 deletions grafana-plugin/pkg/plugin/teams.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ func (a *App) GetTeamsForUser(settings *OnCallPluginSettings, onCallUser *OnCall
var result []Team
err = json.Unmarshal(body, &result)
if err != nil {
return nil, fmt.Errorf("failed to parse JSON response: %v body=%v", err, body)
return nil, fmt.Errorf("failed to parse JSON response: %v body=%v", err, string(body))
}

if res.StatusCode == 200 {
Expand Down Expand Up @@ -115,7 +115,7 @@ func (a *App) GetAllTeams(settings *OnCallPluginSettings) ([]OnCallTeam, error)
var result Teams
err = json.Unmarshal(body, &result)
if err != nil {
return nil, fmt.Errorf("failed to parse JSON response: %v body=%v", err, body)
return nil, fmt.Errorf("failed to parse JSON response: %v body=%v", err, string(body))
}

if res.StatusCode == 200 {
Expand Down Expand Up @@ -161,7 +161,7 @@ func (a *App) GetTeamsMembersForTeam(settings *OnCallPluginSettings, onCallTeam
var result []OrgUser
err = json.Unmarshal(body, &result)
if err != nil {
return nil, fmt.Errorf("failed to parse JSON response: %v body=%v", err, body)
return nil, fmt.Errorf("failed to parse JSON response: %v body=%v", err, string(body))
}

if res.StatusCode == 200 {
Expand Down
2 changes: 1 addition & 1 deletion grafana-plugin/pkg/plugin/users.go
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ func (a *App) GetAllUsers(settings *OnCallPluginSettings) ([]OnCallUser, error)
var result []OrgUser
err = json.Unmarshal(body, &result)
if err != nil {
return nil, fmt.Errorf("failed to parse JSON response: %v body=%v", err, body)
return nil, fmt.Errorf("failed to parse JSON response: %v body=%v", err, string(body))
}

if res.StatusCode == 200 {
Expand Down
2 changes: 1 addition & 1 deletion grafana-plugin/src/components/CardButton/CardButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export const CardButton: FC<CardButtonProps> = (props) => {
>
<div className={styles.icon}>{icon}</div>
<div className={styles.meta}>
<Stack gap={StackSize.xs}>
<Stack gap={StackSize.xs} direction="column">
<Text type="secondary">{description}</Text>
<Text.Title level={1}>{title}</Text.Title>
</Stack>
Expand Down
18 changes: 8 additions & 10 deletions grafana-plugin/src/containers/RotationForm/RotationForm.helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,26 +191,24 @@ export function getDraggableModalCoordinatesOnInit(
return undefined;
}

const scrollBarReferenceElements = document.querySelectorAll<HTMLElement>('.scrollbar-view');
// top navbar display has 2 scrollbar-view elements (navbar & content)
const baseReferenceElRect = (
scrollBarReferenceElements.length === 1 ? scrollBarReferenceElements[0] : scrollBarReferenceElements[1]
).getBoundingClientRect();
const body = document.body;
const baseReferenceElRect = body.getBoundingClientRect();
const { innerHeight } = window;

const { right, bottom } = baseReferenceElRect;

return isTopNavbar()
? {
// values are adjusted by any padding/margin differences
left: -data.node.offsetLeft + 4,
left: -data.node.offsetLeft + 12,
right: right - (data.node.offsetLeft + data.node.offsetWidth) - 12,
top: -offsetTop + GRAFANA_HEADER_HEIGHT + 4,
bottom: bottom - data.node.offsetHeight - offsetTop - 12,
top: -offsetTop + GRAFANA_HEADER_HEIGHT + 12,
bottom: innerHeight - data.node.offsetHeight - offsetTop - 12,
}
: {
left: -data.node.offsetLeft + 4 + GRAFANA_LEGACY_SIDEBAR_WIDTH,
left: -data.node.offsetLeft + 12 + GRAFANA_LEGACY_SIDEBAR_WIDTH,
right: right - (data.node.offsetLeft + data.node.offsetWidth) - 12,
top: -offsetTop + 4,
top: -offsetTop + 12,
bottom: bottom - data.node.offsetHeight - offsetTop - 12,
};
}
3 changes: 2 additions & 1 deletion grafana-plugin/src/containers/RotationForm/RotationForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -809,7 +809,8 @@ export const RotationForm = observer((props: RotationFormProps) => {
return;
}

setDraggableBounds(getDraggableModalCoordinatesOnInit(data, offsetTop));
const bounds = getDraggableModalCoordinatesOnInit(data, offsetTop);
setDraggableBounds(bounds);
}
});

Expand Down
Loading

0 comments on commit 8bdb124

Please sign in to comment.