Skip to content

Commit d26ec6b

Browse files
committed
Modifying view to use store
1 parent 329e75d commit d26ec6b

15 files changed

+758
-40
lines changed

src/Frontend/src/components/failedmessages/EditRetryDialog.vue

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<script setup lang="ts">
22
import { computed, onMounted, ref, watch } from "vue";
3-
import { useRetryEditedMessage } from "../../composables/serviceFailedMessage";
3+
import { useRetryEditedMessage } from "@/composables/serviceFailedMessage.ts";
44
import MessageHeader from "./EditMessageHeader.vue";
55
import { EditAndRetryConfig } from "@/resources/Configuration";
66
import type Header from "@/resources/Header";
@@ -65,11 +65,7 @@ watch(messageBody, (newValue) => {
6565
if (newValue !== origMessageBody) {
6666
localMessage.value.isBodyChanged = true;
6767
}
68-
if (newValue === "") {
69-
localMessage.value.isBodyEmpty = true;
70-
} else {
71-
localMessage.value.isBodyEmpty = false;
72-
}
68+
localMessage.value.isBodyEmpty = newValue === "";
7369
});
7470
7571
function close() {
Lines changed: 345 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,345 @@
1+
<script setup lang="ts">
2+
import { computed, onMounted, ref, watch } from "vue";
3+
import { useRetryEditedMessage } from "@/composables/serviceFailedMessage.ts";
4+
import MessageHeader from "./EditMessageHeader.vue";
5+
import type Header from "@/resources/Header";
6+
import parseContentType from "@/composables/contentTypeParser";
7+
import { CodeLanguage } from "@/components/codeEditorTypes";
8+
import CodeEditor from "@/components/CodeEditor.vue";
9+
import { useMessageViewStore } from "@/stores/MessageViewStore.ts";
10+
11+
interface HeaderWithEditing extends Header {
12+
isLocked: boolean;
13+
isSensitive: boolean;
14+
isMarkedAsRemoved: boolean;
15+
isChanged: boolean;
16+
}
17+
18+
const emit = defineEmits<{
19+
cancel: [];
20+
confirm: [];
21+
}>();
22+
23+
interface LocalMessageState {
24+
isBodyChanged: boolean;
25+
isBodyEmpty: boolean;
26+
isContentTypeSupported: boolean;
27+
language?: CodeLanguage;
28+
bodyContentType: string | undefined;
29+
bodyUnavailable: boolean;
30+
isEvent: boolean;
31+
retried: boolean;
32+
headers: HeaderWithEditing[];
33+
messageBody: string;
34+
}
35+
36+
const panel = ref(0);
37+
const localMessage = ref<LocalMessageState>({
38+
isBodyChanged: false,
39+
isBodyEmpty: false,
40+
isContentTypeSupported: false,
41+
bodyContentType: undefined,
42+
bodyUnavailable: true,
43+
isEvent: false,
44+
retried: false,
45+
headers: [],
46+
messageBody: "",
47+
});
48+
let origMessageBody: string;
49+
50+
const showEditAndRetryConfirmation = ref(false);
51+
const showCancelConfirmation = ref(false);
52+
const showEditRetryGenericError = ref(false);
53+
const store = useMessageViewStore();
54+
const id = computed(() => store.state.data.id ?? "");
55+
const messageBody = computed(() => store.body.data.value);
56+
57+
watch(messageBody, (newValue) => {
58+
if (newValue !== origMessageBody) {
59+
localMessage.value.isBodyChanged = true;
60+
}
61+
localMessage.value.isBodyEmpty = newValue === "";
62+
});
63+
64+
function close() {
65+
emit("cancel");
66+
}
67+
68+
function confirmEditAndRetry() {
69+
showEditAndRetryConfirmation.value = true;
70+
}
71+
72+
function confirmCancel() {
73+
if (localMessage.value.isBodyChanged) {
74+
showCancelConfirmation.value = true;
75+
return;
76+
}
77+
78+
if (localMessage.value.headers.some((header: HeaderWithEditing) => header.isChanged)) {
79+
showCancelConfirmation.value = true;
80+
return;
81+
}
82+
83+
close();
84+
}
85+
86+
function resetBodyChanges() {
87+
localMessage.value.messageBody = origMessageBody;
88+
localMessage.value.isBodyChanged = false;
89+
}
90+
91+
function findHeadersByKey(key: string) {
92+
return localMessage.value.headers.find((header: HeaderWithEditing) => header.key === key);
93+
}
94+
95+
function getContentType() {
96+
const header = findHeadersByKey("NServiceBus.ContentType");
97+
return header?.value;
98+
}
99+
100+
function getMessageIntent() {
101+
const intent = findHeadersByKey("NServiceBus.MessageIntent");
102+
return intent?.value;
103+
}
104+
105+
function removeHeadersMarkedAsRemoved() {
106+
localMessage.value.headers = localMessage.value.headers.filter((header: HeaderWithEditing) => !header.isMarkedAsRemoved);
107+
}
108+
109+
async function retryEditedMessage() {
110+
removeHeadersMarkedAsRemoved();
111+
try {
112+
await useRetryEditedMessage(id.value, localMessage);
113+
localMessage.value.retried = true;
114+
return emit("confirm");
115+
} catch {
116+
showEditAndRetryConfirmation.value = false;
117+
showEditRetryGenericError.value = true;
118+
}
119+
}
120+
121+
function initializeMessageBodyAndHeaders() {
122+
origMessageBody = store.body.data.value ?? "";
123+
localMessage.value = {
124+
isBodyChanged: false,
125+
isBodyEmpty: false,
126+
isContentTypeSupported: false,
127+
bodyContentType: undefined,
128+
bodyUnavailable: store.body.not_found ?? false,
129+
isEvent: false,
130+
retried: store.state.data.failure_status.retried ?? false,
131+
headers: store.headers.data.map((header: Header) => ({ ...header })) as HeaderWithEditing[],
132+
messageBody: store.body.data.value ?? "",
133+
};
134+
localMessage.value.isBodyEmpty = false;
135+
localMessage.value.isBodyChanged = false;
136+
137+
const contentType = getContentType();
138+
localMessage.value.bodyContentType = contentType;
139+
const parsedContentType = parseContentType(contentType);
140+
localMessage.value.isContentTypeSupported = parsedContentType.isSupported;
141+
localMessage.value.language = parsedContentType.language;
142+
143+
const messageIntent = getMessageIntent();
144+
localMessage.value.isEvent = messageIntent === "Publish";
145+
146+
for (let index = 0; index < store.headers.data.length; index++) {
147+
const header: HeaderWithEditing = store.headers.data[index] as HeaderWithEditing;
148+
149+
header.isLocked = false;
150+
header.isSensitive = false;
151+
header.isMarkedAsRemoved = false;
152+
header.isChanged = false;
153+
154+
if (store.edit_and_retry_config.locked_headers.includes(header.key)) {
155+
header.isLocked = true;
156+
} else if (store.edit_and_retry_config.sensitive_headers.includes(header.key)) {
157+
header.isSensitive = true;
158+
}
159+
160+
localMessage.value.headers[index] = header;
161+
}
162+
}
163+
164+
function togglePanel(panelNum: number) {
165+
panel.value = panelNum;
166+
return false;
167+
}
168+
169+
onMounted(() => {
170+
togglePanel(1);
171+
initializeMessageBodyAndHeaders();
172+
});
173+
</script>
174+
175+
<template>
176+
<section name="failed_message_editor">
177+
<div class="model modal-msg-editor" style="z-index: 1050; display: block" role="dialog" aria-label="edit and retry message">
178+
<div class="modal-mask">
179+
<div class="modal-dialog">
180+
<div class="modal-content">
181+
<div class="modal-header">
182+
<div class="modal-title">
183+
<h3>Edit and retry message</h3>
184+
</div>
185+
</div>
186+
<div class="modal-body">
187+
<div class="row">
188+
<div class="col-sm-12">
189+
<div class="row msg-editor-tabs">
190+
<div class="col-sm-12 no-side-padding">
191+
<div role="tablist" class="tabs msg-tabs">
192+
<h5 role="tab" :class="{ active: panel === 1 }" class="nav-item" @click.prevent="togglePanel(1)"><a href="#">Headers</a></h5>
193+
<h5 role="tab" :class="{ active: panel === 2 }" class="nav-item" @click.prevent="togglePanel(2)"><a href="#">Message body</a></h5>
194+
</div>
195+
</div>
196+
</div>
197+
<div class="row msg-editor-content">
198+
<div class="col-sm-12 no-side-padding">
199+
<div class="alert alert-warning" v-if="localMessage.isEvent">
200+
<div class="col-sm-12">
201+
<i class="fa fa-exclamation-circle"></i> This message is an event. If it was already successfully handled by other subscribers, editing it now has the risk of changing the semantic meaning of the event and may result in
202+
altering the system behavior.
203+
</div>
204+
</div>
205+
<div class="alert alert-warning" v-if="!localMessage.isContentTypeSupported || localMessage.bodyUnavailable">
206+
<div role="status" aria-label="cannot edit message body" class="col-sm-12">
207+
<i class="fa fa-exclamation-circle"></i> Message body cannot be edited because content type "{{ localMessage.bodyContentType }}" is not supported. Only messages with content types "application/json" and "text/xml" can be
208+
edited.
209+
</div>
210+
</div>
211+
<div class="row alert alert-danger" v-if="showEditRetryGenericError">
212+
<div class="col-sm-12"><i class="fa fa-exclamation-triangle"></i> An error occurred while retrying the message, please check the ServiceControl logs for more details on the failure.</div>
213+
</div>
214+
<table role="tabpanel" class="table" v-if="panel === 1">
215+
<tbody>
216+
<tr class="interactiveList" v-for="header in localMessage.headers" :key="header.key">
217+
<MessageHeader :header="header"></MessageHeader>
218+
</tr>
219+
</tbody>
220+
</table>
221+
<div role="tabpanel" v-if="panel === 2 && !localMessage.bodyUnavailable" style="height: calc(100% - 260px)">
222+
<div style="margin-top: 1.25rem">
223+
<CodeEditor aria-label="message body" :read-only="!localMessage.isContentTypeSupported" v-model="localMessage.messageBody" :language="localMessage.language" :show-gutter="true"></CodeEditor>
224+
</div>
225+
<span class="empty-error" v-if="localMessage.isBodyEmpty"><i class="fa fa-exclamation-triangle"></i> Message body cannot be empty</span>
226+
<span class="reset-body" v-if="localMessage.isBodyChanged"><i class="fa fa-undo" v-tippy="`Reset changes`"></i> <a @click="resetBodyChanges()" href="javascript:void(0)">Reset changes</a></span>
227+
<div class="alert alert-info" v-if="panel === 2 && localMessage.bodyUnavailable">{{ localMessage.bodyUnavailable }}</div>
228+
</div>
229+
</div>
230+
</div>
231+
</div>
232+
</div>
233+
</div>
234+
<div class="modal-footer" v-if="!showEditAndRetryConfirmation && !showCancelConfirmation">
235+
<button class="btn btn-default" @click="confirmCancel()">Cancel</button>
236+
<button class="btn btn-primary" :disabled="localMessage.isBodyEmpty || localMessage.bodyUnavailable" @click="confirmEditAndRetry()">Retry</button>
237+
</div>
238+
<div class="modal-footer cancel-confirmation" v-if="showCancelConfirmation">
239+
<div>Are you sure you want to cancel? Any changes you made will be lost.</div>
240+
<button class="btn btn-default" @click="close()">Yes</button>
241+
<button class="btn btn-primary" @click="showCancelConfirmation = false">No</button>
242+
</div>
243+
<div class="modal-footer edit-retry-confirmation" v-if="showEditAndRetryConfirmation">
244+
<div>Are you sure you want to continue? If you edited the message, it may cause unexpected consequences in the system.</div>
245+
<button class="btn btn-default" @click="retryEditedMessage()">Yes</button>
246+
<button class="btn btn-primary" @click="showEditAndRetryConfirmation = false">No</button>
247+
</div>
248+
</div>
249+
</div>
250+
</div>
251+
</div>
252+
</section>
253+
</template>
254+
255+
<style scoped>
256+
@import "@/components/modal.css";
257+
258+
.cancel-confirmation,
259+
.edit-retry-confirmation {
260+
background: #181919;
261+
color: #fff;
262+
border-bottom-right-radius: 6px;
263+
border-bottom-left-radius: 6px;
264+
}
265+
266+
.cancel-confirmation div,
267+
.edit-retry-confirmation div {
268+
display: inline-block;
269+
font-weight: bold;
270+
font-size: 14px;
271+
position: relative;
272+
top: 1px;
273+
margin-right: 20px;
274+
}
275+
276+
.modal-msg-editor .reset-body {
277+
color: #00a3c4;
278+
font-weight: bold;
279+
text-align: left;
280+
margin-top: 15px;
281+
display: inline-block;
282+
}
283+
284+
.modal-msg-editor .reset-body a:hover {
285+
cursor: pointer;
286+
}
287+
288+
.modal-msg-editor .reset-body i.fa.fa-undo {
289+
color: #00a3c4;
290+
}
291+
292+
.modal-msg-editor .empty-error {
293+
float: right;
294+
margin-top: 15px;
295+
color: #ce4844;
296+
font-weight: bold;
297+
}
298+
299+
.modal-msg-editor .empty-error i.fa.fa-exclamation-triangle {
300+
color: #ce4844;
301+
}
302+
303+
.modal-msg-editor .row.alert.alert-danger i.fa.fa-exclamation-triangle {
304+
color: #ce4844;
305+
}
306+
307+
.modal-msg-editor .row.alert.alert-warning i.fa.fa-exclamation-circle {
308+
color: #8a6d3b;
309+
}
310+
311+
.modal-msg-editor .modal-dialog {
312+
width: 70%;
313+
}
314+
315+
.modal-msg-editor .modal-body {
316+
overflow-y: auto;
317+
height: 80vh;
318+
}
319+
320+
.modal-msg-editor .msg-tabs {
321+
margin-top: 20px;
322+
}
323+
324+
.modal-msg-editor .row.msg-editor-tabs {
325+
height: 52px;
326+
position: relative;
327+
box-shadow: -10px 20px 20px #fff;
328+
z-index: 10;
329+
}
330+
331+
.modal-msg-editor .row.msg-editor-content table {
332+
margin-top: 20px;
333+
}
334+
335+
.modal-msg-editor .row,
336+
.modal-msg-editor .col-sm-12 {
337+
height: 100%;
338+
}
339+
340+
.modal-msg-editor .row.msg-editor-content {
341+
height: calc(100% - 37px);
342+
overflow-y: auto;
343+
padding-right: 15px;
344+
}
345+
</style>
Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,19 @@
11
<script setup lang="ts">
2-
import { ExtendedFailedMessage } from "@/resources/FailedMessage";
32
import { computed } from "vue";
43
import CodeEditor from "@/components/CodeEditor.vue";
54
import parseContentType from "@/composables/contentTypeParser";
6-
const props = defineProps<{
7-
message: ExtendedFailedMessage;
8-
}>();
5+
import { useMessageViewStore } from "@/stores/MessageViewStore.ts";
96
10-
const contentType = computed(() => parseContentType(props.message.contentType));
7+
const store = useMessageViewStore();
8+
9+
const contentType = computed(() => parseContentType(store.body.data.content_type));
1110
</script>
1211

1312
<template>
14-
<div v-if="props.message.messageBodyNotFound" class="alert alert-info">Could not find message body. This could be because the message URL is invalid or the corresponding message was processed and is no longer tracked by ServiceControl.</div>
15-
<div v-else-if="props.message.bodyUnavailable" class="alert alert-info">Message body unavailable.</div>
16-
<CodeEditor v-else-if="contentType.isSupported" :model-value="props.message.messageBody" :language="contentType.language" :read-only="true" :show-gutter="true"></CodeEditor>
17-
<div v-else class="alert alert-warning">Message body cannot be displayed because content type "{{ props.message.contentType }}" is not supported.</div>
13+
<div v-if="store.body.not_found" class="alert alert-info">Could not find message body. This could be because the message URL is invalid or the corresponding message was processed and is no longer tracked by ServiceControl.</div>
14+
<div v-else-if="store.body.failed_to_load" class="alert alert-info">Message body unavailable.</div>
15+
<CodeEditor v-else-if="contentType.isSupported" :model-value="store.body.data.value ?? ''" :language="contentType.language" :read-only="true" :show-gutter="true"></CodeEditor>
16+
<div v-else class="alert alert-warning">Message body cannot be displayed because content type "{{ store.body.data.content_type }}" is not supported.</div>
1817
</template>
1918

2019
<style scoped></style>

0 commit comments

Comments
 (0)