blob: 97706d9a93720d464b36d84880f43f0e251b9a41 [file] [log] [blame] [edit]
/**
* @module modules/comments-sk
* @description <h2><code>comments-sk</code></h2>
* A Custom element for displaying, adding, and deleteing comments for commits, taskspecs,
* and tasks.
*
* @evt data-update: void - Fires whenever this element has added or deleted a comment.
*
* @property allowAdd: boolean - Support adding comments, if permissions allow.
* @property allowDelete: boolean - Support deleting comments, if permissions allow.
* @property allowEmpty: boolean - Support empty comments.
* @property commentData: CommentData - Comments and metadata (repo, taskspec, commit, etc).
* @property editRights: boolean - If the logged in user has edit rights to add/delete comments.
* @property showFlaky': boolean - Display flaky field of comments.
* @property showIgnoreFailure: boolean - Display ignoreFailure field of comments.
*
*/
import { html, TemplateResult } from 'lit/html.js';
import { define } from '../../../elements-sk/modules/define';
import { errorMessage } from '../../../elements-sk/modules/errorMessage';
import { $$ } from '../../../infra-sk/modules/dom';
import { ElementSk } from '../../../infra-sk/modules/ElementSk';
import { AddCommentRequest, Comment, GetStatusService, StatusService } from '../rpc';
import { escapeAndLinkify } from '../../../infra-sk/modules/linkify';
import '../../../ct/modules/input-sk';
import '../../../infra-sk/modules/human-date-sk';
import '../../../elements-sk/modules/icons/check-box-icon-sk';
import '../../../elements-sk/modules/icons/check-box-outline-blank-icon-sk';
import '../../../elements-sk/modules/icons/delete-icon-sk';
import '../../../elements-sk/modules/checkbox-sk';
// Helper class for parent to set comments to display, as well as metadata
// about them and any comments added by the user.
export class CommentData {
repo: string = '';
taskId: string = '';
taskSpec: string = '';
commit: string = '';
comments: Array<Comment> = [];
}
export class CommentsSk extends ElementSk {
private static template = (el: CommentsSk) => html`
<table class="comments">
${el.comments.length > 0
? html`
<tr>
<th>Time</th>
<th>User</th>
<th>Message</th>
${el.showFlaky ? html`<th>Flaky</th> ` : html``}
${el.showIgnoreFailure ? html`<th>Ignore Failure</th> ` : html``}
${el.allowDelete && el.editRights ? html`<th>Delete</th>` : html``}
</tr>
`
: html``}
${el.comments.map(
(c) => html`
<tr class="comment">
<td>
<human-date-sk .date=${c.timestamp} .diff=${true}></human-date-sk>
ago
</td>
<td>${c.user}</td>
<td class="commentMessage">${escapeAndLinkify(c.message)}</td>
${el.optionalCommentFields(c)}
</tr>
`
)}
${el.allowAdd && el.editRights
? html`
<tr>
<td colspan="3">
<input-sk value="" class="commentField" label="Comment"></input-sk>
</td>
${el.showFlaky
? html`<td>
<checkbox-sk class="commentFlaky" label="Flaky"></checkbox-sk>
</td>`
: html``}
${el.showIgnoreFailure
? html`<td>
<checkbox-sk class="commentIgnoreFailure" label="IgnoreFailure"></checkbox-sk>
</td>`
: html``}
<td>
<button @click=${() => el.addComment()}>Submit</button>
</td>
</tr>
`
: html``}
</table>
`;
private optionalCommentFields(comment: Comment): Array<TemplateResult> {
const ret: Array<TemplateResult> = [];
if (this.showFlaky) {
ret.push(
comment.flaky
? html`<td><check-box-icon-sk></check-box-icon-sk></td> `
: html`<td>
<check-box-outline-blank-icon-sk></check-box-outline-blank-icon-sk>
</td>`
);
}
if (this.showIgnoreFailure) {
ret.push(
comment.ignoreFailure
? html`<td><check-box-icon-sk></check-box-icon-sk></td> `
: html`<td>
<check-box-outline-blank-icon-sk></check-box-outline-blank-icon-sk>
</td>`
);
}
if (this.allowDelete && this.editRights) {
ret.push(
html`<td @click=${() => this.deleteComment(comment)}>
<delete-icon-sk></delete-icon-sk>
</td>`
);
}
return ret;
}
private _commentData: CommentData = new CommentData();
private _allowAdd: boolean = false;
private _allowEmpty: boolean = false;
private _allowDelete: boolean = false;
private _showFlaky: boolean = false;
private _showIgnoreFailure: boolean = false;
private _editRights: boolean = false;
private client: StatusService = GetStatusService();
constructor() {
super(CommentsSk.template);
}
connectedCallback() {
super.connectedCallback();
this._upgradeProperty('allowAdd');
this._upgradeProperty('allowEmpty');
this._upgradeProperty('allowDelete');
this._upgradeProperty('editRights');
this._upgradeProperty('showIngoreFailure');
this._upgradeProperty('showFlaky');
this._upgradeProperty('commentData');
this._render();
}
_render() {
// Don't render of we don't have any information.
if (this.commentData) {
super._render();
}
}
get comments() {
return this.commentData.comments;
}
get editRights(): boolean {
return this._editRights;
}
set editRights(v: boolean) {
this._editRights = v;
this._render();
}
get allowAdd(): boolean {
return this._allowAdd;
}
set allowAdd(v: boolean) {
this._allowAdd = v;
this._render();
}
get allowEmpty(): boolean {
return this._allowEmpty;
}
set allowEmpty(v: boolean) {
this._allowEmpty = v;
this._render();
}
get allowDelete(): boolean {
return this._allowDelete;
}
set allowDelete(v: boolean) {
this._allowDelete = v;
this._render();
}
get showIgnoreFailure(): boolean {
return this._showIgnoreFailure;
}
set showIgnoreFailure(v: boolean) {
this._showIgnoreFailure = v;
this._render();
}
get showFlaky(): boolean {
return this._showFlaky;
}
set showFlaky(v: boolean) {
this._showFlaky = v;
this._render();
}
set commentData(c: CommentData) {
this._commentData = c;
this._render();
}
get commentData(): CommentData {
return this._commentData;
}
private deleteComment(comment: Comment) {
this.client
.deleteComment({
timestamp: comment.timestamp,
repo: this.commentData.repo,
taskId: comment.taskId,
taskSpec: comment.taskSpecName,
commit: comment.commit,
})
.then(() => {
// Visually get rid of the comment that was removed, and fire an event for the parent
// element to refresh so it doesn't reappear.
this.commentData.comments = this.comments.filter((c) => c !== comment);
this.dispatchEvent(new CustomEvent('data-update', { bubbles: true, detail: { a: 1 } }));
this._render();
})
.catch(errorMessage);
}
private addComment() {
const req: AddCommentRequest = {
repo: this.commentData.repo,
taskId: this.commentData.taskId,
taskSpec: this.commentData.taskSpec,
commit: this.commentData.commit,
message: ($$('.commentField') as any).value,
flaky: ($$('.commentFlaky', this) as any)?.checked,
ignoreFailure: ($$('.commentIgnoreFailure', this) as any)?.checked,
};
const comments = this.comments;
this.client
.addComment(req)
.then((resp) => {
(<HTMLInputElement>$$('input-sk', this)).value = '';
// If we haven't altered what's being displayed since we sent the request, add the comment
// to our array so the UI feels snappy, even though the comment hasn't been picked up by
// our parent yet.
if (this.comments === comments) {
this.comments.push({
id: resp.timestamp!,
timestamp: resp.timestamp,
message: req.message,
repo: req.repo,
deleted: false,
flaky: this._showFlaky,
ignoreFailure: req.ignoreFailure,
taskSpecName: req.taskSpec,
taskId: req.taskId,
commit: req.commit,
user: 'You',
});
}
this._render();
this.dispatchEvent(new Event('data-update', { bubbles: true }));
})
.catch(errorMessage);
}
}
define('comments-sk', CommentsSk);