Changeset 203709 in webkit


Ignore:
Timestamp:
Jul 25, 2016 7:45:13 PM (8 years ago)
Author:
rniwa@webkit.org
Message:

Perf dashboard should show the list of a pending A/B testing jobs
https://bugs.webkit.org/show_bug.cgi?id=160138

Rubber-stamped by Chris Dumez.

Add a page to show the list of A/B testing build requests per triggerable. Ideally, we would like to
see a queue per builder but that would require changes to database tables and syncing scripts.

Because this page is most useful when the analysis task with which each build request is associated,
JSON API at /api/build-requests/ has been modified to return the analysis task ID for each request.

Also streamlined the page that shows the list of analysis tasks per Chris' feedback by consolidating
"Bisecting" and "Identified" into "Investigated" and moving the toolbar from the upper left corner
inside the heading to right beneath the heading above the table. Also made the category page serialize
the filter an user had typed in so that reloading the page doesn't clear it.

  • public/api/analysis-tasks.php:

(fetch_associated_data_for_tasks): Removed 'category' from the list of columns returned as the notion
of 'category' is only relevant in UI, and it's better computed in the front-end.
(format_task): Ditto.
(determine_category): Deleted.

  • public/api/test-groups.php:

(main):

  • public/include/build-requests-fetcher.php:

(BuildRequestsFetcher::fetch_for_task): Include the analysis task ID in the rows.
(BuildRequestsFetcher::fetch_for_group): Ditto. Ditto.
(BuildRequestsFetcher::fetch_incomplete_requests_for_triggerable): Ditto.
(BuildRequestsFetcher::results_internal): Ditto.

  • public/v3/index.html:
  • public/v3/main.js:

(main): Create a newly introduced BuildRequestQueuePage as a subpage of AnalysisCategoryPage.

  • public/v3/components/ratio-bar-graph.js:

(RatioBarGraph.prototype.update): Fixed a bogus assertion here. ratio can be any number. The coercion
into [-1, 1] is done inside RatioBarGraph's render() function.

  • public/v3/models/analysis-task.js:

(AnalysisTask.prototype.category): Moved the code to compute the analysis task's category from
determine_category in analysis-tasks.php. Also merged "bisecting" and "identified" into "investigated".
(AnalysisTask.categories): Merged "bisecting" and "identified" into "investigated".

  • public/v3/models/build-request.js:

(BuildRequest): Remember the triggerable and the analysis task associated with this request as well as
the time at when this request was created.
(BuildRequest.prototype.analysisTaskId): Added.
(BuildRequest.prototype.statusLabel): Use a shorter label: "Waiting" for "pending" status.
(BuildRequest.prototype.createdAt): Added.
(BuildRequest.prototype.waitingTime): Added. Returns a human readable time duration since the creation
of this build request such as "2 hours 21 minutes" against a reference time.
(BuildRequest.fetchTriggerables): Added.
(BuildRequest.cachedRequestsForTriggerableID): Added. Used when navigating back to

  • public/v3/pages/analysis-category-page.js:

(AnalysisCategoryPage): Construct AnalysisCategoryToolbar and store it in this._categoryToolbar since it
no longer inherits from Toolbar class, which PageWithHeading recognizes and stores.
(AnalysisCategoryPage.prototype.title):
(AnalysisCategoryPage.prototype.serializeState): Added.
(AnalysisCategoryPage.prototype.stateForCategory): Added. Include the filter in the serialization.
(AnalysisCategoryPage.prototype.updateFromSerializedState): Restore the filter from the URL state.
(AnalysisCategoryPage.prototype.filterDidChange): Added. Called by AnalysisCategoryToolbar to update
the URL state in addition to calling render() as done previously via setFilterCallback.
(AnalysisCategoryPage.prototype.render): Always call _categoryToolbar.render() since the hyperlinks for
the category pages now include the filter, which can be updated in each call.
(AnalysisCategoryPage.cssTemplate):

  • public/v3/pages/analysis-category-toolbar.js:

