diff --git a/app/cdap/api/settings.ts b/app/cdap/api/settings.ts new file mode 100644 index 00000000000..78636152a47 --- /dev/null +++ b/app/cdap/api/settings.ts @@ -0,0 +1,27 @@ +/* + * Copyright © 2025 Cask Data, Inc. + * + * Licensed 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 DataSourceConfigurer from 'services/datasource/DataSourceConfigurer'; +import { apiCreator } from 'services/resource-helper'; + +const dataSrc = DataSourceConfigurer.getInstance(); +const basePath = '/namespaces/:namespace/configuration'; +const userSettingsPath = `${basePath}/user`; + +export const SettingsApi = { + getUserSettings: apiCreator(dataSrc, 'GET', 'REQUEST', `${userSettingsPath}`), + updateUserSettings: apiCreator(dataSrc, 'PUT', 'REQUEST', `${userSettingsPath}`), +}; diff --git a/app/cdap/components/StudioV2/store/common/actions.ts b/app/cdap/components/StudioV2/store/common/actions.ts new file mode 100644 index 00000000000..64b5c2b2a66 --- /dev/null +++ b/app/cdap/components/StudioV2/store/common/actions.ts @@ -0,0 +1,74 @@ +/* + * Copyright © 2025 Cask Data, Inc. + * + * Licensed 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 { MyArtifactApi } from 'api/artifact'; +import { IArtifactSummary, ILabeledArtifactSummary } from 'components/StudioV2/types'; +import { getArtifactDisaplayName } from 'components/StudioV2/utils/artifactUtils'; +import { getCurrentNamespace } from 'services/NamespaceStore'; +import { Theme } from 'services/ThemeHelper'; +import VersionStore from 'services/VersionStore'; +import { GLOBALS } from 'services/global-constants'; +import StudioV2Store from '..'; + +const PREFIX = 'COMMON_ACTIONS'; + +export const CommonActions = { + SET_ARTIFACTS: `${PREFIX}/SET_ARTIFACTS`, + SET_SELECTED_ARTIFACT: `${PREFIX}/SET_SELECTED_ARTIFACT`, +}; + +export const fetchSystemArtifacts = () => { + const cdapVersion = VersionStore.getState().version; + const namespace = getCurrentNamespace(); + + const uiSupportedArtifacts = [GLOBALS.etlDataPipeline]; + if (Theme.showRealtimePipeline !== false) { + uiSupportedArtifacts.push(GLOBALS.etlDataStreams); + } + if (Theme.showSqlPipeline !== false) { + uiSupportedArtifacts.push(GLOBALS.eltSqlPipeline); + } + + MyArtifactApi.listScopedArtifacts({ + namespace, + scope: 'SYSTEM', + }).subscribe((res: IArtifactSummary[]) => { + if (!res.length) { + return; + } + + const filteredArtifacts = res.filter( + (artifact) => artifact.version === cdapVersion && uiSupportedArtifacts.includes(artifact.name) + ); + + const labeledArtifacts = filteredArtifacts.map((artifact) => ({ + ...artifact, + label: getArtifactDisaplayName(artifact.name), + })); + + StudioV2Store.dispatch({ + type: CommonActions.SET_ARTIFACTS, + payload: labeledArtifacts, + }); + }); +}; + +export const setSelectedArtifact = (artifact: ILabeledArtifactSummary) => { + StudioV2Store.dispatch({ + type: CommonActions.SET_SELECTED_ARTIFACT, + payload: artifact, + }); +}; diff --git a/app/cdap/components/StudioV2/store/common/reducer.ts b/app/cdap/components/StudioV2/store/common/reducer.ts new file mode 100644 index 00000000000..df6421b5d62 --- /dev/null +++ b/app/cdap/components/StudioV2/store/common/reducer.ts @@ -0,0 +1,48 @@ +/* + * Copyright © 2025 Cask Data, Inc. + * + * Licensed 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 { ILabeledArtifactSummary } from 'components/StudioV2/types'; +import { getArtifactDisaplayName } from 'components/StudioV2/utils/artifactUtils'; +import { GLOBALS } from 'services/global-constants'; +import { CommonActions } from './actions'; + +interface ICommonState { + artifacts: ILabeledArtifactSummary[]; + selectedArtifact: ILabeledArtifactSummary; +} + +export const defaultSelectedArtifact: ILabeledArtifactSummary = { + name: GLOBALS.etlDataPipeline, + version: '', + scope: 'SYSTEM', + label: getArtifactDisaplayName(GLOBALS.etlDataPipeline), +}; + +export const commonDefaultInitialState: ICommonState = { + artifacts: [], + selectedArtifact: defaultSelectedArtifact, +}; + +export const common = (state: ICommonState = commonDefaultInitialState, action?): ICommonState => { + switch (action.type) { + case CommonActions.SET_ARTIFACTS: + return { ...state, artifacts: action.payload }; + case CommonActions.SET_SELECTED_ARTIFACT: + return { ...state, selectedArtifact: action.payload }; + default: + return state; + } +}; diff --git a/app/cdap/components/StudioV2/store/console/actions.ts b/app/cdap/components/StudioV2/store/console/actions.ts new file mode 100644 index 00000000000..f17fecef303 --- /dev/null +++ b/app/cdap/components/StudioV2/store/console/actions.ts @@ -0,0 +1,45 @@ +/* + * Copyright © 2025 Cask Data, Inc. + * + * Licensed 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 StudioV2Store from '..'; + +const PREFIX = 'CONSOLE_ACTIONS'; + +export const ConsoleActions = { + ADD_MESSAGE: `${PREFIX}/ADD_MESSAGE`, + ADD_MESSAGES: `${PREFIX}/ADD_MESSAGES`, + RESET: `${PREFIX}/RESET`, +}; + +export function resetConsoleMessages() { + StudioV2Store.dispatch({ + type: ConsoleActions.RESET, + }); +} + +export function addConsoleMessage(payload) { + StudioV2Store.dispatch({ + type: ConsoleActions.ADD_MESSAGE, + payload, + }); +} + +export function addConsoleMessages(payload) { + StudioV2Store.dispatch({ + type: ConsoleActions.ADD_MESSAGES, + payload, + }); +} diff --git a/app/cdap/components/StudioV2/store/console/reducer.ts b/app/cdap/components/StudioV2/store/console/reducer.ts new file mode 100644 index 00000000000..d36ef70267b --- /dev/null +++ b/app/cdap/components/StudioV2/store/console/reducer.ts @@ -0,0 +1,44 @@ +/* + * Copyright © 2025 Cask Data, Inc. + * + * Licensed 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 { ConsoleActions } from './actions'; + +interface IConsoleState { + messages: any[]; +} + +export const consoleInitialState: IConsoleState = { + messages: [], +}; + +export const consoleReducer = ( + state: IConsoleState = consoleInitialState, + action? +): IConsoleState => { + switch (action.type) { + case ConsoleActions.ADD_MESSAGE: + return { ...state, messages: [...state.messages, action.payload] }; + + case ConsoleActions.ADD_MESSAGES: + return { ...state, messages: action.payload }; + + case ConsoleActions.RESET: + return { ...consoleInitialState }; + + default: + return state; + } +}; diff --git a/app/cdap/components/StudioV2/store/index.ts b/app/cdap/components/StudioV2/store/index.ts new file mode 100644 index 00000000000..e3f96cfc116 --- /dev/null +++ b/app/cdap/components/StudioV2/store/index.ts @@ -0,0 +1,41 @@ +/* + * Copyright © 2025 Cask Data, Inc. + * + * Licensed 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 { combineReducers, createStore } from 'redux'; +import { common, commonDefaultInitialState } from './common/reducer'; +import { consoleReducer, consoleInitialState } from './console/reducer'; +import { plugins, pluginsInitialState } from './plugins/reducer'; +import { nodes, nodesInitialState } from './nodes/reducer'; + +const defaultInitialState = { + common: commonDefaultInitialState, + console: consoleInitialState, + plugins: pluginsInitialState, + nodes: nodesInitialState, +}; + +const StudioV2Store = createStore( + combineReducers({ + common, + console: consoleReducer, + plugins, + nodes, + }), + defaultInitialState, + (window as any).__REDUX_DEVTOOLS_EXTENSION__ && (window as any).__REDUX_DEVTOOLS_EXTENSION__() +); + +export default StudioV2Store; diff --git a/app/cdap/components/StudioV2/store/nodes/actions.ts b/app/cdap/components/StudioV2/store/nodes/actions.ts new file mode 100644 index 00000000000..a3e2ae65ac4 --- /dev/null +++ b/app/cdap/components/StudioV2/store/nodes/actions.ts @@ -0,0 +1,29 @@ +/* + * Copyright © 2025 Cask Data, Inc. + * + * Licensed 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. + */ + +const PREFIX = 'NODES_ACTIONS'; +export const NodesActions = { + RESET: `${PREFIX}/RESET`, + SET_STATE: `${PREFIX}/SET_STATE`, + UNDO_ACTIONS: `${PREFIX}/UNDO_ACTIONS`, + RESET_ACTIVE_NODE: `${PREFIX}/RESET_ACTIVE_NODE`, + ADD_NODE: `${PREFIX}/ADD_NODE`, + UPDATE_NODE: `${PREFIX}/UPDATE_NODE`, + ADD_CONNECTION: `${PREFIX}/ADD_CONNECTION`, + SET_ACTIVE_NODE: `${PREFIX}/SET_ACTIVE_NODE`, + REMOVE_PREVIOUS_STATE: `${PREFIX}/REMOVE_PREVIOUS_STATE`, + RESET_FUTURE_STATES: `${PREFIX}/RESET_FUTURE_STATES`, +}; \ No newline at end of file diff --git a/app/cdap/components/StudioV2/store/nodes/mutations.ts b/app/cdap/components/StudioV2/store/nodes/mutations.ts new file mode 100644 index 00000000000..b9926da5fcb --- /dev/null +++ b/app/cdap/components/StudioV2/store/nodes/mutations.ts @@ -0,0 +1,326 @@ +/* + * Copyright © 2025 Cask Data, Inc. + * + * Licensed 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 _get from 'lodash/get'; +import _cloneDeep from 'lodash/cloneDeep'; +import _isEqual from 'lodash/isEqual'; +import _assign from 'lodash/assign'; +import uuid from 'uuid'; + +import { INodesState } from './types'; +import { santizeStringForHTMLID } from 'services/helpers'; +import { GLOBALS } from 'services/global-constants'; + +export function addSourceCount_mutating(state: INodesState): INodesState { + state.currentSourceCount++; + return state; +} + +export function addTrasformCount_mutating(state: INodesState): INodesState { + state.currentTransformCount++; + return state; +} + +export function addSinkCount_mutating(state: INodesState): INodesState { + state.currentSinkCount++; + return state; +} + +export function resetSourceCount_mutating(state: INodesState): INodesState { + state.currentSourceCount = 0; + return state; +} + +export function resetTransformCount_mutating(state: INodesState): INodesState { + state.currentTransformCount = 0; + return state; +} + +export function resetSinkCount_mutating(state: INodesState): INodesState { + state.currentSinkCount = 0; + return state; +} + +export function resetPluginCount_mutating(state: INodesState): INodesState { + state.currentSourceCount = 0; + state.currentTransformCount = 0; + state.currentSinkCount = 0; + return state; +} + +export function setCanvasPanning_mutating( + state: INodesState, + panning: { + top: number; + left: number; + } +): INodesState { + state.canvasPanning.top = panning.top; + state.canvasPanning.left = panning.left; + return state; +} + +export function addNode_mutating(state: INodesState, nodeConfig): INodesState { + if (!nodeConfig.name) { + nodeConfig.name = nodeConfig.plugin.label + '_' + uuid.v4(); + } + + if (!nodeConfig.id) { + nodeConfig.id = santizeStringForHTMLID(nodeConfig.plugin.label) + '_' + uuid.v4(); + } + + addStateToHistory_mutating(state); + switch (GLOBALS.pluginConvert[nodeConfig.type]) { + case 'source': + addSourceCount_mutating(state); + break; + case 'sink': + addSinkCount_mutating(state); + break; + default: + addTrasformCount_mutating(state); + break; + } + + state.nodes.push(nodeConfig); + if (!state.adjacencyMap[nodeConfig.id]) { + state.adjacencyMap[nodeConfig.id] = []; + } + + return state; +} + +export function addStateToHistory_mutating(state: INodesState, resetFuture: boolean = true) { + const currentState = _cloneDeep(state); + delete currentState.stateHistory; + + state.stateHistory.past.push(currentState); + if (resetFuture) { + return resetFutureStates_mutating(state); + } + + return state; +} + +export function resetFutureStates_mutating(state: INodesState): INodesState { + state.stateHistory.future = []; + return state; +} + +export function removePreviousState_mutating(state: INodesState): INodesState { + state.stateHistory.past.pop(); + return state; +} + +export function undoActions_mutating(state: INodesState): INodesState { + const past = _get(state, `stateHistory.past`, []); + if (past.length) { + const previousState = state.stateHistory.past.pop(); + const presentState = _cloneDeep(state); + delete presentState.stateHistory; + state.stateHistory.future.unshift(presentState); + previousState.stateHistory = state.stateHistory; + return previousState; + } + + return state; +} + +export function redoActions_mutating(state: INodesState): INodesState { + const future = _get(state, 'stateHistory.future', []); + if (future.length) { + const nextState = state.stateHistory.future.shift(); + const presentState = _cloneDeep(state); + delete presentState.stateHistory; + state.stateHistory.past.push(presentState); + nextState.stateHistory = state.stateHistory; + return nextState; + } + + return state; +} + +export function updateNode_mutating(state: INodesState, nodeId: string, config): INodesState { + const matchNode = state.nodes.filter((node) => node.id === nodeId)[0]; + if (!matchNode) { + return state; + } + + addStateToHistory_mutating(state); + _assign(matchNode, config); + return state; +} + +export function removeNode_mutating(state: INodesState, nodeId: string): INodesState { + const match = state.nodes.filter((node) => node.id === nodeId)[0]; + if (!match) { + return state; + } + + addStateToHistory_mutating(state); + switch (GLOBALS.pluginConvert[match.type]) { + case 'source': + state.currentSourceCount--; + break; + case 'sink': + state.currentSinkCount--; + break; + default: + state.currentTransformCount--; + break; + } + state.nodes.splice(state.nodes.indexOf(match), 1); + state.connections = state.connections.filter( + (conn) => conn.from !== match.name && conn.to !== match.name + ); + state.activeNodeId = null; + delete state.adjacencyMap[nodeId]; + Object.keys(state.adjacencyMap).forEach((key) => { + state.adjacencyMap[key] = state.adjacencyMap[key].filter((n) => n !== nodeId); + }); + + return state; +} + +export function setNodes_mutating(state: INodesState, nodes): INodesState { + state.adjacencyMap = {}; + nodes.forEach((node) => { + if (!node.name) { + node.name = node.label + '_' + uuid.v4(); + } + if (!node.id) { + node.id = santizeStringForHTMLID(node.label) + '_' + uuid.v4(); + } + if (!node.type) { + node.type = node.plugin.type; + } + if (!state.adjacencyMap[node.id]) { + state.adjacencyMap[node.id] = []; + } + }); + state.nodes = _cloneDeep(nodes); + + return state; +} + +export function setActiveNodeId_mutating(state: INodesState, nodeId: string): INodesState { + addStateToHistory_mutating(state, false); + state.activeNodeId = nodeId; + return state; +} + +export function resetActiveNodeId_mutating(state: INodesState): INodesState { + state.activeNodeId = null; + state.nodes.forEach((node) => { + node.selected = false; + }); + + return state; +} + +export function addConnection_mutating(state: INodesState, connection): INodesState { + addStateToHistory_mutating(state); + state.connections.push(_cloneDeep(connection)); + + const { from, to } = connection; + const sourceNode = state.nodes.find((node) => node.name === from); + const targetNode = state.nodes.find((node) => node.name === to); + const sourceNodeId = sourceNode.id || sourceNode.name; + const targetNodeId = targetNode.id || targetNode.name; + + if (!state.adjacencyMap[sourceNodeId]) { + state.adjacencyMap[sourceNodeId] = [targetNodeId]; + } else { + state.adjacencyMap[sourceNodeId].push(targetNodeId); + } + + return state; +} + +export function updateConnections(state: INodesState, connections): INodesState { + addStateToHistory_mutating(state); + setConnections_mutating(state, connections); + return state; +} + +export function removeConnection_mutating(state: INodesState, connection): INodesState { + addStateToHistory_mutating(state); + const { from, to } = connection; + const index = state.connections.findIndex((conn) => conn.from === from && conn.to === to); + const sourceNode = state.nodes.find((node) => node.name === from); + const targetNode = state.nodes.find((node) => node.name === to); + const sourceNodeId = sourceNode.id || sourceNode.name; + const targetNodeId = targetNode.id || targetNode.name; + + state.adjacencyMap[sourceNodeId] = state.adjacencyMap[sourceNodeId].filter( + (target) => target !== targetNodeId + ); + state.connections.splice(index, 1); + + return state; +} + +export function setConnections_mutating(state: INodesState, connections): INodesState { + Object.keys(state.adjacencyMap).forEach((key) => { + state.adjacencyMap[key] = []; + }); + connections.forEach(({ from, to }) => { + const sourceNode = state.nodes.find((node) => node.name === from); + const targetNode = state.nodes.find((node) => node.name === to); + const sourceNodeId = sourceNode.id || sourceNode.name; + const targetNodeId = targetNode.id || targetNode.name; + + if (!state.adjacencyMap[sourceNodeId]) { + state.adjacencyMap[sourceNodeId] = [targetNodeId]; + } else { + state.adjacencyMap[sourceNodeId].push(targetNodeId); + } + }); + state.connections = _cloneDeep(connections); + + return state; +} + +export function setNodesAndConnections_mutating( + state: INodesState, + nodes, + connections +): INodesState { + setNodes_mutating(state, nodes); + state.connections = _cloneDeep(connections); + state.adjacencyMap = {}; + + nodes.forEach((node) => { + let nodeId = node; + if (typeof node === 'object' && typeof node.id === 'string') { + nodeId = node.id; + } + if (!nodeId) { + return; + } + state.adjacencyMap[nodeId] = []; + }); + + connections.forEach(({ from, to }) => { + const sourceNode = state.nodes.find((node) => node.name === from); + const targetNode = state.nodes.find((node) => node.name === to); + const sourceNodeId = sourceNode.id || sourceNode.name; + const targetNodeId = targetNode.id || targetNode.name; + state.adjacencyMap[sourceNodeId].push(targetNodeId); + }); + + return state; +} diff --git a/app/cdap/components/StudioV2/store/nodes/queries.ts b/app/cdap/components/StudioV2/store/nodes/queries.ts new file mode 100644 index 00000000000..74b9cde7b41 --- /dev/null +++ b/app/cdap/components/StudioV2/store/nodes/queries.ts @@ -0,0 +1,108 @@ +/* + * Copyright © 2025 Cask Data, Inc. + * + * Licensed 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 _get from 'lodash/get'; +import _cloneDeep from 'lodash/cloneDeep'; +import _isEqual from 'lodash/isEqual'; +import _assign from 'lodash/assign'; +import { INodesState } from './types'; +import { GLOBALS } from 'services/global-constants'; + +export function getSourceCount(state: INodesState) { + return state.currentSourceCount; +} + +export function getTransformCount(state: INodesState) { + return state.currentTransformCount; +} + +export function getSinkCount(state: INodesState) { + return state.currentSinkCount; +} + +export function getCanvasPanning(state: INodesState) { + return state.canvasPanning; +} + +export function getAdjacencyMap(state: INodesState) { + return state.adjacencyMap; +} + +export function getNodes(state: INodesState) { + return state.nodes; +} + +export function getNodesAsObjects(state: INodesState) { + return state.nodes.reduce((acc, node) => { + acc[node.name] = node; + return acc; + }, {}); +} + +export function getActiveNodeId(state: INodesState) { + return state.activeNodeId; +} + +export function getConnections(state: INodesState) { + return _cloneDeep(state.connections); +} + +export function getUndoStates(state: INodesState) { + return state.stateHistory.past; +} + +export function getRedoStates(state: INodesState) { + return state.stateHistory.future; +} + +export function getNodeInitialPosition(state: INodesState, nodeType: string) { + const canvasPanning = getCanvasPanning(state); + const sourcePosition = { + top: 150 - canvasPanning.top, + left: (10 / 100) * document.documentElement.clientWidth - canvasPanning.left, + }; + const transformPosition = { + top: 150 - canvasPanning.top, + left: (30 / 100) * document.documentElement.clientWidth - canvasPanning.left, + }; + const sinkPosition = { + top: 150 - canvasPanning.top, + left: (50 / 100) * document.documentElement.clientWidth - canvasPanning.left, + }; + + const offset = 35; + + switch (GLOBALS.pluginConvert[nodeType]) { + case 'source': + const sourceOffset = getSourceCount(state) * offset; + return { + top: sourcePosition.top + sourceOffset, + left: sourcePosition.left + sourceOffset, + }; + case 'sink': + const sinkOffset = getSinkCount(state) * offset; + return { + top: sinkPosition.top + sinkOffset, + left: sinkPosition.left + sinkOffset, + }; + default: + const transformOffset = getTransformCount(state) * offset; + return { + top: transformPosition.top + transformOffset, + left: transformPosition.left + transformOffset, + }; + } +} diff --git a/app/cdap/components/StudioV2/store/nodes/reducer.ts b/app/cdap/components/StudioV2/store/nodes/reducer.ts new file mode 100644 index 00000000000..b16820b7e8b --- /dev/null +++ b/app/cdap/components/StudioV2/store/nodes/reducer.ts @@ -0,0 +1,99 @@ +/* + * Copyright © 2025 Cask Data, Inc. + * + * Licensed 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 { produce } from 'immer'; +import _get from 'lodash/get'; +import _cloneDeep from 'lodash/cloneDeep'; +import _isEqual from 'lodash/isEqual'; +import _assign from 'lodash/assign'; + +import { + addConnection_mutating, + addNode_mutating, + removePreviousState_mutating, + resetActiveNodeId_mutating, + resetFutureStates_mutating, + setActiveNodeId_mutating, + undoActions_mutating, + updateNode_mutating, +} from './mutations'; +import { NodesActions } from './actions'; +import { INodesState } from './types'; + +export const nodesInitialState: INodesState = { + nodes: [], + connections: [], + activeNodeId: null, + currentSourceCount: 0, + currentTransformCount: 0, + currentSinkCount: 0, + canvasPanning: { + top: 0, + left: 0, + }, + stateHistory: { + past: [], + future: [], + }, + adjacencyMap: {}, +}; + +export const nodes = (state: INodesState = nodesInitialState, action?): INodesState => { + switch (action.type) { + case NodesActions.RESET: + return _cloneDeep(nodesInitialState); + + case NodesActions.SET_STATE: { + const patchCurrent = action?.meta?.patchCurrent; + if (!patchCurrent) { + return _cloneDeep(action.payload); + } + + return _assign(_cloneDeep(state), action.payload); + } + + case NodesActions.UNDO_ACTIONS: + return produce(state, undoActions_mutating); + + case NodesActions.RESET_ACTIVE_NODE: + return produce(state, resetActiveNodeId_mutating); + + case NodesActions.SET_ACTIVE_NODE: + return produce(state, (draft) => + setActiveNodeId_mutating(draft, action.payload) + ); + + case NodesActions.ADD_NODE: + return produce(state, (draft) => addNode_mutating(draft, action.payload)); + + case NodesActions.UPDATE_NODE: + return produce(state, (draft) => + updateNode_mutating(draft, action.payload.nodeId, action.payload.nodeConfig) + ); + + case NodesActions.ADD_CONNECTION: + return produce(state, (draft) => addConnection_mutating(draft, action.payload)); + + case NodesActions.REMOVE_PREVIOUS_STATE: + return produce(state, removePreviousState_mutating); + + case NodesActions.RESET_FUTURE_STATES: + return produce(state, resetFutureStates_mutating); + + default: + return state; + } +}; diff --git a/app/cdap/components/StudioV2/store/nodes/types.ts b/app/cdap/components/StudioV2/store/nodes/types.ts new file mode 100644 index 00000000000..96ff0c865ac --- /dev/null +++ b/app/cdap/components/StudioV2/store/nodes/types.ts @@ -0,0 +1,37 @@ +/* + * Copyright © 2025 Cask Data, Inc. + * + * Licensed 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 { IDagConnection, IPluginNode } from "components/StudioV2/types"; + +export interface INodesState { + nodes: Array; + connections: Array; + activeNodeId?: string | null; + currentSourceCount: number; + currentTransformCount: number; + currentSinkCount: number; + canvasPanning: { + top: number; + left: number; + }; + stateHistory?: { + past: INodesState[]; + future: INodesState[]; + }; + adjacencyMap: { + [key: string]: Array; + }; +}; \ No newline at end of file diff --git a/app/cdap/components/StudioV2/store/plugins/actions.ts b/app/cdap/components/StudioV2/store/plugins/actions.ts new file mode 100644 index 00000000000..cc55173bd4b --- /dev/null +++ b/app/cdap/components/StudioV2/store/plugins/actions.ts @@ -0,0 +1,77 @@ +/* + * Copyright © 2025 Cask Data, Inc. + * + * Licensed 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 { GLOBALS } from 'services/global-constants'; +import MySettingsService from 'components/StudioV2/utils/settings'; +import { IPlugin } from 'components/StudioV2/types'; +import StudioV2Store from '..'; + +const PREFIX = 'PLUGINS_ACTIONS'; + +export const PluginsActions = { + FETCH_PLUGINS: `${PREFIX}/FETCH_PLUGINS`, + FETCH_ALL_PLUGINS: `${PREFIX}/FETCH_ALL_PLUGINS`, + FETCH_PLUGIN_TEMPLATE: `${PREFIX}/FETCH_PLUGIN_TEMPLATE`, + FETCH_PLUGINS_DEFAULT_VERSIONS: `${PREFIX}/FETCH_PLUGINS_DEFAULT_VERSIONS`, + UPDATE_PLUGINS_DEFAULT_VERSIONS: `${PREFIX}/UPDATE_PLUGINS_DEFAULT_VERSIONS`, + CHECK_AND_UPDATE_PLUGIN_DEFAULT_VERSION: `${PREFIX}/CHECK_AND_UPDATE_PLUGIN_DEFAULT_VERSION`, + FETCH_EXTENSIONS: `${PREFIX}/FETCH_EXTENSIONS`, + RESET: `${PREFIX}/RESET`, +}; + +export const keepUiSupportedExtensions = (pipelineType: string) => (extension: string) => { + const extensionMap = GLOBALS.pluginTypes[pipelineType]; + return Object.keys(extensionMap).filter((ext) => extensionMap[ext] === extension).length; +}; + +export async function fetchPluginsDefaultVersions() { + try { + const pluginDefalutVersion = await MySettingsService.getInstance().get( + 'plugin-default-version' + ); + if (!pluginDefalutVersion) { + return; + } + + StudioV2Store.dispatch({ + type: PluginsActions.FETCH_PLUGINS_DEFAULT_VERSIONS, + payload: pluginDefalutVersion, + }); + } catch (err) { + return; + } +} + +export async function updatePluginDefaultVersion(plugin: IPlugin) { + try { + let pluginDefalutVersion = + (await MySettingsService.getInstance().get('plugin-default-version')) || {}; + const key = `${plugin.name}-${plugin.type}-${plugin.artifact.name}`; + pluginDefalutVersion[key] = plugin.artifact; + await MySettingsService.getInstance().set('plugin-default-version', pluginDefalutVersion); + pluginDefalutVersion = await MySettingsService.getInstance().get('plugin-default-version'); + + if (!pluginDefalutVersion) { + return; + } + StudioV2Store.dispatch({ + type: PluginsActions.FETCH_PLUGINS_DEFAULT_VERSIONS, + payload: pluginDefalutVersion, + }); + } catch (err) { + return; + } +} diff --git a/app/cdap/components/StudioV2/store/plugins/mutations.ts b/app/cdap/components/StudioV2/store/plugins/mutations.ts new file mode 100644 index 00000000000..162defa8738 --- /dev/null +++ b/app/cdap/components/StudioV2/store/plugins/mutations.ts @@ -0,0 +1,87 @@ +/* + * Copyright © 2025 Cask Data, Inc. + * + * Licensed 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 _cloneDeep from 'lodash/cloneDeep'; +import _isEqual from 'lodash/isEqual'; +import _get from 'lodash/get'; +import { getDefaultVersionForPlugin } from "components/StudioV2/utils/pluginUtils"; +import { IPluginTemplatesMap, IPluginToVersionMap, IPluginTypesMap, IPluginsState } from './types'; +import { getTemplatesWithAddedInfo } from 'components/StudioV2/utils/pluginUtils'; + +export function fetchPluginsDefaultVersions_mutating(state: IPluginsState, pluginToVersionMap?: IPluginToVersionMap): IPluginsState { + const defaultPluginVersionsMap = pluginToVersionMap || {}; + const pluginTypesCopy: IPluginTypesMap = {}; + + if (Object.keys(defaultPluginVersionsMap).length) { + const pluginTypes = Object.keys(state.pluginTypes); + // If this is fetched after the all the plugins have been fetched from the backend then we will update them. + pluginTypes.forEach((pluginType) => { + const _plugins = state.pluginTypes[pluginType]; + pluginTypesCopy[pluginType] = _plugins.map((plugin) => { + plugin.defaultArtifact = getDefaultVersionForPlugin(plugin, defaultPluginVersionsMap); + return plugin; + }); + }); + + state.pluginTypes = pluginTypesCopy; + state.pluginToVersionMap = defaultPluginVersionsMap; + } + + return state; +} + +export function checkAndUpdatePluginDefaultVersion_mutating(state: IPluginsState): IPluginsState { + const pluginTypesKeys = Object.keys(state.pluginTypes); + if (!pluginTypesKeys.length) { + return state; + } + + pluginTypesKeys.forEach((pluginType) => { + state.pluginTypes[pluginType].forEach((plugin) => { + if (plugin.pluginTemplate) { + return; + } + const key = `${plugin.name}-${plugin.type}-${plugin.artifact.name}`; + const isArtifactExistsInBackend = plugin.allArtifacts.filter((plug) => + _isEqual(plug.artifact, state.pluginToVersionMap[key]) + ); + if (!isArtifactExistsInBackend.length) { + delete state.pluginToVersionMap[key]; + } + }); + }); + + return state; +} + +export function fetchPluginTemplate_mutating(state: IPluginsState, pipelineType: string, namespace: string, templates: IPluginTemplatesMap): IPluginsState { + const templatesList = _get(templates, `${namespace}.${pipelineType}`); + if (!templatesList) { + return state; + } + + Object.entries(templatesList).forEach(([key, plugins]) => { + const _templates = Object.values(plugins); + const _pluginWithoutTemplates = (state.pluginTypes[key] || []).filter( + (plug) => !plug.pluginTemplate + ); + state.pluginTypes[key] = getTemplatesWithAddedInfo(_templates, key).concat( + _pluginWithoutTemplates + ); + }); + + return state; +} \ No newline at end of file diff --git a/app/cdap/components/StudioV2/store/plugins/reducer.ts b/app/cdap/components/StudioV2/store/plugins/reducer.ts new file mode 100644 index 00000000000..434c5241690 --- /dev/null +++ b/app/cdap/components/StudioV2/store/plugins/reducer.ts @@ -0,0 +1,63 @@ +/* + * Copyright © 2025 Cask Data, Inc. + * + * Licensed 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 {produce} from 'immer'; +import _get from 'lodash/get'; +import _cloneDeep from 'lodash/cloneDeep'; +import _isEqual from 'lodash/isEqual'; + +import { PluginsActions } from './actions'; +import { + checkAndUpdatePluginDefaultVersion_mutating, + fetchPluginTemplate_mutating, + fetchPluginsDefaultVersions_mutating +} from './mutations'; +import { IPluginsState } from './types'; + +export const pluginsInitialState: IPluginsState = { + pluginTypes: {}, + pluginToVersionMap: {}, + extensions: [], +}; + +export const plugins = (state: IPluginsState = pluginsInitialState, action?): IPluginsState => { + switch (action.type) { + case PluginsActions.FETCH_PLUGINS_DEFAULT_VERSIONS: + return produce(state, (draft) => + fetchPluginsDefaultVersions_mutating(draft, action.payload)); + + case PluginsActions.CHECK_AND_UPDATE_PLUGIN_DEFAULT_VERSION: + return produce(state, checkAndUpdatePluginDefaultVersion_mutating); + + case PluginsActions.FETCH_PLUGIN_TEMPLATE: + return produce(state, (draft) => fetchPluginTemplate_mutating( + draft, + action.payload?.pipelineType, + action.payload?.namespace, + action.payload?.templates + )); + + case PluginsActions.FETCH_ALL_PLUGINS: + return { + ...state, + pluginTypes: _cloneDeep(action.payload.pluginTypes), + extensions: _cloneDeep(action.payload.extensions), + }; + + default: + return state; + } +}; diff --git a/app/cdap/components/StudioV2/store/plugins/types.ts b/app/cdap/components/StudioV2/store/plugins/types.ts new file mode 100644 index 00000000000..3b2979b26c9 --- /dev/null +++ b/app/cdap/components/StudioV2/store/plugins/types.ts @@ -0,0 +1,41 @@ +/* + * Copyright © 2025 Cask Data, Inc. + * + * Licensed 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 { IArtifactSummary, IPlugin, IPluginTemplate } from "components/StudioV2/types"; + +export interface IPluginToVersionMap { + [key: string]: IArtifactSummary; +}; + +export interface IPluginTypesMap { + [key: string]: Array; +}; + +export interface IPluginsState { + pluginTypes: IPluginTypesMap; + pluginToVersionMap: IPluginToVersionMap; + extensions: Array; +} + +export interface IPluginTemplatesMap { + [namespace: string]: { + [pipelineType: string]: { + [extenstionType: string]: { + [templateName: string]: IPluginTemplate; + }; + }; + }; +}; \ No newline at end of file diff --git a/app/cdap/components/StudioV2/types.ts b/app/cdap/components/StudioV2/types.ts new file mode 100644 index 00000000000..bffeee0a516 --- /dev/null +++ b/app/cdap/components/StudioV2/types.ts @@ -0,0 +1,105 @@ +/* + * Copyright © 2025 Cask Data, Inc. + * + * Licensed 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 { IConfigurationGroup, IPropertyFilter, IWidgetProperty } from "components/shared/ConfigurationGroup/types"; + +export type ArtifactScope = 'USER' | 'SYSTEM'; + +export interface IArtifactSummary { + name: string; + version: string; + scope: ArtifactScope; +} + +export interface ILabeledArtifactSummary extends IArtifactSummary { + label: string; +} + +export interface IPlugin { + name?: string; + type?: string; + label?: string; + displayName?: string; + description?: string; + className?: string; + pluginTemplate?: string; + + icon?: string; + showCustomIcon?: boolean; + customIconSrc?: string; + + artifact?: IArtifactSummary; + defaultArtifact?: IArtifactSummary; + allArtifacts?: Array; +} + +export interface IPluginTemplate { + artifact?: IArtifactSummary; + description?: string; + lock?: { + [key: string]: any; + }; + nodeClass?: string; + outputSchema?: string; + pluginName?: string; + pluginTemplate?: string; + pluginType?: string; + templateType?: string; + properties?: { + [key: string]: any; + }; +}; + +export interface IDagConnection { + from: string; + to: string; +}; + +export interface IPluginWithProperties extends IPlugin { + properties?: any; +}; + +export interface IPluginNode { + id: string; + name?: string; + description?: string; + type?: string; + + configGroups?: Array; + errorCount?: number; + filters?: Array; + icon?: string; + + implicitSchema?: any; // TODO: add proper type + outputSchema?: any; + + outputSchemaProperty?: string; + outputs?: Array; + plugin?: IPluginWithProperties; + + isPluginAvailable?: boolean; + selected?: boolean; + visibilityMap?: { + [key: string]: boolean; + }; + warning?: boolean; + + _backendProperties?: any; // TODO: add proper types + _uiPosition?: { + top?: string; + left?: string; + }; +}; \ No newline at end of file diff --git a/app/cdap/components/StudioV2/utils/artifactUtils.ts b/app/cdap/components/StudioV2/utils/artifactUtils.ts new file mode 100644 index 00000000000..cdf05132321 --- /dev/null +++ b/app/cdap/components/StudioV2/utils/artifactUtils.ts @@ -0,0 +1,21 @@ +/* + * Copyright © 2025 Cask Data, Inc. + * + * Licensed 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 { GLOBALS } from 'services/global-constants'; + +export function getArtifactDisaplayName(artifactName: string): string { + return GLOBALS.artifactConvert[artifactName] || artifactName; +} \ No newline at end of file diff --git a/app/cdap/components/StudioV2/utils/defer.ts b/app/cdap/components/StudioV2/utils/defer.ts new file mode 100644 index 00000000000..39cb8d1543d --- /dev/null +++ b/app/cdap/components/StudioV2/utils/defer.ts @@ -0,0 +1,28 @@ +/* + * Copyright © 2025 Cask Data, Inc. + * + * Licensed 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. + */ + +export default class Defer { + public promise: Promise; + public resolve: (value: T | PromiseLike) => void; + public reject: (error?: string | Error) => void; + + constructor() { + this.promise = new Promise((resolve, reject) => { + this.resolve = resolve; + this.reject = reject; + }); + } +} diff --git a/app/cdap/components/StudioV2/utils/pluginUtils.ts b/app/cdap/components/StudioV2/utils/pluginUtils.ts new file mode 100644 index 00000000000..eee5ba7be08 --- /dev/null +++ b/app/cdap/components/StudioV2/utils/pluginUtils.ts @@ -0,0 +1,195 @@ +/* + * Copyright © 2025 Cask Data, Inc. + * + * Licensed 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 _isEqual from 'lodash/isEqual'; +import _get from 'lodash/get'; +import _cloneDeep from 'lodash/cloneDeep'; +import { findHighestVersion } from 'services/VersionRange/VersionUtilities'; +import { IArtifactSummary, IPlugin } from '../types'; + +export function getPluginIcon(pluginName: string): string { + const iconMap = { + script: 'icon-script', + scriptfilter: 'icon-scriptfilter', + twitter: 'icon-twitter', + cube: 'icon-cube', + data: 'fa-database', + database: 'icon-database', + table: 'icon-table', + kafka: 'icon-kafka', + jms: 'icon-jms', + projection: 'icon-projection', + amazonsqs: 'icon-amazonsqs', + datagenerator: 'icon-datagenerator', + validator: 'icon-validator', + corevalidator: 'corevalidator', + logparser: 'icon-logparser', + file: 'icon-file', + kvtable: 'icon-kvtable', + s3: 'icon-s3', + s3avro: 'icon-s3avro', + s3parquet: 'icon-s3parquet', + snapshotavro: 'icon-snapshotavro', + snapshotparquet: 'icon-snapshotparquet', + tpfsavro: 'icon-tpfsavro', + tpfsparquet: 'icon-tpfsparquet', + sink: 'icon-sink', + hive: 'icon-hive', + structuredrecordtogenericrecord: 'icon-structuredrecord', + cassandra: 'icon-cassandra', + teradata: 'icon-teradata', + elasticsearch: 'icon-elasticsearch', + hbase: 'icon-hbase', + mongodb: 'icon-mongodb', + pythonevaluator: 'icon-pythonevaluator', + csvformatter: 'icon-csvformatter', + csvparser: 'icon-csvparser', + clonerecord: 'icon-clonerecord', + compressor: 'icon-compressor', + decompressor: 'icon-decompressor', + encoder: 'icon-encoder', + decoder: 'icon-decoder', + jsonformatter: 'icon-jsonformatter', + jsonparser: 'icon-jsonparser', + hdfs: 'icon-hdfs', + hasher: 'icon-hasher', + javascript: 'icon-javascript', + deduper: 'icon-deduper', + distinct: 'icon-distinct', + naivebayestrainer: 'icon-naivebayestrainer', + groupbyaggregate: 'icon-groupbyaggregate', + naivebayesclassifier: 'icon-naivebayesclassifier', + azureblobstore: 'icon-azureblobstore', + xmlreader: 'icon-XMLreader', + xmlparser: 'icon-XMLparser', + ftp: 'icon-FTP', + joiner: 'icon-joiner', + deduplicate: 'icon-deduplicator', + valuemapper: 'icon-valuemapper', + rowdenormalizer: 'icon-rowdenormalizer', + ssh: 'icon-ssh', + sshaction: 'icon-sshaction', + copybookreader: 'icon-COBOLcopybookreader', + excel: 'icon-excelinputsource', + encryptor: 'icon-Encryptor', + decryptor: 'icon-Decryptor', + hdfsfilemoveaction: 'icon-filemoveaction', + hdfsfilecopyaction: 'icon-filecopyaction', + sqlaction: 'icon-SQLaction', + impalahiveaction: 'icon-impalahiveaction', + email: 'icon-emailaction', + kinesissink: 'icon-Amazon-Kinesis', + bigquerysource: 'icon-Big-Query', + tpfsorc: 'icon-ORC', + groupby: 'icon-groupby', + sparkmachinelearning: 'icon-sparkmachinelearning', + solrsearch: 'icon-solr', + sparkstreaming: 'icon-sparkstreaming', + rename: 'icon-rename', + archive: 'icon-archive', + wrangler: 'icon-DataPreparation', + normalize: 'icon-normalize', + xmlmultiparser: 'icon-XMLmultiparser', + xmltojson: 'icon-XMLtoJSON', + decisiontreepredictor: 'icon-decisiontreeanalytics', + decisiontreetrainer: 'icon-DesicionTree', + hashingtffeaturegenerator: 'icon-HashingTF', + ngramtransform: 'icon-NGram', + tokenizer: 'icon-tokenizeranalytics', + skipgramfeaturegenerator: 'icon-skipgram', + skipgramtrainer: 'icon-skipgramtrainer', + logisticregressionclassifier: 'icon-logisticregressionanalytics', + logisticregressiontrainer: 'icon-LogisticRegressionclassifier', + hdfsdelete: 'icon-hdfsdelete', + hdfsmove: 'icon-hdfsmove', + windowssharecopy: 'icon-windowssharecopy', + httppoller: 'icon-httppoller', + window: 'icon-window', + run: 'icon-Run', + oracleexport: 'icon-OracleDump', + snapshottext: 'icon-SnapshotTextSink', + errorcollector: 'fa-exclamation-triangle', + mainframereader: 'icon-MainframeReader', + fastfilter: 'icon-fastfilter', + trash: 'icon-TrashSink', + staterestore: 'icon-Staterestore', + topn: 'icon-TopN', + wordcount: 'icon-WordCount', + datetransform: 'icon-DateTransform', + sftpcopy: 'icon-FTPcopy', + sftpdelete: 'icon-FTPdelete', + validatingxmlconverter: 'icon-XMLvalidator', + wholefilereader: 'icon-Filereader', + xmlschemaaction: 'icon-XMLschemagenerator', + s3toredshift: 'icon-S3toredshift', + redshifttos3: 'icon-redshifttoS3', + verticabulkexportaction: 'icon-Verticabulkexport', + verticabulkimportaction: 'icon-Verticabulkload', + loadtosnowflake: 'icon-snowflake', + kudu: 'icon-apachekudu', + orientdb: 'icon-OrientDB', + recordsplitter: 'icon-recordsplitter', + scalasparkprogram: 'icon-spark', + scalasparkcompute: 'icon-spark', + cdcdatabase: 'icon-database', + cdchbase: 'icon-hbase', + cdckudu: 'icon-apachekudu', + changetrackingsqlserver: 'icon-database', + conditional: 'fa-question-circle-o', + }; + + const actualPluginName = pluginName ? pluginName.toLowerCase() : ''; + const icon = iconMap[actualPluginName] ? iconMap[actualPluginName] : 'fa-plug'; + return icon; +} + +export function getDefaultVersionForPlugin( + plugin: IPlugin = {}, + defaultVersionMap: { [key: string]: IArtifactSummary } = {}, +) { + if (Object.keys(plugin).length === 0) { + return {}; + } + + const defaultVersionsList = Object.keys(defaultVersionMap); + const key = `${plugin.name}-${plugin.type}-${plugin.artifact.name}`; + const isDefaultVersionExists = defaultVersionsList.includes(key); + const isArtifactExistsInBackend = (plugin.allArtifacts || []).filter((plug) => + _isEqual(plug.artifact, defaultVersionMap[key]) + ); + + if (!isDefaultVersionExists || isArtifactExistsInBackend.length === 0) { + const highestVersion = findHighestVersion( + plugin.allArtifacts.map((plugin) => plugin.artifact.version), + true + ); + const latestPluginVersion = plugin.allArtifacts.find( + (plugin) => plugin.artifact.version === highestVersion + ); + return _get(latestPluginVersion, 'artifact'); + } + + return _cloneDeep(defaultVersionMap[key]); +} +export const getTemplatesWithAddedInfo = (templates = [], extension = '') => templates.map((template) => ({ + ...template, + nodeClass: 'plugin-templates', + name: template.pluginTemplate, + pluginName: template.pluginName, + type: extension, + icon: getPluginIcon(template.pluginName), + allArtifacts: [template.artifact], +})); diff --git a/app/cdap/components/StudioV2/utils/settings.ts b/app/cdap/components/StudioV2/utils/settings.ts new file mode 100644 index 00000000000..02236e1f4aa --- /dev/null +++ b/app/cdap/components/StudioV2/utils/settings.ts @@ -0,0 +1,93 @@ +/* + * Copyright © 2025 Cask Data, Inc. + * + * Licensed 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 { SettingsApi } from 'api/settings'; +import _set from 'lodash/set'; +import _get from 'lodash/get'; +import { getCurrentNamespace } from 'services/NamespaceStore'; +import Defer from './defer'; + +export default class MySettingsService { + public static instance: MySettingsService = null; + + public static getInstance() { + if (!MySettingsService.instance) { + MySettingsService.instance = new MySettingsService(); + } + + return MySettingsService.instance; + } + + public data: any; + public pending: Promise; + + constructor() { + if (MySettingsService.instance) { + return MySettingsService.instance; + } + this.data = {}; + this.pending = null; + MySettingsService.instance = this; + } + + public set = (key: string, value) => { + const defefred = new Defer(); + this.data = _set(this.data, key, value); + SettingsApi.updateUserSettings({ namespace: getCurrentNamespace() }, this.data).subscribe( + (res) => { + defefred.resolve(res); + }, + (err) => { + defefred.reject(err); + } + ); + + return defefred.promise; + }; + + public get = async (key: string, force?: boolean) => { + const val = _get(this.data, key); + if (!force && val) { + return val; + } + + if (this.pending) { + await this.pending; + return _get(this.data, key); + } + + const deferred = new Defer(); + this.pending = deferred.promise; + SettingsApi.getUserSettings({ namespace: getCurrentNamespace() }).subscribe( + (res) => { + this.data = res.property; + deferred.resolve(_get(this.data, key)); + }, + (err) => { + deferred.reject(err); + } + ); + + try { + const val = await this.pending; + this.pending = null; + return val; + } catch (err) { + this.pending = null; + return undefined; + } + }; +} diff --git a/package.json b/package.json index 5f704835af6..bf0f2755509 100644 --- a/package.json +++ b/package.json @@ -238,6 +238,7 @@ "history": "4.10.1", "i18n-react": "0.7.0", "ifvisible.js": "1.0.6", + "immer": "^10.1.1", "immutability-helper": "3.0.1", "immutable": "3.8.2", "intersection-observer": "0.7.0", diff --git a/yarn.lock b/yarn.lock index 22b89ccb901..30a7142abc7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12634,6 +12634,11 @@ immer@8.0.1: resolved "https://registry.yarnpkg.com/immer/-/immer-8.0.1.tgz#9c73db683e2b3975c424fb0572af5889877ae656" integrity sha512-aqXhGP7//Gui2+UrEtvxZxSquQVXTpZ7KDxfCcKAF3Vysvw0CViVaW9RZ1j1xlIYqaaaipBoqdqeibkc18PNvA== +immer@^10.1.1: + version "10.1.1" + resolved "https://registry.yarnpkg.com/immer/-/immer-10.1.1.tgz#206f344ea372d8ea176891545ee53ccc062db7bc" + integrity sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw== + immer@^9.0.7: version "9.0.7" resolved "https://registry.yarnpkg.com/immer/-/immer-9.0.7.tgz#b6156bd7db55db7abc73fd2fdadf4e579a701075"