@gitlab/duo-ui 10.20.0 → 10.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/components/agentic_chat/agentic_duo_chat.js +18 -37
  3. package/dist/components/chat/components/duo_chat_header/duo_chat_header.js +4 -4
  4. package/dist/components/chat/components/duo_chat_message_tool_approval/components/create_commit_tool_params.js +148 -0
  5. package/dist/components/chat/components/duo_chat_message_tool_approval/components/create_issue_tool_params.js +88 -0
  6. package/dist/components/chat/components/duo_chat_message_tool_approval/components/create_merge_request_tool_params.js +83 -0
  7. package/dist/components/chat/components/duo_chat_message_tool_approval/components/pre_block.js +38 -0
  8. package/dist/components/chat/components/duo_chat_message_tool_approval/components/run_command_tool_params.js +62 -0
  9. package/dist/components/chat/components/duo_chat_message_tool_approval/message_tool_approval.js +46 -8
  10. package/dist/components/chat/duo_chat.js +35 -54
  11. package/dist/components/chat/mock_data.js +85 -1
  12. package/dist/components/ui/duo_layout/duo_layout.js +100 -0
  13. package/dist/components/ui/side_rail/side_rail.js +67 -0
  14. package/dist/components.css +1 -1
  15. package/dist/components.css.map +1 -1
  16. package/dist/index.js +2 -0
  17. package/dist/tailwind.css +1 -1
  18. package/dist/tailwind.css.map +1 -1
  19. package/dist/utils/object.js +9 -0
  20. package/package.json +5 -4
  21. package/src/components/agentic_chat/agentic_duo_chat.vue +210 -244
  22. package/src/components/chat/components/duo_chat_header/duo_chat_header.vue +24 -22
  23. package/src/components/chat/components/duo_chat_message_tool_approval/components/create_commit_tool_params.vue +155 -0
  24. package/src/components/chat/components/duo_chat_message_tool_approval/components/create_issue_tool_params.vue +80 -0
  25. package/src/components/chat/components/duo_chat_message_tool_approval/components/create_merge_request_tool_params.vue +74 -0
  26. package/src/components/chat/components/duo_chat_message_tool_approval/components/pre_block.vue +5 -0
  27. package/src/components/chat/components/duo_chat_message_tool_approval/components/run_command_tool_params.vue +30 -0
  28. package/src/components/chat/components/duo_chat_message_tool_approval/message_tool_approval.vue +143 -88
  29. package/src/components/chat/duo_chat.scss +1 -2
  30. package/src/components/chat/duo_chat.vue +214 -238
  31. package/src/components/chat/mock_data.js +99 -0
  32. package/src/components/ui/duo_layout/duo_layout.md +0 -0
  33. package/src/components/ui/duo_layout/duo_layout.vue +95 -0
  34. package/src/components/ui/side_rail/side_rail.vue +56 -0
  35. package/src/index.js +2 -0
  36. package/src/utils/object.js +4 -0
  37. package/translations.js +29 -6
