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: gitHubBanner, + insideBanner: slackBannerPopup, + icon: gitHubIcon, + 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: ( +
+ gitHubLogo +
+

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 && ( +
+ +
+ )} + {imagesLoaded && ( + <> + {githubConfiguration?.insideBanner} +
+ {githubConfiguration.header} +
+ {isValue && ( +
+
+ {githubConfiguration.integrateDesc} +
+
+

API Details

+
+ +

API Token

+ {isValue ? ( +
+ {isValue && ( +
+

Repos

+
+
+ + + +
+
+ {formFields?.keys?.connected_repos?.map((repo, index) => { + return ( + updateKeysConnectedRepos(updatedFields, i)} + removeRepo={(i) => { + removeRepoItem(i); + }} + /> + ); + })} + {addNew ? ( + updateKeysConnectedRepos(updatedFields, i)} + removeRepo={(i) => { + removeRepoItem(i); + setAddNew(false); + }} + /> + ) : ( +
setAddNew(!addNew)}> + +
+ )} +
+
+
+ )} +
+ +
+
+
+
+ + )} +
+ ); +}; + +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 ( +
+
+ githubBranchIcon + + { + updateRepo(e); + }} + options={Object?.keys(reposList)} + /> + + + + { + updateBranch(e); + }} + /> + + removeRepo(index)} /> +
+ +
+ ); +}; + +export default IntegrationItem; diff --git a/ui_src/src/domain/administration/integrations/components/integrationItem/index.js b/ui_src/src/domain/administration/integrations/components/integrationItem/index.js index adcd89afc..b7d3f753f 100644 --- a/ui_src/src/domain/administration/integrations/components/integrationItem/index.js +++ b/ui_src/src/domain/administration/integrations/components/integrationItem/index.js @@ -25,6 +25,7 @@ import DataDogIntegration from '../dataDogIntegration'; import GrafanaIntegration from '../grafanaIntegration'; import ElasticIntegration from '../elasticIntegration'; import DebeziumIntegration from '../debeziumIntegration'; +import GitHubIntegration from '../gitHubIntegration'; const IntegrationItem = ({ value }) => { const [state] = useContext(Context); @@ -57,6 +58,16 @@ const IntegrationItem = ({ value }) => { value={ref.current} /> ); + case 'Github': + return ( + { + modalFlip(false); + setIntegrateValue(data); + }} + value={ref.current} + /> + ); case 'S3': return ( img:first-child { + margin-left: 18px; + } + .add-more-repos { + height: 40px; + padding-left: 20px; + align-items: center; + display: flex; + color: #6557ff; + cursor: pointer; + label { + cursor: pointer; + } + } + } +} +.connect-bth-gh { + display: flex; + justify-content: space-between; + span { + display: flex; + align-items: center; + vertical-align: baseline; + .connected { + height: 12px; } } } diff --git a/ui_src/src/utils/styleTemplates.js b/ui_src/src/utils/styleTemplates.js index dd9f09cd1..3a4c837c3 100644 --- a/ui_src/src/utils/styleTemplates.js +++ b/ui_src/src/utils/styleTemplates.js @@ -69,6 +69,8 @@ export function getFontColor(colorType) { export function getBackgroundColor(backgroundColor) { switch (backgroundColor) { + case 'green': + return '#27AE60'; case 'purple': return '#6557FF'; case 'purple-light':