Skip to content

Commit

Permalink
feat(core): Add ability to set Default Tag filters for an application…
Browse files Browse the repository at this point in the history
… in application config (spinnaker#10020)

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
  • Loading branch information
2 people authored and sahititarigoppula committed Jun 5, 2024
1 parent 7b8b31d commit e13dda5
Show file tree
Hide file tree
Showing 7 changed files with 307 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { CORE_APPLICATION_CONFIG_APPLICATIONSNAPSHOTSECTION_COMPONENT } from './
import { CHAOS_MONKEY_CONFIG_COMPONENT } from '../../chaosMonkey/chaosMonkeyConfig.component';
import { SETTINGS } from '../../config/settings';
import { APPLICATION_DATA_SOURCE_EDITOR } from './dataSources/applicationDataSourceEditor.component';
import { DEFAULT_TAG_FILTER_CONFIG } from './defaultTagFilter/defaultTagFilterConfig.component';
import { DELETE_APPLICATION_SECTION } from './deleteApplicationSection.module';
import { CORE_APPLICATION_CONFIG_LINKS_APPLICATIONLINKS_COMPONENT } from './links/applicationLinks.component';
import { ApplicationWriter } from '../service/ApplicationWriter';
Expand All @@ -25,6 +26,7 @@ module(CORE_APPLICATION_CONFIG_APPLICATIONCONFIG_CONTROLLER, [
CHAOS_MONKEY_CONFIG_COMPONENT,
TRAFFIC_GUARD_CONFIG_COMPONENT,
CORE_APPLICATION_CONFIG_LINKS_APPLICATIONLINKS_COMPONENT,
DEFAULT_TAG_FILTER_CONFIG,
]).controller('ApplicationConfigController', [
'$state',
'app',
Expand Down Expand Up @@ -63,6 +65,30 @@ module(CORE_APPLICATION_CONFIG_APPLICATIONCONFIG_CONTROLLER, [
});
};

this.defaultTagFilterProps = {
isSaving: false,
saveError: false,
};
this.updateDefaultTagFilterConfigs = (tagConfigs /* IDefaultTagFilterConfig[] */) => {
const applicationAttributes = cloneDeep(this.application.attributes);
applicationAttributes.defaultFilteredTags = tagConfigs;
$scope.$applyAsync(() => {
this.defaultTagFilterProps.isSaving = true;
this.defaultTagFilterProps.saveError = false;
});
ApplicationWriter.updateApplication(applicationAttributes)
.then(() => {
$scope.$applyAsync(() => {
this.defaultTagFilterProps.isSaving = false;
this.application.attributes = applicationAttributes;
});
})
.catch(() => {
this.defaultTagFilterProps.isSaving = false;
this.defaultTagFilterProps.saveError = true;
});
};

this.notifications = [];
this.updateNotifications = (notifications) => {
$scope.$applyAsync(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,15 @@
>
</custom-banner-config>
</page-section>
<page-section key="default-filters" label="Default Filters">
<default-tag-filter-config
default-tag-filter-configs="config.application.attributes.defaultFilteredTags"
is-saving="config.defaultTagFilterProps.isSaving"
save-error="config.defaultTagFilterProps.saveError"
update-default-tag-filter-configs="config.updateDefaultTagFilterConfigs"
>
</default-tag-filter-config>
</page-section>
<page-section key="delete" label="Delete Application">
<delete-application-section application="config.application"></delete-application-section>
</page-section>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { shallow } from 'enzyme';
import React from 'react';

import type { IDefaultTagFilterConfig } from './DefaultTagFilterConfig';
import { DefaultTagFilterConfig } from './DefaultTagFilterConfig';
import { noop } from '../../../utils';

describe('<DefaultTagFilterConfig />', () => {
let tagConfigs: IDefaultTagFilterConfig[];
let wrapper: any;

beforeEach(() => {
tagConfigs = getTestDefaultFilterTagConfigs();
wrapper = shallow(
<DefaultTagFilterConfig
defaultTagFilterConfigs={tagConfigs}
isSaving={false}
saveError={false}
updateDefaultTagFilterConfigs={noop}
/>,
);
});

describe('view', () => {
it('renders a row for each banner config', () => {
expect(wrapper.find('.default-filter-config-row').length).toEqual(tagConfigs.length);
});
it('renders an "add" button', () => {
expect(wrapper.find('.add-new').length).toEqual(1);
});
});

describe('functionality', () => {
it('update default tag filter config', () => {
expect(wrapper.state('defaultTagFilterConfigsEditing')).toEqual(tagConfigs);
wrapper
.find('textarea')
.at(1)
.simulate('change', { target: { value: 'hello' } });
const updatedConfigs = [
{
...tagConfigs[0],
tagValue: 'hello',
},
{
...tagConfigs[1],
},
];
expect(wrapper.state('defaultTagFilterConfigsEditing')).toEqual(updatedConfigs);
});
it('add default filter tag config', () => {
expect(wrapper.state('defaultTagFilterConfigsEditing').length).toEqual(2);
wrapper.find('.add-new').simulate('click');
expect(wrapper.state('defaultTagFilterConfigsEditing').length).toEqual(3);
});
it('remove default filter tag config', () => {
expect(wrapper.state('defaultTagFilterConfigsEditing').length).toEqual(2);
wrapper.find('.default-filter-config-remove').at(1).simulate('click');
expect(wrapper.state('defaultTagFilterConfigsEditing').length).toEqual(1);
});
});
});

export function getTestDefaultFilterTagConfigs(): IDefaultTagFilterConfig[] {
return [
{
tagName: 'Pipeline Type',
tagValue: 'Deployment Pipelines',
},
{
tagName: 'Pipeline Type',
tagValue: 'Repair Pipelines',
},
];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
import { isEqual } from 'lodash';
import React from 'react';

import { ConfigSectionFooter } from '../footer/ConfigSectionFooter';
import { noop } from '../../../utils';

import './defaultTagFilterConfig.less';

export interface IDefaultTagFilterConfig {
tagName: string;
tagValue: string;
}

export interface IDefaultTagFilterProps {
defaultTagFilterConfigs: IDefaultTagFilterConfig[];
isSaving: boolean;
saveError: boolean;
updateDefaultTagFilterConfigs: (defaultTagFilterConfigs: IDefaultTagFilterConfig[]) => void;
}

export interface IDefaultTagFilterState {
defaultTagFilterConfigsEditing: IDefaultTagFilterConfig[];
}

export class DefaultTagFilterConfig extends React.Component<IDefaultTagFilterProps, IDefaultTagFilterState> {
public static defaultProps: Partial<IDefaultTagFilterProps> = {
defaultTagFilterConfigs: [],
isSaving: false,
saveError: false,
updateDefaultTagFilterConfigs: noop,
};

constructor(props: IDefaultTagFilterProps) {
super(props);
this.state = {
defaultTagFilterConfigsEditing: props.defaultTagFilterConfigs,
};
}

private onTagNameChange = (idx: number, text: string) => {
this.setState({
defaultTagFilterConfigsEditing: this.state.defaultTagFilterConfigsEditing.map((config, i) => {
if (i === idx) {
return {
...config,
tagName: text,
};
}
return config;
}),
});
};

private onTagValueChange = (idx: number, text: string) => {
this.setState({
defaultTagFilterConfigsEditing: this.state.defaultTagFilterConfigsEditing.map((config, i) => {
if (i === idx) {
return {
...config,
tagValue: text,
};
}
return config;
}),
});
};

private addFilterTag = (): void => {
this.setState({
defaultTagFilterConfigsEditing: this.state.defaultTagFilterConfigsEditing.concat([
{
tagName: 'Name of the tag (E.g. Pipeline Type)',
tagValue: 'Value of the tag (E.g. Default Pipelines)',
} as IDefaultTagFilterConfig,
]),
});
};

private removeFilterTag = (idx: number): void => {
this.setState({
defaultTagFilterConfigsEditing: this.state.defaultTagFilterConfigsEditing.filter((_config, i) => i !== idx),
});
};

private isDirty = (): boolean => {
return !isEqual(this.props.defaultTagFilterConfigs, this.state.defaultTagFilterConfigsEditing);
};

private onRevertClicked = (): void => {
this.setState({
defaultTagFilterConfigsEditing: this.props.defaultTagFilterConfigs,
});
};

private onSaveClicked = (): void => {
this.props.updateDefaultTagFilterConfigs(this.state.defaultTagFilterConfigsEditing);
};

public render() {
return (
<div className="default-filter-config-container">
<div className="default-filter-config-description">
Default Tag filters allow you to specify which tags are immediately filtered to when the pipeline execution
page is loaded in.
</div>
<div className="col-md-10 col-md-offset-1">
<table className="table table-condensed">
<thead>
<tr>
<th>Tag Name</th>
<th>Tag Value</th>
</tr>
</thead>
<tbody>
{this.state.defaultTagFilterConfigsEditing.map((defaultTagFilter, idx) => (
<tr key={idx} className="default-filter-config-row">
<td>
<textarea
className="form-control input-sm"
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) =>
this.onTagNameChange(idx, e.target.value)
}
value={defaultTagFilter.tagName}
/>
</td>
<td>
<textarea
className="form-control input-sm"
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) =>
this.onTagValueChange(idx, e.target.value)
}
value={defaultTagFilter.tagValue}
/>
</td>
<td>
<button className="link default-filter-config-remove" onClick={() => this.removeFilterTag(idx)}>
<span className="glyphicon glyphicon-trash" />
</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
<div className="col-md-10 col-md-offset-1">
<button className="btn btn-block add-new" onClick={this.addFilterTag}>
<span className="glyphicon glyphicon-plus-sign" /> Add Default Filter
</button>
</div>
<ConfigSectionFooter
isDirty={this.isDirty()}
isValid={true}
isSaving={this.props.isSaving}
saveError={false}
onRevertClicked={this.onRevertClicked}
onSaveClicked={this.onSaveClicked}
/>
</div>
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { module } from 'angular';
import { react2angular } from 'react2angular';
import { DefaultTagFilterConfig } from './DefaultTagFilterConfig';
import { withErrorBoundary } from '../../../presentation/SpinErrorBoundary';

export const DEFAULT_TAG_FILTER_CONFIG = 'spinnaker.micros.application.defaultTagFilterConfig.component';
module(DEFAULT_TAG_FILTER_CONFIG, []).component(
'defaultTagFilterConfig',
react2angular(withErrorBoundary(DefaultTagFilterConfig, 'defaultTagFilterConfig'), [
'defaultTagFilterConfigs',
'isSaving',
'saveError',
'updateDefaultTagFilterConfigs',
]),
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.default-filter-config-container {
display: flex;
flex-direction: column;
}

.default-filter-config-description {
margin-bottom: 10px;
}
13 changes: 13 additions & 0 deletions packages/core/src/pipeline/executions/Executions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import React from 'react';
import type { Subscription } from 'rxjs';

import type { Application } from '../../application';
import type { IDefaultTagFilterConfig } from '../../application/config/defaultTagFilter/DefaultTagFilterConfig';
import { CreatePipeline } from '../config/CreatePipeline';
import { CreatePipelineButton } from '../create/CreatePipelineButton';
import type { IExecution, IPipeline, IPipelineCommand } from '../../domain';
Expand Down Expand Up @@ -74,6 +75,16 @@ export class Executions extends React.Component<IExecutionsProps, IExecutionsSta
}
};

private loadDefaultFilters = (): void => {
const defaultTags = this.props.app.attributes.defaultFilteredTags;
if (defaultTags != null) {
this.props.app.attributes.defaultFilteredTags.forEach((defaultTag: IDefaultTagFilterConfig) => {
ExecutionState.filterModel.asFilterModel.sortFilter.tags[`${defaultTag.tagName}:${defaultTag.tagValue}`] = true;
});
this.updateExecutionGroups(true);
}
};

private clearFilters = (): void => {
ExecutionFilterService.clearFilters();
this.updateExecutionGroups(true);
Expand Down Expand Up @@ -235,6 +246,8 @@ export class Executions extends React.Component<IExecutionsProps, IExecutionsSta
() => this.dataInitializationFailure(),
);

this.loadDefaultFilters();

$q.all([app.executions.ready(), app.pipelineConfigs.ready()]).then(() => {
this.updateExecutionGroups();
const nameOrIdToStart = ReactInjector.$stateParams.startManualExecution;
Expand Down

0 comments on commit e13dda5

Please sign in to comment.