(AnalysisCategoryToolbar): Inherits from ComponentBase instead of Toolbar since AnalysisCategoryToolbar
no longer works with Heading class unlike other subclasses of Toolbar class.
(AnalysisCategoryToolbar.prototype.setCategoryPage): Added.
(AnalysisCategoryToolbar.prototype.setFilterCallback): Deleted.
(AnalysisCategoryToolbar.prototype.setFilter): Added. Used to restore from a serialized URL state.
(AnalysisCategoryToolbar.prototype.render): Don't recreate the input element as it clears the value as
well as the selection of the element. Also use AnalysisCategoryPage's stateForCategory to serialize the
category name and the current filter for each hyperlink.
(AnalysisCategoryToolbar.prototype._filterMayHaveChanged): Now takes an boolean argument specifying
whether the URL state should be updated or not. We update the URL only when a change event is fired to
avoid constantly updating it while an user is still typing.
(AnalysisCategoryToolbar.cssTemplate): Added.
(AnalysisCategoryToolbar.htmlTemplate): Added a button to open the newly added queue page.

  • public/v3/pages/build-request-queue-page.js:

(BuildRequestQueuePage): Added.
(BuildRequestQueuePage.prototype.routeName): Added.
(BuildRequestQueuePage.prototype.pageTitle): Added.
(BuildRequestQueuePage.prototype.open): Added. Fetch open build requests for every triggerables using
the same API as the syncing scripts.
(BuildRequestQueuePage.prototype.render): Added.
(BuildRequestQueuePage.prototype._constructBuildRequestTable): Added. Construct a table for the list of
pending, scheduled or running build requests in the order syncing scripts would see. Note that the list
of build requests returned by /api/build-requests/* can contain completed, canceled, or failed requests
since the JSON returns all build requests associated with each test group if one of the requests of the
group have not finished. This helps syncing scripts picking the right builder for A/B testing when it
had previously been unloaded or crashed in the middle of processing a test group. This characteristics
of the API actually helps us here because we can reliably compute the total number of build requests in
the group. The first half of this function does this counting as well as collapses all but the first
unfinished build requests into a "contraction" row, which just shows the number of build requests that
are remaining in the group.
(BuildRequestQueuePage.cssTemplate): Added.
(BuildRequestQueuePage.htmlTemplate): Added.

  • public/v3/pages/summary-page.js:

(SummaryPage.prototype.open): Use one-day median instead of seven-day median to compute the status.
(SummaryPageConfigurationGroup): Initialize _ratio to NaN. This was causing assertion failures in
RatioBarGraph's update() while measurement sets are being fetched.

  • server-tests/api-build-requests-tests.js: Updated the tests per change in BuildRequest's statusLabel.
  • unit-tests/analysis-task-tests.js: Ditto.
  • unit-tests/test-groups-tests.js: Ditto.
  • unit-tests/build-request-tests.js: Added tests for BuildRequest's waitingTime.
Location:
trunk/Websites/perf.webkit.org
Files:
2 added
15 edited

Legend:

Unmodified
Added
Removed
  • trunk/Websites/perf.webkit.org/ChangeLog

    r203633 r203709  
     12016-07-23  Ryosuke Niwa  <rniwa@webkit.org>
     2
     3        Perf dashboard should show the list of a pending A/B testing jobs
     4        https://bugs.webkit.org/show_bug.cgi?id=160138
     5
     6        Rubber-stamped by Chris Dumez.
     7
     8        Add a page to show the list of A/B testing build requests per triggerable. Ideally, we would like to
     9        see a queue per builder but that would require changes to database tables and syncing scripts.
     10
     11        Because this page is most useful when the analysis task with which each build request is associated,
     12        JSON API at /api/build-requests/ has been modified to return the analysis task ID for each request.
     13
     14        Also streamlined the page that shows the list of analysis tasks per Chris' feedback by consolidating
     15        "Bisecting" and "Identified" into "Investigated" and moving the toolbar from the upper left corner
     16        inside the heading to right beneath the heading above the table. Also made the category page serialize
     17        the filter an user had typed in so that reloading the page doesn't clear it.
     18
     19        * public/api/analysis-tasks.php:
     20        (fetch_associated_data_for_tasks): Removed 'category' from the list of columns returned as the notion
     21        of 'category' is only relevant in UI, and it's better computed in the front-end.
     22        (format_task): Ditto.
     23        (determine_category): Deleted.
     24
     25        * public/api/test-groups.php:
     26        (main):
     27
     28        * public/include/build-requests-fetcher.php:
     29        (BuildRequestsFetcher::fetch_for_task): Include the analysis task ID in the rows.
     30        (BuildRequestsFetcher::fetch_for_group): Ditto. Ditto.
     31        (BuildRequestsFetcher::fetch_incomplete_requests_for_triggerable): Ditto.
     32        (BuildRequestsFetcher::results_internal): Ditto.
     33
     34        * public/v3/index.html:
     35
     36        * public/v3/main.js:
     37        (main): Create a newly introduced BuildRequestQueuePage as a subpage of AnalysisCategoryPage.
     38
     39        * public/v3/components/ratio-bar-graph.js:
     40        (RatioBarGraph.prototype.update): Fixed a bogus assertion here. ratio can be any number. The coercion
     41        into [-1, 1] is done inside RatioBarGraph's render() function.
     42
     43        * public/v3/models/analysis-task.js:
     44        (AnalysisTask.prototype.category): Moved the code to compute the analysis task's category from
     45        determine_category in analysis-tasks.php. Also merged "bisecting" and "identified" into "investigated".
     46        (AnalysisTask.categories): Merged "bisecting" and "identified" into "investigated".
     47
     48        * public/v3/models/build-request.js:
     49        (BuildRequest): Remember the triggerable and the analysis task associated with this request as well as
     50        the time at when this request was created.       
     51        (BuildRequest.prototype.analysisTaskId): Added.
     52        (BuildRequest.prototype.statusLabel): Use a shorter label: "Waiting" for "pending" status.
     53        (BuildRequest.prototype.createdAt): Added.
     54        (BuildRequest.prototype.waitingTime): Added. Returns a human readable time duration since the creation
     55        of this build request such as "2 hours 21 minutes" against a reference time.
     56        (BuildRequest.fetchTriggerables): Added.
     57        (BuildRequest.cachedRequestsForTriggerableID): Added. Used when navigating back to
     58
     59        * public/v3/pages/analysis-category-page.js:
     60        (AnalysisCategoryPage): Construct AnalysisCategoryToolbar and store it in this._categoryToolbar since it
     61        no longer inherits from Toolbar class, which PageWithHeading recognizes and stores.
     62        (AnalysisCategoryPage.prototype.title):
     63        (AnalysisCategoryPage.prototype.serializeState): Added.
     64        (AnalysisCategoryPage.prototype.stateForCategory): Added. Include the filter in the serialization.
     65        (AnalysisCategoryPage.prototype.updateFromSerializedState): Restore the filter from the URL state.
     66        (AnalysisCategoryPage.prototype.filterDidChange): Added. Called by AnalysisCategoryToolbar to update
     67        the URL state in addition to calling render() as done previously via setFilterCallback.
     68        (AnalysisCategoryPage.prototype.render): Always call _categoryToolbar.render() since the hyperlinks for
     69        the category pages now include the filter, which can be updated in each call.
     70        (AnalysisCategoryPage.cssTemplate):
     71
     72        * public/v3/pages/analysis-category-toolbar.js:
     73        (AnalysisCategoryToolbar): Inherits from ComponentBase instead of Toolbar since AnalysisCategoryToolbar
     74        no longer works with Heading class unlike other subclasses of Toolbar class.
     75        (AnalysisCategoryToolbar.prototype.setCategoryPage): Added.
     76        (AnalysisCategoryToolbar.prototype.setFilterCallback): Deleted.
     77        (AnalysisCategoryToolbar.prototype.setFilter): Added. Used to restore from a serialized URL state.
     78        (AnalysisCategoryToolbar.prototype.render): Don't recreate the input element as it clears the value as
     79        well as the selection of the element. Also use AnalysisCategoryPage's stateForCategory to serialize the
     80        category name and the current filter for each hyperlink.
     81        (AnalysisCategoryToolbar.prototype._filterMayHaveChanged): Now takes an boolean argument specifying
     82        whether the URL state should be updated or not. We update the URL only when a change event is fired to
     83        avoid constantly updating it while an user is still typing.
     84        (AnalysisCategoryToolbar.cssTemplate): Added.
     85        (AnalysisCategoryToolbar.htmlTemplate): Added a button to open the newly added queue page.
     86
     87        * public/v3/pages/build-request-queue-page.js:
     88        (BuildRequestQueuePage): Added.
     89        (BuildRequestQueuePage.prototype.routeName): Added.
     90        (BuildRequestQueuePage.prototype.pageTitle): Added.
     91        (BuildRequestQueuePage.prototype.open): Added. Fetch open build requests for every triggerables using
     92        the same API as the syncing scripts.
     93        (BuildRequestQueuePage.prototype.render): Added.
     94        (BuildRequestQueuePage.prototype._constructBuildRequestTable): Added. Construct a table for the list of
     95        pending, scheduled or running build requests in the order syncing scripts would see. Note that the list
     96        of build requests returned by /api/build-requests/* can contain completed, canceled, or failed requests
     97        since the JSON returns all build requests associated with each test group if one of the requests of the
     98        group have not finished. This helps syncing scripts picking the right builder for A/B testing when it
     99        had previously been unloaded or crashed in the middle of processing a test group. This characteristics
     100        of the API actually helps us here because we can reliably compute the total number of build requests in
     101        the group. The first half of this function does this counting as well as collapses all but the first
     102        unfinished build requests into a "contraction" row, which just shows the number of build requests that
     103        are remaining in the group.
     104        (BuildRequestQueuePage.cssTemplate): Added.
     105        (BuildRequestQueuePage.htmlTemplate): Added.
     106
     107        * public/v3/pages/summary-page.js:
     108        (SummaryPage.prototype.open): Use one-day median instead of seven-day median to compute the status.
     109        (SummaryPageConfigurationGroup): Initialize _ratio to NaN. This was causing assertion failures in
     110        RatioBarGraph's update() while measurement sets are being fetched.
     111
     112        * server-tests/api-build-requests-tests.js: Updated the tests per change in BuildRequest's statusLabel.
     113        * unit-tests/analysis-task-tests.js: Ditto.
     114        * unit-tests/test-groups-tests.js: Ditto.
     115        * unit-tests/build-request-tests.js: Added tests for BuildRequest's waitingTime.
     116
    11172016-07-22  Ryosuke Niwa  <rniwa@webkit.org>
    2118
  • trunk/Websites/perf.webkit.org/public/api/analysis-tasks.php

    r198351 r203709  
    8484        $task['buildRequestCount'] = $build_count['total'];
    8585        $task['finishedBuildRequestCount'] = $build_count['finished'];
    86         $task['category'] = determine_category($task);
    8786    }
    8887
     
    104103        'endRun' => $task_row['task_end_run'],
    105104        'endRunTime' => Database::to_js_time($task_row['task_end_run_time']),
    106         'category' => null,
    107105        'result' => $task_row['task_result'],
    108106        'needed' => $task_row['task_needed'] ? Database::is_true($task_row['task_needed']) : null,
     
    113111}
    114112
    115 function determine_category($task) {
    116     $category = 'unconfirmed';
    117 
    118     $result = $task['result'];
    119     if ($result == 'unchanged' || $result == 'inconclusive' || $task['fixes'] || ($result == 'progression' && $task['causes']))
    120         $category = 'closed';
    121     else if ($task['causes'])
    122         $category = 'identified';
    123     else if ($result)
    124         $category = 'bisecting';
    125 
    126     return $category;
    127 }
    128 
    129113main(array_key_exists('PATH_INFO', $_SERVER) ? explode('/', trim($_SERVER['PATH_INFO'], '/')) : array());
    130114
  • trunk/Websites/perf.webkit.org/public/api/test-groups.php

    r196794 r203709  
    2020            exit_with_error('GroupNotFound', array('id' => $group_id));
    2121        $test_groups = array($group);
    22         $build_requests_fetcher->fetch_for_group($group_id);
     22        $build_requests_fetcher->fetch_for_group($group['testgroup_task'], $group_id);
    2323    } else {
    2424        $task_id = array_get($_GET, 'task');
  • trunk/Websites/perf.webkit.org/public/include/build-requests-fetcher.php

    r199210 r203709  
    1414
    1515    function fetch_for_task($task_id) {
    16         $this->rows = $this->db->query_and_fetch_all('SELECT *
     16        $this->rows = $this->db->query_and_fetch_all('SELECT *, testgroup_task as task_id
    1717            FROM build_requests LEFT OUTER JOIN builds ON request_build = build_id, analysis_test_groups
    1818            WHERE request_group = testgroup_id AND testgroup_task = $1
     
    2020    }
    2121
    22     function fetch_for_group($test_group_id) {
     22    function fetch_for_group($task_id, $test_group_id) {
    2323        $this->rows = $this->db->query_and_fetch_all('SELECT *
    2424            FROM build_requests LEFT OUTER JOIN builds ON request_build = build_id
    2525            WHERE request_group = $1 ORDER BY request_order', array($test_group_id));
     26        foreach ($this->rows as &$row)
     27            $row['task_id'] = $task_id;
    2628    }
    2729
    2830    function fetch_incomplete_requests_for_triggerable($triggerable_id) {
    29         $this->rows = $this->db->query_and_fetch_all('SELECT * FROM build_requests,
    30             (SELECT testgroup_id, (case when testgroup_author is not null then 0 else 1 end) as author_order, testgroup_created_at
     31        $this->rows = $this->db->query_and_fetch_all('SELECT *, test_groups.testgroup_task as task_id FROM build_requests,
     32            (SELECT testgroup_id, testgroup_task, (case when testgroup_author is not null then 0 else 1 end) as author_order, testgroup_created_at
    3133                FROM analysis_test_groups WHERE EXISTS
    3234                    (SELECT 1 FROM build_requests WHERE testgroup_id = request_group AND request_status
     
    6567            array_push($requests, array(
    6668                'id' => $row['request_id'],
     69                'task' => $row['task_id'],
    6770                'triggerable' => $row['request_triggerable'],
    6871                'test' => $resolve_ids ? $test_path_resolver->path_for_test($test_id) : $test_id,
  • trunk/Websites/perf.webkit.org/public/v3/components/ratio-bar-graph.js

    r200449 r203709  
    1212    update(ratio, label, showWarningIcon)
    1313    {
    14         console.assert(isNaN(ratio) || (ratio >= -1 && ratio <= 1));
     14        console.assert(typeof(ratio) == 'number');
    1515        this._ratio = ratio;
    1616        this._label = label;
  • trunk/Websites/perf.webkit.org/public/v3/index.html

    r202001 r203709  
    102102        <script src="pages/analysis-task-page.js"></script>
    103103        <script src="pages/create-analysis-task-page.js"></script>
     104        <script src="pages/build-request-queue-page.js"></script>
    104105        <script src="pages/summary-page.js"></script>
    105106
  • trunk/Websites/perf.webkit.org/public/v3/main.js

    r200449 r203709  
    3131        analysisTaskPage.setParentPage(analysisCategoryPage);
    3232
     33        var buildRequestQueuePage = new BuildRequestQueuePage();
     34        buildRequestQueuePage.setParentPage(analysisCategoryPage);
     35
    3336        var heading = new Heading(manifest.siteTitle);
    3437        heading.addPageGroup([summaryPage, chartsPage, analysisCategoryPage].filter(function (page) { return page; }));
     
    4346        router.addPage(createAnalysisTaskPage);
    4447        router.addPage(analysisTaskPage);
     48        router.addPage(buildRequestQueuePage);
    4549        router.addPage(analysisCategoryPage);
    4650        for (var page of dashboardPages)
  • trunk/Websites/perf.webkit.org/public/v3/models/analysis-task.js

    r201564 r203709  
    7272    platform() { return this._platform; }
    7373    metric() { return this._metric; }
    74     category() { return this._category; }
     74
    7575    changeType() { return this._changeType; }
    7676
     
    147147    }
    148148
     149    category()
     150    {
     151        var category = 'unconfirmed';
     152
     153        if (this._changeType == 'unchanged' || this._changeType == 'inconclusive'
     154            || (this._changeType == 'regression' && this._fixes.length)
     155            || (this._changeType == 'progression' && (this._causes.length || this._fixes.length)))
     156            category = 'closed';
     157        else if (this._causes.length || this._fixes.length || this._changeType == 'regression' || this._changeType == 'progression')
     158            category = 'investigated';
     159
     160        return category;
     161    }
     162
    149163    static categories()
    150164    {
    151165        return [
    152166            'unconfirmed',
    153             'bisecting',
    154             'identified',
     167            'investigated',
    155168            'closed'
    156169        ];
  • trunk/Websites/perf.webkit.org/public/v3/models/build-request.js

    r199332 r203709  
    66    {
    77        super(id, object);
     8        this._triggerable = object.triggerable;
     9        this._analysisTaskId = object.task;
    810        this._testGroupId = object.testGroupId;
    911        console.assert(!object.testGroup || object.testGroup instanceof TestGroup);
     
    2123        this._statusUrl = object.url;
    2224        this._buildId = object.build;
     25        this._createdAt = new Date(object.createdAt);
    2326        this._result = null;
    2427    }
     
    3437    }
    3538
     39    analysisTaskId() { return this._analysisTaskId; }
    3640    testGroupId() { return this._testGroupId; }
    3741    testGroup() { return this._testGroup; }
     
    5054        switch (this._status) {
    5155        case 'pending':
    52             return 'Waiting to be scheduled';
     56            return 'Waiting';
    5357        case 'scheduled':
    5458            return 'Scheduled';
     
    6670
    6771    buildId() { return this._buildId; }
     72    createdAt() { return this._createdAt; }
     73
     74    waitingTime(referenceTime)
     75    {
     76        const units = [
     77            {unit: 'week', length: 7 * 24 * 3600},
     78            {unit: 'day', length: 24 * 3600},
     79            {unit: 'hour', length: 3600},
     80            {unit: 'minute', length: 60},
     81        ];
     82
     83        var diff = (referenceTime - this.createdAt()) / 1000;
     84
     85        var indexOfFirstSmallEnoughUnit = units.length - 1;
     86        for (var i = 0; i < units.length; i++) {
     87            if (diff > 1.5 * units[i].length) {
     88                indexOfFirstSmallEnoughUnit = i;
     89                break;
     90            }
     91        }
     92
     93        var label = '';
     94        var lastUnit = false;
     95        for (var i = indexOfFirstSmallEnoughUnit; !lastUnit; i++) {
     96            lastUnit = i == indexOfFirstSmallEnoughUnit + 1 || i == units.length - 1;
     97            var length = units[i].length;
     98            var valueForUnit = lastUnit ? Math.round(diff / length) : Math.floor(diff / length);
     99
     100            var unit = units[i].unit + (valueForUnit == 1 ? '' : 's');
     101            if (label)
     102                label += ' ';
     103            label += `${valueForUnit} ${unit}`;
     104
     105            diff = diff - valueForUnit * length;
     106        }
     107
     108        return label;
     109    }
    68110
    69111    result() { return this._result; }
     
    72114        this._result = result;
    73115        this._testGroup.didSetResult(this);
     116    }
     117
     118    static fetchTriggerables()
     119    {
     120        return this.cachedFetch('/api/triggerables/').then(function (response) {
     121            return response.triggerables.map(function (entry) { return {id: entry.id, name: entry.name}; });
     122        });
     123    }
     124
     125    // FIXME: Create a real model object for triggerables.
     126    static cachedRequestsForTriggerableID(id)
     127    {
     128        return this.all().filter(function (request) {
     129            return request._triggerable == id;
     130        });
    74131    }
    75132
  • trunk/Websites/perf.webkit.org/public/v3/pages/analysis-category-page.js

    r201607 r203709  
    33    constructor()
    44    {
    5         super('Analysis', new AnalysisCategoryToolbar);
    6         this.toolbar().setFilterCallback(this.render.bind(this));
     5        super('Analysis');
     6        this._categoryToolbar = this.content().querySelector('analysis-category-toolbar').component();
     7        this._categoryToolbar.setCategoryPage(this);
    78        this._renderedList = false;
    89        this._renderedFilter = false;
     
    1314    title()
    1415    {
    15         var category = this.toolbar().currentCategory();
     16        var category = this._categoryToolbar.currentCategory();
    1617        return (category ? category.charAt(0).toUpperCase() + category.slice(1) + ' ' : '') + 'Analysis Tasks';
    1718    }
     
    3132    }
    3233
     34    serializeState()
     35    {
     36        return this.stateForCategory(this._categoryToolbar.currentCategory());
     37    }
     38
     39    stateForCategory(category)
     40    {
     41        var state = {category: category};
     42        var filter = this._categoryToolbar.filter();
     43        if (filter)
     44            state.filter = filter;
     45        return state;
     46    }
     47
    3348    updateFromSerializedState(state, isOpen)
    3449    {
    3550        if (state.category instanceof Set)
    3651            state.category = Array.from(state.category.values())[0];
    37 
    38         if (this.toolbar().setCategoryIfValid(state.category))
     52        if (state.filter instanceof Set)
     53            state.filter = Array.from(state.filter.values())[0];
     54
     55        if (this._categoryToolbar.setCategoryIfValid(state.category))
    3956            this._renderedList = false;
     57
     58        if (state.filter)
     59            this._categoryToolbar.setFilter(state.filter);
    4060
    4161        if (!isOpen)
     
    4363    }
    4464
     65    filterDidChange(shouldUpdateState)
     66    {
     67        this.render();
     68        if (shouldUpdateState)
     69            this.scheduleUrlStateUpdate();
     70    }
     71
    4572    render()
    4673    {
    4774        Instrumentation.startMeasuringTime('AnalysisCategoryPage', 'render');
    4875
    49         if (!this._renderedList) {
    50             super.render();
    51             this.toolbar().render();
    52         }
     76        super.render();
     77        this._categoryToolbar.render();
    5378
    5479        if (this._errorMessage) {
     
    7095        }
    7196
    72         var filter = this.toolbar().filter();
     97        var filter = this._categoryToolbar.filter();
    7398        if (filter || this._renderedFilter) {
    7499            Instrumentation.startMeasuringTime('AnalysisCategoryPage', 'filterByKeywords');
     
    99124
    100125        console.assert(this.router());
    101         var currentCategory = this.toolbar().currentCategory();
     126        var currentCategory = this._categoryToolbar.currentCategory();
    102127
    103128        var tasks = AnalysisTask.all().filter(function (task) {
     
    165190    {
    166191        return `
     192            <div class="toolbar-container"><analysis-category-toolbar></analysis-category-toolbar></div>
    167193            <div class="analysis-task-category">
    168194                <table>
     
    185211    {
    186212        return `
     213            .toolbar-container {
     214                text-align: center;
     215            }
     216
    187217            .analysis-task-category {
    188218                width: calc(100% - 2rem);
  • trunk/Websites/perf.webkit.org/public/v3/pages/analysis-category-toolbar.js

    r201775 r203709  
    11
    2 class AnalysisCategoryToolbar extends Toolbar {
    3     constructor()
     2class AnalysisCategoryToolbar extends ComponentBase {
     3    constructor(categoryPage)
    44    {
    55        super('analysis-category-toolbar');
     6        this._categoryPage = null;
    67        this._categories = AnalysisTask.categories();
    78        this._currentCategory = null;
    89        this._filter = null;
    9         this._filterCallback = null;
    1010        this.setCategoryIfValid(null);
     11
     12        this._filterInput = this.content().querySelector('input');
     13        this._filterInput.oninput = this._filterMayHaveChanged.bind(this, false);
     14        this._filterInput.onchange = this._filterMayHaveChanged.bind(this, true);
    1115    }
    1216
     17    setCategoryPage(categoryPage) { this._categoryPage = categoryPage; }
    1318    currentCategory() { return this._currentCategory; }
    14 
    1519    filter() { return this._filter; }
    16     setFilterCallback(callback)
    17     {
    18         console.assert(!callback || callback instanceof Function);
    19         this._filterCallback = callback;
    20     }
     20    setFilter(filter) { this._filter = filter; }
    2121
    2222    render()
    2323    {
    24         var router = this.router();
     24        if (!this._categoryPage)
     25            return;
     26
     27        var router = this._categoryPage.router();
    2528        console.assert(router);
    26 
    27         var currentPage = router.currentPage();
    28         console.assert(currentPage instanceof AnalysisCategoryPage);
    2929
    3030        super.render();
     
    3333        var link = ComponentBase.createLink;
    3434
    35         var input = element('input',
    36             {
    37                 oninput: this._filterMayHaveChanged.bind(this),
    38                 onchange: this._filterMayHaveChanged.bind(this),
    39             });
    40         if (this._filter != null)
    41             input.value = this._filter;
     35        if (this._filterInput.value != this._filter)
     36            this._filterInput.value = this._filter;
    4237
    4338        var currentCategory = this._currentCategory;
    44         this.renderReplace(this.content().querySelector('.analysis-task-category-toolbar'), [
    45             element('ul', {class: 'buttoned-toolbar'},
    46                 this._categories.map(function (category) {
    47                     return element('li',
    48                         {class: category == currentCategory ? 'selected' : null},
    49                         link(category, router.url(currentPage.routeName(), {category: category})));
    50                 })),
    51             input]);
     39        var categoryPage = this._categoryPage;
     40        this.renderReplace(this.content().querySelector('.analysis-task-category-toolbar'),
     41            this._categories.map(function (category) {
     42                return element('li',
     43                    {class: category == currentCategory ? 'selected' : null},
     44                    link(category, router.url(categoryPage.routeName(), categoryPage.stateForCategory(category))));
     45            }));
    5246    }
    5347
    54     _filterMayHaveChanged(event)
     48    _filterMayHaveChanged(shouldUpdateState, event)
    5549    {
    5650        var input = event.target;
    5751        var oldFilter = this._filter;
    5852        this._filter = input.value;
    59         if (this._filter != oldFilter && this._filterCallback)
    60             this._filterCallback(this._filter);
     53        if (this._filter != oldFilter && this._categoryPage || shouldUpdateState)
     54            this._categoryPage.filterDidChange(shouldUpdateState);
    6155    }
    6256
     
    6862            return false;
    6963        this._currentCategory = category;
     64        return true;
     65    }
    7066
    71         var filterDidChange = !!this._filter;
    72         this._filter = null;
    73         if (filterDidChange && this._filterCallback)
    74             this._filterCallback(this._filter);
    75 
    76         return true;
     67    static cssTemplate()
     68    {
     69        return Toolbar.cssTemplate() + `
     70            .queue-toolbar {
     71                position: absolute;
     72                right: 1rem;
     73            }
     74        `
    7775    }
    7876
    7977    static htmlTemplate()
    8078    {
    81         return `<div class="buttoned-toolbar analysis-task-category-toolbar"></div>`;
     79        return `
     80            <ul class="analysis-task-category-toolbar buttoned-toolbar"></ul>
     81            <input type="text">
     82            <ul class="buttoned-toolbar queue-toolbar">
     83                <li><a href="#/analysis/queue">Queue</a></li>
     84            </ul>`;
    8285    }
    8386}
     87
     88ComponentBase.defineElement('analysis-category-toolbar', AnalysisCategoryToolbar);
  • trunk/Websites/perf.webkit.org/public/v3/pages/summary-page.js

    r201041 r203709  
    3434
    3535        var current = Date.now();
    36         var timeRange = [current - 7 * 24 * 3600 * 1000, current];
     36        var timeRange = [current - 24 * 3600 * 1000, current];
    3737        for (var group of this._configGroups)
    3838            group.fetchAndComputeSummary(timeRange).then(this.render.bind(this));
     
    251251        this._configurationList = [];
    252252        this._setToRatio = new Map;
    253         this._ratio = null;
     253        this._ratio = NaN;
    254254        this._label = null;
    255255        this._missingPlatforms = new Set;
  • trunk/Websites/perf.webkit.org/server-tests/api-build-requests-tests.js

    r199332 r203709  
    178178            assert.ok(!buildRequests[0].hasStarted());
    179179            assert.ok(buildRequests[0].isPending());
    180             assert.equal(buildRequests[0].statusLabel(), 'Waiting to be scheduled');
     180            assert.equal(buildRequests[0].statusLabel(), 'Waiting');
    181181
    182182            assert.equal(buildRequests[1].id(), 701);
     
    189189            assert.ok(!buildRequests[1].hasStarted());
    190190            assert.ok(buildRequests[1].isPending());
    191             assert.equal(buildRequests[1].statusLabel(), 'Waiting to be scheduled');
     191            assert.equal(buildRequests[1].statusLabel(), 'Waiting');
    192192
    193193            assert.equal(buildRequests[2].id(), 702);
     
    200200            assert.ok(!buildRequests[2].hasStarted());
    201201            assert.ok(buildRequests[2].isPending());
    202             assert.equal(buildRequests[2].statusLabel(), 'Waiting to be scheduled');
     202            assert.equal(buildRequests[2].statusLabel(), 'Waiting');
    203203
    204204            assert.equal(buildRequests[3].id(), 703);
     
    211211            assert.ok(!buildRequests[3].hasStarted());
    212212            assert.ok(buildRequests[3].isPending());
    213             assert.equal(buildRequests[3].statusLabel(), 'Waiting to be scheduled');
     213            assert.equal(buildRequests[3].statusLabel(), 'Waiting');
    214214
    215215            let osx = Repository.findById(9);
  • trunk/Websites/perf.webkit.org/unit-tests/analysis-task-tests.js

    r198826 r203709  
    176176                assert.ok(task.hasPendingRequests());
    177177                assert.equal(task.requestLabel(), '6 of 14');
    178                 assert.equal(task.category(), 'identified');
     178                assert.equal(task.category(), 'investigated');
    179179                assert.equal(task.changeType(), 'regression');
    180180                assert.equal(task.startMeasurementId(), 37117949);
  • trunk/Websites/perf.webkit.org/unit-tests/test-groups-tests.js

    r198853 r203709  
    141141            assert.ok(!buildRequests[0].hasStarted());
    142142            assert.ok(buildRequests[0].isPending());
    143             assert.equal(buildRequests[0].statusLabel(), 'Waiting to be scheduled');
     143            assert.equal(buildRequests[0].statusLabel(), 'Waiting');
    144144            assert.equal(buildRequests[0].buildId(), null);
    145145            assert.equal(buildRequests[0].result(), null);
     
    150150            assert.ok(!buildRequests[1].hasStarted());
    151151            assert.ok(buildRequests[1].isPending());
    152             assert.equal(buildRequests[1].statusLabel(), 'Waiting to be scheduled');
     152            assert.equal(buildRequests[1].statusLabel(), 'Waiting');
    153153            assert.equal(buildRequests[1].buildId(), null);
    154154            assert.equal(buildRequests[1].result(), null);
Note: See TracChangeset for help on using the changeset viewer.