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

v1.9.18 #4958

Merged
merged 5 commits into from
Aug 29, 2024
Merged

v1.9.18 #4958

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
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
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,
};
}
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
Loading