Skip to content

Commit e267025

Browse files
luizhf42gustavosbarreto
authored andcommitted
refactor(ui): refactor admin's NamespaceExport
1 parent e562f3a commit e267025

File tree

4 files changed

+128
-106
lines changed

4 files changed

+128
-106
lines changed
Lines changed: 76 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -1,135 +1,106 @@
11
<template>
2-
<v-btn @click="dialog = !dialog" class="mr-2" data-test="namespaces-export-btn">Export CSV</v-btn>
2+
<v-btn @click="showDialog = true" class="mr-2" data-test="namespaces-export-btn">Export CSV</v-btn>
33

4-
<v-dialog v-model="dialog" max-width="400" transition="dialog-bottom-transition">
4+
<v-dialog v-model="showDialog" max-width="400" transition="dialog-bottom-transition">
55
<v-card>
6-
<v-card-title class="text-h5 pb-2"> Export namespaces data </v-card-title>
6+
<v-card-title class="text-h5 pb-2">Export namespaces data</v-card-title>
77
<v-divider />
8-
<v-form @submit.prevent="onSubmit" data-test="form">
8+
<v-form @submit.prevent="handleSubmit" data-test="form">
99
<v-card-text>
10-
<v-container>
11-
<v-radio-group v-model="selected">
12-
<v-row no-gutters class="first-row">
13-
<v-col class="pt-8" cols="12">
14-
<v-radio label="Namespaces with more than:" value="moreThan" mt="8" />
15-
</v-col>
16-
</v-row>
17-
<v-row no-gutters class="d-flex justify-center align-center mb-4 ml-3">
18-
<v-col cols="8">
19-
<v-slider v-model="numberOfDevices" hide-details :min="0" :max="150" />
20-
</v-col>
21-
<v-col cols="4">
22-
<span class="ml-4">{{ numberOfDevicesRound }} devices</span>
23-
</v-col>
24-
</v-row>
25-
<v-row class="mb-4">
26-
<v-col cols="12">
27-
<v-radio label="Namespaces with no devices" value="noDevices" />
28-
</v-col>
29-
</v-row>
30-
<v-row class="mb-4">
31-
<v-col cols="12">
32-
<v-radio value="noSession">
33-
<template v-slot:label>
34-
Namespace with devices but without <br />
35-
sessions
36-
</template>
37-
</v-radio>
38-
</v-col>
39-
</v-row>
40-
</v-radio-group>
41-
</v-container>
10+
<v-radio-group v-model="selectedFilter">
11+
<v-radio label="Namespaces with more than:" :value="NamespaceFilterOptions.MoreThan" />
12+
<v-text-field
13+
class="mt-2 mx-2"
14+
v-model="numberOfDevices"
15+
suffix="devices"
16+
:disabled="selectedFilter !== NamespaceFilterOptions.MoreThan"
17+
label="Number of devices"
18+
color="primary"
19+
density="comfortable"
20+
variant="outlined"
21+
:error-messages="numberOfDevicesError"
22+
/>
23+
<v-radio label="Namespaces with no devices" :value="NamespaceFilterOptions.NoDevices" />
24+
<v-radio label="Namespace with devices, but no sessions" :value="NamespaceFilterOptions.NoSessions" />
25+
</v-radio-group>
4226
</v-card-text>
4327

44-
<v-card-actions class="pa-4">
45-
<v-spacer />
46-
<v-btn class="mr-2" color="dark" @click="dialog = false" type="reset"> Cancel </v-btn>
47-
<v-btn color="dark" type="submit" class="mr-4"> Save </v-btn>
28+
<v-card-actions class="pa-4 d-flex justify-end ga-2">
29+
<v-btn @click="closeDialog">Cancel</v-btn>
30+
<v-btn color="primary" type="submit" :loading="isLoading" :disabled="!!numberOfDevicesError || isLoading">Export</v-btn>
4831
</v-card-actions>
4932
</v-form>
5033
</v-card>
5134
</v-dialog>
5235
</template>
5336

