allow editing querys in templates and make insert data source add the query inline
This commit is contained in:
@@ -162,9 +162,9 @@ export function useReportTemplates(): useReportingTemplates {
|
||||
})
|
||||
.then(({ data }) => {
|
||||
if (payload.format === "html") renderedPreview.value = data;
|
||||
else if (payload.format === "plaintext")
|
||||
renderedPreview.value = `<pre>${data}</pre>`;
|
||||
else renderedPreview.value = URL.createObjectURL(data);
|
||||
else if (payload.format === "pdf")
|
||||
renderedPreview.value = URL.createObjectURL(data);
|
||||
else renderedPreview.value = `<pre>${data}</pre>`;
|
||||
})
|
||||
.catch(() => (isError.value = true))
|
||||
.finally(() => (isLoading.value = false));
|
||||
|
||||
@@ -43,6 +43,7 @@ For details, see: https://license.tacticalrmm.com/ee
|
||||
import { ref, computed, onMounted } from "vue";
|
||||
import { useDialogPluginComponent } from "quasar";
|
||||
import { useSharedReportDataQueries } from "../api/reporting";
|
||||
import { notifyError } from "@/utils/notify";
|
||||
|
||||
// ui imports
|
||||
import TacticalDropdown from "@/components/ui/TacticalDropdown.vue";
|
||||
@@ -50,6 +51,9 @@ import TacticalDropdown from "@/components/ui/TacticalDropdown.vue";
|
||||
// emits
|
||||
defineEmits([...useDialogPluginComponent.emits]);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const props = defineProps<{ dataSources?: any }>();
|
||||
|
||||
// quasar dialog setup
|
||||
const { dialogRef, onDialogHide, onDialogOK } = useDialogPluginComponent();
|
||||
|
||||
@@ -57,15 +61,36 @@ const { reportDataQueries, getReportDataQueries } = useSharedReportDataQueries;
|
||||
|
||||
const selectedQuery = ref<string | null>(null);
|
||||
const loading = ref(false);
|
||||
const queryOptions = computed(() =>
|
||||
reportDataQueries.value.map((query) => query.name)
|
||||
);
|
||||
|
||||
const queryOptions = computed(() => {
|
||||
if (props.dataSources === undefined)
|
||||
return reportDataQueries.value.map((query) => query.name);
|
||||
else return Object.keys(props.dataSources);
|
||||
});
|
||||
|
||||
function submit() {
|
||||
if (selectedQuery.value !== null) {
|
||||
onDialogOK(selectedQuery.value);
|
||||
if (selectedQuery.value === null)
|
||||
notifyError("Select a query from the dropdown");
|
||||
else {
|
||||
let dataQuery;
|
||||
if (props.dataSources === undefined) {
|
||||
dataQuery = reportDataQueries.value.find(
|
||||
(query) => query.name === selectedQuery.value,
|
||||
);
|
||||
} else {
|
||||
dataQuery = {
|
||||
id: 0,
|
||||
name: selectedQuery.value,
|
||||
json_query: props.dataSources[selectedQuery.value],
|
||||
};
|
||||
}
|
||||
onDialogOK(dataQuery);
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(getReportDataQueries);
|
||||
onMounted(() => {
|
||||
if (props.dataSources === undefined) {
|
||||
getReportDataQueries();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -237,20 +237,30 @@ For details, see: https://license.tacticalrmm.com/ee
|
||||
flat
|
||||
dense
|
||||
:ripple="false"
|
||||
icon="mdi-database-arrow-down"
|
||||
@click="insertDataQuery"
|
||||
icon="mdi-database-plus-outline"
|
||||
@click="openQueryAddDialog"
|
||||
>
|
||||
<q-tooltip :delay="500">Insert Data Query</q-tooltip>
|
||||
<q-tooltip :delay="500">Add Data Query</q-tooltip>
|
||||
</q-btn>
|
||||
|
||||
<q-btn
|
||||
flat
|
||||
dense
|
||||
:ripple="false"
|
||||
icon="mdi-database-plus-outline"
|
||||
@click="openQueryAddDialog"
|
||||
icon="mdi-database-arrow-down"
|
||||
@click="insertDataQuery"
|
||||
>
|
||||
<q-tooltip :delay="500">Add Data Query</q-tooltip>
|
||||
<q-tooltip :delay="500">Insert Saved Data Query</q-tooltip>
|
||||
</q-btn>
|
||||
|
||||
<q-btn
|
||||
flat
|
||||
dense
|
||||
:ripple="false"
|
||||
icon="mdi-database-edit"
|
||||
@click="editDataQuery"
|
||||
>
|
||||
<q-tooltip :delay="500">Edit Data Query</q-tooltip>
|
||||
</q-btn>
|
||||
|
||||
<q-btn
|
||||
@@ -263,9 +273,9 @@ For details, see: https://license.tacticalrmm.com/ee
|
||||
<q-tooltip :delay="500">Table</q-tooltip>
|
||||
</q-btn>
|
||||
|
||||
<q-btn flat dense :ripple="false" icon="add_chart" @click="openChartDialog">
|
||||
<!-- <q-btn flat dense :ripple="false" icon="add_chart" @click="openChartDialog">
|
||||
<q-tooltip :delay="500">Add chart</q-tooltip>
|
||||
</q-btn>
|
||||
</q-btn> -->
|
||||
|
||||
<slot name="buttons"></slot>
|
||||
</q-bar>
|
||||
@@ -282,14 +292,15 @@ import { parse, stringify } from "yaml";
|
||||
import ReportDataQueryForm from "./ReportDataQueryForm.vue";
|
||||
import DataQuerySelect from "./DataQuerySelect.vue";
|
||||
import ReportAssetSelect from "./ReportAssetSelect.vue";
|
||||
import ReportChartSelect from "./ReportChartSelect.vue";
|
||||
// import ReportChartSelect from "./ReportChartSelect.vue";
|
||||
import ReportTableMaker from "./ReportTableMaker.vue";
|
||||
|
||||
// utils
|
||||
import { convertCamelCase } from "@/utils/format";
|
||||
|
||||
// types
|
||||
import { ReportTemplateType } from "../types/reporting";
|
||||
import { ReportDataQuery, ReportTemplateType } from "../types/reporting";
|
||||
import { notifyWarning, notifySuccess } from "@/utils/notify";
|
||||
|
||||
// props
|
||||
const props = defineProps<{
|
||||
@@ -311,7 +322,7 @@ onMounted(() => {
|
||||
// disable certain toolbar options if a multiline text selection is made
|
||||
_editor.onDidChangeCursorSelection((evt) => {
|
||||
isMultiLineSelection.value = monaco.Selection.spansMultipleLines(
|
||||
evt.selection
|
||||
evt.selection,
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -366,53 +377,96 @@ function insertCodeBlock() {
|
||||
_editor.focus();
|
||||
}
|
||||
|
||||
function insertDataQuery() {
|
||||
$q.dialog({
|
||||
component: DataQuerySelect,
|
||||
}).onOk((queryName: string) => {
|
||||
let variablesJson = parse(props.variablesEditor.getValue()) || {};
|
||||
if (!("data_sources" in variablesJson) || !variablesJson.data_sources) {
|
||||
variablesJson["data_sources"] = {};
|
||||
}
|
||||
variablesJson["data_sources"][convertCamelCase(queryName)] = queryName;
|
||||
props.variablesEditor?.setValue(stringify(variablesJson));
|
||||
});
|
||||
function _getDataSourcesInTemplate() {
|
||||
let variablesJson = parse(props.variablesEditor.getValue()) || {};
|
||||
|
||||
if (!("data_sources" in variablesJson) || !variablesJson.data_sources)
|
||||
return null;
|
||||
else return variablesJson["data_sources"];
|
||||
}
|
||||
|
||||
function openChartDialog() {
|
||||
$q.dialog({
|
||||
component: ReportChartSelect,
|
||||
}).onOk((data) => {
|
||||
let variablesJson = parse(props.variablesEditor.getValue()) || {};
|
||||
const optionsJson = parse(data.options);
|
||||
function _saveDataSourcesInTemplate(
|
||||
dataQuery: ReportDataQuery,
|
||||
convertNameToCamelCase = true,
|
||||
) {
|
||||
let variablesJson = parse(props.variablesEditor.getValue()) || {};
|
||||
|
||||
if (!("charts" in variablesJson) || !variablesJson.charts) {
|
||||
variablesJson["charts"] = {};
|
||||
}
|
||||
if (!("data_sources" in variablesJson) || !variablesJson.data_sources) {
|
||||
variablesJson["data_sources"] = {};
|
||||
}
|
||||
|
||||
variablesJson["charts"][convertCamelCase(data.name)] = {
|
||||
chartType: data.chartType,
|
||||
outputType: data.outputType,
|
||||
options: optionsJson,
|
||||
};
|
||||
|
||||
props.variablesEditor?.setValue(stringify(variablesJson));
|
||||
});
|
||||
const dataQueryName = convertNameToCamelCase
|
||||
? convertCamelCase(dataQuery.name)
|
||||
: dataQuery.name;
|
||||
variablesJson["data_sources"][dataQueryName] = dataQuery.json_query;
|
||||
props.variablesEditor?.setValue(stringify(variablesJson));
|
||||
}
|
||||
|
||||
function openQueryAddDialog() {
|
||||
$q.dialog({
|
||||
component: ReportDataQueryForm,
|
||||
}).onOk((queryName: string) => {
|
||||
let variablesJson = parse(props.variablesEditor.getValue()) || {};
|
||||
|
||||
if (!("data_sources" in variablesJson) || !variablesJson.data_sources) {
|
||||
variablesJson["data_sources"] = {};
|
||||
}
|
||||
variablesJson["data_sources"][convertCamelCase(queryName)] = queryName;
|
||||
props.variablesEditor?.setValue(stringify(variablesJson));
|
||||
}).onOk((dataQuery: ReportDataQuery) => {
|
||||
_saveDataSourcesInTemplate(dataQuery);
|
||||
});
|
||||
}
|
||||
|
||||
function insertDataQuery() {
|
||||
$q.dialog({
|
||||
component: DataQuerySelect,
|
||||
}).onOk((dataQuery: ReportDataQuery) => {
|
||||
_saveDataSourcesInTemplate(dataQuery);
|
||||
notifySuccess(`${dataQuery.name} was saved successfully in template`);
|
||||
});
|
||||
}
|
||||
|
||||
function editDataQuery() {
|
||||
const dataSources = _getDataSourcesInTemplate();
|
||||
|
||||
if (!dataSources) {
|
||||
notifyWarning("No data sources exist in template variables");
|
||||
return;
|
||||
}
|
||||
|
||||
$q.dialog({
|
||||
component: DataQuerySelect,
|
||||
componentProps: {
|
||||
dataSources,
|
||||
},
|
||||
}).onOk((dataQuery) => {
|
||||
$q.dialog({
|
||||
component: ReportDataQueryForm,
|
||||
componentProps: {
|
||||
dataQuery: dataQuery,
|
||||
editInTemplate: true,
|
||||
},
|
||||
}).onOk((dataQuery: ReportDataQuery) => {
|
||||
_saveDataSourcesInTemplate(dataQuery, false);
|
||||
notifySuccess(`${dataQuery.name} was saved successfully in template`);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// function openChartDialog() {
|
||||
// $q.dialog({
|
||||
// component: ReportChartSelect,
|
||||
// }).onOk((data) => {
|
||||
// let variablesJson = parse(props.variablesEditor.getValue()) || {};
|
||||
// const optionsJson = parse(data.options);
|
||||
|
||||
// if (!("charts" in variablesJson) || !variablesJson.charts) {
|
||||
// variablesJson["charts"] = {};
|
||||
// }
|
||||
|
||||
// variablesJson["charts"][convertCamelCase(data.name)] = {
|
||||
// chartType: data.chartType,
|
||||
// outputType: data.outputType,
|
||||
// options: optionsJson,
|
||||
// };
|
||||
|
||||
// props.variablesEditor?.setValue(stringify(variablesJson));
|
||||
// });
|
||||
// }
|
||||
|
||||
function insertLink() {
|
||||
if (props.templateType === "markdown")
|
||||
insert(`[${linkText.value}](${linkUrl.value})`);
|
||||
@@ -513,8 +567,7 @@ function insert(text: string, moveToNewLine = false) {
|
||||
});
|
||||
}
|
||||
|
||||
model.pushEditOperations(selections, operations, (operations) => {
|
||||
console.log(operations);
|
||||
model.pushEditOperations(selections, operations, (/*operations*/) => {
|
||||
return selections;
|
||||
});
|
||||
}
|
||||
@@ -536,7 +589,7 @@ function insertPrefix(prefix: string, prefixCount = 1) {
|
||||
{
|
||||
lineNumber: end.lineNumber,
|
||||
column: model.getLineMaxColumn(end.lineNumber),
|
||||
}
|
||||
},
|
||||
);
|
||||
let replacementText = [] as string[];
|
||||
|
||||
@@ -570,8 +623,7 @@ function insertPrefix(prefix: string, prefixCount = 1) {
|
||||
});
|
||||
}
|
||||
|
||||
model.pushEditOperations(selections, operations, (operations) => {
|
||||
console.log(operations);
|
||||
model.pushEditOperations(selections, operations, (/*operations*/) => {
|
||||
return newSelections;
|
||||
});
|
||||
}
|
||||
@@ -593,7 +645,7 @@ function insertWrap(prefix: string, suffix: string, includeWholeLine = false) {
|
||||
{
|
||||
lineNumber: end.lineNumber,
|
||||
column: model.getLineMaxColumn(end.lineNumber),
|
||||
}
|
||||
},
|
||||
)
|
||||
: selection;
|
||||
|
||||
@@ -607,9 +659,11 @@ function insertWrap(prefix: string, suffix: string, includeWholeLine = false) {
|
||||
}
|
||||
|
||||
model.pushEditOperations(selections, operations, (operations) => {
|
||||
console.log(operations);
|
||||
return operations.map((operation) =>
|
||||
monaco.Selection.fromRange(operation.range, monaco.SelectionDirection.LTR)
|
||||
monaco.Selection.fromRange(
|
||||
operation.range,
|
||||
monaco.SelectionDirection.LTR,
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ For details, see: https://license.tacticalrmm.com/ee
|
||||
>
|
||||
<q-card>
|
||||
<q-bar>
|
||||
New Data Query
|
||||
{{ props.dataQuery ? "Edit Data Query" : "New Data Query" }}
|
||||
<q-space />
|
||||
<q-btn v-close-popup dense flat icon="close">
|
||||
<q-tooltip class="bg-white text-primary">Close</q-tooltip>
|
||||
@@ -66,7 +66,10 @@ const $q = useQuasar();
|
||||
import { type ReportDataQuery } from "../types/reporting";
|
||||
|
||||
// props
|
||||
const props = defineProps<{ dataQuery?: ReportDataQuery }>();
|
||||
const props = defineProps<{
|
||||
dataQuery?: ReportDataQuery;
|
||||
editInTemplate?: boolean;
|
||||
}>();
|
||||
|
||||
// emits
|
||||
defineEmits([...useDialogPluginComponent.emits]);
|
||||
@@ -90,14 +93,16 @@ const { isLoading, isError, addReportDataQuery, editReportDataQuery } =
|
||||
|
||||
async function submit() {
|
||||
state.json_query = JSON.parse(json_string.value);
|
||||
props.dataQuery
|
||||
? editReportDataQuery(state.id, state)
|
||||
: addReportDataQuery(state);
|
||||
|
||||
await until(isLoading).not.toBeTruthy();
|
||||
if (isError.value) return;
|
||||
if (!props.editInTemplate) {
|
||||
props.dataQuery
|
||||
? editReportDataQuery(state.id, state)
|
||||
: addReportDataQuery(state);
|
||||
|
||||
onDialogOK(state.name);
|
||||
await until(isLoading).not.toBeTruthy();
|
||||
if (isError.value) return;
|
||||
}
|
||||
onDialogOK(state);
|
||||
}
|
||||
|
||||
const queryEditor = ref<HTMLElement | null>(null);
|
||||
|
||||
@@ -660,7 +660,7 @@ function initializeEditor() {
|
||||
|
||||
// make sure to put quotes around any variable values that have { or }
|
||||
function wrapDoubleQuotes() {
|
||||
const matchJsonCharacters = /([^:\s'"]+:\s*)([^']*[{}][^'\n]*)/;
|
||||
const matchJsonCharacters = /([^:\s'"]+:\s*)([^'"]*[{}][^'"\n]*)/;
|
||||
const editorValue = variablesEditor.value?.getValue();
|
||||
if (editorValue && matchJsonCharacters.test(editorValue)) {
|
||||
state.template_variables = editorValue
|
||||
|
||||
Reference in New Issue
Block a user