diff --git a/app/cdap/api/app.js b/app/cdap/api/app.js
index 3cf32e81b12..4f9064c6ca2 100644
--- a/app/cdap/api/app.js
+++ b/app/cdap/api/app.js
@@ -29,4 +29,6 @@ export const MyAppApi = {
batchStatus: apiCreator(dataSrc, 'POST', 'POLL', '/namespaces/:namespace/status'),
batchAppDetail: apiCreator(dataSrc, 'POST', 'REQUEST', '/namespaces/:namespace/appdetail'),
delete: apiCreator(dataSrc, 'DELETE', 'REQUEST', appPath),
+ summarize: apiCreator(dataSrc, 'POST', 'REQUEST', `${appPath}/summarize`),
+ summarizeDraft: apiCreator(dataSrc, 'POST', 'REQUEST', `${basepath}/summarize`),
};
diff --git a/app/cdap/components/PipelineDetails/PipelineDetailsTopPanel/PipelineDetailsButtons/PipelineGenaiSummaryButton.tsx b/app/cdap/components/PipelineDetails/PipelineDetailsTopPanel/PipelineDetailsButtons/PipelineGenaiSummaryButton.tsx
new file mode 100644
index 00000000000..46b41f523a4
--- /dev/null
+++ b/app/cdap/components/PipelineDetails/PipelineDetailsTopPanel/PipelineDetailsButtons/PipelineGenaiSummaryButton.tsx
@@ -0,0 +1,92 @@
+/*
+ * Copyright © 2024 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 React, { useEffect, useRef, useState } from 'react';
+import classnames from 'classnames';
+import GraphicEqIcon from '@material-ui/icons/GraphicEq';
+import T from 'i18n-react';
+import { PrimaryTextLowercaseButton } from 'components/shared/Buttons/PrimaryTextLowercaseButton';
+import styled from 'styled-components';
+import { PipelineGenaiSummaryModal } from '../PipelineGenaiSummaryModal';
+import { MyAppApi } from 'api/app';
+import { getCurrentNamespace } from 'services/NamespaceStore';
+
+const PREFIX = 'features.PipelineDetails.TopPanel';
+
+interface IPipelineGenaiSummaryButtonProps {
+ pipelineName: string;
+}
+
+const StyledSummarizeIcon = styled(GraphicEqIcon)`
+ margin-bottom: -4px;
+`;
+
+const GenaiButton = styled.div`
+ padding: 0;
+`;
+
+export const PipelineGenaiSummaryButton = ({ pipelineName }: IPipelineGenaiSummaryButtonProps) => {
+ const [isOpen, setIsOpen] = useState(false);
+ const [summary, setSummary] = useState('');
+ const buttonRef = useRef(null);
+
+ useEffect(() => {
+ MyAppApi.summarize({
+ namespace: getCurrentNamespace(),
+ appId: pipelineName,
+ }).subscribe((res) => {
+ setSummary(res);
+ });
+ }, []);
+
+ const toggleButton = () => {
+ setIsOpen(!isOpen);
+ };
+
+ const renderPipelineGenaiSummaryBtn = () => {
+ return (
+
+
+
+
+
{T.translate(`${PREFIX}.genaiSummarize`)}
+
+
+
+ );
+ };
+
+ return (
+
+ {renderPipelineGenaiSummaryBtn()}
+
setIsOpen(false)}
+ anchorEl={buttonRef.current}
+ pipelineName={pipelineName}
+ summary={summary}
+ />
+
+ );
+};
diff --git a/app/cdap/components/PipelineDetails/PipelineDetailsTopPanel/PipelineDetailsButtons/index.js b/app/cdap/components/PipelineDetails/PipelineDetailsTopPanel/PipelineDetailsButtons/index.js
index 3fc1c040b56..e89896f99b2 100644
--- a/app/cdap/components/PipelineDetails/PipelineDetailsTopPanel/PipelineDetailsButtons/index.js
+++ b/app/cdap/components/PipelineDetails/PipelineDetailsTopPanel/PipelineDetailsButtons/index.js
@@ -29,7 +29,11 @@ import ApolloClient from 'apollo-boost';
import { InMemoryCache, IntrospectionFragmentMatcher } from 'apollo-cache-inmemory';
import introspectionQueryResultData from '../../../../../../graphql/fragments/fragmentTypes.json';
import SessionTokenStore from 'services/SessionTokenStore';
-import { useFeatureFlagDefaultFalse } from 'services/react/customHooks/useFeatureFlag';
+import {
+ useFeatureFlagDefaultFalse,
+ useFeatureFlagDefaultTrue,
+} from 'services/react/customHooks/useFeatureFlag';
+import { PipelineGenaiSummaryButton } from './PipelineGenaiSummaryButton';
const fragmentMatcher = new IntrospectionFragmentMatcher({
introspectionQueryResultData,
@@ -106,6 +110,8 @@ export default function PipelineDetailsButtons({
const lifecycleManagementEditEnabled = useFeatureFlagDefaultFalse(
'lifecycle.management.edit.enabled'
);
+ // TODO: replace with useFeatureFlagDefaultFalse when productionising
+ const genaiPipelineSummaryEnabled = useFeatureFlagDefaultTrue('genai.pipeline.summary.enabled');
return (
@@ -142,6 +148,9 @@ export default function PipelineDetailsButtons({
/>
{lifecycleManagementEditEnabled && }
+ {genaiPipelineSummaryEnabled && (
+
+ )}
diff --git a/app/cdap/components/PipelineDetails/PipelineDetailsTopPanel/PipelineGenaiSummaryModal.tsx b/app/cdap/components/PipelineDetails/PipelineDetailsTopPanel/PipelineGenaiSummaryModal.tsx
new file mode 100644
index 00000000000..4e3d3d00c9a
--- /dev/null
+++ b/app/cdap/components/PipelineDetails/PipelineDetailsTopPanel/PipelineGenaiSummaryModal.tsx
@@ -0,0 +1,85 @@
+/*
+ * Copyright © 2024 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 React from 'react';
+import styled from 'styled-components';
+import T from 'i18n-react';
+import PipelineModeless from 'components/PipelineDetails/PipelineModeless';
+import MarkdownWithStyles from 'components/shared/Markdown';
+import LoadingSVG from 'components/shared/LoadingSVG';
+
+const PREFIX = 'features.PipelineDetails.TopPanel';
+
+interface IPipelineGenaiSummaryModalProps {
+ pipelineName?: string;
+ summary: string;
+ anchorEl: any;
+ onClose: () => void;
+ isOpen: boolean;
+}
+
+const SummaryContainer = styled.div`
+ height: 100%;
+ width: 100%;
+ max-width: 880px;
+ max-height: min(60vh, 800px);
+ padding: 0 20px;
+ overflow: auto;
+`;
+
+const PopperClass = styled.div`
+ max-width: 880px;
+`;
+
+const LoadingContainer = styled.div`
+ width: 100%;
+ height: 100px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+`;
+
+export const PipelineGenaiSummaryModal = ({
+ pipelineName,
+ summary,
+ anchorEl,
+ onClose,
+ isOpen,
+}: IPipelineGenaiSummaryModalProps) => {
+ return (
+
+
+ {summary ? (
+
+ ) : (
+
+
+
+ )}
+
+
+ );
+};
diff --git a/app/cdap/components/hydrator/components/TopPanel/ActionButtons/index.tsx b/app/cdap/components/hydrator/components/TopPanel/ActionButtons/index.tsx
index 5b51761eafa..06120ca9504 100644
--- a/app/cdap/components/hydrator/components/TopPanel/ActionButtons/index.tsx
+++ b/app/cdap/components/hydrator/components/TopPanel/ActionButtons/index.tsx
@@ -15,7 +15,8 @@
*/
import IconSVG from 'components/shared/IconSVG';
-import React, { useEffect, useState } from 'react';
+import React, { useEffect, useRef, useState } from 'react';
+import GraphicEqIcon from '@material-ui/icons/GraphicEq';
import { objectQuery } from 'services/helpers';
import PipelineConfigurationsStore, {
ACTIONS as PipelineConfigurationsActions,
@@ -24,6 +25,7 @@ import { PipelineConfigure } from '../PipelineConfigure';
import {
ActionButtonsContainer,
BorderRightButton,
+ BorderRightOnlyButton,
ButtonLabel,
CommonButton,
CustomTooltip,
@@ -36,6 +38,8 @@ import {
PreviewModeButton,
RunTimeSpan,
} from './styles';
+import { useFeatureFlagDefaultTrue } from 'services/react/customHooks/useFeatureFlag';
+import { PipelineGenaiSummaryModal } from 'components/PipelineDetails/PipelineDetailsTopPanel/PipelineGenaiSummaryModal';
interface IDuration {
minutes: string;
@@ -53,6 +57,7 @@ interface IActionButtonsProps {
toggleScheduler: () => void;
hasNodes: boolean;
onSaveDraft: () => void;
+ onPipelineSummarize: (summarySetter: (string) => void) => void;
onPublish: () => void;
onImport: () => void;
onFileSelect: (files: FileList) => void;
@@ -90,6 +95,7 @@ export const ActionButtons = ({
toggleScheduler,
hasNodes,
onSaveDraft,
+ onPipelineSummarize,
onPublish,
onImport,
onFileSelect,
@@ -116,6 +122,20 @@ export const ActionButtons = ({
getStoreConfig,
}: IActionButtonsProps) => {
const [selectedFile, setSelectedFile] = useState(null);
+ const [genaiSummary, setGenaiSummary] = useState('');
+ const [isSummaryModalOpen, setSummaryModalOpen] = useState(false);
+ const pipelineGenaiSummaryButtonRef = useRef(null);
+ // TODO: replace with useFeatureFlagDefaultFalse when productionising
+ const genaiPipelineSummaryEnabled = useFeatureFlagDefaultTrue('genai.pipeline.summary.enabled');
+
+ const onGenaiSummary = () => {
+ if (!genaiPipelineSummaryEnabled) {
+ return;
+ }
+ setGenaiSummary('');
+ onPipelineSummarize(setGenaiSummary);
+ setSummaryModalOpen(true);
+ };
const handleFile = (event) => {
if (!objectQuery(event, 'target', 'files', 0)) {
@@ -365,6 +385,27 @@ export const ActionButtons = ({
+ {genaiPipelineSummaryEnabled && (
+
+
+
+
+
+ Summarize
+
+
+
+
+ )}
>
)}
@@ -384,6 +425,14 @@ export const ActionButtons = ({
validatePluginProperties={validatePluginProperties}
getRuntimeArgs={getRuntimeArgs}
>
+ {hasNodes && pipelineGenaiSummaryButtonRef.current && (
+ setSummaryModalOpen(false)}
+ summary={genaiSummary}
+ />
+ )}
>
);
};
diff --git a/app/cdap/components/hydrator/components/TopPanel/ActionButtons/styles.ts b/app/cdap/components/hydrator/components/TopPanel/ActionButtons/styles.ts
index 474e174bbe3..27e2d2b651d 100644
--- a/app/cdap/components/hydrator/components/TopPanel/ActionButtons/styles.ts
+++ b/app/cdap/components/hydrator/components/TopPanel/ActionButtons/styles.ts
@@ -51,6 +51,11 @@ export const BorderRightButton = styled(BaseButton)`
border-right: 1px solid ${greyBorder};
`;
+export const BorderRightOnlyButton = styled(BorderRightButton)`
+ border-left: none;
+ position: relative;
+`;
+
export const PreviewModeButton = styled(BaseButton)`
background-color: ${previewActive};
`;
diff --git a/app/cdap/components/hydrator/components/TopPanel/NameAndDescription/styles.ts b/app/cdap/components/hydrator/components/TopPanel/NameAndDescription/styles.ts
index 33bfcb58056..0e390942879 100644
--- a/app/cdap/components/hydrator/components/TopPanel/NameAndDescription/styles.ts
+++ b/app/cdap/components/hydrator/components/TopPanel/NameAndDescription/styles.ts
@@ -129,7 +129,7 @@ export const DescriptionTextField = styled(TextField)`
`;
export const EditStatus = styled.div`
- width: calc(20vw - 110px);
+ width: calc(20vw - 180px);
flex-wrap: wrap;
display: flex;
background-color: transparent;
diff --git a/app/cdap/components/hydrator/components/TopPanel/TopPanel.tsx b/app/cdap/components/hydrator/components/TopPanel/TopPanel.tsx
index d153ba57dbb..44d4a3707e3 100644
--- a/app/cdap/components/hydrator/components/TopPanel/TopPanel.tsx
+++ b/app/cdap/components/hydrator/components/TopPanel/TopPanel.tsx
@@ -31,6 +31,7 @@ import { editPipeline } from 'services/PipelineUtils';
import downloadFile from 'services/download-file';
import { cleanseAndCompareTwoObjects } from 'services/helpers';
import ThemeWrapper from 'components/ThemeWrapper';
+import { FeatureProvider } from 'services/react/providers/featureFlagProvider';
export interface IGlobalObj {
etlRealtime?: string;
@@ -129,6 +130,7 @@ interface ITopPanelProps {
toggleScheduler: () => void;
hasNodes: boolean;
onSaveDraft: () => void;
+ onPipelineSummarize: (summarySetter: (string) => void) => void;
onPublish: (isEdit) => void;
onImport: () => void;
onFileSelect: (files: FileList) => void;
@@ -181,6 +183,7 @@ export const TopPanel = ({
closeScheduler,
hasNodes,
onSaveDraft,
+ onPipelineSummarize,
onPublish,
onImport,
onFileSelect,
@@ -380,108 +383,111 @@ export const TopPanel = ({
/>
);
return (
-
- {errorState.errorMessage && (
- {
- errorDispatch({ type: 'reset' });
- }}
+
+
+ {errorState.errorMessage && (
+ {
+ errorDispatch({ type: 'reset' });
+ }}
+ />
+ )}
+
+
+
+
+
+ {viewScheduler && (
+
+ )}
+ {viewLogs && (
+
+
+
+ )}
+ {(viewLogs || viewConfig) && (
+ {
+ onCloseLog();
+ if (viewConfig) {
+ toggleConfig();
+ }
+ }}
+ >
+ )}
+
- )}
-
-
-
-
-
- {viewScheduler && (
-
- )}
- {viewLogs && (
-
-
-
- )}
- {(viewLogs || viewConfig) && (
- {
- onCloseLog();
- if (viewConfig) {
- toggleConfig();
- }
- }}
- >
- )}
-
-
+
+
);
};
diff --git a/app/cdap/text/text-en.yaml b/app/cdap/text/text-en.yaml
index 1bdf52a0f8f..75e24c847be 100644
--- a/app/cdap/text/text-en.yaml
+++ b/app/cdap/text/text-en.yaml
@@ -2527,6 +2527,9 @@ features:
edit: Edit
export: Export
exportModalTitle: Export pipeline configuration
+ genaiSummarize: Summarize
+ genaiSummaryOfPipeline: Summary of {pipelineName}
+ genaiSummaryOfUnnamedPipeline: Pipeline summary
history: History
run: Run
schedule: Schedule
diff --git a/app/hydrator/controllers/create/toppanel-ctrl.js b/app/hydrator/controllers/create/toppanel-ctrl.js
index 3999cca705a..6f66ff5b49a 100644
--- a/app/hydrator/controllers/create/toppanel-ctrl.js
+++ b/app/hydrator/controllers/create/toppanel-ctrl.js
@@ -31,6 +31,7 @@ class HydratorPlusPlusTopPanelCtrl {
HydratorPlusPlusPreviewActions,
$interval,
myPipelineApi,
+ myAppsApi,
$state,
myAlertOnValium,
MY_CONFIG,
@@ -63,6 +64,7 @@ class HydratorPlusPlusTopPanelCtrl {
this.previewActions = HydratorPlusPlusPreviewActions;
this.$interval = $interval;
this.myPipelineApi = myPipelineApi;
+ this.myAppsApi = myAppsApi;
this.myPreviewLogsApi = myPreviewLogsApi;
this.myPreferenceApi = myPreferenceApi;
this.DAGPlusPlusNodesStore = DAGPlusPlusNodesStore;
@@ -98,6 +100,7 @@ class HydratorPlusPlusTopPanelCtrl {
this.toggleSchedulerV2 = this.toggleSchedulerV2.bind(this);
this.closeScheduler = this.closeScheduler.bind(this);
this.onSaveDraftV2 = this.onSaveDraftV2.bind(this);
+ this.onPipelineSummarize = this.onPipelineSummarize.bind(this);
this.onPublishV2 = this.onPublishV2.bind(this);
this.onImportV2 = this.onImportV2.bind(this);
this.onExportV2 = this.onExportV2.bind(this);
@@ -571,7 +574,7 @@ class HydratorPlusPlusTopPanelCtrl {
this.HydratorPlusPlusConfigStore.getDraftId()
);
this.$window.localStorage.setItem("LastPreviewId", this.currentPreviewId);
- }
+ }
onClickLogs() {
this.viewLogs = !this.viewLogs;
}
@@ -1156,6 +1159,15 @@ class HydratorPlusPlusTopPanelCtrl {
);
}
+ onPipelineSummarize(summarySetter) {
+ let pipelineConfig = this.HydratorPlusPlusConfigStore.getConfigForExport();
+ this.myAppsApi.summarize({
+ namespace: this.$state.params.namespace,
+ }, pipelineConfig).$promise.then((res) => {
+ summarySetter(res);
+ });
+ }
+
runPreview() {
this.previewLoading = true;
this.loadingLabel = "Starting";
diff --git a/app/hydrator/templates/create/reacttoppanel.html b/app/hydrator/templates/create/reacttoppanel.html
index 700d27d8b9a..d11ce24e7b5 100644
--- a/app/hydrator/templates/create/reacttoppanel.html
+++ b/app/hydrator/templates/create/reacttoppanel.html
@@ -35,6 +35,7 @@
close-scheduler="HydratorPlusPlusTopPanelCtrl.closeScheduler"
has-nodes="HydratorPlusPlusTopPanelCtrl.hasNodes"
on-save-draft="HydratorPlusPlusTopPanelCtrl.onSaveDraftV2"
+ on-pipeline-summarize="HydratorPlusPlusTopPanelCtrl.onPipelineSummarize"
on-publish="HydratorPlusPlusTopPanelCtrl.onPublishV2"
on-import="HydratorPlusPlusTopPanelCtrl.onImportV2"
on-file-select="HydratorPlusPlusTopPanelCtrl.importFile2"
diff --git a/app/services/apps/my-apps-api.js b/app/services/apps/my-apps-api.js
index 016edaad302..d32bdb624c9 100644
--- a/app/services/apps/my-apps-api.js
+++ b/app/services/apps/my-apps-api.js
@@ -30,6 +30,7 @@ angular.module(PKG.name + '.services')
{
delete: myHelpers.getConfig('DELETE', 'REQUEST', detailPath),
list: myHelpers.getConfig('GET', 'REQUEST', listPath, true),
- get: myHelpers.getConfig('GET', 'REQUEST', detailPath)
+ get: myHelpers.getConfig('GET', 'REQUEST', detailPath),
+ summarize: myHelpers.getConfig('POST', 'REQUEST', basePath + '/summarize'),
});
});