Skip to content

Commit

Permalink
upstash frontend
Browse files Browse the repository at this point in the history
  • Loading branch information
Feroze Mohideen committed May 13, 2024
1 parent 27359a7 commit 0cdc16d
Show file tree
Hide file tree
Showing 20 changed files with 416 additions and 20 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 @@ -111,6 +111,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
>;
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,14 @@ import { useDatastoreList } from "lib/hooks/useDatabaseList";
import { useDatastore } from "lib/hooks/useDatastore";
import { useIntercom } from "lib/hooks/useIntercom";
import { useNeon } from "lib/hooks/useNeon";
import { useUpstash } from "lib/hooks/useUpstash";

import { Context } from "shared/Context";

import NeonIntegrationModal from "./shared/NeonIntegrationModal";
import {
NeonIntegrationModal,
UpstashIntegrationModal,
} from "./shared/NeonIntegrationModal";

// todo(ianedwards): refactor button to use more predictable state
export type UpdateDatastoreButtonProps = {
Expand Down Expand Up @@ -53,8 +57,11 @@ const DatastoreFormContextProvider: React.FC<

const [updateDatastoreError, setUpdateDatastoreError] = useState<string>("");
const { getNeonIntegrations } = useNeon();
const { getUpstashIntegrations } = useUpstash();
const [showNeonIntegrationModal, setShowNeonIntegrationModal] =
useState(false);
const [showUpstashIntegrationModal, setShowUpstashIntegrationModal] =
useState(false);

const { showIntercomWithMessage } = useIntercom();

Expand Down Expand Up @@ -117,6 +124,16 @@ const DatastoreFormContextProvider: React.FC<
return;
}
}
if (data.config.type === "upstash") {
const integrations = await getUpstashIntegrations({
projectId: currentProject.id,
});
if (integrations.length === 0) {
setShowUpstashIntegrationModal(true);
return;
}
}

await createDatastore(data);
history.push(`/datastores/${data.name}`);
} catch (err) {
Expand Down Expand Up @@ -154,6 +171,13 @@ const DatastoreFormContextProvider: React.FC<
}}
/>
)}
{showUpstashIntegrationModal && (
<UpstashIntegrationModal
onClose={() => {
setShowUpstashIntegrationModal(false);
}}
/>
)}
</DatastoreFormContext.Provider>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ import React, { useMemo } from "react";

import StatusBar from "components/porter/StatusBar";

import { DATASTORE_TEMPLATE_NEON } from "./constants";
import {
DATASTORE_TEMPLATE_NEON,
DATASTORE_TEMPLATE_UPSTASH,
} from "./constants";
import { useDatastoreContext } from "./DatabaseContextProvider";

const DatastoreProvisioningIndicator: React.FC = () => {
Expand Down Expand Up @@ -40,9 +43,13 @@ const DatastoreProvisioningIndicator: React.FC = () => {
return { percentCompleted, title, titleDescriptor, isCreating };
}, [datastore]);

// TODO: refactor this so the template has the setup/deletion time
const subtitle = useMemo(() => {
return `${isCreating ? "Setup" : "Deletion"} can take up to ${
datastore.template === DATASTORE_TEMPLATE_NEON ? 5 : 20
datastore.template === DATASTORE_TEMPLATE_NEON ||
datastore.template === DATASTORE_TEMPLATE_UPSTASH
? 5
: 20
} minutes. You can close this window and come back later.`;
}, [datastore]);

Expand Down
Loading

0 comments on commit 0cdc16d

Please sign in to comment.