Skip to content

Commit

Permalink
refactor(core): Convert executions to react (#4260)
Browse files Browse the repository at this point in the history
  • Loading branch information
Justin Reynolds authored Oct 13, 2017
1 parent 0572dbf commit a4cfb59
Show file tree
Hide file tree
Showing 21 changed files with 573 additions and 559 deletions.
7 changes: 0 additions & 7 deletions app/scripts/modules/core/src/delivery/delivery.module.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,6 @@ import { EXECUTION_DETAILS_COMPONENT } from './details/executionDetails.componen
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_FILTERS_COMPONENT } from './filter/executionFilters.component';
import { EXECUTION_GROUPS_COMPONENT } from './executionGroup/executionGroups.component';
import { EXECUTIONS_COMPONENT } from './executions/executions.component';
import { STAGE_FAILURE_MESSAGE_COMPONENT } from './stageFailureMessage/stageFailureMessage.component';
import { CORE_DELIVERY_DETAILS_SINGLEEXECUTIONDETAILS } from './details/singleExecutionDetails.component';

Expand All @@ -18,15 +15,11 @@ module.exports = angular.module('spinnaker.delivery', [
EXECUTION_DETAILS_CONTROLLER,
CORE_DELIVERY_DETAILS_SINGLEEXECUTIONDETAILS,
EXECUTION_COMPONENT,
EXECUTION_GROUPS_COMPONENT,
EXECUTION_DETAILS_COMPONENT,
EXECUTIONS_COMPONENT,
require('./details/executionDetailsSectionNav.directive.js').name,

BUILD_DISPLAY_NAME_FILTER,

EXECUTION_FILTERS_COMPONENT,

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

STAGE_FAILURE_MESSAGE_COMPONENT,
Expand Down
10 changes: 4 additions & 6 deletions app/scripts/modules/core/src/delivery/delivery.states.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { module } from 'angular';

import { APPLICATION_STATE_PROVIDER, ApplicationStateProvider } from 'core/application/application.state.provider';
import { INestedState, StateConfigProvider } from 'core/navigation/state.provider';
import {
APPLICATION_STATE_PROVIDER, ApplicationStateProvider,
} from 'core/application/application.state.provider';
import { filterModelConfig } from './filter/executionFilter.model';

import { Executions } from 'core/delivery/executions/Executions';

export const DELIVERY_STATES = 'spinnaker.core.delivery.states';
module(DELIVERY_STATES, [
APPLICATION_STATE_PROVIDER
Expand Down Expand Up @@ -66,9 +66,7 @@ module(DELIVERY_STATES, [
name: 'executions',
url: `?${stateConfigProvider.paramsToQuery(filterModelConfig)}`,
views: {
'pipelines': {
template: '<executions application="$resolve.app"></executions>',
},
'pipelines': { component: Executions, $type: 'react' },
},
params: stateConfigProvider.buildDynamicParams(filterModelConfig),
children: [executionDetails],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,12 @@ export class ExecutionGroups extends React.Component<IExecutionGroupsProps, IExe
super(props);
const { executionFilterModel, executionFilterService, stateEvents } = ReactInjector;
this.state = {
groups: executionFilterModel.asFilterModel.groups,
groups: executionFilterModel.asFilterModel.groups.slice(),
showingDetails: this.showingDetails()
};

this.applicationRefreshUnsubscribe = this.props.application.executions.onRefresh(null, () => { this.forceUpdate(); });
this.groupsUpdatedSubscription = executionFilterService.groupsUpdatedStream.subscribe(() => { this.setState({groups: executionFilterModel.asFilterModel.groups}); });
this.groupsUpdatedSubscription = executionFilterService.groupsUpdatedStream.subscribe(() => { this.setState({groups: executionFilterModel.asFilterModel.groups.slice()}); });
this.stateChangeSuccessSubscription = stateEvents.stateChangeSuccess.subscribe(() => {
const detailsShown = this.showingDetails();
if (detailsShown !== this.state.showingDetails) {
Expand All @@ -44,6 +44,10 @@ export class ExecutionGroups extends React.Component<IExecutionGroupsProps, IExe
return ReactInjector.$state.includes('**.execution');
}

public shouldComponentUpdate(_nextProps_: IExecutionGroupsProps, nextState: IExecutionGroupsState): boolean {
return nextState.groups !== this.state.groups || nextState.showingDetails !== this.state.showingDetails;
}

public componentWillUnmount(): void {
if (this.applicationRefreshUnsubscribe) {
this.applicationRefreshUnsubscribe();
Expand All @@ -63,15 +67,17 @@ export class ExecutionGroups extends React.Component<IExecutionGroupsProps, IExe
const executionGroups = (this.state.groups || []).map((group: IExecutionGroup) => <ExecutionGroup key={group.heading} group={group} application={this.props.application}/>);

return (
<div className={className}>
{ !hasGroups && (
<div className="text-center">
<h4>No executions match the filters you've selected.</h4>
<div className="execution-groups-section">
<div className={className}>
{ !hasGroups && (
<div className="text-center">
<h4>No executions match the filters you've selected.</h4>
</div>
)}
<div className="execution-groups all-execution-groups">
{executionGroups}
</div>
)}
<div className="execution-groups all-execution-groups">
{executionGroups}
</div>
</div>
</div>
);
}
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
execution-groups {
.execution-groups-section {
display: flex;
flex: 1 1 auto;
overflow-x: visible;
Expand Down
151 changes: 151 additions & 0 deletions app/scripts/modules/core/src/delivery/executions/Executions.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import * as React from 'react';
import { ReactWrapper, mount } from 'enzyme';
import { set } from 'lodash';
import { IScope, ITimeoutService, mock, noop } from 'angular';

import { Application } from 'core/application';
import { APPLICATION_MODEL_BUILDER, ApplicationModelBuilder } from 'core/application/applicationModel.builder';
import { EXECUTION_FILTER_MODEL } from 'core/delivery';
import { EXECUTION_FILTER_SERVICE } from 'core/delivery/filter/executionFilter.service';
import { HELP_CONTENTS_REGISTRY } from 'core/help/helpContents.registry';
import { HELP_CONTENTS } from 'core/help/help.contents';
import { INSIGHT_FILTER_STATE_MODEL } from 'core/insight/insightFilterState.model';
import { REACT_MODULE, ReactInjector } from 'core/reactShims';
import { SCROLL_TO_SERVICE } from 'core/utils'
import { IExecutionsProps, IExecutionsState, Executions } from './Executions';

describe('<Executions/>', () => {
let component: ReactWrapper<IExecutionsProps, IExecutionsState>;
let application: Application;
let scope: IScope;
let $timeout: ITimeoutService;

function initializeApplication(data?: any) {
set(application, 'executions.activate', noop);
set(application, 'pipelineConfigs.activate', noop);
if (data && data.executions) {
application.executions.data = data.executions;
application.executions.loaded = true;
}
if (data && data.pipelineConfigs) {
application.pipelineConfigs.data = data.pipelineConfigs;
application.pipelineConfigs.loaded = true;
}

component = mount(<Executions app={application} />);
}

beforeEach(mock.module(
APPLICATION_MODEL_BUILDER,
EXECUTION_FILTER_MODEL,
EXECUTION_FILTER_SERVICE,
HELP_CONTENTS_REGISTRY,
HELP_CONTENTS,
INSIGHT_FILTER_STATE_MODEL,
REACT_MODULE,
SCROLL_TO_SERVICE,
));
beforeEach(mock.inject((_$timeout_: ITimeoutService, $rootScope: IScope, applicationModelBuilder: ApplicationModelBuilder) => {
scope = $rootScope.$new();
$timeout = _$timeout_;
application = applicationModelBuilder.createApplication('app', {key: 'executions', lazy: true}, {key: 'pipelineConfigs', lazy: true});
}));

it('should not set loading flag to false until executions and pipeline configs have been loaded', function () {
initializeApplication();

expect(component.state().loading).toBe(true);
application.executions.dataUpdated();
application.pipelineConfigs.dataUpdated();
scope.$digest();
$timeout.flush();
expect(component.state().loading).toBe(false);
});

describe('auto-scrolling behavior', function () {

beforeEach(function () {
spyOn(ReactInjector.scrollToService, 'scrollTo');
});

it('should scroll execution into view on initialization if an execution is present in state params', function () {
ReactInjector.$stateParams.executionId = 'a';

initializeApplication({ pipelineConfigs: [], executions: []});
scope.$digest();

expect((ReactInjector.scrollToService.scrollTo as any).calls.count()).toBe(1);
});

it('should NOT scroll execution into view on initialization if none present in state params', function () {
initializeApplication();
scope.$digest();

expect((ReactInjector.scrollToService.scrollTo as any).calls.count()).toBe(0);
});

// TODO: Figure out how to test transitions
// it('should scroll execution into view on state change success if no execution id in state params', function () {
// initializeApplication();
// scope.$digest();

// expect((ReactInjector.scrollToService.scrollTo as any).calls.count()).toBe(0);

// scope.$broadcast('$stateChangeSuccess', {name: 'executions.execution'}, {executionId: 'a'}, {name: 'executions'}, {});
// expect((ReactInjector.scrollToService.scrollTo as any).calls.count()).toBe(1);
// });

// it('should scroll execution into view on state change success if execution id changes', function () {
// initializeApplication();
// scope.$digest();

// expect((ReactInjector.scrollToService.scrollTo as any).calls.count()).toBe(0);

// scope.$broadcast('$stateChangeSuccess', {name: 'executions.execution'}, {executionId: 'a'}, {name: 'executions.execution'}, {executionId: 'b'});
// expect((ReactInjector.scrollToService.scrollTo as any).calls.count()).toBe(1);
// });

// it('should scroll into view if no params change, because the user clicked on a link somewhere else in the page', function () {
// const params = {executionId: 'a', step: 'b', stage: 'c', details: 'd'};

// initializeApplication();
// scope.$digest();

// expect((ReactInjector.scrollToService.scrollTo as any).calls.count()).toBe(0);

// scope.$broadcast('$stateChangeSuccess', {name: 'executions.execution'}, params, {name: 'executions.execution'}, params);
// expect((ReactInjector.scrollToService.scrollTo as any).calls.count()).toBe(1);
// });

// it('should NOT scroll into view if step changes', function () {
// const toParams = {executionId: 'a', step: 'b', stage: 'c', details: 'd'},
// fromParams = {executionId: 'a', step: 'c', stage: 'c', details: 'd'};

// initializeApplication();
// scope.$digest();
// scope.$broadcast('$stateChangeSuccess', {name: 'executions'}, toParams, {name: 'executions'}, fromParams);
// expect((ReactInjector.scrollToService.scrollTo as any).calls.count()).toBe(0);
// });

// it('should NOT scroll into view if stage changes', function () {
// const toParams = {executionId: 'a', step: 'b', stage: 'c', details: 'd'},
// fromParams = {executionId: 'a', step: 'b', stage: 'e', details: 'd'};

// initializeApplication();
// scope.$digest();
// scope.$broadcast('$stateChangeSuccess', {name: 'executions'}, toParams, {name: 'executions'}, fromParams);
// expect((ReactInjector.scrollToService.scrollTo as any).calls.count()).toBe(0);
// });

// it('should NOT scroll into view if detail changes', function () {
// const toParams = {executionId: 'a', step: 'b', stage: 'c', details: 'd'},
// fromParams = {executionId: 'a', step: 'b', stage: 'c', details: 'e'};

// initializeApplication();
// scope.$digest();
// scope.$broadcast('$stateChangeSuccess', {name: 'executions'}, toParams, {name: 'executions'}, fromParams);
// expect((ReactInjector.scrollToService.scrollTo as any).calls.count()).toBe(0);
// });

});
});
Loading

0 comments on commit a4cfb59

Please sign in to comment.