Skip to content

Commit

Permalink
feat(2775): Add more search filters for events (#1273)
Browse files Browse the repository at this point in the history
Co-authored-by: Ming Hay Luk <[email protected]>
Co-authored-by: Ming-Hay <[email protected]>
  • Loading branch information
3 people authored Dec 6, 2024
1 parent 1a114de commit 2b21057
Show file tree
Hide file tree
Showing 5 changed files with 151 additions and 66 deletions.
63 changes: 39 additions & 24 deletions app/components/pipeline/modal/search-event/component.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,51 +8,66 @@ export default class PipelineModalSearchEventComponent extends Component {

@service shuttle;

@tracked sha;
@tracked searchField = 'message';

@tracked searchResults;
@tracked searchInput = null;

@tracked invalidSha;
@tracked searchResults = [];

isPr;
@tracked invalidSha = false;

constructor() {
super(...arguments);
isPr = this.router.currentRouteName.includes('pulls');

this.searchResults = [];
this.invalidSha = false;

this.isPr = this.router.currentRouteName.includes('pulls');
}
searchFieldOptions = ['message', 'sha', 'creator', 'author'];

@action
handleInput(inputEvent) {
const inputValue = inputEvent.target.value;
const inputValue = inputEvent?.target?.value?.trim() || this.searchInput;

if (!inputValue) {
this.searchResults = [];
this.invalidSha = false;
this.searchInput = null;

if (inputValue && inputValue.length > 0) {
return;
}

// Validate SHA
if (this.searchField === 'sha') {
const validHex = /^[0-9a-f]{1,40}$/;

if (!validHex.test(inputValue)) {
this.invalidSha = true;
} else {
this.invalidSha = false;
this.invalidSha = !validHex.test(inputValue);

const baseUrl = `/pipelines/${this.args.pipeline.id}/events?sha=${inputValue}&type=`;
const url = this.isPr ? `${baseUrl}pr` : `${baseUrl}pipeline`;
if (this.invalidSha) {
this.searchResults = [];

this.shuttle.fetchFromApi('get', url).then(events => {
this.searchResults = events;
});
return;
}
}

// Construct search URL with proper query parameters
const baseUrl = `/pipelines/${this.args.pipeline.id}/events?${
this.searchField
}=${encodeURIComponent(inputValue)}`;
const url = `${baseUrl}&type=${this.isPr ? 'pr' : 'pipeline'}`;

this.shuttle.fetchFromApi('get', url).then(events => {
this.searchResults = events;
});
}

@action
setSearchField(searchField) {
this.searchField = searchField;
this.handleInput();
}

@action
handleKeyPress(event) {
if (event.key === 'Escape') {
if (this.sha?.length > 0) {
if (this.searchInput?.length > 0) {
event.stopImmediatePropagation();
this.sha = null;
this.searchInput = null;
this.searchResults = [];
this.invalidSha = false;
}
Expand Down
13 changes: 9 additions & 4 deletions app/components/pipeline/modal/search-event/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,12 @@
max-width: 30rem;

#search-input {
#search-field-select {
width: 7rem;
margin-right: 0.5rem;
}

display: flex;
flex-direction: column;
justify-content: space-between;
margin-bottom: 1rem;

Expand All @@ -18,13 +22,14 @@
}

@include input.styles;
}

.invalid-sha {
color: colors.$sd-warn-bg;
}
.invalid-sha {
color: colors.$sd-warn-bg;
}

#search-results {
margin-top: 1rem;
max-height: 35rem;
overflow: scroll;
}
Expand Down
64 changes: 38 additions & 26 deletions app/components/pipeline/modal/search-event/template.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -7,37 +7,49 @@
<div class="modal-title">Search for an event</div>
</modal.header>
<modal.body>
<div id="search-description">
Search for an event by commit message, sha, author, or creator.
</div>
<div id="search-input">
<PowerSelect
id="search-field-select"
@options={{this.searchFieldOptions}}
@renderInPlace={{true}}
@selected={{this.searchField}}
@onChange={{this.setSearchField}}
as |selectedValue|
>
{{selectedValue}}
</PowerSelect>
<Input
@type="search"
@value={{this.sha}}
autofocus={{true}}
maxlength="40"
pattern="^[0-9a-fA-F]{1,40}$"
placeholder="Search for an event by SHA"
@value={{this.searchInput}}
maxlength="50"
placeholder="Search for an event.."
{{on "input" this.handleInput}}
{{on "keydown" this.handleKeyPress}}
/>
{{#if this.invalidSha}}
<div class="invalid-sha">Invalid SHA. SHA can only contain hex values.</div>
{{/if}}
</div>

<div id="search-results">
<VerticalCollection
@items={{this.searchResults}}
@estimateHeight={{150}}
as |event|
>
<Pipeline::Event::Card
@pipeline={{@pipeline}}
@event={{event}}
@jobs={{@jobs}}
@userSettings={{@userSettings}}
@queueName="searchResults"
@onClick={{fn @closeModal}}
/>
</VerticalCollection>
</div>
{{#if this.invalidSha}}
<div class="invalid-sha">Invalid SHA. SHA can only contain hex values.</div>
{{else if (eq this.searchResults.length 0)}}
<div class="no-results">No results found.</div>
{{else}}
<div id="search-results">
<VerticalCollection
@items={{this.searchResults}}
@estimateHeight={{150}}
as |event|
>
<Pipeline::Event::Card
@pipeline={{@pipeline}}
@event={{event}}
@jobs={{@jobs}}
@userSettings={{@userSettings}}
@queueName="searchResults"
/>
</VerticalCollection>
</div>
{{/if}}
</modal.body>
</BsModal>
</BsModal>
4 changes: 2 additions & 2 deletions app/components/pipeline/workflow/event-rail/template.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
@outline={{true}}
@onClick={{this.openSearchEventModal}}
disabled={{this.isSearchDisabled}}
title="Search for an event by SHA"
title="Search for an event"
>
<FaIcon @icon="search" />
{{#if this.showSearchEventModal}}
Expand Down Expand Up @@ -61,4 +61,4 @@
/>
</VerticalCollection>
</div>
</div>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { module, test } from 'qunit';
import { setupRenderingTest } from 'screwdriver-ui/tests/helpers';
import { fillIn, render, triggerKeyEvent } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';
import { selectChoose } from 'ember-power-select/test-support';
import sinon from 'sinon';

module(
Expand Down Expand Up @@ -31,13 +32,16 @@ module(
);

assert.dom('.modal-title').exists({ count: 1 });
assert.dom('#search-description').exists({ count: 1 });
assert.dom('#search-input').exists({ count: 1 });
assert.dom('#search-input #search-field-select').exists({ count: 1 });
assert.dom('#search-input input').exists({ count: 1 });
assert.dom('#search-input .invalid-sha').doesNotExist();
assert.dom('#search-results').exists({ count: 1 });
assert.dom('.invalid-sha').doesNotExist();
assert.dom('#search-results').doesNotExist();
assert.dom('.no-results').exists({ count: 1 });
});

test('it makes API call for valid input', async function (assert) {
test('it makes API call for valid input with default searchField', async function (assert) {
const router = this.owner.lookup('service:router');
const shuttle = this.owner.lookup('service:shuttle');

Expand All @@ -60,17 +64,24 @@ module(
/>`
);

await fillIn('input', 'abc123');
await fillIn('input', 'this is a message');
assert.dom('#search-field-select').hasText('message');
assert.dom('#search-input input').hasValue('this is a message');

assert.equal(shuttle.fetchFromApi.callCount, 1);
});

test('it handles invalid input', async function (assert) {
test('it makes API call for valid input with selected searchField', async function (assert) {
const router = this.owner.lookup('service:router');
const shuttle = this.owner.lookup('service:shuttle');

sinon.stub(router, 'currentRouteName').value('pipeline');
sinon.spy(shuttle);
sinon
.stub(shuttle, 'fetchFromApi')
.onCall(0)
.resolves([])
.onCall(1)
.resolves([]);

this.setProperties({
pipeline: {},
Expand All @@ -88,11 +99,53 @@ module(
/>`
);

await fillIn('input', 'xyz');

await selectChoose('#search-field-select', 'sha');
assert.dom('#search-field-select').hasText('sha');
assert.equal(shuttle.fetchFromApi.callCount, 0);
assert.dom('#search-input input').isNotValid();
assert.dom('#search-input .invalid-sha').exists({ count: 1 });

await fillIn('input', 'fe155eb');
assert.dom('#search-input input').hasValue('fe155eb');
assert.equal(shuttle.fetchFromApi.callCount, 1);

await selectChoose('#search-field-select', 'creator');
assert.dom('#search-field-select').hasText('creator');
assert.dom('#search-input input').hasValue('fe155eb');
assert.equal(shuttle.fetchFromApi.callCount, 2);
});

test('it handles invalid sha input', async function (assert) {
const router = this.owner.lookup('service:router');
const shuttle = this.owner.lookup('service:shuttle');

sinon.stub(router, 'currentRouteName').value('pipeline');
const spyShuttle = sinon.spy(shuttle, 'fetchFromApi');

this.setProperties({
pipeline: {},
jobs: [],
userSettings: {},
closeModal: () => {}
});

await render(
hbs`<Pipeline::Modal::SearchEvent
@pipeline={{this.pipeline}}
@jobs={{this.jobs}}
@userSettings={{this.userSettings}}
@closeModal={{this.closeModal}}
/>`
);

await selectChoose('#search-field-select', 'sha');
assert.dom('#search-field-select').hasText('sha');
assert.dom('#search-input input').hasValue('');
assert.equal(spyShuttle.notCalled, true);

await fillIn('input', ';');
assert.dom('#search-field-select').hasText('sha');
assert.dom('#search-input input').hasValue(';');
assert.dom('.invalid-sha').exists({ count: 1 });
assert.equal(spyShuttle.notCalled, true);
});

test('it clears input and search results when escape key is pressed', async function (assert) {
Expand Down

0 comments on commit 2b21057

Please sign in to comment.