diff --git a/app/cdap/components/NamespaceAdmin/SourceControlManagement/index.tsx b/app/cdap/components/NamespaceAdmin/SourceControlManagement/index.tsx index 7cb436b9816..938051ae986 100644 --- a/app/cdap/components/NamespaceAdmin/SourceControlManagement/index.tsx +++ b/app/cdap/components/NamespaceAdmin/SourceControlManagement/index.tsx @@ -16,6 +16,7 @@ import PrimaryContainedButton from 'components/shared/Buttons/PrimaryContainedButton'; import React, { useState } from 'react'; +import { useHistory } from 'react-router'; import styled from 'styled-components'; import T from 'i18n-react'; import Table from 'components/shared/Table'; @@ -52,6 +53,8 @@ export const SourceControlManagement = () => { const [isUnlinkModalOpen, setIsUnlinkModalOpen] = useState(false); const [errorMessage, setErrorMessage] = useState(null); const [loading, setLoading] = useState(false); + const history = useHistory(); + const sourceControlManagementConfig: ISourceControlManagementConfig = useSelector( (state) => state.sourceControlManagementConfig ); @@ -85,11 +88,16 @@ export const SourceControlManagement = () => { ]; const validateConfigAndRedirect = () => { - setLoading(true); const namespace = getCurrentNamespace(); + if (sourceControlManagementConfig) { + history.push(`/ns/${namespace}/scm/sync`); + return; + } + + setLoading(true); getSourceControlManagement(namespace).subscribe( () => { - window.location.href = `/ns/${namespace}/scm/sync`; + history.push(`/ns/${namespace}/scm/sync`); }, (err) => { setErrorMessage(err.message); diff --git a/app/cdap/components/ResourceCenterEntity/ResourceCenterPipelineEntity.tsx b/app/cdap/components/ResourceCenterEntity/ResourceCenterPipelineEntity.tsx index 5e4aeeecf46..fe8f332dd0f 100644 --- a/app/cdap/components/ResourceCenterEntity/ResourceCenterPipelineEntity.tsx +++ b/app/cdap/components/ResourceCenterEntity/ResourceCenterPipelineEntity.tsx @@ -24,11 +24,8 @@ import { validateImportJSON, adjustConfigNode } from 'services/PipelineErrorFact import { objectQuery } from 'services/helpers'; import IconSVG from 'components/shared/IconSVG'; import PrimaryOutlinedButton from 'components/shared/Buttons/PrimaryOutlinedButton'; -import { defaultState, PullPipelineWizard, reducer } from './PullPipelineWizard'; import PrimaryContainedButton from 'components/shared/Buttons/PrimaryContainedButton'; -import { getSourceControlManagement } from 'components/NamespaceAdmin/store/ActionCreator'; import ButtonLoadingHoc from 'components/shared/Buttons/ButtonLoadingHoc'; -import { useFeatureFlagDefaultFalse } from 'services/react/customHooks/useFeatureFlag'; import { getHydratorUrl } from 'services/UiUtils/UrlGenerator'; require('./ResourceCenterPipelineEntity.scss'); @@ -48,9 +45,6 @@ interface IResourceCenterPipelineEntityProps { export default function ResourceCenterPipelineEntity({ onError, }: IResourceCenterPipelineEntityProps) { - const sourceControlManagementEnabled = useFeatureFlagDefaultFalse( - 'source.control.management.git.enabled' - ); const namespace = getCurrentNamespace(); const resourceCenterId = uuidV4(); const hydratorLinkStateParams = { @@ -109,88 +103,49 @@ export default function ResourceCenterPipelineEntity({ }; }; - const [pullPipelineModalState, dispatch] = useReducer(reducer, defaultState); - - const pullPipelineBtnHandler = () => { - dispatch({ type: 'SET_LOADING', payload: { loading: true } }); - getSourceControlManagement(namespace).subscribe( - () => { - dispatch({ type: 'TOGGLE_MODAL' }); - }, - (err) => { - dispatch({ - type: 'SET_ERROR', - payload: { - error: err.message, - }, - }); - dispatch({ type: 'SET_LOADING', payload: { loading: false } }); - }, - () => { - dispatch({ type: 'SET_LOADING', payload: { loading: false } }); - } - ); - }; - return ( - <> -
-
-
-
- -
+
+
+
+
+
-
-
-

{T.translate(`${PREFIX}.label`)}

-

{T.translate(`${PREFIX}.description`)}

-
+
+
+
+

{T.translate(`${PREFIX}.label`)}

+

{T.translate(`${PREFIX}.description`)}

-
- +
+ + {T.translate(`${PREFIX}.actionbtn0`)} + + +
+ {T.translate(`${PREFIX}.actionbtn1`)} + +
- - +
); } diff --git a/app/cdap/components/SourceControlManagement/LocalPipelineListView/PipelineTable.tsx b/app/cdap/components/SourceControlManagement/LocalPipelineListView/PipelineTable.tsx index 0284d185ca3..a72dd70ec37 100644 --- a/app/cdap/components/SourceControlManagement/LocalPipelineListView/PipelineTable.tsx +++ b/app/cdap/components/SourceControlManagement/LocalPipelineListView/PipelineTable.tsx @@ -30,6 +30,7 @@ import { StyledFixedWidthCell, StyledPopover, } from '../styles'; +import { timeInstantToString } from 'services/DataFormatter'; const PREFIX = 'features.SourceControlManagement.table'; @@ -117,6 +118,7 @@ export const LocalPipelineTable = ({ {T.translate(`${PREFIX}.pipelineName`)} + {T.translate(`${PREFIX}.lastSyncDate`)}
{T.translate(`${PREFIX}.gitStatus`)} @@ -167,6 +169,7 @@ export const LocalPipelineTable = ({ )} {pipeline.name} + {timeInstantToString(pipeline.lastSyncDate)} {pipeline.fileHash ? T.translate(`${PREFIX}.connected`) : '--'} diff --git a/app/cdap/components/SourceControlManagement/LocalPipelineListView/index.tsx b/app/cdap/components/SourceControlManagement/LocalPipelineListView/index.tsx index 5b57b300607..1e0593b5aab 100644 --- a/app/cdap/components/SourceControlManagement/LocalPipelineListView/index.tsx +++ b/app/cdap/components/SourceControlManagement/LocalPipelineListView/index.tsx @@ -18,8 +18,6 @@ import PrimaryContainedButton from 'components/shared/Buttons/PrimaryContainedBu import React, { useEffect, useState } from 'react'; import { useSelector } from 'react-redux'; import { getCurrentNamespace } from 'services/NamespaceStore'; -import Alert from '@material-ui/lab/Alert'; -import AlertTitle from '@material-ui/lab/AlertTitle'; import { countPushFailedPipelines, fetchLatestOperation, @@ -31,7 +29,6 @@ import { setLoadingMessage, setLocalPipelines, setNameFilter, - stopOperation, toggleCommitModal, toggleShowFailedOnly, } from '../store/ActionCreator'; @@ -43,29 +40,15 @@ import cloneDeep from 'lodash/cloneDeep'; import PrimaryTextButton from 'components/shared/Buttons/PrimaryTextButton'; import { LocalPipelineTable } from './PipelineTable'; import { useOnUnmount } from 'services/react/customHooks/useOnUnmount'; -import { - AlertErrorView, - FailStatusDiv, - PipelineListContainer, - StyledSelectionStatusDiv, -} from '../styles'; +import { FailStatusDiv, PipelineListContainer, StyledSelectionStatusDiv } from '../styles'; import { IListResponse, IOperationMetaResponse, IOperationRun } from '../types'; import { useFeatureFlagDefaultFalse } from 'services/react/customHooks/useFeatureFlag'; -import { - getOperationRunMessage, - getOperationStartTime, - getOperationStatusType, - parseOperationResource, -} from '../helpers'; -import Button from '@material-ui/core/Button'; -import { OperationStatus } from '../OperationStatus'; -import ExpandLess from '@material-ui/icons/ExpandLess'; -import ExpandMore from '@material-ui/icons/ExpandMore'; +import { parseOperationResource } from '../helpers'; +import { OperationAlert } from '../OperationAlert'; const PREFIX = 'features.SourceControlManagement.push'; export const LocalPipelineListView = () => { - const [viewErrorExpanded, setViewErrorExpanded] = useState(false); const { ready, localPipelines, @@ -177,55 +160,10 @@ export const LocalPipelineListView = () => { return
{T.translate(`${PREFIX}.emptyPipelineListMessage`, { query: nameFilter })}
; }; - const getOperationAction = () => { - if (!operation.done) { - return ( - - ); - } - - if (operation.status === OperationStatus.FAILED) { - return ( - - ); - } - - return undefined; - }; - return ( - {operation && ( - - {getOperationRunMessage(operation)} - {getOperationStartTime(operation)} - {operation.status === OperationStatus.FAILED && viewErrorExpanded && ( - - Operation ID: {operation.id} -
- Error: {operation.error.message} -
- )} -
- )} + {operation && multiPushEnabled && } {selectedPipelines.length > 0 && (
diff --git a/app/cdap/components/SourceControlManagement/OperationAlert.tsx b/app/cdap/components/SourceControlManagement/OperationAlert.tsx new file mode 100644 index 00000000000..fe7581ce1d3 --- /dev/null +++ b/app/cdap/components/SourceControlManagement/OperationAlert.tsx @@ -0,0 +1,146 @@ +/* + * Copyright © 2023 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, { useState } from 'react'; +import styled from 'styled-components'; +import T from 'i18n-react'; +import { IOperationRun } from './types'; +import Alert from '@material-ui/lab/Alert'; +import AlertTitle from '@material-ui/lab/AlertTitle'; +import { + getOperationRunMessage, + getOperationRunTime, + getOperationStartTime, + getOperationStatusType, + parseOperationResource, +} from './helpers'; +import { Button, CircularProgress } from '@material-ui/core'; +import { stopOperation } from './store/ActionCreator'; +import { getCurrentNamespace } from 'services/NamespaceStore'; +import { OperationStatus } from './OperationStatus'; +import ExpandLess from '@material-ui/icons/ExpandLess'; +import ExpandMore from '@material-ui/icons/ExpandMore'; +import { AlertErrorView } from './styles'; + +interface IOperationBannerProps { + operation: IOperationRun; +} + +const StyledDiv = styled.div` + margin-bottom: 24px; +`; + +const ExpandWrapper = styled.div` + height: 100%; + padding-top: 12px; +`; + +const PREFIX = 'features.SourceControlManagement'; + +export const OperationAlert = ({ operation }: IOperationBannerProps) => { + const [viewErrorExpanded, setViewErrorExpanded] = useState(false); + + const getOperationAction = () => { + if ( + operation.status === OperationStatus.STARTING || + operation.status === OperationStatus.RUNNING + ) { + return ( + + ); + } + + if (operation.status === OperationStatus.STOPPING) { + return ( + + ); + } + + if (operation.status === OperationStatus.FAILED) { + return ( + + + + ); + } + + return undefined; + }; + + const renderOperationTime = () => { + const startTime = getOperationStartTime(operation); + const timeTaken = getOperationRunTime(operation); + if (!timeTaken) { + return T.translate(`${PREFIX}.operationStartedAt`, { startTime }).toString(); + } + + if (operation.done) { + return T.translate(`${PREFIX}.operationRanFor`, { startTime, timeTaken }).toString(); + } + + return T.translate(`${PREFIX}.operationRunningFor`, { startTime, timeTaken }).toString(); + }; + + function renderErrorMessage(message?: string) { + if (!message) { + return operation.error?.message; + } + + const firstColonIndex = message.indexOf(':'); + return message.substring(firstColonIndex + 1); + } + + return ( + + + {getOperationRunMessage(operation)} + {renderOperationTime()} + {operation.status === OperationStatus.FAILED && viewErrorExpanded && ( + + Operation ID: {operation.id} + {operation.error?.details[0] && ( + <> +
+ Pipeline Name: {parseOperationResource(operation.error?.details[0]).name} +
+ Error: {renderErrorMessage(operation.error?.details[0]?.message)} + + )} +
+ )} +
+
+ ); +}; diff --git a/app/cdap/components/SourceControlManagement/RemotePipelineListView/index.tsx b/app/cdap/components/SourceControlManagement/RemotePipelineListView/index.tsx index c2b9684e686..bbeeadfbdda 100644 --- a/app/cdap/components/SourceControlManagement/RemotePipelineListView/index.tsx +++ b/app/cdap/components/SourceControlManagement/RemotePipelineListView/index.tsx @@ -18,15 +18,8 @@ import React, { useEffect, useState } from 'react'; import T from 'i18n-react'; import { useSelector } from 'react-redux'; import cloneDeep from 'lodash/cloneDeep'; -import { default as MuiAlert } from '@material-ui/lab/Alert'; -import AlertTitle from '@material-ui/lab/AlertTitle'; import { SearchBox } from '../SearchBox'; -import { - AlertErrorView, - FailStatusDiv, - PipelineListContainer, - StyledSelectionStatusDiv, -} from '../styles'; +import { FailStatusDiv, PipelineListContainer, StyledSelectionStatusDiv } from '../styles'; import { RemotePipelineTable } from './RemotePipelineTable'; import { countPullFailedPipelines, @@ -41,7 +34,6 @@ import { toggleRemoteShowFailedOnly, pullAndDeployMultipleSelectedRemotePipelines, fetchLatestOperation, - stopOperation, } from '../store/ActionCreator'; import { LoadingAppLevel } from 'components/shared/LoadingAppLevel'; import { getCurrentNamespace } from 'services/NamespaceStore'; @@ -54,16 +46,8 @@ import { SUPPORT } from 'components/StatusButton/constants'; import { IListResponse, IOperationMetaResponse, IOperationRun } from '../types'; import Alert from 'components/shared/Alert'; import { useFeatureFlagDefaultFalse } from 'services/react/customHooks/useFeatureFlag'; -import { - getOperationRunMessage, - getOperationStartTime, - getOperationStatusType, - parseOperationResource, -} from '../helpers'; -import Button from '@material-ui/core/Button'; -import { OperationStatus } from '../OperationStatus'; -import ExpandLess from '@material-ui/icons/ExpandLess'; -import ExpandMore from '@material-ui/icons/ExpandMore'; +import { parseOperationResource } from '../helpers'; +import { OperationAlert } from '../OperationAlert'; const PREFIX = 'features.SourceControlManagement.pull'; @@ -72,7 +56,6 @@ interface IRemotePipelineListViewProps { } export const RemotePipelineListView = ({ redirectOnSubmit }: IRemotePipelineListViewProps) => { - const [viewErrorExpanded, setViewErrorExpanded] = useState(false); const { ready, remotePipelines, @@ -190,34 +173,6 @@ export const RemotePipelineListView = ({ redirectOnSubmit }: IRemotePipelineList return
{T.translate(`${PREFIX}.emptyPipelineListMessage`, { query: nameFilter })}
; }; - const getOperationAction = () => { - if (!operation.done) { - return ( - - ); - } - - if (operation.status === OperationStatus.FAILED) { - return ( - - ); - } - - return undefined; - }; - return ( <> - {operation && ( - - {getOperationRunMessage(operation)} - {getOperationStartTime(operation)} - {operation.status === OperationStatus.FAILED && viewErrorExpanded && ( - - Operation ID: {operation.id} -
- Error: {operation.error.message} -
- )} -
- )} + {operation && multiPullEnabled && } {selectedPipelines.length > 0 && (
diff --git a/app/cdap/components/SourceControlManagement/SearchBox.tsx b/app/cdap/components/SourceControlManagement/SearchBox.tsx index a9842f7138b..cf252fa8099 100644 --- a/app/cdap/components/SourceControlManagement/SearchBox.tsx +++ b/app/cdap/components/SourceControlManagement/SearchBox.tsx @@ -56,12 +56,14 @@ export const SearchBox = ({ nameFilter, setNameFilter }: ISearchBoxProps) => { ), - endAdornment: ( + endAdornment: nameFilter ? ( setNameFilter('')}> + ) : ( + undefined ), }} label={T.translate(`${PREFIX}.searchLabel`)} diff --git a/app/cdap/components/SourceControlManagement/SyncTabs.tsx b/app/cdap/components/SourceControlManagement/SyncTabs.tsx new file mode 100644 index 00000000000..833f4ba2fc5 --- /dev/null +++ b/app/cdap/components/SourceControlManagement/SyncTabs.tsx @@ -0,0 +1,84 @@ +/* + * Copyright © 2023 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 { Tab, Tabs } from '@material-ui/core'; +import React, { useEffect, useState } from 'react'; +import { useSelector } from 'react-redux'; +import { LocalPipelineListView } from './LocalPipelineListView'; +import styled from 'styled-components'; +import T from 'i18n-react'; +import { RemotePipelineListView } from './RemotePipelineListView'; +import { FeatureProvider } from 'services/react/providers/featureFlagProvider'; +import { getNamespacePipelineList, getRemotePipelineList } from './store/ActionCreator'; +import { getCurrentNamespace } from 'services/NamespaceStore'; + +const PREFIX = 'features.SourceControlManagement'; + +const StyledDiv = styled.div` + padding: 10px; + margin-top: 10px; +`; + +const ScmSyncTabs = () => { + const [tabIndex, setTabIndex] = useState(0); + + const { ready: pushStateReady, nameFilter } = useSelector(({ push }) => push); + useEffect(() => { + if (!pushStateReady) { + getNamespacePipelineList(getCurrentNamespace(), nameFilter); + } + }, [pushStateReady]); + + const { ready: pullStateReady } = useSelector(({ pull }) => pull); + useEffect(() => { + if (!pullStateReady) { + getRemotePipelineList(getCurrentNamespace()); + } + }, [pullStateReady]); + + const handleTabChange = (e, newValue) => { + setTabIndex(newValue); + // refetch latest pipeline data, while displaying possibly stale data + if (newValue === 0) { + getNamespacePipelineList(getCurrentNamespace(), nameFilter); + } else { + getRemotePipelineList(getCurrentNamespace()); + } + }; + + return ( + <> + + + + + + + + + {tabIndex === 0 ? : } + + + + ); +}; + +export default ScmSyncTabs; diff --git a/app/cdap/components/SourceControlManagement/helpers.ts b/app/cdap/components/SourceControlManagement/helpers.ts index c8657bb58c2..a6ac2dbe6b9 100644 --- a/app/cdap/components/SourceControlManagement/helpers.ts +++ b/app/cdap/components/SourceControlManagement/helpers.ts @@ -17,8 +17,14 @@ import moment from 'moment'; import { OperationStatus } from './OperationStatus'; import { OperationType } from './OperationType'; -import { IResource, IOperationResource, IOperationRun, ITimeInstant } from './types'; +import { + IResource, + IOperationResource, + IOperationRun, + IOperationResourceScopedErrorMessage, +} from './types'; import T from 'i18n-react'; +import { ITimeInstant, timeInstantToString } from 'services/DataFormatter'; const PREFIX = 'features.SourceControlManagement'; @@ -34,6 +40,20 @@ export const parseOperationResource = (resource: IOperationResource): IResource }; }; +export const parseErrorMessage = (errorMessage: string): IOperationResourceScopedErrorMessage => { + const firstColonIndex = errorMessage.indexOf(':'); + if (firstColonIndex === -1) { + return { + message: errorMessage, + }; + } + + return { + type: errorMessage.substring(0, firstColonIndex).trim(), + message: errorMessage.substring(firstColonIndex + 1).trim(), + }; +}; + export const getOperationRunMessage = (operation: IOperationRun) => { const n = operation.metadata?.resources?.length || ''; @@ -121,5 +141,16 @@ export const compareTimeInstant = (t1: ITimeInstant, t2: ITimeInstant): number = }; export const getOperationStartTime = (operation: IOperationRun): string => { - return moment(operation.metadata?.createTime.seconds * 1000).format('DD-MM-YYYY HH:mm:ss A'); + return timeInstantToString(operation.metadata?.createTime); +}; + +export const getOperationRunTime = (operation: IOperationRun): string => { + if (operation.metadata?.createTime && operation.metadata?.endTime) { + return moment + .duration( + (operation.metadata?.endTime.seconds - operation.metadata?.createTime.seconds) * 1000 + ) + .humanize(); + } + return null; }; diff --git a/app/cdap/components/SourceControlManagement/index.tsx b/app/cdap/components/SourceControlManagement/index.tsx index af6dad7d893..6f4f5229408 100644 --- a/app/cdap/components/SourceControlManagement/index.tsx +++ b/app/cdap/components/SourceControlManagement/index.tsx @@ -14,31 +14,25 @@ * the License. */ -import { Tab, Tabs } from '@material-ui/core'; import { EntityTopPanel } from 'components/EntityTopPanel'; -import React, { useState } from 'react'; -import { LocalPipelineListView } from './LocalPipelineListView'; +import React from 'react'; import { Provider } from 'react-redux'; import SourceControlManagementSyncStore from './store'; -import styled from 'styled-components'; import T from 'i18n-react'; -import { RemotePipelineListView } from './RemotePipelineListView'; import { getCurrentNamespace } from 'services/NamespaceStore'; -import { FeatureProvider } from 'services/react/providers/featureFlagProvider'; +import { useHistory } from 'react-router'; +import { useOnUnmount } from 'services/react/customHooks/useOnUnmount'; +import { reset, resetRemote } from './store/ActionCreator'; +import ScmSyncTabs from './SyncTabs'; const PREFIX = 'features.SourceControlManagement'; -const StyledDiv = styled.div` - padding: 10px; - margin-top: 10px; -`; - const SourceControlManagementSyncView = () => { - const [tabIndex, setTabIndex] = useState(0); - - const handleTabChange = (e, newValue) => { - setTabIndex(newValue); - }; + const history = useHistory(); + useOnUnmount(() => { + resetRemote(); + reset(); + }); const closeAndBackLink = `/ns/${getCurrentNamespace()}/details/scm`; @@ -47,29 +41,14 @@ const SourceControlManagementSyncView = () => { { - window.location.href = closeAndBackLink; + history.push(closeAndBackLink); }} breadCrumbAnchorLabel={T.translate('commons.namespaceAdmin').toString()} onBreadCrumbClick={() => { - window.location.href = closeAndBackLink; + history.push(closeAndBackLink); }} /> - - - - - - - - - {tabIndex === 0 ? : } - - + ); }; diff --git a/app/cdap/components/SourceControlManagement/store/ActionCreator.ts b/app/cdap/components/SourceControlManagement/store/ActionCreator.ts index 999ae6828ee..e4250eac356 100644 --- a/app/cdap/components/SourceControlManagement/store/ActionCreator.ts +++ b/app/cdap/components/SourceControlManagement/store/ActionCreator.ts @@ -30,6 +30,7 @@ import { LongRunningOperationApi } from 'api/longRunningOperation'; import { IPipeline, IPushResponse, IRepositoryPipeline, IOperationRun } from '../types'; import { SUPPORT } from 'components/StatusButton/constants'; import { compareTimeInstant } from '../helpers'; +import { getCurrentNamespace } from 'services/NamespaceStore'; const PREFIX = 'features.SourceControlManagement'; @@ -46,6 +47,7 @@ export const getNamespacePipelineList = (namespace, nameFilter = null) => { return { name: pipeline.name, fileHash: pipeline.sourceControlMeta?.fileHash, + lastSyncDate: pipeline.sourceControlMeta?.lastSyncedAt, error: null, status: null, }; @@ -432,3 +434,8 @@ export const stopOperation = (namespace: string, operation: IOperationRun) => () // The operation status will be updated by the ongoing poll for the operation. }); }; + +export const refetchAllPipelines = () => { + getNamespacePipelineList(getCurrentNamespace()); + getRemotePipelineList(getCurrentNamespace()); +}; diff --git a/app/cdap/components/SourceControlManagement/types.ts b/app/cdap/components/SourceControlManagement/types.ts index 952e80bdcbe..c1b17e4324b 100644 --- a/app/cdap/components/SourceControlManagement/types.ts +++ b/app/cdap/components/SourceControlManagement/types.ts @@ -18,23 +18,20 @@ import { IArtifactObj } from 'components/PipelineContextMenu/PipelineTypes'; import { SUPPORT } from 'components/StatusButton/constants'; import { OperationType } from './OperationType'; import { OperationStatus } from './OperationStatus'; +import { ITimeInstant } from 'services/DataFormatter'; export interface IRepositoryPipeline { name: string; fileHash: string; error: string; status: SUPPORT; + lastSyncDate?: ITimeInstant; } export interface IOperationResource { resourceUri: string; } -export interface ITimeInstant { - seconds: number; - nanos: number; -} - export interface IOperationMeta { resources: IOperationResource[]; createTime?: ITimeInstant; @@ -51,6 +48,11 @@ export interface IOperationResourceScopedError { message?: string; } +export interface IOperationResourceScopedErrorMessage { + type?: string; + message: string; +} + export interface IOperationRun { id: string; type: OperationType; @@ -79,6 +81,7 @@ export interface IPipeline { }; sourceControlMeta: { fileHash: string; + lastSyncedAt?: ITimeInstant; }; } diff --git a/app/cdap/services/DataFormatter/index.ts b/app/cdap/services/DataFormatter/index.ts index c523d56d5a8..545fb5b0848 100644 --- a/app/cdap/services/DataFormatter/index.ts +++ b/app/cdap/services/DataFormatter/index.ts @@ -17,6 +17,11 @@ import moment from 'moment'; import { convertBytesToHumanReadable, humanReadableNumber, truncateNumber } from 'services/helpers'; +export interface ITimeInstant { + seconds: number; + nanos?: number; +} + export const TYPES = { STRING: 'STRING', NUMBER: 'NUMBER', @@ -117,3 +122,11 @@ export function format(value, type, options: { concise?: boolean } = {}) { export function formatAsPercentage(str: string) { return `${str}%`; } + +export function timeInstantToString(t?: ITimeInstant): string { + if (!t) { + return EMPTY_DATE; + } + + return moment(t.seconds * 1000).format('DD-MM-YYYY HH:mm:ss A'); +} diff --git a/app/cdap/text/text-en.yaml b/app/cdap/text/text-en.yaml index d2db1f1b5a9..83fa93a91bf 100644 --- a/app/cdap/text/text-en.yaml +++ b/app/cdap/text/text-en.yaml @@ -3236,6 +3236,10 @@ features: pipelineSyncMessage: Syncing 1 pipeline with the remote repository pipelineSyncedSuccess: Successfully synced 1 pipeline with the remote repository pipelineSyncedFail: Failed to sync 1 pipeline with the remote repository + stopOperation: STOP + operationStartedAt: Started at {startTime} + operationRanFor: Started at {startTime}, Completed in {timeTaken} + operationRunningFor: Started at {startTime}, Running for {timeTaken} pull: emptyPipelineListMessage: There are no pipelines in the remote repository or no pipelines matching the search query "{query}" modalTitle: Pull pipeline from remote repository @@ -3247,12 +3251,12 @@ features: pipelinesSelected: "{selected} of {total} pipelines selected" pullAppMessage: Pulling {appId} from remote repository now... pullAppMessageMulti: Pulling {n} pipelines from the remote repository now... - pullButton: Pull to namespace + pullButton: Pull from repository pullChipTooltip: Click to pull the latest from Git pullSuccess: Pipeline {pipelineName} updated pullSuccessMulti: Successfully pulled {n} pipelines from the remote repository. pullFailureMulti: Failed to pull {n} pipelines from the remote repository. - tab: Remote pipelines + tab: Repository pipelines upToDate: Pipeline is already up to date. stopOperation: STOP push: @@ -3268,16 +3272,17 @@ features: pipelinesSelected: "{selected} of {total} pipelines selected" pushAppMessage: Pushing {appId} to remote repository now... pushAppMessageMulti: Pushing {n} pipelines to the remote repository now... - pushButton: Push to remote + pushButton: Push to repository pushSuccess: Successfully pushed pipeline {pipelineName} pushSuccessMulti: Successfully pushed {n} pipelines to the remote repository. pushFailureMulti: Failed to push {n} pipelines to the remote repository. searchLabel: Search by batch pipeline name - tab: Local pipelines + tab: Namespace pipelines stopOperation: STOP table: connected: Connected pipelineName: Pipeline name + lastSyncDate: Last sync date gitStatus: Connected to Git gitStatusHelperText: This status indicates that the pipeline has been pushed to or pulled from the git repository in the past. It does not necessarily mean the content is up to date. pullFail: Failed to pull this pipeline from remote.