Skip to content

Commit

Permalink
deprisma alerting config (#1903)
Browse files Browse the repository at this point in the history
in prep for encrypting alerting configs
  • Loading branch information
serprex authored Jul 5, 2024
1 parent a722b20 commit a21141b
Show file tree
Hide file tree
Showing 10 changed files with 153 additions and 94 deletions.
63 changes: 63 additions & 0 deletions flow/cmd/alerts.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package cmd

import (
"context"
"fmt"

"github.com/jackc/pgx/v5"

"github.com/PeerDB-io/peer-flow/generated/protos"
)

func (h *FlowRequestHandler) GetAlertConfigs(ctx context.Context, req *protos.GetAlertConfigsRequest) (*protos.GetAlertConfigsResponse, error) {
rows, err := h.pool.Query(ctx, "select id, service_type, service_config from peerdb_stats.alerting_config")
if err != nil {
return nil, err
}

configs, err := pgx.CollectRows(rows, func(row pgx.CollectableRow) (*protos.AlertConfig, error) {
config := &protos.AlertConfig{}
err := row.Scan(&config.Id, &config.ServiceType, &config.ServiceConfig)
return config, err
})
if err != nil {
return nil, err
}

return &protos.GetAlertConfigsResponse{Configs: configs}, nil
}

func (h *FlowRequestHandler) PostAlertConfig(ctx context.Context, req *protos.PostAlertConfigRequest) (*protos.PostAlertConfigResponse, error) {
if req.Id == -1 {
var id int32
if err := h.pool.QueryRow(
ctx,
"insert into peerdb_stats.alerting_config (service_type, service_config) values ($1, $2) returning id",
req.ServiceType,
req.ServiceConfig,
).Scan(&id); err != nil {
// TODO don't send back json
return nil, fmt.Errorf("cannot add %s: %w", req.ServiceConfig, err)
}
return &protos.PostAlertConfigResponse{Id: id}, nil
} else if _, err := h.pool.Exec(
ctx,
"update peerdb_stats.alerting_config set service_type = $1, service_config = $2 where id = $3",
req.ServiceType,
req.ServiceConfig,
req.Id,
); err != nil {
return nil, err
}
return &protos.PostAlertConfigResponse{Id: req.Id}, nil
}

func (h *FlowRequestHandler) DeleteAlertConfig(
ctx context.Context,
req *protos.DeleteAlertConfigRequest,
) (*protos.DeleteAlertConfigResponse, error) {
if _, err := h.pool.Exec(ctx, "delete from peerdb_stats.alerting_config where id = $1", req.Id); err != nil {
return nil, err
}
return &protos.DeleteAlertConfigResponse{}, nil
}
1 change: 0 additions & 1 deletion flow/cmd/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,6 @@ func (h *FlowRequestHandler) CreateQRepFlow(
}, nil
}

// updateQRepConfigInCatalog updates the qrep config in the catalog
func (h *FlowRequestHandler) updateQRepConfigInCatalog(
ctx context.Context,
cfg *protos.QRepConfig,
Expand Down
35 changes: 35 additions & 0 deletions protos/route.proto
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,31 @@ message CreateCustomSyncResponse {
bool ok = 4;
}

message AlertConfig {
int32 id = 1;
string service_type = 2;
string service_config = 3;
}
message GetAlertConfigsRequest {
int32 id = 1;
}
message PostAlertConfigRequest {
int32 id = 1;
string service_type = 2;
string service_config = 3;
}
message DeleteAlertConfigRequest {
int32 id = 1;
}
message GetAlertConfigsResponse {
repeated AlertConfig configs = 1;
}
message PostAlertConfigResponse {
int32 id = 3;
}
message DeleteAlertConfigResponse {
}

message ValidatePeerRequest {
peerdb_peers.Peer peer = 1;
}
Expand Down Expand Up @@ -292,6 +317,16 @@ service FlowService {
};
}

rpc GetAlertConfigs(GetAlertConfigsRequest) returns (GetAlertConfigsResponse) {
option (google.api.http) = { get: "/v1/alerts/config" };
}
rpc PostAlertConfig(PostAlertConfigRequest) returns (PostAlertConfigResponse) {
option (google.api.http) = { post: "/v1/alerts/config", body: "*" };
}
rpc DeleteAlertConfig(DeleteAlertConfigRequest) returns (DeleteAlertConfigResponse) {
option (google.api.http) = { delete: "/v1/alerts/config/{id}" };
}

