| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| * |
| */ |
| import { useState, useEffect, useCallback, useMemo } from 'react' |
| import { useHistory } from 'react-router-dom' |
| import { ToastNotification } from '@/components/Toast' |
| import { DEVLAKE_ENDPOINT } from '@/utils/config' |
| import request from '@/utils/request' |
| import { NullConnection } from '@/data/NullConnection' |
| import { |
| Providers, |
| ProviderConnectionLimits, |
| ConnectionStatus, |
| ConnectionStatusLabels |
| } from '@/data/Providers' |
| |
| import useNetworkOfflineMode from '@/hooks/useNetworkOfflineMode' |
| |
| function useConnectionManager ( |
| { |
| activeProvider, |
| connectionId, |
| }, |
| updateMode = false |
| ) { |
| const history = useHistory() |
| const { handleOfflineMode } = useNetworkOfflineMode() |
| |
| const [provider, setProvider] = useState(activeProvider) |
| const [name, setName] = useState() |
| const [endpointUrl, setEndpointUrl] = useState() |
| const [proxy, setProxy] = useState() |
| const [token, setToken] = useState() |
| const [initialTokenStore, setInitialTokenStore] = useState({ |
| 0: '', |
| 1: '', |
| 2: '' |
| }) |
| const [username, setUsername] = useState() |
| const [password, setPassword] = useState() |
| |
| const [isSaving, setIsSaving] = useState(false) |
| const [isFetching, setIsFetching] = useState(false) |
| const [isRunning, setIsRunning] = useState(false) |
| const [isTesting, setIsTesting] = useState(false) |
| // eslint-disable-next-line no-unused-vars |
| const [isDeleting, setIsDeleting] = useState(false) |
| const [errors, setErrors] = useState([]) |
| const [showError, setShowError] = useState(false) |
| const [testStatus, setTestStatus] = useState(0) // 0=Pending, 1=Success, 2=Failed |
| const [testResponse, setTestResponse] = useState() |
| const [allTestResponses, setAllTestResponses] = useState({}) |
| const [sourceLimits, setConnectionLimits] = useState(ProviderConnectionLimits) |
| |
| const [activeConnection, setActiveConnection] = useState(NullConnection) |
| const [allConnections, setAllConnections] = useState([]) |
| const [allProviderConnections, setAllProviderConnections] = useState([]) |
| const [domainRepositories, setDomainRepositories] = useState([]) |
| const [testedConnections, setTestedConnections] = useState([]) |
| const [connectionCount, setConnectionCount] = useState(0) |
| const [connectionLimitReached, setConnectionLimitReached] = useState(false) |
| |
| const [saveComplete, setSaveComplete] = useState(false) |
| const [deleteComplete, setDeleteComplete] = useState(false) |
| const connectionTestPayload = useMemo(() => ({ endpoint: endpointUrl, username, password, token, proxy }), [endpointUrl, password, proxy, token, username]) |
| |
| const testConnection = useCallback( |
| ( |
| notify = true, |
| manualPayload = {}, |
| onSuccess = () => {}, |
| onFail = () => {} |
| ) => { |
| setIsTesting(true) |
| setShowError(false) |
| ToastNotification.clear() |
| setTestResponse(null) |
| |
| const runTest = async () => { |
| const payload = Object.keys(manualPayload).length > 0 ? manualPayload : connectionTestPayload |
| const testUrl = `${DEVLAKE_ENDPOINT}/plugins/${provider.id}/test` |
| console.log( |
| 'INFO >>> Endpoint URL & Payload for testing: ', |
| testUrl, |
| payload |
| ) |
| const res = await request.post(testUrl, payload) |
| setTestResponse(res.data) |
| if ([Providers.GITHUB].includes(provider.id)) { |
| console.log('>>> SETTING TOKEN TEST RESPONSE FOR TOKEN >>>', manualPayload?.token || connectionTestPayload.token) |
| setAllTestResponses(tRs => ({ ...tRs, [manualPayload?.token]: res.data })) |
| } |
| if (res.data?.success && res.status === 200) { |
| setIsTesting(false) |
| setTestStatus(1) |
| if (notify) { |
| ToastNotification.show({ |
| message: `Connection test OK. ${payload.endpoint}`, |
| intent: 'success', |
| icon: 'small-tick', |
| }) |
| } |
| onSuccess(res) |
| } else { |
| setIsTesting(false) |
| setTestStatus(2) |
| const errorMessage = |
| 'Connection test FAILED. ' + (res.data ? res.data.message : '') |
| if (notify) { |
| ToastNotification.show({ |
| message: errorMessage, |
| intent: 'danger', |
| icon: 'error', |
| }) |
| } |
| onFail(res) |
| } |
| } |
| runTest() |
| }, |
| [provider?.id, connectionTestPayload] |
| ) |
| |
| const saveConnection = (configurationSettings = {}) => { |
| setIsSaving(true) |
| let connectionPayload = { ...configurationSettings } |
| switch (provider.id) { |
| case Providers.JIRA: |
| connectionPayload = { |
| name: name, |
| endpoint: endpointUrl, |
| username: username, |
| password: password, |
| proxy: proxy, |
| ...connectionPayload, |
| } |
| break |
| case Providers.GITHUB: |
| connectionPayload = { |
| name: name, |
| endpoint: endpointUrl, |
| token: token, |
| proxy: proxy, |
| ...connectionPayload, |
| } |
| break |
| case Providers.JENKINS: |
| // eslint-disable-next-line max-len |
| connectionPayload = { |
| name: name, |
| endpoint: endpointUrl, |
| username: username, |
| password: password, |
| ...connectionPayload, |
| } |
| break |
| case Providers.GITLAB: |
| connectionPayload = { |
| name: name, |
| endpoint: endpointUrl, |
| token: token, |
| proxy: proxy, |
| ...connectionPayload, |
| } |
| break |
| } |
| |
| let saveResponse = { |
| success: false, |
| connection: { |
| ...connectionPayload, |
| }, |
| errors: [], |
| } |
| |
| const saveConfiguration = async (configPayload) => { |
| try { |
| setShowError(false) |
| setErrors([]) |
| ToastNotification.clear() |
| const s = await request.post( |
| `${DEVLAKE_ENDPOINT}/plugins/${provider.id}/connections`, |
| configPayload |
| ) |
| console.log('>> CONFIGURATION SAVED SUCCESSFULLY', configPayload, s) |
| saveResponse = { |
| ...saveResponse, |
| success: [200, 201].includes(s.status), |
| connection: { ...s.data }, |
| errors: s.isAxiosError ? [s.message] : [], |
| } |
| } catch (e) { |
| saveResponse.errors.push(e.message) |
| setErrors(saveResponse.errors) |
| console.log('>> CONFIGURATION FAILED TO SAVE', configPayload, e) |
| } |
| } |
| |
| const modifyConfiguration = async (configPayload) => { |
| try { |
| setShowError(false) |
| setErrors([]) |
| ToastNotification.clear() |
| // eslint-disable-next-line max-len |
| const s = await request.patch( |
| `${DEVLAKE_ENDPOINT}/plugins/${provider.id}/connections/${ |
| activeConnection.id || activeConnection.ID |
| }`, |
| configPayload |
| ) |
| const silentRefetch = true |
| console.log('>> CONFIGURATION MODIFIED SUCCESSFULLY', configPayload, s) |
| saveResponse = { |
| ...saveResponse, |
| success: [200, 201].includes(s.status), |
| connection: { ...s.data }, |
| errors: s.isAxiosError ? [s.message] : [], |
| } |
| fetchConnection(silentRefetch) |
| } catch (e) { |
| saveResponse.errors.push(e.message) |
| setErrors(saveResponse.errors) |
| console.log('>> CONFIGURATION FAILED TO UPDATE', configPayload, e) |
| } |
| } |
| |
| if (updateMode && activeConnection?.id !== null) { |
| modifyConfiguration(connectionPayload) |
| } else { |
| saveConfiguration(connectionPayload) |
| } |
| |
| setTimeout(() => { |
| if (saveResponse.success && errors.length === 0) { |
| ToastNotification.show({ |
| message: 'Connection saved successfully.', |
| intent: 'success', |
| icon: 'small-tick', |
| }) |
| setShowError(false) |
| setIsSaving(false) |
| setSaveComplete(saveResponse.connection) |
| if ( |
| [Providers.GITHUB, Providers.JIRA, Providers.GITLAB].includes(provider.id) && |
| token !== '' && |
| token?.toString().split(',').length > 1 |
| ) { |
| testConnection() |
| } |
| if (!updateMode) { |
| history.push(`/integrations/${provider.id}`) |
| } |
| } else { |
| ToastNotification.show({ |
| message: 'Connection failed to save, please try again.', |
| intent: 'danger', |
| icon: 'error', |
| }) |
| setShowError(true) |
| setIsSaving(false) |
| setSaveComplete(false) |
| } |
| }, 2000) |
| } |
| |
| const runCollection = (options = {}) => { |
| setIsRunning(true) |
| ToastNotification.show({ |
| message: 'Triggered Collection Process', |
| intent: 'info', |
| icon: 'info', |
| }) |
| console.log('>> RUNNING COLLECTION PROCESS', isRunning) |
| // Run Collection Tasks... |
| } |
| |
| const fetchConnection = useCallback( |
| (silent = false, notify = false, cId = null) => { |
| console.log(`>> FETCHING CONNECTION [PROVIDER = ${provider.id}]....`) |
| try { |
| setIsFetching(!silent) |
| setErrors([]) |
| ToastNotification.clear() |
| console.log('>> FETCHING CONNECTION SOURCE') |
| const fetch = async () => { |
| const f = await request.get( |
| `${DEVLAKE_ENDPOINT}/plugins/${provider.id}/connections/${cId || connectionId}` |
| ) |
| const connectionData = f.data |
| console.log('>> RAW CONNECTION DATA FROM API...', connectionData) |
| setActiveConnection({ |
| ...connectionData, |
| ID: connectionData.ID || connectionData.id, |
| name: connectionData.name || connectionData.Name, |
| endpoint: connectionData.endpoint || connectionData.Endpoint, |
| proxy: connectionData.proxy || connectionData.Proxy, |
| username: connectionData.username || connectionData.Username, |
| password: connectionData.password || connectionData.Password, |
| token: connectionData.token || connectionData.auth |
| }) |
| setTimeout(() => { |
| setIsFetching(false) |
| }, 500) |
| } |
| fetch() |
| } catch (e) { |
| setIsFetching(false) |
| setActiveConnection(NullConnection) |
| setErrors([e.message]) |
| ToastNotification.show({ |
| message: `${e}`, |
| intent: 'danger', |
| icon: 'error', |
| }) |
| console.log('>> FAILED TO FETCH CONNECTION', e) |
| } |
| }, |
| [provider?.id, connectionId] |
| ) |
| |
| const fetchAllConnections = useCallback( |
| async (notify = false, allSources = false) => { |
| try { |
| setIsFetching(true) |
| setErrors([]) |
| ToastNotification.clear() |
| console.log('>> FETCHING ALL CONNECTION SOURCES') |
| let c = null |
| if (allSources) { |
| const aC = await Promise.all([ |
| request.get( |
| `${DEVLAKE_ENDPOINT}/plugins/${Providers.JIRA}/connections` |
| ), |
| request.get( |
| `${DEVLAKE_ENDPOINT}/plugins/${Providers.GITLAB}/connections` |
| ), |
| request.get( |
| `${DEVLAKE_ENDPOINT}/plugins/${Providers.JENKINS}/connections` |
| ), |
| request.get( |
| `${DEVLAKE_ENDPOINT}/plugins/${Providers.GITHUB}/connections` |
| ), |
| ]) |
| const builtConnections = aC |
| .map((providerResponse) => [].concat(providerResponse.data || []).map(c => ({ |
| ...c, |
| connectionId: c.id, |
| provider: providerResponse.config?.url?.split('/')[3], |
| // @todo: inject realtime connection status... |
| status: ConnectionStatus.ONLINE |
| }))) |
| setAllProviderConnections(builtConnections.flat()) |
| console.log( |
| '>> ALL SOURCE CONNECTIONS: FETCHING ALL CONNECTION FROM ALL DATA SOURCES' |
| ) |
| console.log('>> ALL SOURCE CONNECTIONS: ', aC) |
| } else { |
| c = await request.get( |
| `${DEVLAKE_ENDPOINT}/plugins/${provider.id}/connections` |
| ) |
| } |
| |
| console.log('>> RAW ALL CONNECTIONS DATA FROM API...', c?.data) |
| const providerConnections = [] |
| .concat(Array.isArray(c?.data) ? c?.data : []) |
| .map((conn, idx) => { |
| return { |
| ...conn, |
| status: ConnectionStatus.OFFLINE, |
| ID: conn.ID || conn.id, |
| name: conn.name, |
| endpoint: conn.endpoint, |
| errors: [], |
| } |
| }) |
| if (notify) { |
| ToastNotification.show({ |
| message: 'Loaded all connections.', |
| intent: 'success', |
| icon: 'small-tick', |
| }) |
| } |
| setAllConnections(providerConnections) |
| setConnectionCount(c?.data?.length) |
| setConnectionLimitReached( |
| sourceLimits[provider.id] && |
| c.data?.length >= sourceLimits[provider.id] |
| ) |
| setIsFetching(false) |
| } catch (e) { |
| console.log('>> FAILED TO FETCH ALL CONNECTIONS', e) |
| ToastNotification.show({ |
| message: `Failed to Load Connections - ${e.message}`, |
| intent: 'danger', |
| icon: 'error', |
| }) |
| setIsFetching(false) |
| setAllConnections([]) |
| setConnectionCount(0) |
| setConnectionLimitReached(false) |
| setErrors([e.message]) |
| handleOfflineMode(e.response?.status, e.response) |
| } |
| }, |
| [provider?.id, sourceLimits, handleOfflineMode] |
| ) |
| |
| const deleteConnection = useCallback( |
| async (connection) => { |
| try { |
| setIsDeleting(true) |
| setErrors([]) |
| console.log('>> TRYING TO DELETE CONNECTION...', connection) |
| const d = await request.delete( |
| `${DEVLAKE_ENDPOINT}/plugins/${provider.id}/connections/${ |
| connection.ID || connection.id |
| }` |
| ) |
| console.log('>> CONNECTION DELETED...', d) |
| setIsDeleting(false) |
| setDeleteComplete({ |
| provider: activeProvider, |
| connection: d.data, |
| }) |
| } catch (e) { |
| setIsDeleting(false) |
| setDeleteComplete(false) |
| setErrors([e.message]) |
| console.log('>> FAILED TO DELETE CONNECTION', e) |
| } |
| }, |
| [provider?.id, activeProvider] |
| ) |
| |
| const getConnectionName = useCallback((connectionId, connections) => { |
| const source = connections.find((s) => s.id === connectionId) |
| return source ? source.title : '(Instance)' |
| }, []) |
| |
| const testAllConnections = useCallback( |
| (connections) => { |
| console.log('>> TESTING ALL CONNECTION SOURCES...') |
| connections.forEach((c, cIdx) => { |
| console.log('>>> TESTING CONNECTION INSTANCE...', c) |
| const notify = false |
| const payload = { |
| endpoint: c.endpoint, |
| username: c.username, |
| password: c.password, |
| token: c.token, |
| proxy: c.proxy, |
| } |
| const onSuccess = (res) => { |
| setTestedConnections((testedConnections) => [ |
| ...new Set([ |
| ...testedConnections.filter((oC) => oC.id !== c.id), |
| { ...c, status: ConnectionStatus.ONLINE }, |
| ]), |
| ]) |
| } |
| const onFail = (res) => { |
| setTestedConnections((testedConnections) => [ |
| ...new Set([ |
| ...testedConnections.filter((oC) => oC.ID !== c.ID), |
| { ...c, status: ConnectionStatus.DISCONNECTED }, |
| ]), |
| ]) |
| } |
| testConnection(notify, payload, onSuccess, onFail) |
| }) |
| }, |
| [testConnection] |
| ) |
| |
| const fetchDomainLayerRepositories = useCallback(() => { |
| console.log('>> FETCHING DOMAIN LAYER REPOS....') |
| try { |
| setIsFetching(true) |
| setErrors([]) |
| ToastNotification.clear() |
| const fetch = async () => { |
| const r = await request.get(`${DEVLAKE_ENDPOINT}/domainlayer/repos`) |
| console.log('>> RAW REPOSITORY DATA FROM API...', r.data?.repos) |
| setDomainRepositories(r.data?.repos || []) |
| setTimeout(() => { |
| setIsFetching(false) |
| }, 500) |
| } |
| fetch() |
| } catch (e) { |
| setIsFetching(false) |
| setDomainRepositories([]) |
| setErrors([e.message]) |
| ToastNotification.show({ |
| message: `${e}`, |
| intent: 'danger', |
| icon: 'error', |
| }) |
| console.log('>> FAILED TO FETCH DOMAIN LAYER REPOS', e) |
| } |
| }, []) |
| |
| const clearConnection = useCallback(() => { |
| setName('') |
| setEndpointUrl('') |
| setUsername('') |
| setPassword('') |
| setToken('') |
| setInitialTokenStore({ |
| 0: '', |
| 1: '', |
| 2: '' |
| }) |
| setProxy('') |
| }, []) |
| |
| useEffect(() => { |
| if (activeConnection && activeConnection.id !== null) { |
| const connectionToken = activeConnection.auth || activeConnection.token || activeConnection.basicAuthEncoded |
| setName(activeConnection.name) |
| setEndpointUrl(activeConnection.endpoint) |
| switch (provider.id) { |
| case Providers.JENKINS: |
| setUsername(activeConnection.username) |
| setPassword(activeConnection.password) |
| break |
| case Providers.GITLAB: |
| setToken(activeConnection.basicAuthEncoded || activeConnection.token || activeConnection.auth) |
| setProxy(activeConnection.Proxy || activeConnection.proxy) |
| break |
| case Providers.GITHUB: |
| setToken(connectionToken) |
| setInitialTokenStore(connectionToken?.split(',')?.reduce((tS, cT, id) => ({ ...tS, [id]: cT }), {})) |
| setProxy(activeConnection.Proxy || activeConnection.proxy) |
| break |
| case Providers.JIRA: |
| // setToken(activeConnection.basicAuthEncoded || activeConnection.token) |
| setUsername(activeConnection.username) |
| setPassword(activeConnection.password) |
| setProxy(activeConnection.Proxy || activeConnection.proxy) |
| break |
| } |
| ToastNotification.clear() |
| // ToastNotification.show({ message: `Fetched settings for ${activeConnection.name}.`, intent: 'success', icon: 'small-tick' }) |
| console.log('>> FETCHED CONNECTION FOR MODIFY', activeConnection) |
| } |
| }, [activeConnection, provider?.id]) |
| |
| useEffect(() => { |
| if (saveComplete && saveComplete.ID) { |
| console.log('>>> CONNECTION MANAGER - SAVE COMPLETE EFFECT RUNNING...') |
| setActiveConnection((ac) => { |
| return { |
| ...ac, |
| ...saveComplete, |
| } |
| }) |
| } |
| }, [saveComplete]) |
| |
| useEffect(() => { |
| console.log( |
| '>> CONNECTION MANAGER - SELECTING ACTIVE PROVIDER...', |
| provider |
| ) |
| if (provider && provider?.id) { |
| // console.log(activeProvider) |
| } |
| }, [provider]) |
| |
| useEffect(() => { |
| if (connectionId !== null && connectionId !== undefined) { |
| console.log('>>>> CONFIGURING CONNECTION ID ... ', connectionId) |
| fetchConnection() |
| } |
| }, [connectionId, fetchConnection]) |
| |
| useEffect(() => { |
| console.log('>> TESTED CONNECTION RESULTS...', testedConnections) |
| }, [testedConnections]) |
| |
| useEffect(() => { |
| console.log('>> CONNECTION MANAGER, ACTIVE PROVIDER CHANGED ====>', activeProvider) |
| setProvider(activeProvider) |
| }, [activeProvider]) |
| |
| return { |
| activeConnection, |
| fetchConnection, |
| fetchAllConnections, |
| fetchDomainLayerRepositories, |
| testAllConnections, |
| testConnection, |
| saveConnection, |
| deleteConnection, |
| runCollection, |
| isSaving, |
| isTesting, |
| isFetching, |
| errors, |
| showError, |
| testStatus, |
| name, |
| endpointUrl, |
| proxy, |
| username, |
| password, |
| token, |
| initialTokenStore, |
| provider, |
| setActiveConnection, |
| setProvider, |
| setName, |
| setEndpointUrl, |
| setProxy, |
| setToken, |
| setInitialTokenStore, |
| setUsername, |
| setPassword, |
| setIsSaving, |
| setIsTesting, |
| setIsFetching, |
| setErrors, |
| setShowError, |
| setTestStatus, |
| setTestResponse, |
| setAllTestResponses, |
| setConnectionLimits, |
| setSaveComplete, |
| allConnections, |
| allProviderConnections, |
| domainRepositories, |
| testedConnections, |
| sourceLimits, |
| connectionCount, |
| connectionLimitReached, |
| connectionTestPayload, |
| Providers, |
| saveComplete, |
| deleteComplete, |
| getConnectionName, |
| clearConnection, |
| testResponse, |
| allTestResponses |
| } |
| } |
| |
| export default useConnectionManager |