Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implemented: Updated the DataManager Logs page to be dynamic in Job Manager(#680) #729

Merged
merged 15 commits into from
Nov 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions src/components/DownloadLogsFilePopover.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<template>
<ion-content>
<ion-list>
<ion-list-header>{{ dataManagerLog.logId }}</ion-list-header>
<ion-item button @click="downloadFile('logFile')">
{{ translate('Log file') }}
</ion-item>
<ion-item button @click="downloadFile('uploadedFile')">
{{ translate('Uploaded file') }}
</ion-item>
<ion-item button :disabled="!dataManagerLog?.errorRecordContentId" lines="none" @click="downloadFile('failedRecords')">
{{ translate('Failed records') }}
</ion-item>
</ion-list>
</ion-content>
</template>

<script lang="ts">
import {
IonContent,
IonItem,
IonList,
IonListHeader,
popoverController
} from "@ionic/vue";
import { defineComponent } from "vue";
import { translate } from "@hotwax/dxp-components";
import { JobService } from "@/services/JobService";
import { saveDataFile, showToast } from '@/utils';
import logger from "@/logger";

export default defineComponent({
name: "DownloadLogsFilePopover",
components: {
IonContent,
IonItem,
IonList,
IonListHeader
},
props: ["dataManagerLog"],
methods: {
async downloadFile(type: string) {
let dataResource = {} as any;

if (type === 'logFile') {
dataResource.dataResourceId = this.dataManagerLog.logFileDataResourceId
dataResource.name = this.dataManagerLog.logFileContentName
} else if (type === 'uploadedFile') {
dataResource.name = this.dataManagerLog.contentName
dataResource.dataResourceId = this.dataManagerLog.dataResourceId
} else if (type === 'failedRecords') {
dataResource.dataResourceId = this.dataManagerLog.errorRecordDataResourceId
dataResource.name = this.dataManagerLog.errorRecordContentName
}

if (dataResource.dataResourceId) {
try {
const response = await JobService.fetchFileData({
dataResourceId: dataResource.dataResourceId
});
saveDataFile(response.data, dataResource.name);
} catch (error) {
showToast(translate('Error downloading file'))
logger.error(error)
}
}
popoverController.dismiss();
}
},
setup() {
return {
translate
}
}
});
</script>
137 changes: 97 additions & 40 deletions src/components/JobConfiguration.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
<ion-button fill="outline" slot="end" v-if="isRefreshRequired" @click="refreshCurrentJob">
<ion-icon :icon="refreshOutline" slot="icon-only" />
</ion-button>
<ion-badge slot="end" color="dark" v-if="currentJob?.runTime && currentJob.statusId !== 'SERVICE_DRAFT' && !isRefreshRequired">{{ translate("running") }} {{ timeTillJob(currentJob.runTime) }}</ion-badge>
<ion-badge slot="end" color="dark" v-if="currentJob.cancelDateTime || currentJob.finishDateTime">{{ currentJob.statusId == "SERVICE_CANCELLED" || currentJob.statusId == "SERVICE_CRASHED" ? timeTillJob(currentJob.cancelDateTime) : timeTillJob(currentJob.finishDateTime) }}</ion-badge>
<ion-badge slot="end" color="dark" v-else-if="currentJob?.runTime && currentJob.statusId !== 'SERVICE_DRAFT' && !isRefreshRequired">{{ translate("running") }} {{ timeTillJob(currentJob.runTime) }}</ion-badge>
</ion-item>

<ion-list>
Expand All @@ -19,33 +20,45 @@

<ion-item>
<ion-icon slot="start" :icon="timeOutline" />
<ion-select interface="popover" :placeholder="translate('Select')" :value="runTime" @ionChange="updateRunTime($event)">
<div slot="label" class="ion-text-wrap">{{ translate("Run time") }}</div>
<ion-select-option v-for="runTime in runTimes" :key="runTime.value" :value="runTime.value">{{ translate(runTime.label) }}</ion-select-option>
</ion-select>
<!-- TODO: display a button when we are not having a runtime and open the datetime component
on click of that button
Currently, when mapping the same datetime component for label and button so it's not working so for
now commented the button and added a fallback string -->
<!-- <ion-button id="open-run-time-modal" size="small" fill="outline" color="medium" v-show="!currentJob?.runTime">{{ translate("Select run time") }}</ion-button> -->
<ion-modal class="date-time-modal" :is-open="isDateTimeModalOpen" @didDismiss="() => isDateTimeModalOpen = false">
<ion-content force-overscroll="false">
<ion-datetime
show-default-buttons
hour-cycle="h23"
:value="runTime ? (isCustomRunTime(runTime) ? getDateTime(runTime) : getDateTime(DateTime.now().toMillis() + runTime)) : getNowTimestamp()"
@ionChange="updateCustomTime($event)"
/>
</ion-content>
</ion-modal>
<template v-if="historyJobConfig">
ymaheshwari1 marked this conversation as resolved.
Show resolved Hide resolved
<ion-label class="ion-text-wrap">{{ translate("Run time") }}</ion-label>
<ion-label slot="end">{{ currentJob.runTime ? getTime(currentJob.runTime) : '' }}</ion-label>
</template>
<template v-else>
<ion-select interface="popover" :placeholder="translate('Select')" :value="runTime" @ionChange="updateRunTime($event)">
<div slot="label" class="ion-text-wrap">{{ translate("Run time") }}</div>
<ion-select-option v-for="runTime in runTimes" :key="runTime.value" :value="runTime.value">{{ translate(runTime.label) }}</ion-select-option>
</ion-select>
<!-- TODO: display a button when we are not having a runtime and open the datetime component
on click of that button
Currently, when mapping the same datetime component for label and button so it's not working so for
now commented the button and added a fallback string -->
<!-- <ion-button id="open-run-time-modal" size="small" fill="outline" color="medium" v-show="!currentJob?.runTime">{{ translate("Select run time") }}</ion-button> -->
<ion-modal class="date-time-modal" :is-open="isDateTimeModalOpen" @didDismiss="() => isDateTimeModalOpen = false">
<ion-content force-overscroll="false">
<ion-datetime
show-default-buttons
hour-cycle="h23"
:value="runTime ? (isCustomRunTime(runTime) ? getDateTime(runTime) : getDateTime(DateTime.now().toMillis() + runTime)) : getNowTimestamp()"
@ionChange="updateCustomTime($event)"
/>
</ion-content>
</ion-modal>
</template>
</ion-item>

<ion-item>
<ion-icon slot="start" :icon="timerOutline" />
<ion-select :value="jobStatus" :interface-options="{ header: translate('Frequency') }" interface="popover" :placeholder="translate('Disabled')" @ionChange="jobStatus = $event.detail.value" @ionDismiss="jobStatus == 'CUSTOM' && setCustomFrequency()">
<div slot="label" class="ion-text-wrap">{{ translate("Schedule") }}</div>
<ion-select-option v-for="freq in frequencyOptions" :key="freq.id" :value="freq.id">{{ freq.description }}</ion-select-option>
</ion-select>
<template v-if="historyJobConfig">
<ion-label class="ion-text-wrap">{{ translate("Schedule") }}</ion-label>
<ion-label slot="end">{{ currentJob.tempExprId ? temporalExpr(currentJob.tempExprId)?.description : "🙃" }}</ion-label>
</template>
<template v-else>
<ion-select :value="jobStatus" :interface-options="{ header: translate('Frequency') }" interface="popover" :placeholder="translate('Disabled')" @ionChange="jobStatus = $event.detail.value" @ionDismiss="jobStatus == 'CUSTOM' && setCustomFrequency()">
<div slot="label" class="ion-text-wrap">{{ translate("Schedule") }}</div>
<ion-select-option v-for="freq in frequencyOptions" :key="freq.id" :value="freq.id">{{ freq.description }}</ion-select-option>
</ion-select>
</template>
</ion-item>

<ion-item lines="none">
Expand Down Expand Up @@ -76,43 +89,67 @@
</ion-item> -->
</ion-list>

<div class="actions desktop-only">
<div class="actions desktop-only" :disabled="historyJobConfig">
<div>
<ion-button size="small" fill="outline" color="medium" :disabled="!hasPermission(Actions.APP_JOB_UPDATE) || currentJob.statusId === 'SERVICE_DRAFT' || isRefreshRequired" @click="skipJob(currentJob)">{{ translate("Skip once") }}</ion-button>
<ion-button size="small" fill="outline" color="danger" :disabled="!hasPermission(Actions.APP_JOB_UPDATE) || currentJob.statusId === 'SERVICE_DRAFT' || isRefreshRequired" @click="cancelJob(currentJob)">{{ translate("Disable") }}</ion-button>
<ion-button size="small" fill="outline" color="medium" :disabled="!hasPermission(Actions.APP_JOB_UPDATE) || currentJob.statusId === 'SERVICE_DRAFT' || isRefreshRequired || historyJobConfig" @click="skipJob(currentJob)">{{ translate("Skip once") }}</ion-button>
<ion-button size="small" fill="outline" color="danger" :disabled="!hasPermission(Actions.APP_JOB_UPDATE) || currentJob.statusId === 'SERVICE_DRAFT' || isRefreshRequired || historyJobConfig" @click="cancelJob(currentJob)">{{ translate("Disable") }}</ion-button>
</div>
<div>
<ion-button :disabled="!hasPermission(Actions.APP_JOB_UPDATE) || isRequiredParametersMissing || isRefreshRequired" size="small" fill="outline" @click="saveChanges()">{{ translate("Save changes") }}</ion-button>
<ion-button :disabled="!hasPermission(Actions.APP_JOB_UPDATE) || isRequiredParametersMissing || isRefreshRequired || historyJobConfig" size="small" fill="outline" @click="saveChanges()">{{ translate("Save changes") }}</ion-button>
</div>
</div>

<div class=" actions mobile-only">
<ion-button size="small" expand="block" fill="outline" color="medium" :disabled="!hasPermission(Actions.APP_JOB_UPDATE) || status === 'SERVICE_DRAFT' || isRefreshRequired" @click="skipJob(currentJob)">{{ translate("Skip once") }}</ion-button>
<ion-button size="small" expand="block" fill="outline" color="danger" :disabled="!hasPermission(Actions.APP_JOB_UPDATE) || status === 'SERVICE_DRAFT' || isRefreshRequired" @click="cancelJob(currentJob)">{{ translate("Disable") }}</ion-button>
<ion-button :disabled="!hasPermission(Actions.APP_JOB_UPDATE) || isRequiredParametersMissing || isRefreshRequired" expand="block" @click="saveChanges()">{{ translate("Save changes") }}</ion-button>
<ion-button size="small" expand="block" fill="outline" color="medium" :disabled="!hasPermission(Actions.APP_JOB_UPDATE) || status === 'SERVICE_DRAFT' || isRefreshRequired || historyJobConfig" @click="skipJob(currentJob)">{{ translate("Skip once") }}</ion-button>
<ion-button size="small" expand="block" fill="outline" color="danger" :disabled="!hasPermission(Actions.APP_JOB_UPDATE) || status === 'SERVICE_DRAFT' || isRefreshRequired || historyJobConfig" @click="cancelJob(currentJob)">{{ translate("Disable") }}</ion-button>
<ion-button :disabled="!hasPermission(Actions.APP_JOB_UPDATE) || isRequiredParametersMissing || isRefreshRequired || historyJobConfig" expand="block" @click="saveChanges()">{{ translate("Save changes") }}</ion-button>
</div>
</section>
<div class="more-actions">
<ion-item @click="viewJobHistory(currentJob)" button>
<ion-icon slot="start" :icon="timeOutline" />
{{ translate("History") }}
</ion-item>
<ion-item :disabled="!hasPermission(Actions.APP_JOB_UPDATE)" @click="runNow(currentJob)" button>
<ion-item :disabled="!hasPermission(Actions.APP_JOB_UPDATE) || historyJobConfig" @click="runNow(currentJob)" button>
<ion-icon slot="start" :icon="flashOutline" />
{{ translate("Run now") }}
</ion-item>
<ion-item @click="copyJobInformation(currentJob)" button>
<ion-icon slot="start" :icon="copyOutline" />
{{ translate("Copy details") }}
</ion-item>
<ion-item @click="updatePinnedJobs(currentJob?.systemJobEnumId)" button>
<ion-item :disabled="historyJobConfig" @click="updatePinnedJobs(currentJob?.systemJobEnumId)" button>
<ion-icon slot="start" :icon="pinOutline" />
<ion-checkbox :checked="pinnedJobs && pinnedJobs.includes(currentJob.systemJobEnumId)">
<ion-label>{{ translate("Pin job") }}</ion-label>
</ion-checkbox>
</ion-item>
</div>

<!-- Import logs -->
<section v-if="historyJobConfig && currentJob.runtimeData?.configId && getDataManagerLogs?.length">
<ion-item lines="none">
<h1>{{ translate('Import logs') }}</h1>
<ion-button slot="end" fill="clear" @click="openImportLogsDetails()">{{ translate('View details') }}</ion-button>
</ion-item>
<ion-progress-bar :value="(getProcessedFileCount() - getErrorFileCount()) / getDataManagerLogs.length"></ion-progress-bar>
<ion-list>
<ion-item>
<ion-icon slot="start" :icon="fileTrayFullOutline" />
{{ translate('Files received') }}
<ion-label slot="end">{{ getDataManagerLogs.length }}</ion-label>
</ion-item>
<ion-item>
<ion-icon slot="start" :icon="codeWorkingOutline" />
{{ translate('Files processed') }}
<ion-label slot="end">{{ getProcessedFileCount() }}</ion-label>
</ion-item>
<ion-item lines="none">
<ion-icon slot="start" :icon="warningOutline" />
{{ translate('Files with errors') }}
<ion-label slot="end">{{ getErrorFileCount() }}</ion-label>
</ion-item>
</ion-list>
</section>
</template>

<script lang="ts">
Expand All @@ -129,6 +166,7 @@ import {
IonLabel,
IonList,
IonModal,
IonProgressBar,
IonRow,
IonSelect,
IonSelectOption,
Expand All @@ -138,15 +176,18 @@ import {
import {
addOutline,
calendarClearOutline,
codeWorkingOutline,
flashOutline,
fileTrayFullOutline,
listCircleOutline,
copyOutline,
timeOutline,
timerOutline,
syncOutline,
personCircleOutline,
pinOutline,
refreshOutline
refreshOutline,
warningOutline
} from "ionicons/icons";
import JobHistoryModal from '@/components/JobHistoryModal.vue'
import { Plugins } from '@capacitor/core';
Expand All @@ -173,6 +214,7 @@ export default defineComponent({
IonLabel,
IonList,
IonModal,
IonProgressBar,
IonRow,
IonSelect,
IonSelectOption,
Expand Down Expand Up @@ -205,7 +247,7 @@ export default defineComponent({
this.generateRunTimes(this.runTime)
this.generateFrequencyOptions(this.jobStatus)
},
props: ["isBrokerJob", "status", "type"],
props: ["isBrokerJob", "status", "type", "historyJobConfig"],
computed: {
...mapGetters({
pinnedJobs: 'user/getPinnedJobs',
Expand All @@ -215,6 +257,8 @@ export default defineComponent({
currentEComStore: 'user/getCurrentEComStore',
currentJob: 'job/getCurrentJob',
pendingJobs: 'job/getPendingJobs',
getDataManagerLogs: 'job/getDataManagerLogs',
temporalExpr: 'job/getTemporalExpr'
}),
isRequiredParametersMissing() {
return this.customRequiredParameters.some((parameter: any) => !parameter.value?.trim())
Expand All @@ -225,6 +269,16 @@ export default defineComponent({
}
},
methods: {
getProcessedFileCount() {
return this.getDataManagerLogs?.filter((log: any) => log.statusId === "SERVICE_FINISHED").length
},
getErrorFileCount() {
return this.getDataManagerLogs?.filter((log: any) => log.errorRecordContentId !== null).length
},
openImportLogsDetails() {
const jobId = this.currentJob.jobId
this.router.push({ name: 'DataManagerLogDetails', params: { jobId } })
},
getDateTime(time: any) {
return DateTime.fromMillis(time).toISO()
},
Expand Down Expand Up @@ -525,10 +579,12 @@ export default defineComponent({
Actions,
addOutline,
calendarClearOutline,
codeWorkingOutline,
copyOutline,
DateTime,
listCircleOutline,
flashOutline,
fileTrayFullOutline,
hasPermission,
isCustomRunTime,
getNowTimestamp,
Expand All @@ -540,17 +596,18 @@ export default defineComponent({
personCircleOutline,
pinOutline,
refreshOutline,
translate
translate,
warningOutline
};
}
});
</script>

<style scoped>
ion-list {
margin: 0 0 var(--spacer-base);
section {
margin-top: var(--spacer-sm);
margin-bottom: var(--spacer-sm);
}

.actions > ion-button {
margin: var(--spacer-sm);
}
Expand Down
2 changes: 1 addition & 1 deletion src/components/JobParameterModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
</ion-buttons>
<ion-title>{{ translate('Custom Parameters') }}</ion-title>
<ion-buttons slot="end">
<ion-button color="primary" :disabled="currentJob.statusId === 'SERVICE_PENDING'" @click="save()">{{ translate('Save') }}</ion-button>
<ion-button color="primary" :disabled="currentJob.statusId !== 'SERVICE_DRAFT'" @click="save()">{{ translate('Save') }}</ion-button>
</ion-buttons>
</ion-toolbar>
</ion-header>
Expand Down
Loading
Loading