blob: 63c3832fafd279203080d8700b583a48e498d22c [file] [log] [blame]
Milutin Kristofic03ec98d2024-02-20 19:56:39 +01001/**
2 * @license
3 * Copyright 2023 Google LLC
4 * SPDX-License-Identifier: Apache-2.0
5 */
6import '../../shared/gr-button/gr-button';
7import '../../shared/gr-icon/gr-icon';
8import '../../shared/gr-copy-clipboard/gr-copy-clipboard';
9import '../gr-suggestion-diff-preview/gr-suggestion-diff-preview';
10import {css, html, LitElement} from 'lit';
11import {customElement, state, query, property} from 'lit/decorators.js';
12import {fire} from '../../../utils/event-util';
13import {getDocUrl} from '../../../utils/url-util';
14import {subscribe} from '../../lit/subscription-controller';
15import {resolve} from '../../../models/dependency';
16import {configModelToken} from '../../../models/config/config-model';
17import {GrSuggestionDiffPreview} from '../gr-suggestion-diff-preview/gr-suggestion-diff-preview';
18import {changeModelToken} from '../../../models/change/change-model';
Milutin Kristofica8347152024-04-09 09:49:34 +020019import {Comment, PatchSetNumber} from '../../../types/common';
Milutin Kristofic03ec98d2024-02-20 19:56:39 +010020import {OpenFixPreviewEventDetail} from '../../../types/events';
Milutin Kristofice28cde52024-03-20 13:07:29 +010021import {pluginLoaderToken} from '../gr-js-api-interface/gr-plugin-loader';
22import {SuggestionsProvider} from '../../../api/suggestions';
Milutin Kristofic56cb7502024-04-03 21:16:39 +020023import {PROVIDED_FIX_ID} from '../../../utils/comment-util';
Milutin Kristofic5b541f12024-04-16 13:45:36 +020024import {when} from 'lit/directives/when.js';
Milutin Kristofic03ec98d2024-02-20 19:56:39 +010025
26/**
27 * gr-fix-suggestions is UI for comment.fix_suggestions.
28 * gr-fix-suggestions is wrapper for gr-suggestion-diff-preview with buttons
29 * to preview and apply fix and for giving a context about suggestion.
30 */
31@customElement('gr-fix-suggestions')
32export class GrFixSuggestions extends LitElement {
33 @query('gr-suggestion-diff-preview')
34 suggestionDiffPreview?: GrSuggestionDiffPreview;
35
36 @property({type: Object})
37 comment?: Comment;
38
39 @state() private docsBaseUrl = '';
40
41 @state() private applyingFix = false;
42
43 @state() latestPatchNum?: PatchSetNumber;
44
Milutin Kristofice28cde52024-03-20 13:07:29 +010045 @state()
46 suggestionsProvider?: SuggestionsProvider;
47
Milutin Kristofic5b541f12024-04-16 13:45:36 +020048 @state() private isOwner = false;
49
Milutin Kristofic03ec98d2024-02-20 19:56:39 +010050 private readonly getConfigModel = resolve(this, configModelToken);
51
52 private readonly getChangeModel = resolve(this, changeModelToken);
53
Milutin Kristofice28cde52024-03-20 13:07:29 +010054 private readonly getPluginLoader = resolve(this, pluginLoaderToken);
55
Milutin Kristofic03ec98d2024-02-20 19:56:39 +010056 constructor() {
57 super();
58 subscribe(
59 this,
60 () => this.getConfigModel().docsBaseUrl$,
61 docsBaseUrl => (this.docsBaseUrl = docsBaseUrl)
62 );
63 subscribe(
64 this,
65 () => this.getChangeModel().latestPatchNum$,
66 x => (this.latestPatchNum = x)
67 );
Milutin Kristofic5b541f12024-04-16 13:45:36 +020068 subscribe(
69 this,
70 () => this.getChangeModel().isOwner$,
71 x => (this.isOwner = x)
72 );
Milutin Kristofic03ec98d2024-02-20 19:56:39 +010073 }
74
Milutin Kristofice28cde52024-03-20 13:07:29 +010075 override connectedCallback() {
76 super.connectedCallback();
77 this.getPluginLoader()
78 .awaitPluginsLoaded()
79 .then(() => {
80 const suggestionsPlugins =
81 this.getPluginLoader().pluginsModel.getState().suggestionsPlugins;
82 // We currently support results from only 1 provider.
83 this.suggestionsProvider = suggestionsPlugins?.[0]?.provider;
84 });
85 }
86
Milutin Kristofic03ec98d2024-02-20 19:56:39 +010087 static override get styles() {
88 return [
89 css`
90 .header {
91 background-color: var(--background-color-primary);
92 border: 1px solid var(--border-color);
93 padding: var(--spacing-xs) var(--spacing-xl);
94 display: flex;
95 align-items: center;
96 border-top-left-radius: var(--border-radius);
97 border-top-right-radius: var(--border-radius);
98 }
99 .header .title {
100 flex: 1;
101 }
102 .copyButton {
103 margin-right: var(--spacing-l);
104 }
105 `,
106 ];
107 }
108
109 override render() {
Milutin Kristofice28cde52024-03-20 13:07:29 +0100110 if (!this.comment?.fix_suggestions) return;
111 const fix_suggestions = this.comment.fix_suggestions;
Milutin Kristofic03ec98d2024-02-20 19:56:39 +0100112 return html`<div class="header">
113 <div class="title">
Milutin Kristofice28cde52024-03-20 13:07:29 +0100114 <span
115 >${this.suggestionsProvider?.getFixSuggestionTitle?.(
116 fix_suggestions
117 ) || 'Suggested edit'}</span
118 >
Milutin Kristofic03ec98d2024-02-20 19:56:39 +0100119 <a
Milutin Kristofice28cde52024-03-20 13:07:29 +0100120 href=${this.suggestionsProvider?.getDocumentationLink?.(
121 fix_suggestions
122 ) || getDocUrl(this.docsBaseUrl, 'user-suggest-edits.html')}
Milutin Kristofic03ec98d2024-02-20 19:56:39 +0100123 target="_blank"
124 rel="noopener noreferrer"
125 ><gr-icon icon="help" title="read documentation"></gr-icon
126 ></a>
127 </div>
128 <div>
129 <gr-button
130 secondary
131 flatten
132 class="action show-fix"
133 @click=${this.handleShowFix}
134 >
135 Show edit
136 </gr-button>
Milutin Kristofic5b541f12024-04-16 13:45:36 +0200137 ${when(
138 this.isOwner,
139 () =>
140 html`<gr-button
141 secondary
142 flatten
143 .loading=${this.applyingFix}
144 .disabled=${this.isApplyEditDisabled()}
145 class="action show-fix"
146 @click=${this.handleApplyFix}
147 .title=${this.computeApplyEditTooltip()}
148 >
149 Apply edit
150 </gr-button>`
151 )}
Milutin Kristofic03ec98d2024-02-20 19:56:39 +0100152 </div>
153 </div>
154 <gr-suggestion-diff-preview
155 .fixSuggestionInfo=${this.comment?.fix_suggestions?.[0]}
156 ></gr-suggestion-diff-preview>`;
157 }
158
159 handleShowFix() {
160 if (!this.comment?.fix_suggestions || !this.comment?.patch_set) return;
161 const eventDetail: OpenFixPreviewEventDetail = {
162 fixSuggestions: this.comment.fix_suggestions.map(s => {
163 return {
164 ...s,
Milutin Kristofic56cb7502024-04-03 21:16:39 +0200165 fix_id: PROVIDED_FIX_ID,
166 description:
167 this.suggestionsProvider?.getFixSuggestionTitle?.(
168 this.comment?.fix_suggestions
169 ) || 'Suggested edit',
Milutin Kristofic03ec98d2024-02-20 19:56:39 +0100170 };
171 }),
172 patchNum: this.comment.patch_set,
Milutin Kristofic0a2be4f2024-04-09 10:16:09 +0200173 onCloseFixPreviewCallbacks: [
174 fixApplied => {
175 if (fixApplied) fire(this, 'apply-user-suggestion', {});
176 },
177 ],
Milutin Kristofic03ec98d2024-02-20 19:56:39 +0100178 };
179 fire(this, 'open-fix-preview', eventDetail);
180 }
181
182 async handleApplyFix() {
183 if (!this.comment?.fix_suggestions) return;
184 this.applyingFix = true;
185 try {
186 await this.suggestionDiffPreview?.applyFixSuggestion();
187 } finally {
188 this.applyingFix = false;
189 }
190 }
191
192 private isApplyEditDisabled() {
193 if (this.comment?.patch_set === undefined) return true;
Milutin Kristofic03ec98d2024-02-20 19:56:39 +0100194 return this.comment.patch_set !== this.latestPatchNum;
195 }
196
197 private computeApplyEditTooltip() {
198 if (this.comment?.patch_set === undefined) return '';
199 return this.comment.patch_set !== this.latestPatchNum
200 ? 'You cannot apply this fix because it is from a previous patchset'
201 : '';
202 }
203}
204
205declare global {
206 interface HTMLElementTagNameMap {
207 'gr-fix-suggestions': GrFixSuggestions;
208 }
209}