diff --git a/CurrentReleaseNotes.md b/CurrentReleaseNotes.md index 6c066168..1ba61a8f 100644 --- a/CurrentReleaseNotes.md +++ b/CurrentReleaseNotes.md @@ -1,17 +1,35 @@ -## Related Issues +## Issues fixes -- ### [Issue0093](https://github.com/expertasolutions/VstsDashboard/issues/93) - - Azure DevOps API query optimization - - Optimization on Builds and Release Data is pull from Azure DevOps API - - Fix to show latest environment released in showed in the Deployment Health - - Fix the order of Released showed in the Deployment Health column when periodical refresh is made - - Fix Build showed in double randomly - - Fix General display update - - Rename the label "x pending build(s)" to "x other(s) run(s)" +- ### [Issue0109](https://github.com/expertasolutions/VstsDashboard/Delivery-20April-01/issues/109) + - Add fullscreen capability + + ![Issue0109-01](_ReleaseNotes/Delivery200430/Issue0109/Issue0109-01.png) -- ### [Issue0105](https://github.com/expertasolutions/VstsDashboard/issues/105) - - Set minimal Azure DevOps Server minimal version to 2019 Update 1 (17.153.29207.5) and later + ![Issue0109-02](_ReleaseNotes/Delivery200430/Issue0109/Issue0109-02.png) - For more informations regarding Azure DevOps Server versions, see [Azure DevOps Server build numbers](https://docs.microsoft.com/en-us/azure/devops/release-notes/features-timeline#server-build-numbers) - - - No minimal version has been set for Azure DevOps Service \ No newline at end of file +- ### [Issue0113](https://github.com/expertasolutions/VstsDashboard/Delivery-20April-01/issues/113) + - On 'All Runs', Fix the duplication of two differents status for the same build run. + +- ### [Issue0115](https://github.com/expertasolutions/VstsDashboard/Delivery-20April-01/issues/115) + - Add Build Pipeline Reference Status by Pipeline + + ![Issue0115-01](_ReleaseNotes/Delivery200430/Issue0115/Issue0115-01.png) + +- ### Un Issued changes + + - ***Globally***: + - Move 'Duration' field on the same line of pipeline 'start time' + - Review columns width on 'Summary' and 'All Runs' views + - Move 'send a request' from the header to the same level of 'fullscreen' mode option + - 'Send a request' icon redirect directly to GitHub project new issue page + + - On '***Summary***' view': + - 'Failure/Partial on top' dropdownlist has been renamed to 'Cancelled/Failed/Partial on top' + - Fix the problems of Pipeline wasn't properly showed on top when in 'Pending/Running' status + + - On '***All Runs***' view: + - Add the link to the Build Definition Reference + - Add a 'Zero Data' message when no builds is present + - When a Team project is unselected, related build are removed from the view + +#### All these issues are part of the milestone [Delivery-200430](https://github.com/expertasolutions/VstsDashboard/milestone/2) \ No newline at end of file diff --git a/_ReleaseNotes/Delivery200430/Globally/Globally-200401-01.png b/_ReleaseNotes/Delivery200430/Globally/Globally-200401-01.png new file mode 100644 index 00000000..e69de29b diff --git a/_ReleaseNotes/Delivery200430/Issue0109/Issue0109-01.png b/_ReleaseNotes/Delivery200430/Issue0109/Issue0109-01.png new file mode 100644 index 00000000..f1cbbc75 Binary files /dev/null and b/_ReleaseNotes/Delivery200430/Issue0109/Issue0109-01.png differ diff --git a/_ReleaseNotes/Delivery200430/Issue0109/Issue0109-02.png b/_ReleaseNotes/Delivery200430/Issue0109/Issue0109-02.png new file mode 100644 index 00000000..300c8a3a Binary files /dev/null and b/_ReleaseNotes/Delivery200430/Issue0109/Issue0109-02.png differ diff --git a/_ReleaseNotes/Delivery200430/Issue0115/Issue0115-01.png b/_ReleaseNotes/Delivery200430/Issue0115/Issue0115-01.png new file mode 100644 index 00000000..0de1bc7b Binary files /dev/null and b/_ReleaseNotes/Delivery200430/Issue0115/Issue0115-01.png differ diff --git a/ci-vstsdashboardbuild.yml b/ci-vstsdashboardbuild.yml index e882a885..04399576 100644 --- a/ci-vstsdashboardbuild.yml +++ b/ci-vstsdashboardbuild.yml @@ -3,9 +3,11 @@ name: VstsDashboard-v1.$(date:yy)$(DayOfYear)$(rev:.r)-$(Build.SourceBranchName) trigger: - master - Issue* +- Delivery* pr: - master +- Delivery* pool: vmImage: 'vs2017-win2016' diff --git a/screenshots/CI_CD_Dashboard.png b/screenshots/CI_CD_Dashboard.png index 8971b066..cdb2af8f 100644 Binary files a/screenshots/CI_CD_Dashboard.png and b/screenshots/CI_CD_Dashboard.png differ diff --git a/src/ext/Dashboard/PipelineServices.tsx b/src/ext/Dashboard/PipelineServices.tsx index 90f263b6..6160e3a7 100644 --- a/src/ext/Dashboard/PipelineServices.tsx +++ b/src/ext/Dashboard/PipelineServices.tsx @@ -113,7 +113,6 @@ export async function getBuilds(projectName: string, isFirstLoad: boolean, timeR result.push(...inProgressResult); result.push(...cancellingResult); result.push(...notStartedResult); - result.push(...notStartedResult); result.push(...postponedResult); result.push(...noneResult); result.push(...completedResult); diff --git a/src/ext/Dashboard/cells/build.tsx b/src/ext/Dashboard/cells/build.tsx index 737e994b..81e621b7 100644 --- a/src/ext/Dashboard/cells/build.tsx +++ b/src/ext/Dashboard/cells/build.tsx @@ -7,6 +7,7 @@ import { getPipelineIndicator, } from "./common"; +import { BuildDefinitionReference, BuildStatus } from "azure-devops-extension-api/Build"; import { Ago } from "azure-devops-ui/Ago"; import { Duration } from "azure-devops-ui/Duration"; import { Link } from "azure-devops-ui/Link"; @@ -26,6 +27,11 @@ export function getBuildStatus(build: Build) : IStatusIndicatorData { return getPipelineIndicator(build.result, build.status); } +function getBuildDefinitionUrl(buildDefs: BuildDefinitionReference[], buildDefId: number) { + let buildDefRef = buildDefs.find(x=> x.id === buildDefId); + return buildDefRef ? buildDefRef._links.web.href : "#"; +} + export function renderBuildStatus ( rowIndex: number, columnIndex: number, @@ -34,19 +40,27 @@ export function renderBuildStatus ( ): JSX.Element { let projectName = tableItem.project.name; return ( - - -
-
{tableItem.definition.name}
-
{projectName}
-
-
+ + {(context) => ( + + +
+
+ + {tableItem.definition.name} + +
+
{projectName}
+
+
+ )} +
); } @@ -153,8 +167,7 @@ export function renderBuildInfo02Cell( if(lastBuildRun.startTime != undefined) { buildTimeCtrl = (
 {queueName}
-
 
-
 
+
   
); } else { buildTimeCtrl = ( diff --git a/src/ext/Dashboard/cells/buildReference.tsx b/src/ext/Dashboard/cells/buildReference.tsx index 09386f7f..82544043 100644 --- a/src/ext/Dashboard/cells/buildReference.tsx +++ b/src/ext/Dashboard/cells/buildReference.tsx @@ -156,8 +156,7 @@ export function renderLastBuild02( if(lastBuildRun.startTime != undefined) { buildTimeCtrl = (
 {queueName}
-
 
-
 
+
   
); } else { buildTimeCtrl = ( diff --git a/src/ext/Dashboard/cells/common.tsx b/src/ext/Dashboard/cells/common.tsx index 7c55d6a6..872915c6 100644 --- a/src/ext/Dashboard/cells/common.tsx +++ b/src/ext/Dashboard/cells/common.tsx @@ -9,7 +9,6 @@ import { Pill, PillVariant } from "azure-devops-ui/Pill"; import { PillGroup, PillGroupOverflow } from "azure-devops-ui/PillGroup"; import { Build } from "azure-devops-extension-api/Build"; import { Link } from "azure-devops-ui/Link"; -import { findDOMNode } from "react-dom"; const lightGreen: IColor = { red: 204, diff --git a/src/ext/Dashboard/dashboard.tsx b/src/ext/Dashboard/dashboard.tsx index a64edc57..c2aa290e 100644 --- a/src/ext/Dashboard/dashboard.tsx +++ b/src/ext/Dashboard/dashboard.tsx @@ -15,10 +15,11 @@ import { Tab, TabBar, TabSize } from "azure-devops-ui/Tabs"; import { Surface, SurfaceBackground } from "azure-devops-ui/Surface"; import { Page } from "azure-devops-ui/Page"; import { Link } from "azure-devops-ui/Link"; -import { Icon } from "azure-devops-ui/Icon"; +import { Icon, IconSize } from "azure-devops-ui/Icon"; +import { Status, Statuses, StatusSize } from "azure-devops-ui/Status"; import { TeamProjectReference } from "azure-devops-extension-api/Core"; -import { BuildDefinitionReference, Build } from "azure-devops-extension-api/Build"; +import { BuildDefinitionReference, Build, BuildStatus, BuildResult } from "azure-devops-extension-api/Build"; import { Deployment } from "azure-devops-extension-api/Release"; import { showRootComponent } from "../../Common"; @@ -31,10 +32,21 @@ import { IListBoxItem } from "azure-devops-ui/ListBox"; import { Filter, FILTER_CHANGE_EVENT, FILTER_RESET_EVENT } from "azure-devops-ui/Utilities/Filter"; import { FilterBar } from "azure-devops-ui/FilterBar"; import { ZeroData } from "azure-devops-ui/ZeroData"; -import { CommonServiceIds, IProjectPageService } from "azure-devops-extension-api"; +import { CommonServiceIds, IProjectPageService, IHostPageLayoutService } from "azure-devops-extension-api"; + +const isFullScreen = new ObservableValue(false); + +const buildNeverQueued = new ObservableValue(0); +const buildCancelled = new ObservableValue(0); +const buildInPending = new ObservableValue(0); +const buildInProgress = new ObservableValue(0); +const buildSucceeded = new ObservableValue(0); +const buildInWarning = new ObservableValue(0); +const buildInError = new ObservableValue(0); class CICDDashboard extends React.Component<{}, {}> { private isLoading = new ObservableValue(true); + private selectedTabId = new ObservableValue("summary"); private refreshUI = new ObservableValue(new Date().toTimeString()); @@ -64,18 +76,18 @@ class CICDDashboard extends React.Component<{}, {}> { constructor(props: {}) { super(props); - this.filter = new Filter(); setInterval(()=> this.updateFromProject(false), 10000); } state = { - buildDefs: Array(), - builds: Array(), - releases: Array(), - projects: Array(), + buildDefs: new Array(), + builds: new Array(), + releases: new Array(), + projects: new Array(), showAllBuildDeployment: false, - refreshUI: new Date().toTimeString() + refreshUI: new Date().toTimeString(), + fullScreenMode: false }; private onFilterReset = async () => { @@ -101,12 +113,16 @@ class CICDDashboard extends React.Component<{}, {}> { this.filterBuildsData(); this.refreshUI.value = new Date().toTimeString(); } - + + private isFullScreenValueChanged = () => { + this.setState({ fullScreenMode: isFullScreen.value }); + } + // BuildDefinition Summary private filterData() { let filterState = this.filter.getState(); - let buildDefList = Array(); + let buildDefList = new Array(); if(filterState.pipelineKeyWord !== undefined && filterState.pipelineKeyWord !== null && filterState.pipelineKeyWord.value !== "") { let pipelineFilterText = filterState.pipelineKeyWord.value.toLowerCase(); @@ -130,7 +146,7 @@ class CICDDashboard extends React.Component<{}, {}> { ); buildDefList = allBuildWithRelease; } - + buildDefList = sortBuildReferences(buildDefList, this.showErrorsOnSummaryOnTop); this.buildReferenceProvider = new ObservableValue>(new ArrayItemProvider(buildDefList)); } @@ -193,8 +209,17 @@ class CICDDashboard extends React.Component<{}, {}> { } } } - + this.setState({ buildDefs: currentDef }); + this.buildReferenceProvider = new ObservableValue>(new ArrayItemProvider(currentDef)); + + // Get Build Reference Status + buildNeverQueued.value = this.getCompletedBuildStatusCount(BuildStatus.None, BuildResult.None); + buildCancelled.value = this.getCompletedBuildStatusCount(BuildStatus.None, BuildResult.Canceled); + buildSucceeded.value = this.getCompletedBuildStatusCount(BuildStatus.Completed, BuildResult.Succeeded); + buildInWarning.value = this.getCompletedBuildStatusCount(BuildStatus.Completed, BuildResult.PartiallySucceeded); + buildInError.value = this.getCompletedBuildStatusCount(BuildStatus.Completed, BuildResult.Failed); + this.filterData(); }).then(()=> { SDK.ready().then(()=> { this.isLoading.value = false; }); @@ -219,7 +244,6 @@ class CICDDashboard extends React.Component<{}, {}> { } } } - this.setState({ releases: currentReleases }); }); @@ -229,45 +253,43 @@ class CICDDashboard extends React.Component<{}, {}> { } getBuildsV1(this.currentSelectedProjects, this.buildTimeRangeHasChanged, this.lastBuildsDisplay).then(result => { - let newResult = new Array(); let currentResult = this.state.builds; - if(this.buildTimeRangeHasChanged) { - currentResult = result; - } else { - for(let i=0;i x.id === newElement.id); + for(let i=0;i x.id === newElement.id); + if(existingElement !== undefined) { + let buildIndex = currentResult.indexOf(existingElement, 0); + if(buildIndex > -1) { + currentResult[buildIndex] = newElement; + let buildDefs = this.state.buildDefs; + let buildDef = buildDefs.find(x=> x.id === newElement.definition.id); + if(buildDef !== undefined && buildDef.latestBuild.id <= newElement.id) { + let buildDefIndex = buildDefs.indexOf(buildDef, 0); - if(existingElement !== undefined) { - let buildIndex = currentResult.indexOf(existingElement, 0); - - if(buildIndex > -1) { - currentResult[buildIndex] = newElement; - let buildDefs = this.state.buildDefs; - let buildDef = buildDefs.find(x=> x.id === newElement.definition.id); - - if(buildDef !== undefined && buildDef.latestBuild.id <= newElement.id) { - let buildDefIndex = buildDefs.indexOf(buildDef, 0); - - if(buildDefIndex > -1) { - buildDefs[buildDefIndex].latestBuild = newElement; - this.setState({ buildDefs: buildDefs }); - } + if(buildDefIndex > -1) { + buildDefs[buildDefIndex].latestBuild = newElement; + let newbuildDef = sortBuildReferences(this.state.buildDefs, this.showErrorsOnSummaryOnTop); + this.setState({ buildDefs: newbuildDef }); + this.filterData(); } } - } else { - currentResult.push(newElement); } + } else { + currentResult.push(newElement); } } - newResult = currentResult; - newResult = sortBuilds(newResult); + currentResult = sortBuilds(currentResult); - this.setState({ builds: newResult }); + // Get Build Reference Status + buildInPending.value = this.getActiveBuildStatusCount(BuildStatus.NotStarted, currentResult); + buildInProgress.value = this.getActiveBuildStatusCount(BuildStatus.InProgress, currentResult); + + this.setState({ builds: currentResult }); this.refreshUI.value = new Date().toTimeString(); this.buildTimeRangeHasChanged = false; + this.filterBuildsData(); }); } @@ -313,12 +335,15 @@ class CICDDashboard extends React.Component<{}, {}> { this.buildTimeRangeHasChanged = true; filterState.pipelineKeyWord = null; this.filter.setState(filterState); + + this.setState({ builds: new Array() }); + this.buildProvider.value = new ArrayItemProvider(this.state.builds); + this.updateFromProject(true); } private onLastBuildsDisplaySelected = (event: React.SyntheticEvent, item: IListBoxItem<{}>) => { if(item.text !== undefined) { - console.log(item.id + " onLastBuildDisplaySelected"); this.lastBuildsDisplay = item.id; } this.buildTimeRangeHasChanged = true; @@ -335,11 +360,13 @@ class CICDDashboard extends React.Component<{}, {}> { this.initializeState(); this.filter.subscribe(this.onFilterChanged, FILTER_CHANGE_EVENT); this.filter.subscribe(this.onFilterReset, FILTER_RESET_EVENT); + isFullScreen.subscribe(this.isFullScreenValueChanged); } public componentWillMount() { this.filter.unsubscribe(this.onFilterChanged, FILTER_CHANGE_EVENT); this.filter.unsubscribe(this.onFilterReset, FILTER_RESET_EVENT); + isFullScreen.unsubscribe(this.isFullScreenValueChanged); } private async initializeState(): Promise { @@ -355,8 +382,7 @@ class CICDDashboard extends React.Component<{}, {}> { let currentProject = await projectService.getProject(); await this.loadProjects(); - this.setState({ builds: new Array() }); - this.setState({ releases: new Array() }); + this.setState({ releases: new Array(), builds: new Array() }); if(currentProject != undefined){ this.initialProjectName = currentProject.name; @@ -438,6 +464,21 @@ class CICDDashboard extends React.Component<{}, {}> { /> ); + } else if(tabId === "builds" && this.buildProvider.value.length === 0) { + return ( +
+ + If it's not an holiday, are you sure that your team is working ? ;) + + } + imageAltText="No builds has been runs from a while..." + imagePath="https://ms.gallerycdn.vsassets.io/extensions/ms/vss-releasemanagement-web/18.166.0.311329757/1586412473334/release-landing/zerodata-release-management-new.png" + /> +
+ ); } else { return (
); } @@ -461,7 +502,7 @@ class CICDDashboard extends React.Component<{}, {}> { ) } else { - return (
) + return this.renderZeroData(tabId); } } else if(tabId === "builds") { return ( @@ -473,9 +514,9 @@ class CICDDashboard extends React.Component<{}, {}> { role="table"/> )} - ) + ); } else { - return (
); + return this.renderZeroData(tabId); } } @@ -483,43 +524,95 @@ class CICDDashboard extends React.Component<{}, {}> { return ( + tabSize={TabSize.Tall} + renderAdditionalContent={this.renderOptionsFilterView}> ); } + private getActiveBuildStatusCount(statusToFind:BuildStatus, builds:Array) { + if(statusToFind === BuildStatus.InProgress || statusToFind === BuildStatus.NotStarted) { + return builds.filter(x=> x.status === statusToFind).length; + } + return 0; + } + + private getCompletedBuildStatusCount(statusToFind:BuildStatus, resultToFind:BuildResult) { + if(statusToFind === BuildStatus.None && resultToFind === BuildResult.None){ + return this.state.buildDefs.filter(x=> x.latestCompletedBuild === undefined && x.latestBuild === undefined).length; + } + else if(statusToFind === BuildStatus.InProgress || statusToFind === BuildStatus.NotStarted) { + return this.state.builds.filter(x=> x.status === statusToFind).length; + } else if(statusToFind === BuildStatus.None) { + return this.state.buildDefs.filter(x=> x.latestBuild !== undefined && x.latestBuild.result == resultToFind).length; + } + return this.state.buildDefs.filter(x=> x.latestBuild !== undefined && x.latestBuild.status === statusToFind && x.latestBuild.result == resultToFind).length; + } + + public renderOptionsFilterView() : JSX.Element { + return ( +
+  {buildNeverQueued.value}   +  {buildCancelled.value}   +  {buildInPending.value}   +  {buildInProgress.value}   +  {buildSucceeded.value}   +  {buildInWarning.value}   +  {buildInError.value}  |   + + +     + { + isFullScreen.value = !isFullScreen.value; + const layoutService = await SDK.getService(CommonServiceIds.HostPageLayoutService); + layoutService.setFullScreenMode(isFullScreen.value); + }} > + + +
+ ); + } + + public renderHeader() : JSX.Element { + if(!isFullScreen.value) { + return ( + + + + + CI/CD Dashboard + + + + {this.extensionVersion}  + + + + ); + } else { + return (
); + } + } + public render() : JSX.Element { return ( - - - - - CI/CD Dashboard - - - - {this.extensionVersion}  - send a request - - - + {this.renderHeader()}
{this.renderTabBar()}
- - {(props: { selectedTabId: string, isLoading: boolean }) => { + + {(props: { selectedTabId: string, isLoading: boolean}) => { let errorOnTopFilter = ( { onSelect={this.onAllDeploymentSelected} selection={this.allDeploymentSelection} hideClearAction={true}/> + { return ( {(props: { selectedTabId: string, refreshUI: string }) => { - if(this.state.buildDefs.length === 0){ + if(this.state.buildDefs === undefined || this.state.buildDefs.length === 0){ + return this.renderZeroData(this.selectedTabId.value); + } else if(this.state.buildDefs.length > 0 && this.state.builds.length === 0 && props.selectedTabId === "builds") { return this.renderZeroData(this.selectedTabId.value); } else { return ( @@ -617,9 +713,9 @@ class CICDDashboard extends React.Component<{}, {}> { -
- { this.renderTab(props.selectedTabId) } -
+
+ { this.renderTab(props.selectedTabId) } +
); @@ -630,7 +726,6 @@ class CICDDashboard extends React.Component<{}, {}> { } }} -
diff --git a/src/ext/Dashboard/dataContext.tsx b/src/ext/Dashboard/dataContext.tsx index 199c80f1..1fc2cbac 100644 --- a/src/ext/Dashboard/dataContext.tsx +++ b/src/ext/Dashboard/dataContext.tsx @@ -10,7 +10,8 @@ export const DataContext = React.createContext( builds: Array(), releases: Array(), projects: Array(), - showAllBuildDeployment: false + showAllBuildDeployment: false, + fullScreenMode: false } } ); diff --git a/src/ext/Dashboard/tableData.tsx b/src/ext/Dashboard/tableData.tsx index 56d4aa87..caa0c822 100644 --- a/src/ext/Dashboard/tableData.tsx +++ b/src/ext/Dashboard/tableData.tsx @@ -41,10 +41,10 @@ export const buildColumns : ITableColumn[] = [ }, { id: "BuildInfo01", - name: "Build # | Branch/commit", + name: "Build # | Branch & commit", onSize: buildOnSize, renderCell: renderBuildInfo01Cell, - width: new ObservableValue(-30), + width: new ObservableValue(-25), }, { id: "ReleaseInfo01", @@ -55,7 +55,7 @@ export const buildColumns : ITableColumn[] = [ { id: "BuildInfo02", renderCell: renderBuildInfo02Cell, - width: 150, + width: 160, } ] @@ -72,7 +72,7 @@ export const dashboardColumns : ITableColumn[] = [ name: "Last Run", onSize: buildRefOnSize, renderCell: renderLastBuild01, - width: new ObservableValue(-30) + width: new ObservableValue(-25) }, { id: "ReleaseInfo01", @@ -83,6 +83,6 @@ export const dashboardColumns : ITableColumn[] = [ { id:"LastBuildInfo02", renderCell: renderLastBuild02, - width: 150 + width: 160 } ]; \ No newline at end of file