feat: Adds the ability to edit saved segments (#7254)

* feat: Ability to edit saved filters

* chore: Adds edit contact segment

* chore: Minor fixes

* fix: code climate

* chore: Minor fixes

* chore: Adds ability to custom view name

* chore: Minor fixes

* chore: Adds spec for helper

* chore: Revert contact filter to split to new PR

* Delete editSegmentMixin.js

* chore: Revert fixes

* Update app/javascript/dashboard/i18n/locale/en/advancedFilters.json

Co-authored-by: Shivam Mishra <scm.mymail@gmail.com>

* Update app/javascript/dashboard/i18n/locale/en/advancedFilters.json

Co-authored-by: Shivam Mishra <scm.mymail@gmail.com>

* chore: Moved from mixin to helper for reusing in segments

* Delete editFolderMixin.js

* chore: Fix specs and added new specs

* chore: review comment fixes

* chore: Minor fixes

* fix: Not resetting applied filter

* feat: Adds the ability to edit saved segments

* feat: Adds specs for API part

---------

Co-authored-by: Shivam Mishra <scm.mymail@gmail.com>
This commit is contained in:
Sivin Varghese
2023-06-08 21:25:51 +05:30
committed by GitHub
parent b3b76d00ff
commit 7fd220c177
5 changed files with 184 additions and 27 deletions

View File

@@ -211,6 +211,7 @@
"FILTER_CONTACTS": "Filter", "FILTER_CONTACTS": "Filter",
"FILTER_CONTACTS_SAVE": "Save filter", "FILTER_CONTACTS_SAVE": "Save filter",
"FILTER_CONTACTS_DELETE": "Delete filter", "FILTER_CONTACTS_DELETE": "Delete filter",
"FILTER_CONTACTS_EDIT": "Edit segment",
"LIST": { "LIST": {
"LOADING_MESSAGE": "Loading contacts...", "LOADING_MESSAGE": "Loading contacts...",
"404": "No contacts matches your search 🔍", "404": "No contacts matches your search 🔍",

View File

@@ -2,13 +2,18 @@
"CONTACTS_FILTER": { "CONTACTS_FILTER": {
"TITLE": "Filter Contacts", "TITLE": "Filter Contacts",
"SUBTITLE": "Add filters below and hit 'Submit' to filter contacts.", "SUBTITLE": "Add filters below and hit 'Submit' to filter contacts.",
"EDIT_CUSTOM_SEGMENT": "Edit Segment",
"CUSTOM_VIEWS_SUBTITLE": "Add or remove filters and update your segment.",
"ADD_NEW_FILTER": "Add Filter", "ADD_NEW_FILTER": "Add Filter",
"CLEAR_ALL_FILTERS": "Clear All Filters", "CLEAR_ALL_FILTERS": "Clear All Filters",
"FILTER_DELETE_ERROR": "You should have atleast one filter to save", "FILTER_DELETE_ERROR": "You should have atleast one filter to save",
"SUBMIT_BUTTON_LABEL": "Submit", "SUBMIT_BUTTON_LABEL": "Submit",
"UPDATE_BUTTON_LABEL": "Update Segment",
"CANCEL_BUTTON_LABEL": "Cancel", "CANCEL_BUTTON_LABEL": "Cancel",
"CLEAR_BUTTON_LABEL": "Clear Filters", "CLEAR_BUTTON_LABEL": "Clear Filters",
"EMPTY_VALUE_ERROR": "Value is required", "EMPTY_VALUE_ERROR": "Value is required",
"SEGMENT_LABEL": "Segment Name",
"SEGMENT_QUERY_LABEL": "Segment Query",
"TOOLTIP_LABEL": "Filter contacts", "TOOLTIP_LABEL": "Filter contacts",
"QUERY_DROPDOWN_LABELS": { "QUERY_DROPDOWN_LABELS": {
"AND": "AND", "AND": "AND",

View File

@@ -1,9 +1,25 @@
<template> <template>
<div class="column"> <div class="column">
<woot-modal-header :header-title="$t('CONTACTS_FILTER.TITLE')"> <woot-modal-header :header-title="filterModalHeaderTitle">
<p>{{ $t('CONTACTS_FILTER.SUBTITLE') }}</p> <p>{{ filterModalSubTitle }}</p>
</woot-modal-header> </woot-modal-header>
<div class="row modal-content"> <div class="column modal-content">
<div v-if="isSegmentsView" class="columns">
<label class="input-label" :class="{ error: !activeSegmentNewName }">
{{ $t('CONTACTS_FILTER.SEGMENT_LABEL') }}
<input
v-model="activeSegmentNewName"
type="text"
class="name-input"
/>
<span v-if="!activeSegmentNewName" class="message">
{{ $t('CONTACTS_FILTER.EMPTY_VALUE_ERROR') }}
</span>
</label>
<label class="input-label">
{{ $t('CONTACTS_FILTER.SEGMENT_QUERY_LABEL') }}
</label>
</div>
<div class="medium-12 columns filters-wrap"> <div class="medium-12 columns filters-wrap">
<filter-input-box <filter-input-box
v-for="(filter, i) in appliedFilters" v-for="(filter, i) in appliedFilters"
@@ -36,7 +52,7 @@
{{ $t('CONTACTS_FILTER.ADD_NEW_FILTER') }} {{ $t('CONTACTS_FILTER.ADD_NEW_FILTER') }}
</woot-button> </woot-button>
<woot-button <woot-button
v-if="hasAppliedFilters" v-if="hasAppliedFilters && !isSegmentsView"
icon="subtract" icon="subtract"
color-scheme="alert" color-scheme="alert"
variant="smooth" variant="smooth"
@@ -52,7 +68,14 @@
<woot-button class="button clear" @click.prevent="onClose"> <woot-button class="button clear" @click.prevent="onClose">
{{ $t('CONTACTS_FILTER.CANCEL_BUTTON_LABEL') }} {{ $t('CONTACTS_FILTER.CANCEL_BUTTON_LABEL') }}
</woot-button> </woot-button>
<woot-button @click="submitFilterQuery"> <woot-button
v-if="isSegmentsView"
:disabled="!activeSegmentNewName"
@click="updateSegment"
>
{{ $t('CONTACTS_FILTER.UPDATE_BUTTON_LABEL') }}
</woot-button>
<woot-button v-else @click="submitFilterQuery">
{{ $t('CONTACTS_FILTER.SUBMIT_BUTTON_LABEL') }} {{ $t('CONTACTS_FILTER.SUBMIT_BUTTON_LABEL') }}
</woot-button> </woot-button>
</div> </div>
@@ -85,6 +108,18 @@ export default {
type: Array, type: Array,
default: () => [], default: () => [],
}, },
initialAppliedFilters: {
type: Array,
default: () => [],
},
isSegmentsView: {
type: Boolean,
default: false,
},
activeSegmentName: {
type: String,
default: '',
},
}, },
validations: { validations: {
appliedFilters: { appliedFilters: {
@@ -105,7 +140,8 @@ export default {
data() { data() {
return { return {
show: true, show: true,
appliedFilters: [], appliedFilters: this.initialAppliedFilters,
activeSegmentNewName: this.activeSegmentName,
filterTypes: this.initialFilterTypes, filterTypes: this.initialFilterTypes,
filterGroups: [], filterGroups: [],
allCustomAttributes: [], allCustomAttributes: [],
@@ -121,12 +157,22 @@ export default {
hasAppliedFilters() { hasAppliedFilters() {
return this.getAppliedContactFilters.length; return this.getAppliedContactFilters.length;
}, },
filterModalHeaderTitle() {
return !this.isSegmentsView
? this.$t('CONTACTS_FILTER.TITLE')
: this.$t('CONTACTS_FILTER.EDIT_CUSTOM_SEGMENT');
},
filterModalSubTitle() {
return !this.isSegmentsView
? this.$t('CONTACTS_FILTER.SUBTITLE')
: this.$t('CONTACTS_FILTER.CUSTOM_VIEWS_SUBTITLE');
},
}, },
mounted() { mounted() {
this.setFilterAttributes(); this.setFilterAttributes();
if (this.getAppliedContactFilters.length) { if (this.getAppliedContactFilters.length) {
this.appliedFilters = [...this.getAppliedContactFilters]; this.appliedFilters = [...this.getAppliedContactFilters];
} else { } else if (!this.isSegmentsView) {
this.appliedFilters.push({ this.appliedFilters.push({
attribute_key: 'name', attribute_key: 'name',
filter_operator: 'equal_to', filter_operator: 'equal_to',
@@ -177,11 +223,11 @@ export default {
if (key === 'created_at' || key === 'last_activity_at') if (key === 'created_at' || key === 'last_activity_at')
if (operator === 'days_before') return 'plain_text'; if (operator === 'days_before') return 'plain_text';
const type = this.filterTypes.find(filter => filter.attributeKey === key); const type = this.filterTypes.find(filter => filter.attributeKey === key);
return type.inputType; return type?.inputType;
}, },
getOperators(key) { getOperators(key) {
const type = this.filterTypes.find(filter => filter.attributeKey === key); const type = this.filterTypes.find(filter => filter.attributeKey === key);
return type.filterOperators; return type?.filterOperators;
}, },
getDropdownValues(type) { getDropdownValues(type) {
const allCustomAttributes = this.$store.getters[ const allCustomAttributes = this.$store.getters[
@@ -230,12 +276,31 @@ export default {
} }
}, },
appendNewFilter() { appendNewFilter() {
if (this.isSegmentsView) {
this.setQueryOperatorOnLastQuery();
} else {
this.appliedFilters.push({ this.appliedFilters.push({
attribute_key: 'name', attribute_key: 'name',
filter_operator: 'equal_to', filter_operator: 'equal_to',
values: '', values: '',
query_operator: 'and', query_operator: 'and',
}); });
}
},
setQueryOperatorOnLastQuery() {
const lastItemIndex = this.appliedFilters.length - 1;
this.appliedFilters[lastItemIndex] = {
...this.appliedFilters[lastItemIndex],
query_operator: 'and',
};
this.$nextTick(() => {
this.appliedFilters.push({
attribute_key: 'name',
filter_operator: 'equal_to',
values: '',
query_operator: 'and',
});
});
}, },
removeFilter(index) { removeFilter(index) {
if (this.appliedFilters.length <= 1) { if (this.appliedFilters.length <= 1) {
@@ -259,6 +324,13 @@ export default {
})), })),
}); });
}, },
updateSegment() {
this.$emit(
'updateSegment',
this.appliedFilters,
this.activeSegmentNewName
);
},
resetFilter(index, currentFilter) { resetFilter(index, currentFilter) {
this.appliedFilters[index].filter_operator = this.filterTypes.find( this.appliedFilters[index].filter_operator = this.filterTypes.find(
filter => filter.attributeKey === currentFilter.attribute_key filter => filter.attributeKey === currentFilter.attribute_key

View File

@@ -1,6 +1,6 @@
<template> <template>
<div class="contacts-page row"> <div class="contacts-page row">
<div class="left-wrap" :class="wrapClas"> <div class="left-wrap" :class="wrapClass">
<contacts-header <contacts-header
:search-query="searchQuery" :search-query="searchQuery"
:segments-id="segmentsId" :segments-id="segmentsId"
@@ -13,6 +13,7 @@
:header-title="pageTitle" :header-title="pageTitle"
@on-toggle-save-filter="onToggleSaveFilters" @on-toggle-save-filter="onToggleSaveFilters"
@on-toggle-delete-filter="onToggleDeleteFilters" @on-toggle-delete-filter="onToggleDeleteFilters"
@on-toggle-edit-filter="onToggleFilters"
/> />
<contacts-table <contacts-table
:contacts="records" :contacts="records"
@@ -58,14 +59,18 @@
</woot-modal> </woot-modal>
<woot-modal <woot-modal
:show.sync="showFiltersModal" :show.sync="showFiltersModal"
:on-close="onToggleFilters" :on-close="closeAdvanceFiltersModal"
size="medium" size="medium"
> >
<contacts-advanced-filters <contacts-advanced-filters
v-if="showFiltersModal" v-if="showFiltersModal"
:on-close="onToggleFilters" :on-close="closeAdvanceFiltersModal"
:initial-filter-types="contactFilterItems" :initial-filter-types="contactFilterItems"
:initial-applied-filters="appliedFilter"
:active-segment-name="activeSegmentName"
:is-segments-view="hasActiveSegments"
@applyFilter="onApplyFilter" @applyFilter="onApplyFilter"
@updateSegment="onUpdateSegment"
@clearFilters="clearFilters" @clearFilters="clearFilters"
/> />
</woot-modal> </woot-modal>
@@ -87,6 +92,8 @@ import filterQueryGenerator from '../../../../helper/filterQueryGenerator';
import AddCustomViews from 'dashboard/routes/dashboard/customviews/AddCustomViews'; import AddCustomViews from 'dashboard/routes/dashboard/customviews/AddCustomViews';
import DeleteCustomViews from 'dashboard/routes/dashboard/customviews/DeleteCustomViews'; import DeleteCustomViews from 'dashboard/routes/dashboard/customviews/DeleteCustomViews';
import { CONTACTS_EVENTS } from '../../../../helper/AnalyticsHelper/events'; import { CONTACTS_EVENTS } from '../../../../helper/AnalyticsHelper/events';
import countries from 'shared/constants/countries.js';
import { generateValuesForEditCustomViews } from 'dashboard/helper/customViewsHelper';
const DEFAULT_PAGE = 1; const DEFAULT_PAGE = 1;
const FILTER_TYPE_CONTACT = 1; const FILTER_TYPE_CONTACT = 1;
@@ -128,6 +135,7 @@ export default {
filterType: FILTER_TYPE_CONTACT, filterType: FILTER_TYPE_CONTACT,
showAddSegmentsModal: false, showAddSegmentsModal: false,
showDeleteSegmentsModal: false, showDeleteSegmentsModal: false,
appliedFilter: [],
}; };
}, },
computed: { computed: {
@@ -175,7 +183,7 @@ export default {
showContactViewPane() { showContactViewPane() {
return this.selectedContactId !== ''; return this.selectedContactId !== '';
}, },
wrapClas() { wrapClass() {
return this.showContactViewPane ? 'medium-9' : 'medium-12'; return this.showContactViewPane ? 'medium-9' : 'medium-12';
}, },
pageParameter() { pageParameter() {
@@ -194,6 +202,9 @@ export default {
} }
return undefined; return undefined;
}, },
activeSegmentName() {
return this.activeSegment?.name;
},
}, },
watch: { watch: {
label() { label() {
@@ -345,7 +356,14 @@ export default {
}); });
}, },
onToggleFilters() { onToggleFilters() {
this.showFiltersModal = !this.showFiltersModal; if (this.hasActiveSegments) {
this.initializeSegmentToFilterModal(this.activeSegment);
}
this.showFiltersModal = true;
},
closeAdvanceFiltersModal() {
this.showFiltersModal = false;
this.appliedFilter = [];
}, },
onApplyFilter(payload) { onApplyFilter(payload) {
this.closeContactInfoPanel(); this.closeContactInfoPanel();
@@ -355,10 +373,59 @@ export default {
}); });
this.showFiltersModal = false; this.showFiltersModal = false;
}, },
onUpdateSegment(payload, segmentName) {
const payloadData = {
...this.activeSegment,
name: segmentName,
query: filterQueryGenerator(payload),
};
this.$store.dispatch('customViews/update', payloadData);
this.closeAdvanceFiltersModal();
},
clearFilters() { clearFilters() {
this.$store.dispatch('contacts/clearContactFilters'); this.$store.dispatch('contacts/clearContactFilters');
this.fetchContacts(this.pageParameter); this.fetchContacts(this.pageParameter);
}, },
setParamsForEditSegmentModal() {
// Here we are setting the params for edit segment modal to show the existing values.
// For custom attributes we get only attribute key.
// So we are mapping it to find the input type of the attribute to show in the edit segment modal.
const params = {
countries: countries,
filterTypes: contactFilterItems,
allCustomAttributes: this.$store.getters[
'attributes/getAttributesByModel'
]('contact_attribute'),
};
return params;
},
initializeSegmentToFilterModal(activeSegment) {
// Here we are setting the params for edit segment modal.
// To show the existing values. when we click on edit segment button.
// Here we get the query from the active segment.
// And we are mapping the query to the actual values.
// To show in the edit segment modal by the help of generateValuesForEditCustomViews helper.
const query = activeSegment?.query?.payload;
if (!Array.isArray(query)) return;
this.appliedFilter.push(
...query.map(filter => ({
attribute_key: filter.attribute_key,
attribute_model: filter.attribute_model,
filter_operator: filter.filter_operator,
values: Array.isArray(filter.values)
? generateValuesForEditCustomViews(
filter,
this.setParamsForEditSegmentModal()
)
: [],
query_operator: filter.query_operator,
custom_attribute_type: filter.custom_attribute_type,
}))
);
},
openSavedItemInSegment() { openSavedItemInSegment() {
const lastItemInSegments = this.segments[this.segments.length - 1]; const lastItemInSegments = this.segments[this.segments.length - 1];
const lastItemId = lastItemInSegments.id; const lastItemId = lastItemInSegments.id;

View File

@@ -27,8 +27,16 @@
{{ $t('CONTACTS_PAGE.SEARCH_BUTTON') }} {{ $t('CONTACTS_PAGE.SEARCH_BUTTON') }}
</woot-button> </woot-button>
</div> </div>
<div v-if="hasActiveSegments">
<woot-button
class="margin-right-1 clear"
color-scheme="secondary"
icon="edit"
@click="onToggleEditSegmentsModal"
>
{{ $t('CONTACTS_PAGE.FILTER_CONTACTS_EDIT') }}
</woot-button>
<woot-button <woot-button
v-if="hasActiveSegments"
class="margin-right-1 clear" class="margin-right-1 clear"
color-scheme="alert" color-scheme="alert"
icon="delete" icon="delete"
@@ -36,6 +44,7 @@
> >
{{ $t('CONTACTS_PAGE.FILTER_CONTACTS_DELETE') }} {{ $t('CONTACTS_PAGE.FILTER_CONTACTS_DELETE') }}
</woot-button> </woot-button>
</div>
<div v-if="!hasActiveSegments" class="filters__button-wrap"> <div v-if="!hasActiveSegments" class="filters__button-wrap">
<div v-if="hasAppliedFilters" class="filters__applied-indicator" /> <div v-if="hasAppliedFilters" class="filters__applied-indicator" />
<woot-button <woot-button
@@ -147,6 +156,9 @@ export default {
onToggleSegmentsModal() { onToggleSegmentsModal() {
this.$emit('on-toggle-save-filter'); this.$emit('on-toggle-save-filter');
}, },
onToggleEditSegmentsModal() {
this.$emit('on-toggle-edit-filter');
},
onToggleDeleteSegmentsModal() { onToggleDeleteSegmentsModal() {
this.$emit('on-toggle-delete-filter'); this.$emit('on-toggle-delete-filter');
}, },