5437
<script setup lang="ts">
55-
import { computed, ref } from "vue";
38+
import { ref, watch } from "vue";
39+
import * as yup from "yup";
40+
import { useField } from "vee-validate";
5641
import { saveAs } from "file-saver";
5742
import useNamespacesStore from "@admin/store/modules/namespaces";
43+
import getFilter from "@admin/hooks/namespaceExport";
44+
import { NamespaceFilterOptions } from "@admin/interfaces/IFilter";
5845
import useSnackbar from "@/helpers/snackbar";
46+
import handleError from "@/utils/handleError";
5947
60-
const numberOfDevices = ref(0);
61-
const dialog = ref(false);
62-
const selected = ref("moreThan");
48+
const showDialog = ref(false);
49+
const isLoading = ref(false);
50+
const selectedFilter = ref(NamespaceFilterOptions.MoreThan);
6351
const snackbar = useSnackbar();
6452
const namespacesStore = useNamespacesStore();
53+
const { value: numberOfDevices,
54+
errorMessage: numberOfDevicesError,
55+
setErrors: setNumberOfDevicesErrors,
56+
} = useField<number>("numberOfDevices", yup.number().integer().required().min(0), { initialValue: 0 });
6557
66-
const numberOfDevicesRound = computed(() => Math.round(numberOfDevices.value));
67-
68-
const generateEncodedFilter = (encodeFilter: string) => {
69-
let filter;
70-
switch (encodeFilter) {
71-
case "moreThan":
72-
filter = [
73-
{
74-
type: "property",
75-
params: {
76-
name: "devices",
77-
operator: "gt",
78-
value: String(numberOfDevicesRound.value),
79-
},
80-
},
81-
];
82-
break;
83-
case "noDevices":
84-
filter = [
85-
{
86-
type: "property",
87-
params: { name: "devices", operator: "eq", value: 0 },
88-
},
89-
];
90-
break;
91-
case "noSession":
92-
filter = [
93-
{
94-
type: "property",
95-
params: { name: "devices", operator: "gt", value: "0" },
96-
},
97-
{
98-
type: "property",
99-
params: { name: "sessions", operator: "eq", value: 0 },
100-
},
101-
{ type: "operator", params: { name: "and" } },
102-
];
103-
break;
104-
default:
105-
break;
58+
watch(selectedFilter, (newValue) => {
59+
if (newValue !== NamespaceFilterOptions.MoreThan) {
60+
setNumberOfDevicesErrors("");
10661
}
107-
return btoa(JSON.stringify(filter));
62+
});
63+
64+
const encodeFilter = () => btoa(JSON.stringify(getFilter(selectedFilter.value, numberOfDevices.value)));
65+
66+
const getFilename = () => {
67+
const filterSuffixes = {
68+
[NamespaceFilterOptions.MoreThan]: `more_than_${numberOfDevices.value}_devices`,
69+
[NamespaceFilterOptions.NoDevices]: "no_devices",
70+
[NamespaceFilterOptions.NoSessions]: "with_devices_but_no_sessions",
71+
};
72+
73+
const suffix = filterSuffixes[selectedFilter.value] ?? "export";
74+
return `namespaces_${suffix}.csv`;
75+
};
76+
77+
const exportCsv = async () => {
78+
const encodedFilter = encodeFilter();
79+
await namespacesStore.setFilterNamespaces(encodedFilter);
80+
const response = await namespacesStore.exportNamespacesToCsv();
81+
const blob = new Blob([response], { type: "text/csv;charset=utf-8" });
82+
saveAs(blob, getFilename());
10883
};
10984
110-
const onSubmit = async () => {
111-
const encodedFilter = generateEncodedFilter(selected.value);
85+
const handleSubmit = async () => {
86+
isLoading.value = true;
11287
try {
113-
await namespacesStore.setFilterNamespaces(encodedFilter);
114-
const response = await namespacesStore.exportNamespacesToCsv();
115-
const blob = new Blob([response], { type: "content-disposition" });
116-
saveAs(
117-
blob,
118-
`namespaces_${
119-
selected.value === "moreThanN"
120-
? `more_than_${String(numberOfDevices.value)}_devices`
121-
: selected.value
122-
}.csv`,
123-
);
88+
await exportCsv();
12489
snackbar.showSuccess("Namespaces exported successfully.");
125-
} catch {
90+
} catch (error) {
91+
handleError(error);
12692
snackbar.showError("Error exporting namespaces.");
12793
}
94+
isLoading.value = false;
95+
};
96+
97+
const resetForm = () => {
98+
numberOfDevices.value = 0;
99+
selectedFilter.value = NamespaceFilterOptions.MoreThan;
128100
};
129-
</script>
130101
131-
<style scoped>
132-
.first-row {
133-
height: 70px;
134-
}
135-
</style>
102+
const closeDialog = () => {
103+
showDialog.value = false;
104+
resetForm();
105+
};
106+
</script>
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { IFilter, NamespaceFilterOptions } from "@admin/interfaces/IFilter";
2+
3+
const getFilter = (option: NamespaceFilterOptions, numberOfDevices: number): IFilter[] => {
4+
const filters: Record<NamespaceFilterOptions, IFilter[]> = {
5+
[NamespaceFilterOptions.MoreThan]: [
6+
{
7+
type: "property",
8+
params: {
9+
name: "devices",
10+
operator: "gt",
11+
value: numberOfDevices,
12+
},
13+
},
14+
],
15+
[NamespaceFilterOptions.NoDevices]: [
16+
{
17+
type: "property",
18+
params: { name: "devices", operator: "eq", value: 0 },
19+
},
20+
],
21+
[NamespaceFilterOptions.NoSessions]: [
22+
{
23+
type: "property",
24+
params: { name: "devices", operator: "gt", value: 0 },
25+
},
26+
{
27+
type: "property",
28+
params: { name: "sessions", operator: "eq", value: 0 },
29+
},
30+
{ type: "operator", params: { name: "and" } },
31+
],
32+
};
33+
34+
return filters[option];
35+
};
36+
37+
export default getFilter;

ui/admin/src/interfaces/IFilter.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
export enum NamespaceFilterOptions {
2+
MoreThan = "moreThan",
3+
NoDevices = "noDevices",
4+
NoSessions = "noSessions",
5+
}
6+
7+
export interface IFilter {
8+
type: "property" | "operator";
9+
params: {
10+
name?: string;
11+
operator?: string;
12+
value?: number;
13+
};
14+
}

ui/admin/tests/unit/views/Namespaces/__snapshots__/index.spec.ts.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ exports[`Namespaces > Renders the component 1`] = `
4545
<!---->
4646
</div>
4747
</div>
48-
<div class="mt-sm-4"><button data-v-05ae4279="" type="button" class="v-btn v-btn--elevated v-theme--light v-btn--density-default v-btn--size-default v-btn--variant-elevated mr-2" data-test="namespaces-export-btn"><span class="v-btn__overlay"></span><span class="v-btn__underlay"></span>
48+
<div class="mt-sm-4"><button type="button" class="v-btn v-btn--elevated v-theme--light v-btn--density-default v-btn--size-default v-btn--variant-elevated mr-2" data-test="namespaces-export-btn"><span class="v-btn__overlay"></span><span class="v-btn__underlay"></span>
4949
<!----><span class="v-btn__content" data-no-activator="">Export CSV</span>
5050
<!---->
5151
<!---->

0 commit comments

Comments
 (0)