Ver Fonte

Delete flow Test (#3249)

* delete email

* Update Delete

* Update delete.go

* Update delete.go

* Update Tests
sdess09 há 2 anos atrás
pai
commit
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/server/shared/config"
 	"github.com/porter-dev/porter/api/types"
 	"github.com/porter-dev/porter/api/types"
 	"github.com/porter-dev/porter/internal/models"
 	"github.com/porter-dev/porter/internal/models"
+	"github.com/porter-dev/porter/internal/notifier"
 )
 )
 
 
 type ProjectDeleteHandler struct {
 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)
 	deletedProject, err := p.Repo().Project().DeleteProject(proj)
 	if err != nil {
 	if err != nil {

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

@@ -7,10 +7,11 @@ import (
 // FakeUserNotifier just stores data about a single notification,
 // FakeUserNotifier just stores data about a single notification,
 // without sending the data anywhere
 // without sending the data anywhere
 type FakeUserNotifier struct {
 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 {
 func NewFakeUserNotifier() notifier.UserNotifier {
@@ -52,3 +53,8 @@ func (f *FakeUserNotifier) SendProjectInviteEmail(opts *notifier.SendProjectInvi
 func (f *FakeUserNotifier) GetSendProjectInviteEmailLastOpts() *notifier.SendProjectInviteEmailOpts {
 func (f *FakeUserNotifier) GetSendProjectInviteEmailLastOpts() *notifier.SendProjectInviteEmailOpts {
 	return f.lastProjInvOpts
 	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"`
 	SendgridProjectInviteTemplateID    string `env:"SENDGRID_INVITE_TEMPLATE_ID"`
 	SendgridIncidentAlertTemplateID    string `env:"SENDGRID_INCIDENT_ALERT_TEMPLATE_ID"`
 	SendgridIncidentAlertTemplateID    string `env:"SENDGRID_INCIDENT_ALERT_TEMPLATE_ID"`
 	SendgridIncidentResolvedTemplateID string `env:"SENDGRID_INCIDENT_RESOLVED_TEMPLATE_ID"`
 	SendgridIncidentResolvedTemplateID string `env:"SENDGRID_INCIDENT_RESOLVED_TEMPLATE_ID"`
+	SendgridDeleteProjectTemplateID    string `env:"SENDGRID_DELETE_PROJECT_TEMPLATE_ID"`
 	SendgridSenderEmail                string `env:"SENDGRID_SENDER_EMAIL"`
 	SendgridSenderEmail                string `env:"SENDGRID_SENDER_EMAIL"`
 
 
 	SlackClientID     string `env:"SLACK_CLIENT_ID"`
 	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,
 			PWGHTemplateID:          envConf.ServerConf.SendgridPWGHTemplateID,
 			VerifyEmailTemplateID:   envConf.ServerConf.SendgridVerifyEmailTemplateID,
 			VerifyEmailTemplateID:   envConf.ServerConf.SendgridVerifyEmailTemplateID,
 			ProjectInviteTemplateID: envConf.ServerConf.SendgridProjectInviteTemplateID,
 			ProjectInviteTemplateID: envConf.ServerConf.SendgridProjectInviteTemplateID,
+			DeleteProjectTemplateID: envConf.ServerConf.SendgridDeleteProjectTemplateID,
 		})
 		})
 		res.Logger.Info().Msg("Created new user notifier")
 		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 _ from "lodash";
 import Link from "components/porter/Link";
 import Link from "components/porter/Link";
 import Spacer from "components/porter/Spacer";
 import Spacer from "components/porter/Spacer";
+import ProjectDeleteConsent from "./ProjectDeleteConsent";
 
 
 type PropsType = RouteComponentProps & WithAuthProps & {};
 type PropsType = RouteComponentProps & WithAuthProps & {};
 
 
@@ -23,6 +24,7 @@ type StateType = {
   projectName: string;
   projectName: string;
   currentTab: string;
   currentTab: string;
   tabOptions: { value: string; label: string }[];
   tabOptions: { value: string; label: string }[];
+  showCostConfirmModal: boolean;
 };
 };
 
 
 class ProjectSettings extends Component<PropsType, StateType> {
 class ProjectSettings extends Component<PropsType, StateType> {
@@ -30,6 +32,7 @@ class ProjectSettings extends Component<PropsType, StateType> {
     projectName: "",
     projectName: "",
     currentTab: "manage-access",
     currentTab: "manage-access",
     tabOptions: [] as { value: string; label: string }[],
     tabOptions: [] as { value: string; label: string }[],
+    showCostConfirmModal: false,
   };
   };
 
 
   componentDidUpdate(prevProps: PropsType) {
   componentDidUpdate(prevProps: PropsType) {
@@ -145,6 +148,8 @@ class ProjectSettings extends Component<PropsType, StateType> {
       return (
       return (
         <>
         <>
           <Heading isAtTop={true}>Delete project</Heading>
           <Heading isAtTop={true}>Delete project</Heading>
+          <Helper>
+          </Helper>
           <Helper>
           <Helper>
             Permanently delete this project. This will destroy all clusters tied
             Permanently delete this project. This will destroy all clusters tied
             to this project that have been provisioned by Porter. Note that this
             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.
             delete the registries, please do so manually in your cloud console.
           </Helper>
           </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
           <DeleteButton
             onClick={() => {
             onClick={() => {
-              this.context.setCurrentModal("UpdateProjectModal", {
-                currentProject: this.context.currentProject,
-              });
+              this.setState({ showCostConfirmModal: true });
             }}
             }}
           >
           >
             Delete project
             Delete project
           </DeleteButton>
           </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
 	PWGHTemplateID          string
 	VerifyEmailTemplateID   string
 	VerifyEmailTemplateID   string
 	ProjectInviteTemplateID string
 	ProjectInviteTemplateID string
+	DeleteProjectTemplateID string
 }
 }
 
 
 func NewUserNotifier(opts *UserNotifierOpts) notifier.UserNotifier {
 func NewUserNotifier(opts *UserNotifierOpts) notifier.UserNotifier {
@@ -150,3 +151,35 @@ func (s *UserNotifier) SendProjectInviteEmail(opts *notifier.SendProjectInviteEm
 
 
 	return err
 	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
 	ProjectOwnerEmail string
 }
 }
 
 
+type SendProjectDeleteEmailOpts struct {
+	Project string
+	Email   string
+}
+
 type UserNotifier interface {
 type UserNotifier interface {
 	SendPasswordResetEmail(opts *SendPasswordResetEmailOpts) error
 	SendPasswordResetEmail(opts *SendPasswordResetEmailOpts) error
 	SendGithubRelinkEmail(opts *SendGithubRelinkEmailOpts) error
 	SendGithubRelinkEmail(opts *SendGithubRelinkEmailOpts) error
 	SendEmailVerification(opts *SendEmailVerificationOpts) error
 	SendEmailVerification(opts *SendEmailVerificationOpts) error
 	SendProjectInviteEmail(opts *SendProjectInviteEmailOpts) error
 	SendProjectInviteEmail(opts *SendProjectInviteEmailOpts) error
+	SendProjectDeleteEmail(opts *SendProjectDeleteEmailOpts) error
 }
 }
 
 
 type EmptyUserNotifier struct{}
 type EmptyUserNotifier struct{}
@@ -46,3 +52,7 @@ func (e *EmptyUserNotifier) SendEmailVerification(opts *SendEmailVerificationOpt
 func (e *EmptyUserNotifier) SendProjectInviteEmail(opts *SendProjectInviteEmailOpts) error {
 func (e *EmptyUserNotifier) SendProjectInviteEmail(opts *SendProjectInviteEmailOpts) error {
 	return nil
 	return nil
 }
 }
+
+func (e *EmptyUserNotifier) SendProjectDeleteEmail(opts *SendProjectDeleteEmailOpts) error {
+	return nil
+}