Skip to content

Commit

Permalink
refactor(core/delivery): Convert ExecutionDetails to react (#4282)
Browse files Browse the repository at this point in the history
  • Loading branch information
Justin Reynolds authored Oct 20, 2017
1 parent 5ab83af commit 5ca919a
Show file tree
Hide file tree
Showing 51 changed files with 758 additions and 656 deletions.
11 changes: 6 additions & 5 deletions app/scripts/modules/core/src/delivery/delivery.module.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,28 @@
const angular = require('angular');

import { DELIVERY_STATES } from './delivery.states';
import { EXECUTION_DETAILS_COMPONENT } from './details/executionDetails.component';
import { EXECUTION_DETAILS_CONTROLLER } from './details/executionDetails.controller';
import { BUILD_DISPLAY_NAME_FILTER } from './executionBuild/buildDisplayName.filter';
import { EXECUTION_COMPONENT } from './executionGroup/execution/execution.component';
import { EXECUTION_DETAILS_SECTION_NAV } from './details/executionDetailsSectionNav.component';
import { EXECUTION_FILTER_SERVICE } from 'core/delivery/filter/executionFilter.service';
import { STAGE_FAILURE_MESSAGE_COMPONENT } from './stageFailureMessage/stageFailureMessage.component';
import { CORE_DELIVERY_DETAILS_SINGLEEXECUTIONDETAILS } from './details/singleExecutionDetails.component';

import { STAGE_DETAILS_COMPONENT } from './details/stageDetails.component';
import { STAGE_SUMMARY_COMPONENT } from './details/stageSummary.component';

module.exports = angular.module('spinnaker.delivery', [
EXECUTION_DETAILS_CONTROLLER,
CORE_DELIVERY_DETAILS_SINGLEEXECUTIONDETAILS,
EXECUTION_COMPONENT,
EXECUTION_DETAILS_COMPONENT,
EXECUTION_DETAILS_SECTION_NAV,
EXECUTION_FILTER_SERVICE,

BUILD_DISPLAY_NAME_FILTER,

require('./manualExecution/manualPipelineExecution.controller.js').name,

STAGE_FAILURE_MESSAGE_COMPONENT,
STAGE_DETAILS_COMPONENT,
STAGE_SUMMARY_COMPONENT,

require('../utils/appendTransform.js').name,
require('../utils/moment.js').name,
Expand Down
193 changes: 191 additions & 2 deletions app/scripts/modules/core/src/delivery/details/ExecutionDetails.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,197 @@
import * as React from 'react';
import { Subscription } from 'rxjs';

import { Application } from 'core/application/application.model';
import { IExecution } from 'core/domain';
import { IExecution, IExecutionStage, IExecutionStageSummary, IStageTypeConfig } from 'core/domain';
import { ReactInjector } from 'core/reactShims';
import { StageDetails } from './StageDetails';
import { StageSummary } from './StageSummary';

import './executionDetails.less';

export interface IExecutionDetailsProps {
application: Application;
execution: IExecution;
standalone: boolean;
standalone?: boolean;
}

export interface IExecutionDetailsState {
detailsStageConfig: IStageTypeConfig;
stageSummary: IExecutionStageSummary;
stage: IExecutionStage;
summaryStageConfig: IStageTypeConfig;
}

export interface IExecutionStateParams {
stage?: number;
subStage?: number;
step?: number;
stageId?: string;
refId?: string;
}

export class ExecutionDetails extends React.Component<IExecutionDetailsProps, IExecutionDetailsState> {
public static defaultProps: Partial<IExecutionDetailsProps> = {
standalone: false
};

private groupsUpdatedSubscription: Subscription;
private locationChangeUnsubscribe: Function;

constructor(props: IExecutionDetailsProps) {
super(props);
this.state = this.getUpdatedState();
}

private getStageParamsFromStageId(stageId: string, summaries: IExecutionStageSummary[]): IExecutionStateParams {
let stage, subStage, step;
summaries.some((summary, index) => {
let stepIndex = (summary.stages || []).findIndex(s2 => s2.id === stageId);
if (stepIndex !== -1) {
step = stepIndex;
stage = index;
return true;
}
if (summary.type === 'group' && summary.groupStages) {
summary.groupStages.some((groupStage, subIndex) => {
stepIndex = (groupStage.stages || []).findIndex((gs) => gs.id === stageId);
if (stepIndex !== -1) {
step = stepIndex;
stage = index;
subStage = subIndex;
return true;
}
return false;
});
}
return false;
});

if (stage) {
return { stage, subStage, step, stageId: null };
}
return null;
}

private getStageParamsFromRefId(refId: string, summaries: IExecutionStageSummary[]): IExecutionStateParams {
let stage, subStage;

const stageIndex = summaries.findIndex((summary) => summary.refId === refId);
if (stageIndex !== -1) {
return { stage: stageIndex, refId: null };
}

summaries.some((summary, index) => {
if (summary.type === 'group' && summary.groupStages) {
const subStageIndex = summary.groupStages.findIndex(s2 => s2.refId === refId);
if (subStageIndex !== -1) {
stage = index;
subStage = subStageIndex;
return true;
}
}
return false;
});
if (stage && subStage !== undefined) {
return { stage, subStage, refId: null };
}

return { refId: null };
}

private getCurrentStage(summaries: IExecutionStageSummary[]): { stage: number, subStage: number } {
const { $state, $stateParams } = ReactInjector;
if ($stateParams.stageId) {
const params = this.getStageParamsFromStageId($stateParams.stageId, summaries);
if (params) {
$state.go('.', params, { location: 'replace' });
return { stage: params.stage, subStage: params.subStage };
}
}
if ($stateParams.refId) {
const params = this.getStageParamsFromRefId($stateParams.refId, summaries);
if (params) {
$state.go('.', params, { location: 'replace' });
return { stage: params.stage, subStage: params.subStage };
}
}

return { stage: parseInt($stateParams.stage, 10), subStage: parseInt($stateParams.subStage, 10) };
}

private getCurrentStep() {
return parseInt(ReactInjector.$stateParams.step, 10);
}

private getStageSummary() {
const stages = this.props.execution.stageSummaries || [];
const { stage, subStage } = this.getCurrentStage(stages);
let currentStage = null;
if (stage !== undefined) {
currentStage = stages[stage];
if (currentStage && subStage !== undefined && currentStage.groupStages) {
currentStage = currentStage.groupStages[subStage];
}
}
return currentStage;
}

private getDetailsStageConfig(stageSummary: IExecutionStageSummary): IStageTypeConfig {
if (stageSummary && ReactInjector.$stateParams.step !== undefined) {
const step = stageSummary.stages[this.getCurrentStep()] || stageSummary.masterStage;
return ReactInjector.pipelineConfig.getStageConfig(step);
}
return null;
}

private getSummaryStageConfig(stageSummary: IExecutionStageSummary): IStageTypeConfig {
if (stageSummary && ReactInjector.$stateParams.stage !== undefined) {
return ReactInjector.pipelineConfig.getStageConfig(stageSummary);
}
return {} as IStageTypeConfig;
}

public getUpdatedState(): IExecutionDetailsState {
const stageSummary = this.getStageSummary();
if (stageSummary) {
const stage = stageSummary.stages[this.getCurrentStep()] || stageSummary.masterStage;
const summaryStageConfig = this.getSummaryStageConfig(stageSummary);
const detailsStageConfig = this.getDetailsStageConfig(stageSummary);
return { stageSummary, stage, summaryStageConfig, detailsStageConfig };
}
return {} as IExecutionDetailsState;
}

public updateStage() {
this.setState(this.getUpdatedState());
}

public componentDidMount(): void {
this.locationChangeUnsubscribe = ReactInjector.$uiRouter.transitionService.onSuccess({}, () => this.updateStage());
// Since stages and tasks can get updated without the reference to the execution changing, subscribe to the execution updated stream here too
this.groupsUpdatedSubscription = ReactInjector.executionFilterService.groupsUpdatedStream.subscribe(() => this.updateStage());

this.updateStage();
}

public componentWillReceiveProps() {
this.updateStage();
}

public componentWillUnmount(): void {
this.groupsUpdatedSubscription.unsubscribe();
this.locationChangeUnsubscribe();
}

public render() {
const { application, execution } = this.props;
const { detailsStageConfig, stage, stageSummary, summaryStageConfig } = this.state;

return (
<div className="execution-details">
<StageSummary application={application} execution={execution} config={summaryStageConfig} stage={stage} stageSummary={stageSummary} />
<StageDetails application={application} execution={execution} stage={stage} config={detailsStageConfig} />
</div>
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const Section = (props: { section: string }): JSX.Element => {
<li>
<UISrefActive class="active">
<UISref to=".execution" params={{details: props.section}}>
<span onClick={clicked}>{robotToHuman(props.section)}</span>
<a onClick={clicked}>{robotToHuman(props.section)}</a>
</UISref>
</UISrefActive>
</li>
Expand Down
70 changes: 70 additions & 0 deletions app/scripts/modules/core/src/delivery/details/StageDetails.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import * as React from 'react';
import { BindAll } from 'lodash-decorators';

import { Application } from 'core/application';
import { IExecution, IExecutionStage, IStageTypeConfig } from 'core/domain';
import { NgReact } from 'core/reactShims';
import { StatusGlyph } from 'core/task/StatusGlyph';
import { robotToHuman } from 'core/presentation/robotToHumanFilter/robotToHuman.filter';

export interface IStageDetailsProps {
application: Application;
config: IStageTypeConfig;
execution: IExecution;
stage: IExecutionStage;
}

export interface IStageDetailsState {
sourceUrl?: string;
configSections?: string[];
}

@BindAll()
export class StageDetails extends React.Component<IStageDetailsProps, IStageDetailsState> {
constructor(props: IStageDetailsProps) {
super(props);
this.state = this.getState();
}

private getState(): IStageDetailsState {
let configSections: string[] = [];
let sourceUrl = require('./defaultExecutionDetails.html');

const stageConfig = this.props.config;
if (stageConfig) {
if (stageConfig.executionConfigSections) {
configSections = stageConfig.executionConfigSections;
}
if (stageConfig.executionDetailsUrl) {
sourceUrl = stageConfig.executionDetailsUrl;
}
}
return { configSections, sourceUrl };
}

public componentWillReceiveProps() {
this.setState(this.getState());
}

public render(): React.ReactElement<StageDetails> {
const { application, execution, stage } = this.props;
const { sourceUrl, configSections } = this.state;

const { StageDetailsWrapper } = NgReact;

if ( sourceUrl ) {
return (
<div className="stage-details">
<div className="stage-details-heading">
<h5>
<StatusGlyph item={stage} />
{robotToHuman(stage.name || stage.type)}
</h5>
</div>
<StageDetailsWrapper application={application} execution={execution} sourceUrl={sourceUrl} configSections={configSections} stage={stage} />
</div>
);
}
return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Application } from 'core/application';
import { IExecution, IExecutionStage } from 'core/domain';

export interface IStageDetailsWrapperProps {
application: Application;
configSections: string[];
execution: IExecution;
stage: IExecutionStage;
sourceUrl: string;
}
39 changes: 39 additions & 0 deletions app/scripts/modules/core/src/delivery/details/StageSummary.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import * as React from 'react';
import { BindAll } from 'lodash-decorators';

import { Application } from 'core/application';
import { IExecution, IExecutionStage, IExecutionStageSummary, IStageTypeConfig } from 'core/domain';
import { NgReact } from 'core/reactShims';

export interface IStageSummaryProps {
application: Application;
execution: IExecution;
config: IStageTypeConfig;
stage: IExecutionStage;
stageSummary: IExecutionStageSummary;
}

export interface IStageSummaryState {
}

@BindAll()
export class StageSummary extends React.Component<IStageSummaryProps, IStageSummaryState> {

private getSourceUrl(): string {
return this.props.config.executionSummaryUrl || require('../../pipeline/config/stages/core/executionSummary.html');
}

public render(): React.ReactElement<StageSummary> {
const sourceUrl = this.getSourceUrl();
if (sourceUrl) {
const { application, execution, stage, stageSummary } = this.props;
const { StageSummaryWrapper } = NgReact;
return (
<div className="stage-summary">
<StageSummaryWrapper application={application} execution={execution} sourceUrl={sourceUrl} stage={stage} stageSummary={stageSummary} />
</div>
);
}
return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Application } from 'core/application';
import { IExecution, IExecutionStage, IExecutionStageSummary } from 'core/domain';

export interface IStageSummaryWrapperProps {
application: Application;
execution: IExecution;
sourceUrl: string;
stage: IExecutionStage;
stageSummary: IExecutionStageSummary;
}

This file was deleted.

Loading

0 comments on commit 5ca919a

Please sign in to comment.