@@ -0,0 +1,155 @@
1
+ <script>
2
+ import { GlSprintf, GlAccordion, GlAccordionItem } from '@gitlab/ui';
3
+ import { translate, translatePlural, sprintf } from '../../../../../utils/i18n';
4
+ import PreBlock from './pre_block.vue';
5
+
6
+ export default {
7
+ name: 'CreateCommitToolParams',
8
+ components: {
9
+ GlSprintf,
10
+ GlAccordion,
11
+ GlAccordionItem,
12
+ PreBlock,
13
+ },
14
+ props: {
15
+ projectId: {
16
+ type: [String, Number],
17
+ required: true,
18
+ },
19
+ projectPath: {
20
+ type: String,
21
+ required: false,
22
+ default: '',
23
+ },
24
+ branch: {
25
+ type: String,
26
+ required: true,
27
+ },
28
+ startBranch: {
29
+ type: String,
30
+ required: false,
31
+ default: '',
32
+ },
33
+ commitMessage: {
34
+ type: String,
35
+ required: true,
36
+ },
37
+ actions: {
38
+ type: Array,
39
+ required: true,
40
+ },
41
+ },
42
+ computed: {
43
+ actionsCount() {
44
+ return this.actions.length;
45
+ },
46
+ actionsCountMessage() {
47
+ return sprintf(
48
+ translatePlural(
49
+ 'CreateCommitToolParams.actionsCountMessage',
50
+ 'The commit contains %{count} file change.',
51
+ 'The commit contains %{count} file changes.'
52
+ )(this.actionsCount),
53
+ {
54
+ count: this.actionsCount,
55
+ }
56
+ );
57
+ },
58
+ },
59
+ methods: {
60
+ getActionTitle({ action, file_path: filePath, previous_path: previousPath }) {
61
+ switch (action) {
62
+ case 'create':
63
+ return sprintf(this.$options.i18n.CREATE_FILE_ACTION_LABEL, { filePath });
64
+ case 'update':
65
+ return sprintf(this.$options.i18n.UPDATE_FILE_ACTION_LABEL, { filePath });
66
+ case 'delete':
67
+ return sprintf(this.$options.i18n.DELETE_FILE_ACTION_LABEL, { filePath });
68
+ case 'move':
69
+ return sprintf(this.$options.i18n.MOVE_FILE_ACTION_LABEL, { filePath: previousPath });
70
+ case 'chmod':
71
+ return sprintf(this.$options.i18n.CHMOD_FILE_ACTION_LABEL, { filePath });
72
+ default:
73
+ return sprintf(this.$options.i18n.UNKNOWN_FILE_ACTION_LABEL, { filePath });
74
+ }
75
+ },
76
+ getActionContent({ action, content, encoding }) {
77
+ if (!['create', 'update'].includes(action)) {
78
+ return this.$options.i18n.ACTION_WITH_NO_CONTENT;
79
+ }
80
+
81
+ return (!encoding || encoding === 'text') && content ? content : '';
82
+ },
83
+ },
84
+ i18n: {
85
+ COMMIT_SUMMARY_MESSAGE: translate(
86
+ 'CreateCommitToolParams.commitSummaryMessage',
87
+ 'Create a commit in the branch %{branch} and project %{project}.'
88
+ ),
89
+ READ_COMMIT_MESSAGE: translate(
90
+ 'CreateCommitToolParams.readCommitMessage',
91
+ 'Read commit message'
92
+ ),
93
+ CREATE_FILE_ACTION_LABEL: translate(
94
+ 'CreateCommitToolParams.createFileActionLabel',
95
+ 'Create file %{filePath}'
96
+ ),
97
+ UPDATE_FILE_ACTION_LABEL: translate(
98
+ 'CreateCommitToolParams.updateFileActionLabel',
99
+ 'Update file %{filePath}'
100
+ ),
101
+ DELETE_FILE_ACTION_LABEL: translate(
102
+ 'CreateCommitToolParams.deleteFileActionLabel',
103
+ 'Delete file %{filePath}'
104
+ ),
105
+ MOVE_FILE_ACTION_LABEL: translate(
106
+ 'CreateCommitToolParams.moveFileActionLabel',
107
+ 'Move file %{filePath}'
108
+ ),
109
+ CHMOD_FILE_ACTION_LABEL: translate(
110
+ 'CreateCommitToolParams.chmodFileActionLabel',
111
+ 'Change permissions for file %{filePath}'
112
+ ),
113
+ UNKNOWN_FILE_ACTION_LABEL: translate(
114
+ 'CreateCommitToolParams.unknownFileActionLabel',
115
+ 'Modify file %{filePath}'
116
+ ),
117
+ ACTION_WITH_NO_CONTENT: translate(
118
+ 'CreateCommitToolParams.actionWithNoContent',
119
+ 'This action does not have any content.'
120
+ ),
121
+ EXPAND_CHANGES: translate('CreateCommitToolParams.expandFileChanges', 'Expand file changes'),
122
+ },
123
+ };
124
+ </script>
125
+ <template>
126
+ <div class="gl-flex gl-flex-col">
127
+ <div>
128
+ <gl-sprintf :message="$options.i18n.COMMIT_SUMMARY_MESSAGE">
129
+ <template #project>
130
+ <code>{{ projectPath || projectId }}</code>
131
+ </template>
132
+ <template #branch>
133
+ <code>{{ branch }}</code>
134
+ </template>
135
+ </gl-sprintf>
136
+ {{ actionsCountMessage }}
137
+ </div>
138
+ <gl-accordion class="-gl-ml-2 gl-mt-3" :header-level="3">
139
+ <gl-accordion-item :title="$options.i18n.READ_COMMIT_MESSAGE">
140
+ <pre-block>{{ commitMessage }}</pre-block>
141
+ </gl-accordion-item>
142
+ <gl-accordion-item :title="$options.i18n.EXPAND_CHANGES">
143
+ <gl-accordion :header-level="4">
144
+ <gl-accordion-item
145
+ v-for="(action, index) in actions"
146
+ :key="index"
147
+ :title="getActionTitle(action)"
148
+ >
149
+ <pre-block>{{ getActionContent(action) }}</pre-block>
150
+ </gl-accordion-item>
151
+ </gl-accordion>
152
+ </gl-accordion-item>
153
+ </gl-accordion>
154
+ </div>
155
+ </template>
@@ -0,0 +1,80 @@
1
+ <script>
2
+ import { GlSprintf, GlAccordion, GlAccordionItem } from '@gitlab/ui';
3
+ import { translate } from '../../../../../utils/i18n';
4
+ import PreBlock from './pre_block.vue';
5
+
6
+ export default {
7
+ name: 'CreateIssueToolParams',
8
+ components: {
9
+ GlSprintf,
10
+ GlAccordion,
11
+ GlAccordionItem,
12
+ PreBlock,
13
+ },
14
+ props: {
15
+ projectId: {
16
+ type: [String, Number],
17
+ required: true,
18
+ },
19
+ projectPath: {
20
+ type: String,
21
+ required: false,
22
+ default: '',
23
+ },
24
+ title: {
25
+ type: String,
26
+ required: true,
27
+ },
28
+ description: {
29
+ type: String,
30
+ required: true,
31
+ },
32
+ labels: {
33
+ type: String,
34
+ required: false,
35
+ default: '',
36
+ },
37
+ },
38
+ computed: {
39
+ issueMessage() {
40
+ const baseMessage = this.$options.i18n.ISSUE_SUMMARY_MESSAGE_BASE;
41
+ const labelsMessage = this.$options.i18n.ISSUE_SUMMARY_MESSAGE_WITH_LABELS;
42
+
43
+ return this.labels ? `${baseMessage} ${labelsMessage}` : baseMessage;
44
+ },
45
+ },
46
+ i18n: {
47
+ ISSUE_SUMMARY_MESSAGE_BASE: translate(
48
+ 'CreateIssueToolParams.ISSUE_SUMMARY_MESSAGE_BASE',
49
+ 'Open an issue with title "%{title}" in project %{project}.'
50
+ ),
51
+ ISSUE_SUMMARY_MESSAGE_WITH_LABELS: translate(
52
+ 'CreateIssueToolParams.ISSUE_SUMMARY_MESSAGE_WITH_LABELS',
53
+ 'Assign the labels %{labels}.'
54
+ ),
55
+ ACCORDION_TITLE: translate('CreateIssueToolParams.ACCORDION_TITLE', 'Read description'),
56
+ },
57
+ };
58
+ </script>
59
+ <template>
60
+ <div class="gl-flex gl-flex-col">
61
+ <div>
62
+ <gl-sprintf :message="issueMessage">
63
+ <template #title>
64
+ <em>{{ title }}</em>
65
+ </template>
66
+ <template #project>
67
+ <code>{{ projectPath || projectId }}</code>
68
+ </template>
69
+ <template #labels>
70
+ <code>{{ labels }}</code>
71
+ </template>
72
+ </gl-sprintf>
73
+ </div>
74
+ <gl-accordion class="-gl-ml-2 gl-mt-3" :header-level="3">
75
+ <gl-accordion-item :title="$options.i18n.ACCORDION_TITLE">
76
+ <pre-block>{{ description }}</pre-block>
77
+ </gl-accordion-item>
78
+ </gl-accordion>
79
+ </div>
80
+ </template>
@@ -0,0 +1,74 @@
1
+ <script>
2
+ import { GlSprintf, GlAccordion, GlAccordionItem } from '@gitlab/ui';
3
+ import { translate } from '../../../../../utils/i18n';
4
+ import PreBlock from './pre_block.vue';
5
+
6
+ export default {
7
+ name: 'CreateMergeRequestToolParams',
8
+ components: {
9
+ GlSprintf,
10
+ GlAccordion,
11
+ GlAccordionItem,
12
+ PreBlock,
13
+ },
14
+ props: {
15
+ projectId: {
16
+ type: [String, Number],
17
+ required: true,
18
+ },
19
+ projectPath: {
20
+ type: String,
21
+ required: false,
22
+ default: '',
23
+ },
24
+ title: {
25
+ type: String,
26
+ required: true,
27
+ },
28
+ sourceBranch: {
29
+ type: String,
30
+ required: true,
31
+ },
32
+ targetBranch: {
33
+ type: String,
34
+ required: true,
35
+ },
36
+ description: {
37
+ type: String,
38
+ required: true,
39
+ },
40
+ },
41
+ i18n: {
42
+ MERGE_REQUEST_SUMMARY_MESSAGE: translate(
43
+ 'CreateMergeRequestToolParams.MERGE_REQUEST_SUMMARY_MESSAGE',
44
+ 'Open a merge request with title "%{title}" in project %{project} from branch %{sourceBranch} to branch %{targetBranch}.'
45
+ ),
46
+ ACCORDION_TITLE: translate('CreateMergeRequestToolParams.ACCORDION_TITLE', 'Read description'),
47
+ },
48
+ };
49
+ </script>
50
+ <template>
51
+ <div class="gl-flex gl-flex-col">
52
+ <div>
53
+ <gl-sprintf :message="$options.i18n.MERGE_REQUEST_SUMMARY_MESSAGE">
54
+ <template #title>
55
+ <em>{{ title }}</em>
56
+ </template>
57
+ <template #project>
58
+ <code>{{ projectPath || projectId }}</code>
59
+ </template>
60
+ <template #sourceBranch>
61
+ <code>{{ sourceBranch }}</code>
62
+ </template>
63
+ <template #targetBranch>
64
+ <code>{{ targetBranch }}</code>
65
+ </template>
66
+ </gl-sprintf>
67
+ </div>
68
+ <gl-accordion class="-gl-ml-2 gl-mt-3" :header-level="3">
69
+ <gl-accordion-item :title="$options.i18n.ACCORDION_TITLE">
70
+ <pre-block>{{ description }}</pre-block>
71
+ </gl-accordion-item>
72
+ </gl-accordion>
73
+ </div>
74
+ </template>
@@ -0,0 +1,5 @@
1
+ <template>
2
+ <pre
3
+ class="gl-border gl-grid gl-text-pretty gl-rounded-lg gl-border-strong gl-bg-strong gl-p-5 gl-font-monospace"
4
+ ><code><slot></slot></code></pre>
5
+ </template>
@@ -0,0 +1,30 @@
1
+ <script>
2
+ import { GlIcon } from '@gitlab/ui';
3
+
4
+ export default {
5
+ name: 'RunCommandToolParams',
6
+ components: {
7
+ GlIcon,
8
+ },
9
+ props: {
10
+ program: {
11
+ type: String,
12
+ required: true,
13
+ },
14
+ args: {
15
+ type: String,
16
+ required: true,
17
+ },
18
+ },
19
+ computed: {
20
+ formattedCommand() {
21
+ return `${this.program} ${this.args}`;
22
+ },
23
+ },
24
+ };
25
+ </script>
26
+ <template>
27
+ <div class="gl-flex gl-items-center gl-gap-3">
28
+ <gl-icon name="terminal" /> <code>{{ formattedCommand }}</code>
29
+ </div>
30
+ </template>
@@ -1,6 +1,18 @@
1
1
  <script>