rpc GetSchemas(PostgresPeerActivityInfoRequest) returns (PeerSchemasResponse) {
option (google.api.http) = { get: "/v1/peers/schemas" };
}
Expand Down
22 changes: 10 additions & 12 deletions ui/app/alert-config/new.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import {
export type ServiceType = 'slack' | 'email';

export interface AlertConfigProps {
id?: bigint;
id?: number;
serviceType: ServiceType;
alertConfig: serviceConfigType;
forEdit?: boolean;
Expand Down Expand Up @@ -173,18 +173,17 @@ export function NewConfig(alertProps: AlertConfigProps) {
const serviceValidity = serviceSchema.safeParse(config);
if (!serviceValidity?.success) {
notifyErr(
'Invalid alert service configuration for ' +
serviceType +
'. ' +
serviceValidity.error.issues[0].message
`Invalid alert service configuration for ${
serviceType
}. ${serviceValidity.error.issues[0].message}`
);
return;
}

const serviceConfig = serviceValidity.data;
const alertConfigReq: alertConfigType = {
id: Number(alertProps.id || -1),
serviceType: serviceType,
serviceType,
serviceConfig,
};

Expand All @@ -193,19 +192,18 @@ export function NewConfig(alertProps: AlertConfigProps) {
notifyErr(alertReqValidity.error.issues[0].message);
return;
}
(alertConfigReq as any).serviceConfig = JSON.stringify(
alertConfigReq.serviceConfig
);

setLoading(true);
if (alertProps.forEdit) {
alertConfigReq.id = Number(alertProps.id);
}
const createRes = await fetch('/api/alert-config', {
method: alertProps.forEdit ? 'PUT' : 'POST',
method: 'POST',
body: JSON.stringify(alertConfigReq),
});

const createStatus = await createRes.text();
setLoading(false);
if (createStatus !== 'success') {
if (!createRes.ok) {
notifyErr('Something went wrong. Please try again');
return;
}
Expand Down
41 changes: 19 additions & 22 deletions ui/app/alert-config/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use client';
import AlertDropdown from '@/components/AlertDropdown';
import ConfigJSONView from '@/components/ConfigJSONView';
import { AlertConfig, GetAlertConfigsResponse } from '@/grpc_generated/route';
import { Button } from '@/lib/Button';
import { Icon } from '@/lib/Icon';
import { Label } from '@/lib/Label';
Expand All @@ -9,7 +10,6 @@ import Image from 'next/image';
import React, { useState } from 'react';
import { PulseLoader } from 'react-spinners';
import useSWR from 'swr';
import { UAlertConfigResponse } from '../dto/AlertDTO';
import { tableStyle } from '../peers/[peerName]/style';
import { fetcher } from '../utils/swr';
import { AlertConfigProps, NewConfig, ServiceType } from './new';
Expand All @@ -20,23 +20,21 @@ const ServiceIcon = ({
}: {
serviceType: string;
size: number;
}) => {
return (
<Image
src={`/images/${serviceType}.png`}
height={size}
width={size}
alt={serviceType}
/>
);
};
}) => (
<Image
src={`/images/${serviceType}.png`}
height={size}
width={size}
alt={serviceType}
/>
);

const AlertConfigPage: React.FC = () => {
const {
data: alerts,
isLoading,
}: {
data: UAlertConfigResponse[];
data: GetAlertConfigsResponse;
error: any;
isLoading: boolean;
} = useSWR('/api/alert-config', fetcher);
Expand All @@ -57,14 +55,13 @@ const AlertConfigPage: React.FC = () => {
const [editAlertConfig, setEditAlertConfig] =
useState<AlertConfigProps>(blankAlert);

const onEdit = (alertConfig: UAlertConfigResponse, id: bigint) => {
const onEdit = (alertConfig: AlertConfig, id: number) => {
setInEditOrAddMode(true);
const configJSON = JSON.stringify(alertConfig.service_config);

setEditAlertConfig({
id,
serviceType: alertConfig.service_type as ServiceType,
alertConfig: JSON.parse(configJSON),
serviceType: alertConfig.serviceType as ServiceType,
alertConfig: JSON.parse(alertConfig.serviceConfig),
forEdit: true,
});
};
Expand All @@ -89,8 +86,8 @@ const AlertConfigPage: React.FC = () => {
</div>
<div style={{ ...tableStyle, marginTop: '2rem', maxHeight: '25em' }}>
<Table>
{alerts?.length ? (
alerts.map((alertConfig: UAlertConfigResponse, index) => (
{alerts?.configs?.length ? (
alerts.configs.map((alertConfig: AlertConfig, index) => (
<TableRow key={index}>
<TableCell style={{ width: 20 }}>
<div
Expand All @@ -109,12 +106,12 @@ const AlertConfigPage: React.FC = () => {
}}
>
<ServiceIcon
serviceType={alertConfig.service_type}
serviceType={alertConfig.serviceType}
size={30}
/>
<Label>
{alertConfig.service_type.charAt(0).toUpperCase() +
alertConfig.service_type.slice(1)}
{alertConfig.serviceType.charAt(0).toUpperCase() +
alertConfig.serviceType.slice(1)}
</Label>
</div>
</div>
Expand All @@ -123,7 +120,7 @@ const AlertConfigPage: React.FC = () => {
<div style={{ height: '10em' }}>
<ConfigJSONView
config={JSON.stringify(
alertConfig.service_config,
JSON.parse(alertConfig.serviceConfig),
null,
2
)}
Expand Down
64 changes: 22 additions & 42 deletions ui/app/api/alert-config/route.ts
Original file line number Diff line number Diff line change
@@ -1,50 +1,30 @@
import { alertConfigType } from '@/app/alert-config/validation';
import prisma from '@/app/utils/prisma';
import { alerting_config } from '@prisma/client';
import { GetFlowHttpAddressFromEnv } from '@/rpc/http';
import { NextRequest } from 'next/server';

export async function GET() {
const configs: alerting_config[] = await prisma.alerting_config.findMany();
const serializedConfigs = configs.map((config) => ({
...config,
id: String(config.id),
}));
return new Response(JSON.stringify(serializedConfigs));
const flowServiceAddr = GetFlowHttpAddressFromEnv();
return fetch(`${flowServiceAddr}/v1/alerts/config`, { cache: 'no-store' });
}

export async function POST(request: Request) {
const alertConfigReq: alertConfigType = await request.json();
const createRes = await prisma.alerting_config.create({
data: {
service_type: alertConfigReq.serviceType,
service_config: alertConfigReq.serviceConfig,
},
});

return new Response(createRes.id ? 'success' : 'error');
const flowServiceAddr = GetFlowHttpAddressFromEnv();
return fetch(`${flowServiceAddr}/v1/alerts/config`, {
method: 'POST',
cache: 'no-store',
body: request.body,
duplex: 'half',
} as any);
}

export async function DELETE(request: Request) {
const configDeleteReq: { id: number } = await request.json();
const deleteRes = await prisma.alerting_config.delete({
where: {
id: configDeleteReq.id,
},
});

return new Response(deleteRes.id ? 'success' : 'error');
}

export async function PUT(request: Request) {
const alertConfigReq: alertConfigType = await request.json();
const editRes = await prisma.alerting_config.update({
data: {
service_type: alertConfigReq.serviceType,
service_config: alertConfigReq.serviceConfig,
},
where: {
id: alertConfigReq.id,
},
});

return new Response(editRes.id ? 'success' : 'error');
export async function DELETE(request: NextRequest) {
const flowServiceAddr = GetFlowHttpAddressFromEnv();
return fetch(
`${flowServiceAddr}/v1/alerts/config/${Number(
request.nextUrl.searchParams.get('id')
)}`,
{
method: 'DELETE',
cache: 'no-store',
}
);
}
4 changes: 1 addition & 3 deletions ui/app/api/peers/info/[peerName]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ export async function GET(
const flowServiceAddr = GetFlowHttpAddressFromEnv();
return fetch(
`${flowServiceAddr}/v1/peers/info/${encodeURIComponent(context.params.peerName)}`,
{
cache: 'no-store',
}
{ cache: 'no-store' }
);
}
8 changes: 0 additions & 8 deletions ui/app/dto/AlertDTO.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,3 @@
import { Prisma } from '@prisma/client';

export type UAlertConfigResponse = {
id: bigint;
service_type: string;
service_config: Prisma.JsonValue;
};

export enum LogType {
ERROR = 'ERROR',
WARNING = 'WARNING',
Expand Down
2 changes: 1 addition & 1 deletion ui/components/AlertDropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const AlertDropdown = ({
onEdit,
}: {
disable: boolean;
alertId: bigint;
alertId: number;
onEdit: () => void;
}) => {
const [open, setOpen] = useState(false);
Expand Down
Loading

0 comments on commit a21141b

Please sign in to comment.