ソースを参照

Delete flow Test (#3249)

* delete email

* Update Delete

* Update delete.go

* Update delete.go

* Update Tests
sdess09 2 年 前
コミット
e9dbe40fdf

+ 12 - 0
api/server/handlers/project/delete.go

@@ -12,6 +12,7 @@ import (
 	"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/notifier"
 )
 
 type ProjectDeleteHandler struct {
@@ -69,6 +70,17 @@ func (p *ProjectDeleteHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
 			}
 		}
 	}
+	fmt.Println("Deleteing", user.Email)
+	err := p.Config().UserNotifier.SendProjectDeleteEmail(
+		&notifier.SendProjectDeleteEmailOpts{
+			Email:   "support@porter.run",
+			Project: proj.Name,
+		},
+	)
+	if err != nil {
+		p.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+		return
+	}
 
 	deletedProject, err := p.Repo().Project().DeleteProject(proj)
 	if err != nil {

+ 10 - 4
api/server/shared/apitest/notifier.go

@@ -7,10 +7,11 @@ import (
 // FakeUserNotifier just stores data about a single notification,
 // without sending the data anywhere
 type FakeUserNotifier struct {
-	lastPWResetOpts  *notifier.SendPasswordResetEmailOpts
-	lastGHResetOpts  *notifier.SendGithubRelinkEmailOpts
-	lastEmailVerOpts *notifier.SendEmailVerificationOpts
-	lastProjInvOpts  *notifier.SendProjectInviteEmailOpts
+	lastPWResetOpts       *notifier.SendPasswordResetEmailOpts
+	lastGHResetOpts       *notifier.SendGithubRelinkEmailOpts
+	lastEmailVerOpts      *notifier.SendEmailVerificationOpts
+	lastProjInvOpts       *notifier.SendProjectInviteEmailOpts
+	lastDeleteProjectOpts *notifier.SendProjectDeleteEmailOpts
 }
 
 func NewFakeUserNotifier() notifier.UserNotifier {
@@ -52,3 +53,8 @@ func (f *FakeUserNotifier) SendProjectInviteEmail(opts *notifier.SendProjectInvi
 func (f *FakeUserNotifier) GetSendProjectInviteEmailLastOpts() *notifier.SendProjectInviteEmailOpts {
 	return f.lastProjInvOpts
 }
+
+func (f *FakeUserNotifier) SendProjectDeleteEmail(opts *notifier.SendProjectDeleteEmailOpts) error {
+	f.lastDeleteProjectOpts = opts
+	return nil
+}

+ 1 - 0
api/server/shared/config/env/envconfs.go

@@ -62,6 +62,7 @@ type ServerConf struct {
 	SendgridProjectInviteTemplateID    string `env:"SENDGRID_INVITE_TEMPLATE_ID"`
 	SendgridIncidentAlertTemplateID    string `env:"SENDGRID_INCIDENT_ALERT_TEMPLATE_ID"`
 	SendgridIncidentResolvedTemplateID string `env:"SENDGRID_INCIDENT_RESOLVED_TEMPLATE_ID"`
+	SendgridDeleteProjectTemplateID    string `env:"SENDGRID_DELETE_PROJECT_TEMPLATE_ID"`
 	SendgridSenderEmail                string `env:"SENDGRID_SENDER_EMAIL"`
 
 	SlackClientID     string `env:"SLACK_CLIENT_ID"`

+ 1 - 0
api/server/shared/config/loader/loader.go

@@ -141,6 +141,7 @@ func (e *EnvConfigLoader) LoadConfig() (res *config.Config, err error) {
 			PWGHTemplateID:          envConf.ServerConf.SendgridPWGHTemplateID,
 			VerifyEmailTemplateID:   envConf.ServerConf.SendgridVerifyEmailTemplateID,
 			ProjectInviteTemplateID: envConf.ServerConf.SendgridProjectInviteTemplateID,
+			DeleteProjectTemplateID: envConf.ServerConf.SendgridDeleteProjectTemplateID,
 		})
 		res.Logger.Info().Msg("Created new user notifier")
 	}

+ 91 - 0
dashboard/src/main/home/project-settings/ProjectDeleteConsent.tsx

@@ -0,0 +1,91 @@
+import React, { useState, useContext } from "react";
+import styled from "styled-components";
+
+import { Context } from "shared/Context";
+import api from "shared/api";
+
+import Modal from "components/porter/Modal";
+import Text from "components/porter/Text";
+import Spacer from "components/porter/Spacer";
+import Fieldset from "components/porter/Fieldset";
+import Button from "components/porter/Button";
+import ExpandableSection from "components/porter/ExpandableSection";
+import Input from "components/porter/Input";
+import Link from "components/porter/Link";
+
+type Props = {
+  setShowCostConfirmModal: (show: boolean) => void;
+  show: boolean
+};
+
+const ProjectDeleteConsent: React.FC<Props> = ({
+  setShowCostConfirmModal,
+  show,
+}) => {
+  const [confirmDelete, setDeleteCost] = useState("");
+  const {
+    currentProject, setCurrentModal
+  } = useContext(Context);
+  return show ? (
+    <>
+      <Modal
+        closeModal={() => {
+          setDeleteCost("");
+          setShowCostConfirmModal(false);
+        }}
+      >
+        <Text size={16}>Delete {currentProject.name}?</Text>
+        <Spacer y={1} />
+        <Text color="red">
+          Destruction of resources sometimes results in dangling resources. To
+          ensure that everything has been properly destroyed, please visit
+          your cloud provider's console.
+        </Text>
+        <Link
+
+          target="_blank"
+          hasunderline
+          to="https://docs.porter.run/other/deleting-dangling-resources"
+        >
+          Deletion instructions
+        </Link>
+        <Spacer y={1} />
+        <Text color="yellow"> This action cannot be undone.</Text>
+        <Spacer y={1} />
+        <Text color="helper">
+          To acknowledge, enter the project name in the text input field.
+        </Text>
+        <Input
+          placeholder={currentProject.name}
+          value={confirmDelete}
+          setValue={setDeleteCost}
+          width="100%"
+          height="40px"
+        />
+        <Spacer y={1} />
+        <Button
+          disabled={confirmDelete !== currentProject?.name}
+          onClick={() => {
+            setShowCostConfirmModal(false);
+            setCurrentModal("UpdateProjectModal", {
+              currentProject: currentProject,
+            });
+          }}
+        >
+          Continue
+        </Button>
+      </Modal>
+    </>) : null
+};
+
+export default ProjectDeleteConsent;
+
+const Cost = styled.div`
+  font-weight: 600;
+  font-size: 20px;
+`;
+
+const Tab = styled.span`
+  margin-left: 20px;
+  height: 1px;
+`;

+ 10 - 19
dashboard/src/main/home/project-settings/ProjectSettings.tsx

@@ -16,6 +16,7 @@ import APITokensSection from "./APITokensSection";
 import _ from "lodash";
 import Link from "components/porter/Link";
 import Spacer from "components/porter/Spacer";
+import ProjectDeleteConsent from "./ProjectDeleteConsent";
 
 type PropsType = RouteComponentProps & WithAuthProps & {};
 
@@ -23,6 +24,7 @@ type StateType = {
   projectName: string;
   currentTab: string;
   tabOptions: { value: string; label: string }[];
+  showCostConfirmModal: boolean;
 };
 
 class ProjectSettings extends Component<PropsType, StateType> {
@@ -30,6 +32,7 @@ class ProjectSettings extends Component<PropsType, StateType> {
     projectName: "",
     currentTab: "manage-access",
     tabOptions: [] as { value: string; label: string }[],
+    showCostConfirmModal: false,
   };
 
   componentDidUpdate(prevProps: PropsType) {
@@ -145,6 +148,8 @@ class ProjectSettings extends Component<PropsType, StateType> {
       return (
         <>
           <Heading isAtTop={true}>Delete project</Heading>
+          <Helper>
+          </Helper>
           <Helper>
             Permanently delete this project. This will destroy all clusters tied
             to this project that have been provisioned by Porter. Note that this
@@ -152,31 +157,17 @@ class ProjectSettings extends Component<PropsType, StateType> {
             delete the registries, please do so manually in your cloud console.
           </Helper>
 
-          <Helper>
-            Destruction of resources sometimes results in dangling resources. To
-            ensure that everything has been properly destroyed, please visit
-            your cloud provider's console.
-            <Spacer inline width="5px" />
-            <Link
-              target="_blank"
-              hasunderline
-              to="https://docs.porter.run/other/deleting-dangling-resources"
-            >
-              Deletion instructions
-            </Link>
-          </Helper>
-
-          <Warning highlight={true}>This action cannot be undone.</Warning>
-
           <DeleteButton
             onClick={() => {
-              this.context.setCurrentModal("UpdateProjectModal", {
-                currentProject: this.context.currentProject,
-              });
+              this.setState({ showCostConfirmModal: true });
             }}
           >
             Delete project
           </DeleteButton>
+          <ProjectDeleteConsent
+            setShowCostConfirmModal={(show: boolean) => this.setState({ showCostConfirmModal: show })}
+            show={this.state.showCostConfirmModal}  // <-- Pass these props
+          />
         </>
       );
     }

+ 33 - 0
internal/notifier/sendgrid/user_notifier.go

@@ -16,6 +16,7 @@ type UserNotifierOpts struct {
 	PWGHTemplateID          string
 	VerifyEmailTemplateID   string
 	ProjectInviteTemplateID string
+	DeleteProjectTemplateID string
 }
 
 func NewUserNotifier(opts *UserNotifierOpts) notifier.UserNotifier {
@@ -150,3 +151,35 @@ func (s *UserNotifier) SendProjectInviteEmail(opts *notifier.SendProjectInviteEm
 
 	return err
 }
+
+func (s *UserNotifier) SendProjectDeleteEmail(opts *notifier.SendProjectDeleteEmailOpts) error {
+	request := sendgrid.GetRequest(s.opts.APIKey, "/v3/mail/send", "https://api.sendgrid.com")
+	request.Method = "POST"
+
+	sgMail := &mail.SGMailV3{
+		Personalizations: []*mail.Personalization{
+			{
+				To: []*mail.Email{
+					{
+						Address: opts.Email,
+					},
+				},
+				DynamicTemplateData: map[string]interface{}{
+					"email":   opts.Email,
+					"project": opts.Project,
+				},
+			},
+		},
+		From: &mail.Email{
+			Address: s.opts.SenderEmail,
+			Name:    "Porter",
+		},
+		TemplateID: s.opts.ProjectInviteTemplateID,
+	}
+
+	request.Body = mail.GetRequestBody(sgMail)
+
+	_, err := sendgrid.API(request)
+
+	return err
+}

+ 10 - 0
internal/notifier/user_notifier.go

@@ -22,11 +22,17 @@ type SendProjectInviteEmailOpts struct {
 	ProjectOwnerEmail string
 }
 
+type SendProjectDeleteEmailOpts struct {
+	Project string
+	Email   string
+}
+
 type UserNotifier interface {
 	SendPasswordResetEmail(opts *SendPasswordResetEmailOpts) error
 	SendGithubRelinkEmail(opts *SendGithubRelinkEmailOpts) error
 	SendEmailVerification(opts *SendEmailVerificationOpts) error
 	SendProjectInviteEmail(opts *SendProjectInviteEmailOpts) error
+	SendProjectDeleteEmail(opts *SendProjectDeleteEmailOpts) error
 }
 
 type EmptyUserNotifier struct{}
@@ -46,3 +52,7 @@ func (e *EmptyUserNotifier) SendEmailVerification(opts *SendEmailVerificationOpt
 func (e *EmptyUserNotifier) SendProjectInviteEmail(opts *SendProjectInviteEmailOpts) error {
 	return nil
 }
+
+func (e *EmptyUserNotifier) SendProjectDeleteEmail(opts *SendProjectDeleteEmailOpts) error {
+	return nil
+}