2
- import { GlButton, GlIcon, GlFormTextarea, GlFormGroup } from '@gitlab/ui';
2
+ import { GlButton, GlFormTextarea, GlFormGroup, GlCard, GlBadge } from '@gitlab/ui';
3
3
  import { translate } from '../../../../utils/i18n';
4
+ import { convertKeysToCamelCase } from '../../../../utils/object';
5
+ import CreateCommitToolParams from './components/create_commit_tool_params.vue';
6
+ import CreateIssueToolParams from './components/create_issue_tool_params.vue';
7
+ import CreateMergeRequestToolParams from './components/create_merge_request_tool_params.vue';
8
+ import RunCommandToolParams from './components/run_command_tool_params.vue';
9
+
10
+ export const APPROVAL_TOOL_NAMES = {
11
+ createCommit: 'create_commit',
12
+ createIssue: 'create_issue',
13
+ createMergeRequest: 'create_merge_request',
14
+ runCommand: 'run_command',
15
+ };
4
16
 
5
17
  export const PROCESSING_STATE = {
6
18
  APPROVING: 'approving',
@@ -9,10 +21,6 @@ export const PROCESSING_STATE = {
9
21
  };
10
22
 
11
23
  export const i18n = {
12
- TOOL_APPROVAL_TITLE: translate(
13
- 'MessageToolApproval.toolApprovalTitle',
14
- 'Duo would like to execute a tool. Do you want to proceed?'
15
- ),
16
24
  TOOL_APPROVAL_DESCRIPTION: translate(
17
25
  'MessageToolApproval.toolApprovalDescription',
18
26
  'GitLab Duo Agentic Chat wants to execute a tool. Do you want to proceed?'
@@ -34,15 +42,46 @@ export const i18n = {
34
42
  "Tell Duo why you're rejecting this tool execution..."
35
43
  ),
36
44
  CANCEL_TEXT: translate('MessageToolApproval.cancelText', 'Cancel'),
45
+ TOOL_APPROVAL_TITLES: {
46
+ [APPROVAL_TOOL_NAMES.createCommit]: translate(
47
+ 'MessageToolApproval.createCommit',
48
+ 'Duo wants to push a commit.'
49
+ ),
50
+ [APPROVAL_TOOL_NAMES.createIssue]: translate(
51
+ 'MessageToolApproval.createIssue',
52
+ 'Duo wants to open an issue.'
53
+ ),
54
+ [APPROVAL_TOOL_NAMES.createMergeRequest]: translate(
55
+ 'MessageToolApproval.createMergeRequest',
56
+ 'Duo wants to create a merge request.'
57
+ ),
58
+ [APPROVAL_TOOL_NAMES.runCommand]: translate(
59
+ 'MessageToolApproval.runCommand',
60
+ 'Duo wants to run a command.'
61
+ ),
62
+ },
63
+ TOOL_STATUS: translate('MessageToolApproval.toolStatus', 'Pending'),
64
+ };
65
+
66
+ const TOOL_PARAMS_VIEW_COMPONENTS = {
67
+ [APPROVAL_TOOL_NAMES.createCommit]: 'CreateCommitToolParams',
68
+ [APPROVAL_TOOL_NAMES.createIssue]: 'CreateIssueToolParams',
69
+ [APPROVAL_TOOL_NAMES.createMergeRequest]: 'CreateMergeRequestToolParams',
70
+ [APPROVAL_TOOL_NAMES.runCommand]: 'RunCommandToolParams',
37
71
  };
38
72
 
39
73
  export default {
40
74
  name: 'MessageToolApproval',
41
75
  components: {
42
76
  GlButton,
43
- GlIcon,
77
+ GlCard,
44
78
  GlFormTextarea,
45
79
  GlFormGroup,
80
+ GlBadge,
81
+ CreateCommitToolParams,
82
+ CreateIssueToolParams,
83
+ CreateMergeRequestToolParams,
84
+ RunCommandToolParams,
46
85
  },
47
86
  props: {
48
87
  message: {
@@ -69,9 +108,15 @@ export default {
69
108
  toolParameters() {
70
109
  return this.message?.tool_info?.args || {};
71
110
  },
111
+ camelCaseToolParameters() {
112
+ return convertKeysToCamelCase(this.toolParameters);
113
+ },
72
114
  hasToolParameters() {
73
115
  return Object.keys(this.toolParameters).length > 0;
74
116
  },
117
+ toolApprovalTitle() {
118
+ return i18n.TOOL_APPROVAL_TITLES[this.toolName];
119
+ },
75
120
  isApproving() {
76
121
  return this.isProcessing && this.localProcessingState === PROCESSING_STATE.APPROVING;
77
122
  },
@@ -87,6 +132,9 @@ export default {
87
132
  denyButtonText() {
88
133
  return this.isDenying ? this.$options.i18n.DENYING_TEXT : this.$options.i18n.DENY_TEXT;
89
134
  },
135
+ toolParamsViewComponent() {
136
+ return TOOL_PARAMS_VIEW_COMPONENTS[this.toolName];
137
+ },
90
138
  },
91
139
  watch: {
92
140
  // Reset local state when processing completes
@@ -127,104 +175,111 @@ export default {
127
175
  },
128
176
  },
129
177
  i18n,
178
+ APPROVAL_TOOL_NAMES,
130
179
  };
131
180
  </script>
132
181
 
133
182
  <template>
134
- <div
135
- class="md gl-border gl-rounded-bl-none gl-border-1 gl-border-solid gl-border-transparent gl-bg-subtle gl-p-4 gl-leading-20 gl-text-default gl-break-anywhere"
136
- >
137
- <p class="gl-mb-3 gl-text-gray-700">
138
- {{ $options.i18n.TOOL_APPROVAL_TITLE }}
139
- </p>
140
-
141
- <div class="gl-mb-3 gl-flex gl-items-center gl-gap-2">
142
- <gl-icon name="work-item-maintenance" class="gl-text-gray-500" />
143
- <strong>{{ toolName }}</strong>
144
- </div>
145
-
146
- <div class="gl-border gl-mb-4 gl-rounded-base gl-border-gray-200 gl-bg-gray-50 gl-p-3">
147
- <p class="gl-mb-1 gl-text-sm gl-text-gray-500">
148
- {{ $options.i18n.REQUEST_TEXT }}
149
- </p>
150
- <code
151
- v-if="hasToolParameters"
152
- class="gl-whitespace-pre-wrap gl-text-sm gl-text-default gl-font-monospace"
153
- data-testid="tool-parameters"
154
- >{{ JSON.stringify(toolParameters, null, 2) }}</code
155
- >
156
- <span v-else class="gl-text-sm gl-text-gray-500" data-testid="no-parameters-message">
157
- {{ $options.i18n.NO_PARAMETERS_TEXT }}
158
- </span>
159
- </div>
160
-
161
- <div v-if="!showDenialReason" class="gl-flex gl-justify-between">
162
- <gl-button
163
- variant="danger"
164
- size="small"
165
- icon="cancel"
166
- data-testid="deny-tool-inline"
167
- :disabled="buttonsDisabled"
168
- :loading="isDenying"
169
- @click="handleDeny"
170
- >
171
- {{ denyButtonText }}
172
- </gl-button>
173
-
174
- <gl-button
175
- variant="confirm"
176
- size="small"
177
- icon="play"
178
- data-testid="approve-tool-inline"
179
- :disabled="buttonsDisabled"
180
- :loading="isApproving"
181
- @click="handleApprove"
182
- >
183
- {{ approveButtonText }}
184
- </gl-button>
183
+ <gl-card>
184
+ <template #header>
185
+ <div class="gl-flex gl-items-center gl-justify-between">
186
+ <span>
187
+ {{ toolApprovalTitle }}
188
+ </span>
189
+ <gl-badge>
190
+ {{ $options.i18n.TOOL_STATUS }}
191
+ </gl-badge>
192
+ </div>
193
+ </template>
194
+ <component
195
+ :is="toolParamsViewComponent"
196
+ v-if="toolParamsViewComponent"
197
+ class="gl-leading-20"
198
+ v-bind="camelCaseToolParameters"
199
+ />
200
+ <div
201
+ v-else
202
+ class="md gl-border gl-rounded-bl-none gl-border-1 gl-border-solid gl-border-transparent gl-bg-subtle gl-p-4 gl-leading-20 gl-text-default gl-break-anywhere"
203
+ >
204
+ <div class="gl-border gl-mb-4 gl-rounded-base gl-border-gray-200 gl-bg-gray-50 gl-p-3">
205
+ <p class="gl-mb-1 gl-text-sm gl-text-gray-500">
206
+ {{ $options.i18n.REQUEST_TEXT }}
207
+ </p>
208
+ <code
209
+ v-if="hasToolParameters"
210
+ class="gl-whitespace-pre-wrap gl-text-sm gl-text-default gl-font-monospace"
211
+ data-testid="tool-parameters"
212
+ >{{ JSON.stringify(toolParameters, null, 2) }}</code
213
+ >
214
+ <span v-else class="gl-text-sm gl-text-gray-500" data-testid="no-parameters-message">
215
+ {{ $options.i18n.NO_PARAMETERS_TEXT }}
216
+ </span>
217
+ </div>
185
218
  </div>
186
- <div v-else class="gl-mt-3">
187
- <gl-form-group
188
- :label="$options.i18n.DENIAL_REASON_LABEL"
189
- label-for="inline-rejection-reason"
190
- :optional="true"
191
- class="gl-mb-3"
192
- >
193
- <gl-form-textarea
194
- id="inline-rejection-reason"
195
- v-model="denialReason"
196
- :placeholder="$options.i18n.DENIAL_REASON_PLACEHOLDER"
197
- :rows="2"
198
- :no-resize="true"
199
- :submit-on-enter="false"
200
- :disabled="buttonsDisabled"
201
- data-testid="denial-reason-textarea"
202
- autofocus
203
- @submit="submitDenial"
204
- />
205
- </gl-form-group>
206
-
207
- <div class="gl-flex gl-gap-3">
219
+ <template #footer>
220
+ <div v-if="!showDenialReason" class="gl-flex gl-gap-2">
208
221
  <gl-button
222
+ variant="confirm"
209
223
  size="small"
210
- data-testid="cancel-denial"
224
+ data-testid="approve-tool-inline"
211
225
  :disabled="buttonsDisabled"
212
- @click="cancelDenial"
226
+ :loading="isApproving"
227
+ @click="handleApprove"
213
228
  >
214
- {{ $options.i18n.CANCEL_TEXT }}
229
+ {{ approveButtonText }}
215
230
  </gl-button>
216
231
  <gl-button
217
- variant="danger"
218
232
  size="small"
219
- icon="cancel"
220
- data-testid="submit-denial"
233
+ data-testid="deny-tool-inline"
221
234
  :disabled="buttonsDisabled"
222
235
  :loading="isDenying"
223
- @click="submitDenial"
236
+ @click="handleDeny"
224
237
  >
225
238
  {{ denyButtonText }}
226
239
  </gl-button>
227
240
  </div>
228
- </div>
229
- </div>
241
+ <div v-else>
242
+ <gl-form-group
243
+ :label="$options.i18n.DENIAL_REASON_LABEL"
244
+ label-for="inline-rejection-reason"
245
+ :optional="true"
246
+ class="gl-mb-3"
247
+ >
248
+ <gl-form-textarea
249
+ id="inline-rejection-reason"
250
+ v-model="denialReason"
251
+ :placeholder="$options.i18n.DENIAL_REASON_PLACEHOLDER"
252
+ :rows="2"
253
+ :no-resize="true"
254
+ :submit-on-enter="false"
255
+ :disabled="buttonsDisabled"
256
+ data-testid="denial-reason-textarea"
257
+ autofocus
258
+ @submit="submitDenial"
259
+ />
260
+ </gl-form-group>
261
+
262
+ <div class="gl-flex gl-gap-3">
263
+ <gl-button
264
+ size="small"
265
+ data-testid="submit-denial"
266
+ variant="confirm"
267
+ :disabled="buttonsDisabled"
268
+ :loading="isDenying"
269
+ @click="submitDenial"
270
+ >
271
+ {{ denyButtonText }}
272
+ </gl-button>
273
+ <gl-button
274
+ size="small"
275
+ data-testid="cancel-denial"
276
+ :disabled="buttonsDisabled"
277
+ @click="cancelDenial"
278
+ >
279
+ {{ $options.i18n.CANCEL_TEXT }}
280
+ </gl-button>
281
+ </div>
282
+ </div>
283
+ </template>
284
+ </gl-card>
230
285
  </template>
@@ -141,7 +141,6 @@ $drawer-width: 400px;
141
141
  overflow: hidden;
142
142
  background: var(--gl-control-background-color-default);
143
143
  box-shadow: inset 0 0 0 $gl-border-size-1 var(--gl-control-border-color-default);
144
- border-radius: px-to-rem(20px);
145
144
 
146
145
  &:focus-within {
147
146
  @include gl-focus($color: var(--gl-control-border-color-focus));
@@ -152,7 +151,6 @@ $drawer-width: 400px;
152
151
  resize: none;
153
152
  max-height: 240px;
154
153
  padding-right: 40px;
155
- border-radius: px-to-rem(20px);
156
154
  }
157
155
 
158
156
  &::after {
@@ -160,6 +158,7 @@ $drawer-width: 400px;
160
158
  @apply gl-invisible;
161
159
  @apply gl-p-4;
162
160
  @apply gl-font-regular;
161
+ @apply gl-absolute;
163
162
  padding-right: 40px;
164
163
  word-break: break-word;
165
164
  }