Explorar o código

Merge branch '0.7.0-slack-notifications' of https://github.com/porter-dev/porter into 0.7.0-slack-notifications

merge w/ remote
Alexander Belanger %!s(int64=4) %!d(string=hai) anos
pai
achega
5f187275e0

+ 3 - 20
dashboard/src/main/home/integrations/IntegrationCategories.tsx

@@ -9,6 +9,8 @@ import IntegrationList from "./IntegrationList";
 import api from "shared/api";
 import { pushFiltered } from "shared/routing";
 import Loading from "../../../components/Loading";
+import ConfirmOverlay from "../../../components/ConfirmOverlay";
+import SlackIntegrationList from "./SlackIntegrationList";
 
 type Props = RouteComponentProps & {
   category: string;
@@ -136,26 +138,7 @@ const IntegrationCategories: React.FC<Props> = (props) => {
       {loading ? (
         <Loading />
       ) : props.category == "slack" ? (
-        <StyledIntegrationList>
-          {slackData.map((inst) => {
-            return (
-              <Integration
-                onClick={() => {}}
-                disabled={false}
-                key={`${inst.team_id}-{inst.channel}`}
-              >
-                <MainRow disabled={false}>
-                  <Flex>
-                    <Icon src={inst.team_icon_url && inst.team_icon_url} />
-                    <Label>
-                      {inst.team_id} - {inst.channel}
-                    </Label>
-                  </Flex>
-                </MainRow>
-              </Integration>
-            );
-          })}
-        </StyledIntegrationList>
+        <SlackIntegrationList slackData={slackData} />
       ) : (
         <IntegrationList
           currentCategory={props.category}

+ 139 - 0
dashboard/src/main/home/integrations/SlackIntegrationList.tsx

@@ -0,0 +1,139 @@
+import React, { useState } from "react";
+import ConfirmOverlay from "../../../components/ConfirmOverlay";
+import styled from "styled-components";
+
+interface Props {
+  slackData: any[];
+}
+
+const SlackIntegrationList: React.FC<Props> = (props) => {
+  const [isDelete, setIsDelete] = useState(false);
+  const [deleteObj, setDeleteObj] = useState({
+    id: 0,
+    team_name: "",
+    team_id: "",
+    channel: "",
+  }); // guaranteed to be set when used
+
+  return (
+    <>
+      <ConfirmOverlay
+        show={isDelete}
+        message={`Are you sure you want to delete the slack integration for team ${
+          deleteObj.team_name || deleteObj.team_id
+        } in channel ${deleteObj.channel}?`}
+        onYes={() => {
+          setIsDelete(false);
+        }}
+        onNo={() => setIsDelete(false)}
+      />
+      <StyledIntegrationList>
+        {props.slackData.map((inst) => {
+          return (
+            <Integration
+              onClick={() => {}}
+              disabled={false}
+              key={`${inst.team_id}-${inst.channel}`}
+            >
+              <MainRow disabled={false}>
+                <Flex>
+                  <Icon src={inst.team_icon_url && inst.team_icon_url} />
+                  <Label>
+                    {inst.team_name || inst.team_id} - {inst.channel}
+                  </Label>
+                </Flex>
+                <i
+                  className="material-icons"
+                  onClick={() => {
+                    setDeleteObj(inst);
+                    setIsDelete(true);
+                  }}
+                >
+                  delete
+                </i>
+              </MainRow>
+            </Integration>
+          );
+        })}
+      </StyledIntegrationList>
+    </>
+  );
+};
+
+export default SlackIntegrationList;
+
+const Label = styled.div`
+  color: #ffffff;
+  font-size: 14px;
+  font-weight: 500;
+`;
+
+const StyledIntegrationList = styled.div`
+  margin-top: 20px;
+  margin-bottom: 80px;
+`;
+
+const MainRow = styled.div`
+  height: 70px;
+  width: 100%;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 25px;
+  border-radius: 5px;
+  :hover {
+    background: ${(props: { disabled: boolean }) =>
+      props.disabled ? "" : "#ffffff11"};
+    > i {
+      background: ${(props: { disabled: boolean }) =>
+        props.disabled ? "" : "#ffffff11"};
+    }
+  }
+
+  > i {
+    border-radius: 20px;
+    font-size: 18px;
+    padding: 5px;
+    color: #ffffff44;
+    margin-right: -7px;
+    :hover {
+      background: ${(props: { disabled: boolean }) =>
+        props.disabled ? "" : "#ffffff11"};
+    }
+  }
+`;
+
+const Integration = styled.div`
+  margin-left: -2px;
+  display: flex;
+  flex-direction: column;
+  background: #26282f;
+  cursor: ${(props: { disabled: boolean }) =>
+    props.disabled ? "not-allowed" : "pointer"};
+  margin-bottom: 15px;
+  border-radius: 5px;
+  box-shadow: 0 5px 8px 0px #00000033;
+`;
+
+const Icon = styled.img`
+  width: 27px;
+  margin-right: 12px;
+  margin-bottom: -1px;
+`;
+
+const Flex = styled.div`
+  display: flex;
+  align-items: center;
+
+  > i {
+    cursor: pointer;
+    font-size 24px;
+    color: #969Fbbaa;
+    padding: 3px;
+    margin-right: 11px;
+    border-radius: 100px;
+    :hover {
+      background: #ffffff11;
+    }
+  }
+`;

+ 12 - 1
internal/repository/gorm/slack.go

@@ -23,7 +23,7 @@ func NewSlackIntegrationRepository(
 	return &SlackIntegrationRepository{db, key}
 }
 
-// CreateKubeIntegration creates a new kube auth mechanism
+// CreateSlackIntegration creates a new kube auth mechanism
 func (repo *SlackIntegrationRepository) CreateSlackIntegration(
 	slackInt *ints.SlackIntegration,
 ) (*ints.SlackIntegration, error) {
@@ -58,6 +58,17 @@ func (repo *SlackIntegrationRepository) ListSlackIntegrationsByProjectID(
 	return slackInts, nil
 }
 
+// DeleteSlackIntegration deletes a slack integration by ID
+func (repo *SlackIntegrationRepository) DeleteSlackIntegration(
+	integrationID uint,
+) error {
+	if err := repo.db.Where("id = ?", integrationID).Delete(&ints.SlackIntegration{}).Error; err != nil {
+		return err
+	}
+
+	return nil
+}
+
 // EncryptSlackIntegrationData will encrypt the slack integration data before
 // writing to the DB
 func (repo *SlackIntegrationRepository) EncryptSlackIntegrationData(

+ 1 - 0
internal/repository/integrations.go

@@ -49,6 +49,7 @@ type GithubAppOAuthIntegrationRepository interface {
 type SlackIntegrationRepository interface {
 	CreateSlackIntegration(slackInt *ints.SlackIntegration) (*ints.SlackIntegration, error)
 	ListSlackIntegrationsByProjectID(projectID uint) ([]*ints.SlackIntegration, error)
+	DeleteSlackIntegration(integrationID uint) error
 }
 
 // AWSIntegrationRepository represents the set of queries on the AWS auth

+ 38 - 0
server/api/oauth_slack_handler.go

@@ -128,3 +128,41 @@ func (app *App) HandleListSlackIntegrations(w http.ResponseWriter, r *http.Reque
 		return
 	}
 }
+
+// HandleDeleteSlackIntegration deletes a slack integration for a project by ID
+func (app *App) HandleDeleteSlackIntegration(w http.ResponseWriter, r *http.Request) {
+	// check that slack integration belongs to given project
+	projID, err := strconv.ParseUint(chi.URLParam(r, "project_id"), 0, 64)
+
+	if err != nil || projID == 0 {
+		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
+		return
+	}
+
+	integrationID, err := strconv.ParseUint(chi.URLParam(r, "slack_integration_id"), 0, 64)
+
+	if err != nil || projID == 0 {
+		app.handleErrorFormDecoding(err, ErrProjectDecode, w)
+		return
+	}
+
+	slackInts, err := app.Repo.SlackIntegration.ListSlackIntegrationsByProjectID(uint(projID))
+
+	if err != nil {
+		app.handleErrorRead(err, ErrProjectDataRead, w)
+		return
+	}
+
+	for _, slackInt := range slackInts {
+		if slackInt.ID == uint(integrationID) {
+			err = app.Repo.SlackIntegration.DeleteSlackIntegration(slackInt.ID)
+			if err != nil {
+				app.handleErrorInternal(err, w)
+				return
+			}
+			w.WriteHeader(http.StatusOK)
+		}
+	}
+
+	w.WriteHeader(http.StatusNotFound)
+}

+ 10 - 0
server/router/router.go

@@ -868,6 +868,16 @@ func New(a *api.App) *chi.Mux {
 				),
 			)
 
+			r.Method(
+				"DELETE",
+				"/projects/{project_id}/slack_integrations/{slack_integration_id}",
+				auth.DoesUserHaveProjectAccess(
+					requestlog.NewHandler(a.HandleDeleteSlackIntegration, l),
+					mw.URLParam,
+					mw.WriteAccess,
+				),
+			)
+
 			// /api/projects/{project_id}/helmrepos routes
 			r.Method(
 				"POST",