Commit 8271c47e authored by Kelvin Jiang's avatar Kelvin Jiang Committed by Commit Bot

[Extensions] Add filtering/searching to the activity log stream

This CL adds search functionality to the activity log stream. It works
slightly differently than searching in the history tab.

Stream search is done on the client side and is a case-insensitive,
partial match on an activity's name (API call or content script name),
activity type or page url. Activities not matched with the search term
(pre-existing and new activities) will appear/reappear when the search
term is modified to include them.

Screenshot: https://imgur.com/a/o00bDWu

Bug: 932768
Change-Id: Ib535d6aa231b9c6789eb701be22ca6d9b7200fca
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1542716Reviewed-by: default avatarDemetrios Papadopoulos <dpapad@chromium.org>
Reviewed-by: default avatarEsmael El-Moslimany <aee@chromium.org>
Commit-Queue: Kelvin Jiang <kelvinjiang@chromium.org>
Cr-Commit-Position: refs/heads/master@{#646559}
parent 7ced697d
...@@ -35,6 +35,7 @@ js_library("activity_log_stream") { ...@@ -35,6 +35,7 @@ js_library("activity_log_stream") {
deps = [ deps = [
":activity_log_stream_item", ":activity_log_stream_item",
"//third_party/polymer/v1_0/components-chromium/iron-list:iron-list-extracted", "//third_party/polymer/v1_0/components-chromium/iron-list:iron-list-extracted",
"//ui/webui/resources/cr_elements/cr_search_field:cr_search_field",
"//ui/webui/resources/js:cr", "//ui/webui/resources/js:cr",
] ]
externs_list = [ "$externs_path/activity_log_private.js" ] externs_list = [ "$externs_path/activity_log_private.js" ]
......
<link rel="import" href="chrome://resources/html/polymer.html"> <link rel="import" href="chrome://resources/html/polymer.html">
<link rel="import" href="chrome://resources/html/cr.html"> <link rel="import" href="chrome://resources/html/cr.html">
<link rel="import" href="chrome://resources/cr_elements/cr_search_field/cr_search_field.html">
<link rel="import" href="chrome://resources/cr_elements/paper_button_style_css.html"> <link rel="import" href="chrome://resources/cr_elements/paper_button_style_css.html">
<link rel="import" href="chrome://resources/polymer/v1_0/paper-button/paper-button.html"> <link rel="import" href="chrome://resources/polymer/v1_0/paper-button/paper-button.html">
<link rel="import" href="chrome://resources/polymer/v1_0/iron-list/iron-list.html"> <link rel="import" href="chrome://resources/polymer/v1_0/iron-list/iron-list.html">
...@@ -19,6 +20,10 @@ ...@@ -19,6 +20,10 @@
flex-direction: column; flex-direction: column;
} }
cr-search-field {
margin-inline-end: auto;
}
.activity-table-headings { .activity-table-headings {
width: var(--activity-log-call-and-time-width); width: var(--activity-log-call-and-time-width);
} }
...@@ -42,6 +47,9 @@ ...@@ -42,6 +47,9 @@
} }
</style> </style>
<div class="activity-subpage-header"> <div class="activity-subpage-header">
<cr-search-field label="$i18n{activityLogSearchLabel}"
on-search-changed="onSearchChanged_">
</cr-search-field >
<paper-button id="toggle-stream-button" on-click="onToggleButtonClick_"> <paper-button id="toggle-stream-button" on-click="onToggleButtonClick_">
<span hidden$="[[isStreamOn_]]"> <span hidden$="[[isStreamOn_]]">
$i18n{startActivityStream} $i18n{startActivityStream}
...@@ -64,13 +72,18 @@ ...@@ -64,13 +72,18 @@
$i18n{emptyStreamStarted} $i18n{emptyStreamStarted}
</span> </span>
</div> </div>
<div id="empty-search-message" class="activity-message"
hidden$="[[!shouldShowEmptySearchMessage_(
activityStream_.length, filteredActivityStream_.length)]]">
$i18n{noSearchResults}
</div>
<div class="activity-table-headings" <div class="activity-table-headings"
hidden$="[[isStreamEmpty_(activityStream_.length)]]"> hidden$="[[isFilteredStreamEmpty_(filteredActivityStream_.length)]]">
<span id="activity-type">$i18n{activityLogTypeColumn}</span> <span id="activity-type">$i18n{activityLogTypeColumn}</span>
<span id="activity-key">$i18n{activityLogNameColumn}</span> <span id="activity-key">$i18n{activityLogNameColumn}</span>
<span id="activity-time">$i18n{activityLogTimeColumn}</span> <span id="activity-time">$i18n{activityLogTimeColumn}</span>
</div> </div>
<iron-list items="[[activityStream_]]"> <iron-list items="[[filteredActivityStream_]]">
<template> <template>
<activity-log-stream-item data="[[item]]"></activity-log-stream-item> <activity-log-stream-item data="[[item]]"></activity-log-stream-item>
</template> </template>
......
...@@ -66,11 +66,24 @@ cr.define('extensions', function() { ...@@ -66,11 +66,24 @@ cr.define('extensions', function() {
value: false, value: false,
}, },
/** @private {!Array<!chrome.activityLogPrivate.ExtensionActivity>} */ /** @private {!Array<!extensions.StreamItem>} */
activityStream_: { activityStream_: {
type: Array, type: Array,
value: () => [], value: () => [],
}, },
/** @private {!Array<!extensions.StreamItem>} */
filteredActivityStream_: {
type: Array,
computed:
'computeFilteredActivityStream_(activityStream_.*, lastSearch_)',
},
/** @private */
lastSearch_: {
type: String,
value: '',
},
}, },
listeners: { listeners: {
...@@ -144,6 +157,22 @@ cr.define('extensions', function() { ...@@ -144,6 +157,22 @@ cr.define('extensions', function() {
return this.activityStream_.length == 0; return this.activityStream_.length == 0;
}, },
/**
* @private
* @return {boolean}
*/
isFilteredStreamEmpty_: function() {
return this.filteredActivityStream_.length == 0;
},
/**
* @private
* @return {boolean}
*/
shouldShowEmptySearchMessage_: function() {
return !this.isStreamEmpty_() && this.isFilteredStreamEmpty_();
},
/** /**
* @private * @private
* @param {!chrome.activityLogPrivate.ExtensionActivity} activity * @param {!chrome.activityLogPrivate.ExtensionActivity} activity
...@@ -160,6 +189,46 @@ cr.define('extensions', function() { ...@@ -160,6 +189,46 @@ cr.define('extensions', function() {
// Used to update the scrollbar. // Used to update the scrollbar.
this.$$('iron-list').notifyResize(); this.$$('iron-list').notifyResize();
}, },
/**
* @private
* @param {!CustomEvent<string>} e
*/
onSearchChanged_: function(e) {
// Remove all whitespaces from the search term, as API call names and
// URLs should not contain any whitespace. As of now, only single term
// search queries are allowed.
const searchTerm = e.detail.replace(/\s+/g, '').toLowerCase();
if (searchTerm === this.lastSearch_) {
return;
}
this.lastSearch_ = searchTerm;
},
/**
* @private
* @return {!Array<!extensions.StreamItem>}
*/
computeFilteredActivityStream_: function() {
if (!this.lastSearch_) {
return this.activityStream_.slice();
}
// Match on these properties for each activity.
const propNames = [
'name',
'pageUrl',
'activityType',
];
return this.activityStream_.filter(act => {
return propNames.some(prop => {
return act[prop] &&
act[prop].toLowerCase().includes(this.lastSearch_);
});
});
},
}); });
return { return {
......
...@@ -65,6 +65,7 @@ ...@@ -65,6 +65,7 @@
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
width: 100%;
} }
#args-list, #args-list,
......
...@@ -87,7 +87,6 @@ suite('ExtensionsActivityLogStreamTest', function() { ...@@ -87,7 +87,6 @@ suite('ExtensionsActivityLogStreamTest', function() {
'new activity events are only shown while the stream is started', 'new activity events are only shown while the stream is started',
function() { function() {
Polymer.dom.flush(); Polymer.dom.flush();
testVisible('#activity-stream-list', false);
proxyDelegate.getOnExtensionActivity().callListeners(activity1); proxyDelegate.getOnExtensionActivity().callListeners(activity1);
Polymer.dom.flush(); Polymer.dom.flush();
...@@ -122,6 +121,50 @@ suite('ExtensionsActivityLogStreamTest', function() { ...@@ -122,6 +121,50 @@ suite('ExtensionsActivityLogStreamTest', function() {
streamItems[1].$$('#activity-name').innerText, 'testAPI.DOMMethod'); streamItems[1].$$('#activity-name').innerText, 'testAPI.DOMMethod');
}); });
test('activities shown match search query', function() {
Polymer.dom.flush();
testVisible('#empty-stream-message', true);
proxyDelegate.getOnExtensionActivity().callListeners(activity1);
proxyDelegate.getOnExtensionActivity().callListeners(activity2);
Polymer.dom.flush();
expectEquals(2, getStreamItems().length);
const search = activityLogStream.$$('cr-search-field');
assertTrue(!!search);
// Search for the apiCall of |activity1|.
search.setValue('testMethod');
Polymer.dom.flush();
const filteredStreamItems = getStreamItems();
expectEquals(1, getStreamItems().length);
expectEquals(
filteredStreamItems[0].$$('#activity-name').innerText,
'testAPI.testMethod');
// search again, expect none
search.setValue('not expecting any activities to match');
Polymer.dom.flush();
expectEquals(0, getStreamItems().length);
testVisible('#empty-stream-message', false);
testVisible('#empty-search-message', true);
// Another activity comes in while the stream is listening but search
// returns no results.
proxyDelegate.getOnExtensionActivity().callListeners(contentScriptActivity);
search.$$('#clearSearch').click();
Polymer.dom.flush();
// We expect 4 activities to appear as |contentScriptActivity| (which is
// split into 2 items) should be processed and stored in the stream
// regardless of the search input.
expectEquals(4, getStreamItems().length);
});
test('content script events are split by content script names', function() { test('content script events are split by content script names', function() {
proxyDelegate.getOnExtensionActivity().callListeners(contentScriptActivity); proxyDelegate.getOnExtensionActivity().callListeners(contentScriptActivity);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment