diff --git a/ui_src/src/assets/images/githubBanner.webp b/ui_src/src/assets/images/githubBanner.webp
new file mode 100644
index 000000000..196b4fe74
Binary files /dev/null and b/ui_src/src/assets/images/githubBanner.webp differ
diff --git a/ui_src/src/assets/images/githubBannerPopup.webp b/ui_src/src/assets/images/githubBannerPopup.webp
new file mode 100644
index 000000000..c1a4bb083
Binary files /dev/null and b/ui_src/src/assets/images/githubBannerPopup.webp differ
diff --git a/ui_src/src/assets/images/githubBranchIcon.svg b/ui_src/src/assets/images/githubBranchIcon.svg
new file mode 100644
index 000000000..b5eb72789
--- /dev/null
+++ b/ui_src/src/assets/images/githubBranchIcon.svg
@@ -0,0 +1,3 @@
+
diff --git a/ui_src/src/assets/images/githubIntegrationIcon.svg b/ui_src/src/assets/images/githubIntegrationIcon.svg
new file mode 100644
index 000000000..1d4c0fecd
--- /dev/null
+++ b/ui_src/src/assets/images/githubIntegrationIcon.svg
@@ -0,0 +1,12 @@
+
diff --git a/ui_src/src/assets/images/tickCircle.svg b/ui_src/src/assets/images/tickCircle.svg
new file mode 100644
index 000000000..ceee16e14
--- /dev/null
+++ b/ui_src/src/assets/images/tickCircle.svg
@@ -0,0 +1,3 @@
+
diff --git a/ui_src/src/const/apiEndpoints.js b/ui_src/src/const/apiEndpoints.js
index 5b5412fe8..f3d7977bf 100644
--- a/ui_src/src/const/apiEndpoints.js
+++ b/ui_src/src/const/apiEndpoints.js
@@ -83,6 +83,7 @@ export const ApiEndpoints = {
GET_ALL_INTEGRATION: '/integrations/getAllIntegrations',
DISCONNECT_INTEGRATION: '/integrations/disconnectIntegration',
REQUEST_INTEGRATION: '/integrations/requestIntegration',
+ GET_SOURCE_CODE_BRANCHES: '/integrations/getSourceCodeBranches',
//Configuration
GET_CLUSTER_CONFIGURATION: '/configurations/getClusterConfig',
diff --git a/ui_src/src/const/integrationList.js b/ui_src/src/const/integrationList.js
index 099a6bae7..c492194a3 100644
--- a/ui_src/src/const/integrationList.js
+++ b/ui_src/src/const/integrationList.js
@@ -14,7 +14,6 @@ import datadogBannerPopup from '../assets/images/datadogBannerPopup.webp';
import elasticBannerPopup from '../assets/images/elasticBannerPopup.webp';
import grafanaBannerPopup from '../assets/images/grafanaBannerPopup.webp';
import debeziumBannerPopup from '../assets/images/debeziumBannerPopup.webp';
-
import slackBannerPopup from '../assets/images/slackBannerPopup.webp';
import pagerdutyBanner from '../assets/images/pagerdutyBanner.webp';
import influxDBBanner from '../assets/images/influxDBBanner.webp';
@@ -25,6 +24,9 @@ import datadogBanner from '../assets/images/datadogBanner.webp';
import grafanaBanner from '../assets/images/grafanaBanner.webp';
import debeziumBanner from '../assets/images/debeziumBanner.webp';
import pagerDutyIcon from '../assets/images/pagerDutyIcon.svg';
+import githubIntegrationIcon from '../assets/images/githubIntegrationIcon.svg';
+import githubBannerPopup from '../assets/images/githubBannerPopup.webp';
+import githubBanner from '../assets/images/githubBanner.webp';
import newrelicIcon from '../assets/images/newrelicIcon.svg';
import influxDBIcon from '../assets/images/influxDBIcon.svg';
import slackBanner from '../assets/images/slackBanner.webp';
@@ -59,6 +61,10 @@ export const CATEGORY_LIST = {
CDC: {
name: 'CDC',
color: ColorPalette[11]
+ },
+ SourceCode: {
+ name: 'Source Code',
+ color: ColorPalette[6]
}
};
@@ -270,6 +276,34 @@ export const INTEGRATION_LIST = {
)
},
+ GitHub: {
+ name: 'Github',
+ by: 'memphis',
+ banner: ,
+ insideBanner: ,
+ icon: ,
+ description:
+ 'GitHub is an open source code repository and collaborative software development platform. Use GitHub repositories to manage your Schemaverse schemas and Functions source code.',
+ category: CATEGORY_LIST['SourceCode'],
+ header: (
+
+
+
+
GitHub
+ by memphis
+
+
+ ),
+ integrateDesc: (
+
+
Description
+
+ GitHub is an open source code repository and collaborative software development platform. Use GitHub repositories to manage your Schemaverse schemas
+ and Functions source code.
+
+
+ )
+ },
Elasticsearch: {
name: 'Elasticsearch observability',
by: 'memphis',
diff --git a/ui_src/src/domain/administration/integrations/components/gitHubIntegration/index.js b/ui_src/src/domain/administration/integrations/components/gitHubIntegration/index.js
new file mode 100644
index 000000000..d3bb0049e
--- /dev/null
+++ b/ui_src/src/domain/administration/integrations/components/gitHubIntegration/index.js
@@ -0,0 +1,355 @@
+// Copyright 2022-2023 The Memphis.dev Authors
+// Licensed under the Memphis Business Source License 1.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// Changed License: [Apache License, Version 2.0 (https://www.apache.org/licenses/LICENSE-2.0), as published by the Apache Foundation.
+//
+// https://github.com/memphisdev/memphis/blob/master/LICENSE
+//
+// Additional Use Grant: You may make use of the Licensed Work (i) only as part of your own product or service, provided it is not a message broker or a message queue product or service; and (ii) provided that you do not use, provide, distribute, or make available the Licensed Work as a Service.
+// A "Service" is a commercial offering, product, hosted, or managed service, that allows third parties (other than your own employees and contractors acting on your behalf) to access and/or use the Licensed Work or a substantial set of the features or functionality of the Licensed Work to third parties as a software-as-a-service, platform-as-a-service, infrastructure-as-a-service or other similar services that compete with Licensor products or services.
+
+import React, { useState, useContext, useEffect } from 'react';
+import { Form, message } from 'antd';
+import tickCircle from '../../../../../assets/images/tickCircle.svg';
+import { FiPlus } from 'react-icons/fi';
+import { INTEGRATION_LIST } from '../../../../../const/integrationList';
+import { ApiEndpoints } from '../../../../../const/apiEndpoints';
+import { httpRequest } from '../../../../../services/http';
+import Button from '../../../../../components/button';
+import { Context } from '../../../../../hooks/store';
+import Input from '../../../../../components/Input';
+import Loader from '../../../../../components/loader';
+import IntegrationItem from './integratedItem';
+
+const GitHubIntegration = ({ close, value }) => {
+ const isValue = value && Object.keys(value)?.length !== 0;
+ const githubConfiguration = INTEGRATION_LIST['GitHub'];
+ const [creationForm] = Form.useForm();
+ const [state, dispatch] = useContext(Context);
+ const [formFields, setFormFields] = useState({
+ name: 'github',
+ keys: {
+ token: '',
+ connected_repos: []
+ }
+ });
+ const [loadingCreate, setLoadingCreate] = useState(false);
+ const [loadingSubmit, setLoadingSubmit] = useState(false);
+ const [loadingDisconnect, setLoadingDisconnect] = useState(false);
+ const [imagesLoaded, setImagesLoaded] = useState(false);
+ const [repos, setRepos] = useState([]);
+ const [addNew, setAddNew] = useState(false);
+
+ useEffect(() => {
+ const images = [];
+ images.push(INTEGRATION_LIST['GitHub'].banner.props.src);
+ images.push(INTEGRATION_LIST['GitHub'].insideBanner.props.src);
+ images.push(INTEGRATION_LIST['GitHub'].icon.props.src);
+ const promises = [];
+
+ images.forEach((imageUrl) => {
+ const image = new Image();
+ promises.push(
+ new Promise((resolve) => {
+ image.onload = resolve;
+ })
+ );
+ image.src = imageUrl;
+ });
+
+ Promise.all(promises).then(() => {
+ setImagesLoaded(true);
+ });
+ getIntegration();
+ }, []);
+
+ const updateKeysConnectedRepos = (repo, index) => {
+ let updatedValue = { ...formFields.keys };
+ if (index < updatedValue.connected_repos?.length) updatedValue.connected_repos[index] = repo;
+ else if (index === updatedValue.connected_repos?.length) {
+ updatedValue.connected_repos.push(repo);
+ }
+ setFormFields((formFields) => ({ ...formFields, keys: updatedValue }));
+ setAddNew(false);
+ };
+
+ const cleanEmptyFields = () => {
+ let updatedValue = { ...formFields.keys };
+ updatedValue.connected_repos = updatedValue.connected_repos.filter((repo) => repo.branch !== '');
+ setFormFields((formFields) => ({ ...formFields, keys: updatedValue }));
+ return { name: 'github', keys: updatedValue };
+ };
+
+ const removeRepoItem = (index) => {
+ let updatedValue = { ...formFields.keys };
+ updatedValue.connected_repos.splice(index, 1);
+ setFormFields((formFields) => ({ ...formFields, keys: updatedValue }));
+ };
+
+ const updateKeysState = (field, value) => {
+ let updatedValue = { ...formFields.keys };
+ updatedValue[field] = value;
+ setFormFields((formFields) => ({ ...formFields, keys: updatedValue }));
+ };
+
+ const closeModal = (data, disconnect = false) => {
+ setTimeout(() => {
+ disconnect ? setLoadingDisconnect(false) : setLoadingSubmit(false);
+ }, 1000);
+ close(data);
+ message.success({
+ key: 'memphisSuccessMessage',
+ content: disconnect ? 'The integration was successfully disconnected' : 'The integration connected successfully',
+ duration: 5,
+ style: { cursor: 'pointer' },
+ onClick: () => message.destroy('memphisSuccessMessage')
+ });
+ };
+
+ const updateIntegration = async () => {
+ setLoadingSubmit(true);
+ const updatedFields = cleanEmptyFields();
+ try {
+ const data = await httpRequest('POST', ApiEndpoints.UPDATE_INTEGRATION, updatedFields);
+ dispatch({ type: 'UPDATE_INTEGRATION', payload: data });
+ closeModal(data);
+ } catch (err) {
+ setLoadingSubmit(false);
+ }
+ };
+
+ const createIntegration = async () => {
+ try {
+ setLoadingCreate(true);
+ const data = await httpRequest('POST', ApiEndpoints.CREATE_INTEGRATION, formFields);
+ dispatch({ type: 'ADD_INTEGRATION', payload: data });
+ getIntegration();
+ setLoadingCreate(false);
+ } catch (err) {
+ setLoadingCreate(false);
+ }
+ };
+
+ const getIntegration = async () => {
+ try {
+ const data = await httpRequest('GET', `${ApiEndpoints.GET_INTEGRATION_DETAILS}?name=github`);
+ if (data) {
+ updateKeysState('connected_repos', data?.integaraion?.keys?.connected_repos || []);
+ setRepos(data?.repos);
+ } else
+ setFormFields({
+ name: 'github',
+ keys: {
+ token: '',
+ connected_repos: []
+ }
+ });
+ } catch (error) {}
+ };
+
+ const disconnect = async () => {
+ setLoadingDisconnect(true);
+ try {
+ await httpRequest('DELETE', ApiEndpoints.DISCONNECT_INTEGRATION, {
+ name: formFields.name
+ });
+ dispatch({ type: 'REMOVE_INTEGRATION', payload: formFields.name });
+
+ closeModal({}, true);
+ } catch (err) {
+ setLoadingDisconnect(false);
+ }
+ };
+
+ return (
+
+ {!imagesLoaded && (
+
+
+
+ >
+ )}
+
+ );
+};
+
+export default GitHubIntegration;
diff --git a/ui_src/src/domain/administration/integrations/components/gitHubIntegration/integratedItem.js b/ui_src/src/domain/administration/integrations/components/gitHubIntegration/integratedItem.js
new file mode 100644
index 000000000..9374ade23
--- /dev/null
+++ b/ui_src/src/domain/administration/integrations/components/gitHubIntegration/integratedItem.js
@@ -0,0 +1,111 @@
+// Copyright 2022-2023 The Memphis.dev Authors
+// Licensed under the Memphis Business Source License 1.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// Changed License: [Apache License, Version 2.0 (https://www.apache.org/licenses/LICENSE-2.0), as published by the Apache Foundation.
+//
+// https://github.com/memphisdev/memphis/blob/master/LICENSE
+//
+// Additional Use Grant: You may make use of the Licensed Work (i) only as part of your own product or service, provided it is not a message broker or a message queue product or service; and (ii) provided that you do not use, provide, distribute, or make available the Licensed Work as a Service.
+// A "Service" is a commercial offering, product, hosted, or managed service, that allows third parties (other than your own employees and contractors acting on your behalf) to access and/or use the Licensed Work or a substantial set of the features or functionality of the Licensed Work to third parties as a software-as-a-service, platform-as-a-service, infrastructure-as-a-service or other similar services that compete with Licensor products or services.
+
+import React, { useState, useEffect } from 'react';
+import { Divider, Form } from 'antd';
+import { FiMinusCircle } from 'react-icons/fi';
+
+import githubBranchIcon from '../../../../../assets/images/githubBranchIcon.svg';
+import { ApiEndpoints } from '../../../../../const/apiEndpoints';
+import { httpRequest } from '../../../../../services/http';
+import SelectComponent from '../../../../../components/select';
+
+const IntegrationItem = ({ index, repo, reposList, updateIntegrationList, removeRepo }) => {
+ const [isEditting, setIsEditting] = useState(false);
+ const [formFields, setFormFields] = useState({
+ type: 'functions',
+ repo_name: '',
+ repo_owner: '',
+ branch: ''
+ });
+ const [branches, setBranches] = useState([]);
+
+ useEffect(() => {
+ repo.repo_name && repo.repo_owner && getSourceCodeBranches(repo.repo_name, repo.repo_owner);
+ setFormFields({ repo_name: repo.repo_name, repo_owner: repo.repo_owner, branch: repo.branch, type: 'functions' });
+ }, []);
+
+ useEffect(() => {
+ branches?.length > 0 && isEditting && updateBranch(branches[0]);
+ }, [branches]);
+
+ useEffect(() => {
+ updateIntegrationList(formFields, index);
+ }, [formFields.branch]);
+
+ useEffect(() => {
+ isEditting && formFields?.repo_name && formFields?.repo_owner && getSourceCodeBranches(formFields?.repo_name, formFields?.repo_owner);
+ }, [formFields?.repo_name]);
+
+ const updateRepo = (repo) => {
+ setIsEditting(true);
+ getSourceCodeBranches(repo, reposList[repo]);
+ setFormFields((formFields) => ({ ...formFields, ...{ repo_name: repo, repo_owner: reposList[repo], branch: '' } }));
+ };
+
+ const updateBranch = (branch) => {
+ setIsEditting(true);
+ setFormFields((formFields) => ({ ...formFields, ...{ branch: branch } }));
+ };
+
+ const getSourceCodeBranches = async (repo, repo_owner) => {
+ try {
+ const data = await httpRequest('GET', `${ApiEndpoints.GET_SOURCE_CODE_BRANCHES}?repo_name=${repo}&repo_owner=${repo_owner}`);
+ setBranches(data?.branches[repo]);
+ } catch (error) {}
+ };
+
+ return (
+