blob: 7ef2ed75401557276ef31090dd6481a726ea58b2 [file] [log] [blame]
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +01001// Copyright 2019 The Chromium Authors
Copybara854996b2021-09-07 19:36:02 +00002// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5import {LitElement, html, css} from 'lit-element';
6import page from 'page';
7import {connectStore} from 'reducers/base.js';
8import * as issueV0 from 'reducers/issueV0.js';
9import 'elements/chops/chops-choice-buttons/chops-choice-buttons.js';
10import '../mr-mode-selector/mr-mode-selector.js';
11import './mr-grid-dropdown.js';
12import {SERVER_LIST_ISSUES_LIMIT} from 'shared/consts/index.js';
13import {urlWithNewParams} from 'shared/helpers.js';
14import {fieldsForIssue} from 'shared/issue-fields.js';
15
16// A list of the valid default field names available in an issue grid.
17// High cardinality fields must be excluded, so the grid only includes a subset
18// of AVAILABLE FIELDS.
19export const DEFAULT_GRID_FIELDS = Object.freeze([
20 'Project',
21 'Attachments',
22 'Blocked',
23 'BlockedOn',
24 'Blocking',
25 'Component',
26 'MergedInto',
27 'Reporter',
28 'Stars',
29 'Status',
30 'Type',
31 'Owner',
32]);
33
34/**
35 * Component for displaying the controls shown on the Monorail issue grid page.
36 * @extends {LitElement}
37 */
38export class MrGridControls extends connectStore(LitElement) {
39 /** @override */
40 static get styles() {
41 return css`
42 :host {
43 display: flex;
44 justify-content: space-between;
45 align-items: center;
46 box-sizing: border-box;
47 margin: 0.5em 0;
48 height: 32px;
49 }
50 mr-grid-dropdown {
51 padding-right: 20px;
52 }
53 .left-controls {
54 display: flex;
55 align-items: center;
56 justify-content: flex-start;
57 flex-grow: 0;
58 }
59 .right-controls {
60 display: flex;
61 align-items: center;
62 flex-grow: 0;
63 }
64 .issue-count {
65 display: inline-block;
66 padding-right: 20px;
67 }
68 `;
69 };
70
71 /** @override */
72 render() {
73 const hideCounts = this.totalIssues === 0;
74 return html`
75 <div class="left-controls">
76 <mr-grid-dropdown
77 class="row-selector"
78 .text=${'Rows'}
79 .items=${this.gridOptions}
80 .selection=${this.queryParams.y}
81 @change=${this._rowChanged}>
82 </mr-grid-dropdown>
83 <mr-grid-dropdown
84 class="col-selector"
85 .text=${'Cols'}
86 .items=${this.gridOptions}
87 .selection=${this.queryParams.x}
88 @change=${this._colChanged}>
89 </mr-grid-dropdown>
90 <chops-choice-buttons
91 class="cell-selector"
92 .options=${this.cellOptions}
93 .value=${this.cellType}>
94 </chops-choice-buttons>
95 </div>
96 <div class="right-controls">
97 ${hideCounts ? '' : html`
98 <div class="issue-count">
99 ${this.issueCount}
100 of
101 ${this.totalIssuesDisplay}
102 </div>
103 `}
104 <mr-mode-selector
105 .projectName=${this.projectName}
106 .queryParams=${this.queryParams}
107 value="grid"
108 ></mr-mode-selector>
109 </div>
110 `;
111 }
112
113 /** @override */
114 constructor() {
115 super();
116 this.gridOptions = this._computeGridOptions([]);
117 this.queryParams = {};
118
119 this.totalIssues = 0;
120
121 this._page = page;
122 };
123
124 /** @override */
125 static get properties() {
126 return {
127 gridOptions: {type: Array},
128 projectName: {tupe: String},
129 queryParams: {type: Object},
130 issueCount: {type: Number},
131 totalIssues: {type: Number},
132 _issues: {type: Array},
133 };
134 };
135
136 /** @override */
137 stateChanged(state) {
138 this.totalIssues = issueV0.totalIssues(state) || 0;
139 this._issues = issueV0.issueList(state) || [];
140 }
141
142 /** @override */
143 update(changedProperties) {
144 if (changedProperties.has('_issues')) {
145 this.gridOptions = this._computeGridOptions(this._issues);
146 }
147 super.update(changedProperties);
148 }
149
150 /**
151 * Gets what issue filtering options exist on the grid view.
152 * @param {Array<Issue>} issues The issues to find values on.
153 * @param {Array<string>=} defaultFields Available built in fields.
154 * @return {Array<string>} Array of names of fields you can filter by.
155 */
156 _computeGridOptions(issues, defaultFields = DEFAULT_GRID_FIELDS) {
157 const availableFields = new Set(defaultFields);
158 issues.forEach((issue) => {
159 fieldsForIssue(issue, true).forEach((field) => {
160 availableFields.add(field);
161 });
162 });
163 const options = [...availableFields].sort();
164 options.unshift('None');
165 return options;
166 }
167
168 /**
169 * @return {string} Display text of total issue number.
170 */
171 get totalIssuesDisplay() {
172 if (this.issueCount === 1) {
173 return `${this.issueCount} issue shown`;
174 } else if (this.issueCount === SERVER_LIST_ISSUES_LIMIT) {
175 // Server has hard limit up to 100,000 list results
176 return `100,000+ issues shown`;
177 }
178 return `${this.issueCount} issues shown`;
179 }
180
181 /**
182 * @return {string} What cell mode the user has selected.
183 * ie: Tiles, IDs, Counts
184 */
185 get cellType() {
186 const cells = this.queryParams.cells;
187 return cells || 'tiles';
188 }
189
190 /**
191 * @return {Array<Object>} Cell options available to the user, formatted for
192 * <mr-mode-selector>
193 */
194 get cellOptions() {
195 return [
196 {text: 'Tile', value: 'tiles',
197 url: this._updatedGridViewUrl({}, ['cells'])},
198 {text: 'IDs', value: 'ids',
199 url: this._updatedGridViewUrl({cells: 'ids'})},
200 {text: 'Counts', value: 'counts',
201 url: this._updatedGridViewUrl({cells: 'counts'})},
202 ];
203 }
204
205 /**
206 * Changes the URL parameters on the page in response to a user changing
207 * their row setting.
208 * @param {Event} e 'change' event fired by <mr-grid-dropdown>
209 */
210 _rowChanged(e) {
211 const y = e.target.selection;
212 let deletedParams;
213 if (y === 'None') {
214 deletedParams = ['y'];
215 }
216 this._changeUrlParams({y}, deletedParams);
217 }
218
219 /**
220 * Changes the URL parameters on the page in response to a user changing
221 * their col setting.
222 * @param {Event} e 'change' event fired by <mr-grid-dropdown>
223 */
224 _colChanged(e) {
225 const x = e.target.selection;
226 let deletedParams;
227 if (x === 'None') {
228 deletedParams = ['x'];
229 }
230 this._changeUrlParams({x}, deletedParams);
231 }
232
233 /**
234 * Helper method to update URL params with a new grid view URL.
235 * @param {Array<Object>} newParams
236 * @param {Array<string>} deletedParams
237 */
238 _changeUrlParams(newParams, deletedParams) {
239 const newUrl = this._updatedGridViewUrl(newParams, deletedParams);
240 this._page(newUrl);
241 }
242
243 /**
244 * Helper to generate a new grid view URL given a set of params.
245 * @param {Array<Object>} newParams
246 * @param {Array<string>} deletedParams
247 * @return {string} The generated URL.
248 */
249 _updatedGridViewUrl(newParams, deletedParams) {
250 return urlWithNewParams(`/p/${this.projectName}/issues/list`,
251 this.queryParams, newParams, deletedParams);
252 }
253};
254
255customElements.define('mr-grid-controls', MrGridControls);