Skip to content

Commit

Permalink
upstash frontend (#4641)
Browse files Browse the repository at this point in the history
  • Loading branch information
Feroze Mohideen authored May 14, 2024
1 parent 409f673 commit 82db8f4
Show file tree
Hide file tree
Showing 24 changed files with 479 additions and 38 deletions.
3 changes: 3 additions & 0 deletions api/server/handlers/datastore/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,9 @@ func (h *UpdateDatastoreHandler) ServeHTTP(w http.ResponseWriter, r *http.Reques
case "NEON":
datastoreProto.Kind = porterv1.EnumDatastoreKind_ENUM_DATASTORE_KIND_NEON
datastoreProto.KindValues = &porterv1.ManagedDatastore_NeonKind{}
case "UPSTASH":
datastoreProto.Kind = porterv1.EnumDatastoreKind_ENUM_DATASTORE_KIND_UPSTASH
datastoreProto.KindValues = &porterv1.ManagedDatastore_UpstashKind{}
default:
err = telemetry.Error(ctx, span, nil, "invalid datastore type")
h.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest))
Expand Down
2 changes: 1 addition & 1 deletion api/server/handlers/neon_integration/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (
"github.com/porter-dev/porter/internal/telemetry"
)

// ListNeonIntegrationsHandler is a struct for listing all noen integrations for a given project
// ListNeonIntegrationsHandler is a struct for listing all neon integrations for a given project
type ListNeonIntegrationsHandler struct {
handlers.PorterHandlerReadWriter
}
Expand Down
1 change: 1 addition & 0 deletions api/server/handlers/oauth_callback/upstash.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ func (p *OAuthCallbackUpstashHandler) ServeHTTP(w http.ResponseWriter, r *http.R

oauthInt := integrations.UpstashIntegration{
SharedOAuthModel: integrations.SharedOAuthModel{
ClientID: []byte(p.Config().UpstashConf.ClientID),
AccessToken: []byte(token.AccessToken),
RefreshToken: []byte(token.RefreshToken),
Expiry: token.Expiry,
Expand Down
69 changes: 69 additions & 0 deletions api/server/handlers/upstash_integration/list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package upstash_integration

import (
"net/http"
"time"

"github.com/porter-dev/porter/api/server/handlers"
"github.com/porter-dev/porter/api/server/shared"
"github.com/porter-dev/porter/api/server/shared/apierrors"
"github.com/porter-dev/porter/api/server/shared/config"
"github.com/porter-dev/porter/api/types"
"github.com/porter-dev/porter/internal/models"
"github.com/porter-dev/porter/internal/telemetry"
)

// ListUpstashIntegrationsHandler is a struct for listing all upstash integrations for a given project
type ListUpstashIntegrationsHandler struct {
handlers.PorterHandlerReadWriter
}

// NewListUpstashIntegrationsHandler constructs a ListUpstashIntegrationsHandler
func NewListUpstashIntegrationsHandler(
config *config.Config,
decoderValidator shared.RequestDecoderValidator,
writer shared.ResultWriter,
) *ListUpstashIntegrationsHandler {
return &ListUpstashIntegrationsHandler{
PorterHandlerReadWriter: handlers.NewDefaultPorterHandler(config, decoderValidator, writer),
}
}

// UpstashIntegration describes a upstash integration
type UpstashIntegration struct {
CreatedAt time.Time `json:"created_at"`
}

// ListUpstashIntegrationsResponse describes the list upstash integrations response body
type ListUpstashIntegrationsResponse struct {
// Integrations is a list of upstash integrations
Integrations []UpstashIntegration `json:"integrations"`
}

// ServeHTTP returns a list of upstash integrations associated with the specified project
func (h *ListUpstashIntegrationsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
ctx, span := telemetry.NewSpan(r.Context(), "serve-list-upstash-integrations")
defer span.End()

project, _ := ctx.Value(types.ProjectScope).(*models.Project)

resp := ListUpstashIntegrationsResponse{}
integrationList := make([]UpstashIntegration, 0)

integrations, err := h.Repo().UpstashIntegration().Integrations(ctx, project.ID)
if err != nil {
err := telemetry.Error(ctx, span, err, "error getting datastores")
h.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
return
}

for _, int := range integrations {
integrationList = append(integrationList, UpstashIntegration{
CreatedAt: int.CreatedAt,
})
}

resp.Integrations = integrationList

h.WriteResult(w, r, resp)
}
28 changes: 28 additions & 0 deletions api/server/router/project.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (

"github.com/porter-dev/porter/api/server/handlers/cloud_provider"
"github.com/porter-dev/porter/api/server/handlers/neon_integration"
"github.com/porter-dev/porter/api/server/handlers/upstash_integration"

"github.com/porter-dev/porter/api/server/handlers/deployment_target"

Expand Down Expand Up @@ -2049,5 +2050,32 @@ func getProjectRoutes(
Router: r,
})

// GET /api/projects/{project_id}/upstash-integrations -> apiContract.NewListUpstashIntegrationsHandler
listUpstashIntegrationsEndpoint := factory.NewAPIEndpoint(
&types.APIRequestMetadata{
Verb: types.APIVerbGet,
Method: types.HTTPVerbGet,
Path: &types.Path{
Parent: basePath,
RelativePath: relPath + "/upstash-integrations",
},
Scopes: []types.PermissionScope{
types.UserScope,
types.ProjectScope,
},
},
)

listUpstashIntegrationsHandler := upstash_integration.NewListUpstashIntegrationsHandler(
config,
factory.GetDecoderValidator(),
factory.GetResultWriter(),
)
routes = append(routes, &router.Route{
Endpoint: listUpstashIntegrationsEndpoint,
Handler: listUpstashIntegrationsHandler,
Router: r,
})

return routes, newPath
}
15 changes: 15 additions & 0 deletions dashboard/src/assets/upstash.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 10 additions & 0 deletions dashboard/src/lib/databases/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ const datastoreTypeValidator = z.enum([
"MANAGED_REDIS",
"MANAGED_POSTGRES",
"NEON",
"UPSTASH",
]);
const datastoreEngineValidator = z.enum([
"UNKNOWN",
Expand Down Expand Up @@ -114,6 +115,10 @@ export const DATASTORE_TYPE_NEON: DatastoreType = {
name: "NEON" as const,
displayName: "Neon",
};
export const DATASTORE_TYPE_UPSTASH: DatastoreType = {
name: "UPSTASH" as const,
displayName: "Upstash",
};

export type DatastoreState = {
state: z.infer<typeof datastoreValidator>["status"];
Expand Down Expand Up @@ -334,6 +339,10 @@ const neonValidator = z.object({
type: z.literal("neon"),
});

const upstashValidator = z.object({
type: z.literal("upstash"),
});

export const dbFormValidator = z.object({
name: z
.string()
Expand All @@ -355,6 +364,7 @@ export const dbFormValidator = z.object({
managedRedisConfigValidator,
managedPostgresConfigValidator,
neonValidator,
upstashValidator,
]),
clusterId: z.number(),
});
Expand Down
21 changes: 20 additions & 1 deletion dashboard/src/lib/hooks/useDatastore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,13 @@ type DatastoreHook = {
};
type CreateDatastoreInput = {
name: string;
type: "RDS" | "ELASTICACHE" | "MANAGED-POSTGRES" | "MANAGED-REDIS" | "NEON";
type:
| "RDS"
| "ELASTICACHE"
| "MANAGED-POSTGRES"
| "MANAGED-REDIS"
| "NEON"
| "UPSTASH";
engine: "POSTGRES" | "AURORA-POSTGRES" | "REDIS";
values: object;
};
Expand Down Expand Up @@ -147,6 +153,19 @@ const clientDbToCreateInput = (values: DbFormData): CreateDatastoreInput => {
engine: "POSTGRES",
})
)
.with(
{ config: { type: "upstash" } },
(values): CreateDatastoreInput => ({
name: values.name,
values: {
config: {
name: values.name,
},
},
type: "UPSTASH",
engine: "REDIS",
})
)
.exhaustive();
};

Expand Down
41 changes: 41 additions & 0 deletions dashboard/src/lib/hooks/useUpstash.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { z } from "zod";

import {
upstashIntegrationValidator,
type ClientUpstashIntegration,
} from "lib/upstash/types";

import api from "shared/api";

type TUseUpstash = {
getUpstashIntegrations: ({
projectId,
}: {
projectId: number;
}) => Promise<ClientUpstashIntegration[]>;
};
export const useUpstash = (): TUseUpstash => {
const getUpstashIntegrations = async ({
projectId,
}: {
projectId: number;
}): Promise<ClientUpstashIntegration[]> => {
const response = await api.getUpstashIntegrations(
"<token>",
{},
{
projectId,
}
);

const results = await z
.object({ integrations: z.array(upstashIntegrationValidator) })
.parseAsync(response.data);

return results.integrations;
};

return {
getUpstashIntegrations,
};
};
8 changes: 8 additions & 0 deletions dashboard/src/lib/upstash/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { z } from "zod";

export const upstashIntegrationValidator = z.object({
created_at: z.string(),
});
export type ClientUpstashIntegration = z.infer<
typeof upstashIntegrationValidator
>;
40 changes: 39 additions & 1 deletion dashboard/src/main/home/database-dashboard/DatabaseHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from "react";
import React, { useContext, useState } from "react";
import styled from "styled-components";
import { match } from "ts-pattern";

Expand All @@ -7,15 +7,23 @@ import Icon from "components/porter/Icon";
import Spacer from "components/porter/Spacer";
import StatusDot from "components/porter/StatusDot";
import Text from "components/porter/Text";
import Tooltip from "components/porter/Tooltip";

import { Context } from "shared/Context";
import { readableDate } from "shared/string_utils";
import trash from "assets/trash.png";

import { useDatastoreContext } from "./DatabaseContextProvider";
import { DeleteDatastoreModal } from "./tabs/SettingsTab";
import EngineTag from "./tags/EngineTag";

const DatabaseHeader: React.FC = () => {
const { datastore } = useDatastoreContext();

const [showDeleteDatastoreModal, setShowDeleteDatastoreModal] =
useState(false);
const { user } = useContext(Context);

return (
<>
<Container row style={{ width: "100%" }}>
Expand All @@ -28,6 +36,29 @@ const DatabaseHeader: React.FC = () => {
<Container row>
<EngineTag engine={datastore.template.engine} heightPixels={15} />
</Container>
{user?.isPorterUser && (
<>
<Spacer inline x={1} />
<Tooltip
content={
<Text>
Delete this datastore and all of its resources (only
visible to Porter operators).
</Text>
}
position={"right"}
>
<div
style={{ cursor: "pointer" }}
onClick={() => {
setShowDeleteDatastoreModal(true);
}}
>
<Icon src={trash} height={"15px"} />
</div>
</Tooltip>
</>
)}
</Container>
{match(datastore.status)
.with("AVAILABLE", () => (
Expand All @@ -51,6 +82,13 @@ const DatabaseHeader: React.FC = () => {
</div>
<Spacer y={0.5} />
</CreatedAtContainer>
{showDeleteDatastoreModal && (
<DeleteDatastoreModal
onClose={() => {
setShowDeleteDatastoreModal(false);
}}
/>
)}
</>
);
};
Expand Down
Loading

0 comments on commit 82db8f4

Please sign in to comment.