Skip to content

Commit

Permalink
simpler PinnedFilters in SearchControl
Browse files Browse the repository at this point in the history
  • Loading branch information
olmobrutall committed Apr 14, 2023
1 parent 1cad7eb commit e970549
Show file tree
Hide file tree
Showing 19 changed files with 425 additions and 280 deletions.
15 changes: 12 additions & 3 deletions Signum.Entities.Extensions/UserQueries/UserQueryEntity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -454,11 +454,20 @@ internal PinnedQueryFilterEmbedded FromXml(XElement p, IFromXmlContext ctx)
Label = p.Attribute("Label")?.Value;
Column = p.Attribute("Column")?.Value.ToInt();
Row = p.Attribute("Row")?.Value.ToInt();
Active = p.Attribute("Active")?.Value.ToEnum<PinnedFilterActive>() ?? (p.Attribute("DisableOnNull")?.Value.ToBool() == true ? PinnedFilterActive.WhenHasValue : PinnedFilterActive.Always);
Active = ModernizeActive(p.Attribute("Active")?.Value)?.ToEnum<PinnedFilterActive>() ?? PinnedFilterActive.Always;
SplitText = p.Attribute("SplitText")?.Value.ToBool() ?? false;
return this;
}

private string? ModernizeActive(string? str) => str switch
{
"Checkbox_StartChecked" => "Checkbox_Checked",
"Checkbox_StartUnchecked" => "Checkbox_Unchecked",
"NotCheckbox_StartChecked" => "NotCheckbox_Checked",
"NotCheckbox_StartUnchecked" => "NotCheckbox_Unchecked",
_ => str
};

internal XElement ToXml(IToXmlContext ctx)
{
return new XElement("Pinned",
Expand Down Expand Up @@ -488,10 +497,10 @@ public static List<Filter> ToFilterList(this IEnumerable<QueryFilterEmbedded> fi

if (filter.Pinned != null)
{
if (filter.Pinned.Active == PinnedFilterActive.Checkbox_StartUnchecked)
if (filter.Pinned.Active == PinnedFilterActive.Checkbox_Unchecked)
return null;

if (filter.Pinned.Active == PinnedFilterActive.NotCheckbox_StartChecked)
if (filter.Pinned.Active == PinnedFilterActive.NotCheckbox_Checked)
return null;

if (filter.Pinned.SplitText && !filter.ValueString.HasText())
Expand Down
36 changes: 27 additions & 9 deletions Signum.Entities/DynamicQuery/Filter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -379,24 +379,42 @@ public override IEnumerable<string> GetKeywords()
[InTypeScript(true), DescriptionOptions(DescriptionOptions.Members | DescriptionOptions.Description)]
public enum FilterOperation
{
[Description("equal to")]
EqualTo,
[Description("distinct to")]
DistinctTo,
[Description("greater than")]
GreaterThan,
[Description("greater than or equal")]
GreaterThanOrEqual,
[Description("less than")]
LessThan,
[Description("less than or equal")]
LessThanOrEqual,
[Description("contains")]
Contains,
[Description("starts with")]
StartsWith,
[Description("ends with")]
EndsWith,
[Description("like")]
Like,
[Description("not contains")]
NotContains,
[Description("not starts with")]
NotStartsWith,
[Description("not ends with")]
NotEndsWith,
[Description("not like")]
NotLike,
[Description("is in")]
IsIn,
[Description("is not in")]
IsNotIn,

[Description("complex condition")]
ComplexCondition, //Full Text Search
[Description("free text")]
FreeText, //Full Text Search
}

Expand Down Expand Up @@ -491,7 +509,7 @@ public override IEnumerable<string> GetKeywords()
return this.SearchCondition.Split(" ");

return this.SearchCondition.Split(new string[] { "AND", "OR", "NOT", "NEAR", "(", ")", "*" }, StringSplitOptions.RemoveEmptyEntries)
.Select(a => a.Trim(' ').Trim('\'', '"'))
.Select(a => a.Trim(' ', '\r', '\n', '\t').Trim('"'))
.Where(a => a.Length > 0);
}
}
Expand Down Expand Up @@ -533,14 +551,14 @@ public enum PinnedFilterActive
{
Always,
WhenHasValue,
[Description("Checkbox (start checked)")]
Checkbox_StartChecked,
[Description("Checkbox (start unchecked)")]
Checkbox_StartUnchecked,
[Description("Not Checkbox (start checked)")]
NotCheckbox_StartChecked,
[Description("Not Checkbox (start unchecked)")]
NotCheckbox_StartUnchecked,
[Description("Checkbox (checked)")]
Checkbox_Checked,
[Description("Checkbox (unchecked)")]
Checkbox_Unchecked,
[Description("Not Checkbox (checked)")]
NotCheckbox_Checked,
[Description("Not Checkbox (unchecked)")]
NotCheckbox_Unchecked,
}

[InTypeScript(true), DescriptionOptions(DescriptionOptions.Members | DescriptionOptions.Description)]
Expand Down
24 changes: 18 additions & 6 deletions Signum.Entities/EnumMessages.cs
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,10 @@ public enum SearchMessage
AddFilter,
[Description("Add group")]
AddGroup,

[Description("Group Prefix")]
GroupPrefix,

[Description("Add value")]
AddValue,
[Description("Delete filter")]
Expand Down Expand Up @@ -253,10 +257,20 @@ public enum SearchMessage
ReturnNewEntity,
[Description("Do you want to return the new {0} ({1})?")]
DoYouWantToSelectTheNew01_G,
[Description("Show pinned filter options")]
ShowPinnedFiltersOptions,
[Description("Hide pinned filter options")]
HidePinnedFiltersOptions,
[Description("Edit pinned filters")]
EditPinnedFilters,

[Description("Pin filter")]
PinFilter,
[Description("Unpin filter")]
UnpinFilter,

[Description("Is Active")]
IsActive,

[Description("Split")]
Split,

[Description("Summary header")]
SummaryHeader,
[Description("Summary header must be an aggregate (like Sum, Count, etc..)")]
Expand All @@ -275,8 +289,6 @@ public enum SearchMessage
MoreThanOne0Selected,
CombineRowsWith,

PinnFilter,
UnpinnFilter,
SwitchViewMode,
}

Expand Down
38 changes: 2 additions & 36 deletions Signum.Entities/Translations/Signum.Entities.en.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,6 @@
<Type Name="CollectionAnyAllType" />
<Type Name="CollectionElementType" />
<Type Name="ColumnOptionsMode" />
<Type Name="ComparisonType">
<Member Name="DistinctTo" Description="distinct to" />
<Member Name="EqualTo" Description="equal to" />
<Member Name="GreaterThan" Description="greater than" />
<Member Name="LessThan" Description="less than" />
</Type>
<Type Name="ConnectionMessage" />
<Type Name="CorruptMixin" />
<Type Name="DeleteLogParametersEmbedded" />
Expand All @@ -24,28 +18,8 @@
<Type Name="EnumEntity`1" />
<Type Name="ExceptionEntity" />
<Type Name="FilterGroupOperation" />
<Type Name="FilterOperation">
<Member Name="Contains" Description="contains" />
<Member Name="DistinctTo" Description="distinct to" />
<Member Name="EndsWith" Description="ends with" />
<Member Name="EqualTo" Description="equal to" />
<Member Name="GreaterThan" Description="greater than" />
<Member Name="GreaterThanOrEqual" Description="greater than or equal" />
<Member Name="IsIn" Description="is in" />
<Member Name="IsNotIn" Description="is not in" />
<Member Name="LessThan" Description="less than" />
<Member Name="LessThanOrEqual" Description="less than or equal" />
<Member Name="Like" Description="like" />
<Member Name="NotContains" Description="not contains" />
<Member Name="NotEndsWith" Description="not ends with" />
<Member Name="NotLike" Description="not like" />
<Member Name="NotStartsWith" Description="not starts with" />
<Member Name="StartsWith" Description="starts with" />
</Type>

<Type Name="IEntity" />
<Type Name="ImmutableEntity">
<Member Name="AllowChange" Description="Allow Change" />
</Type>
<Type Name="IUserEntity" />
<Type Name="JavascriptMessage" />
<Type Name="LiteMessage" />
Expand All @@ -67,14 +41,6 @@
<Type Name="QueryTokenMessage" />
<Type Name="QuickLinkMessage" />
<Type Name="RoundingType" />
<Type Name="SearchMessage">
<Member Name="DeleteFilter" Description="Delete Filter" />
<Member Name="NewColumnSName" Description="New Column's Name" />
</Type>
<Type Name="SelectorMessage">
<Member Name="PleaseChooseAValueToContinue" Description="Please, choose a value to continue:" />
<Member Name="PleaseSelectAConstructor" Description="Please Select a Constructor" />
</Type>
<Type Name="SemiSymbol" />
<Type Name="StringCase" />
<Type Name="Symbol" />
Expand All @@ -84,4 +50,4 @@
<Type Name="TypeEntity" />
<Type Name="ValidationMessage" />
<Type Name="VoidEnumMessage" />
</Translations>
</Translations>
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ export function start(options: { routes: RouteObject[], adGroups: boolean }) {
],
},
{
pinned: { label: () => ActiveDirectoryMessage.OnlyActiveUsers.niceToString(), active: "Checkbox_StartChecked", column: 2, row: 0 },
pinned: { label: () => ActiveDirectoryMessage.OnlyActiveUsers.niceToString(), active: "Checkbox_Checked", column: 2, row: 0 },
token: "AccountEnabled", operation: "EqualTo", value: true
},
{ token: "CreationType", operation: "DistinctTo", value: "Invitation" }
Expand Down
4 changes: 2 additions & 2 deletions Signum.React.Extensions/Authorization/AuthAdminClient.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export function start(options: { routes: RouteObject[], types: boolean; properti
{
token: UserEntity.token(a => a.state),
value: UserState.value("Active"),
pinned: { label: () => AuthAdminMessage.OnlyActive.niceToString(), column: 2, active: "Checkbox_StartChecked" },
pinned: { label: () => AuthAdminMessage.OnlyActive.niceToString(), column: 2, active: "Checkbox_Checked" },
},
],
entityFormatter: new Finder.EntityFormatter((row, cols, sc) => !row.entity || !Navigator.isViewable(row.entity.EntityType, { isSearch: true }) ? undefined : <EntityLink lite={row.entity}
Expand Down Expand Up @@ -104,7 +104,7 @@ export function start(options: { routes: RouteObject[], types: boolean; properti
{
token: RoleEntity.token(a => a.entity.isTrivialMerge),
value: false,
pinned: { active: "NotCheckbox_StartUnchecked", label: ()=> AuthAdminMessage.IncludeTrivialMerges.niceToString(), column: 2 }
pinned: { active: "NotCheckbox_Unchecked", label: ()=> AuthAdminMessage.IncludeTrivialMerges.niceToString(), column: 2 }
}
],
extraButtons: scl => [isPermissionAuthorized(BasicPermission.AdminRules) && {
Expand Down
5 changes: 3 additions & 2 deletions Signum.React.Extensions/Chart/Templates/ChartRequestView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -126,9 +126,10 @@ export default function ChartRequestView(p: ChartRequestViewProps) {
p.onFiltersChanged(cr.filterOptions);
}

function handlePinnedFilterChanged() {
function handlePinnedFilterChanged(fop: FilterOptionParsed[], avoidSearch?: boolean) {
handleFiltersChanged();
handleOnDrawClick();
if (!avoidSearch)
handleOnDrawClick();
}

function handleOnFullScreen(e: React.MouseEvent<any>) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ export default function CombinedUserChartPart(p: PanelPartContentProps<CombinedU
{infos.map((info, i) => <PinnedFilterBuilder key={i}
filterOptions={info.chartRequest!.filterOptions}
pinnedFilterVisible={fop => fop.dashboardBehaviour == null}
onFiltersChanged={() => info.makeQuery!()} extraSmall={true} />
onFiltersChanged={(fpo, avoidSearch) => !avoidSearch && info.makeQuery!()} extraSmall={true} />
)}
{p.content.allowChangeShowData &&
<label>
Expand Down
2 changes: 1 addition & 1 deletion Signum.React.Extensions/Dashboard/View/UserChartPart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ export default function UserChartPart(p: PanelPartContentProps<UserChartPartEnti

return (
<div className="d-flex flex-column flex-grow-1">
<PinnedFilterBuilder filterOptions={chartRequest.filterOptions} onFiltersChanged={() => reloadQuery()} pinnedFilterVisible={fop => fop.dashboardBehaviour == null} extraSmall={true} />
<PinnedFilterBuilder filterOptions={chartRequest.filterOptions} onFiltersChanged={(fops, avoidSearch) => !avoidSearch && reloadQuery()} pinnedFilterVisible={fop => fop.dashboardBehaviour == null} extraSmall={true} />
{p.content.allowChangeShowData &&
<label>
<input type="checkbox" className="form-check-input" checked={showData} onChange={e => setShowData(e.currentTarget.checked)} />
Expand Down
2 changes: 1 addition & 1 deletion Signum.React.Extensions/Workflow/Case/Case.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ export default function CaseComponent(p: CaseComponentProps) {
queryName: CaseActivityEntity,
filterOptions: [
{ token: CaseActivityEntity.token(e => e.case), value: ctx.value },
{ token: CaseActivityEntity.token(e => e.doneDate), operation: "EqualTo", value: null, pinned: { active: "Checkbox_StartUnchecked", label: WorkflowActivityMessage.InprogressCaseActivities.niceToString(), column: 2 } },
{ token: CaseActivityEntity.token(e => e.doneDate), operation: "EqualTo", value: null, pinned: { active: "Checkbox_Unchecked", label: WorkflowActivityMessage.InprogressCaseActivities.niceToString(), column: 2 } },
],
columnOptionsMode: "ReplaceAll",
columnOptions: [
Expand Down
10 changes: 5 additions & 5 deletions Signum.React/Scripts/FindOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,14 +95,14 @@ export function isFilterGroupOptionParsed(fo: FilterOptionParsed): fo is FilterG
}

export function isActive(fo: FilterOptionParsed) {
return !(fo.dashboardBehaviour == "UseAsInitialSelection" || fo.pinned && (fo.pinned.active == "Checkbox_StartUnchecked" || fo.pinned.active == "NotCheckbox_StartUnchecked" || fo.pinned.active == "WhenHasValue" && fo.value == null));
return !(fo.dashboardBehaviour == "UseAsInitialSelection" || fo.pinned && (fo.pinned.active == "Checkbox_Unchecked" || fo.pinned.active == "NotCheckbox_Unchecked" || fo.pinned.active == "WhenHasValue" && fo.value == null));
}

export function isCheckBox(active: PinnedFilterActive | undefined) {
return active == "Checkbox_StartChecked" ||
active == "Checkbox_StartUnchecked" ||
active == "NotCheckbox_StartChecked" ||
active == "NotCheckbox_StartUnchecked";
return active == "Checkbox_Checked" ||
active == "Checkbox_Unchecked" ||
active == "NotCheckbox_Checked" ||
active == "NotCheckbox_Unchecked";
}

export interface FilterConditionOptionParsed {
Expand Down
4 changes: 2 additions & 2 deletions Signum.React/Scripts/Finder.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -937,7 +937,7 @@ interface OverridenValue {

export function toFilterRequest(fop: FilterOptionParsed, overridenValue?: OverridenValue): FilterRequest | undefined {

if (fop.pinned && (fop.pinned.active == "Checkbox_StartUnchecked" || fop.pinned.active == "NotCheckbox_StartChecked"))
if (fop.pinned && (fop.pinned.active == "Checkbox_Unchecked" || fop.pinned.active == "NotCheckbox_Checked"))
return undefined;

if (fop.dashboardBehaviour == "UseAsInitialSelection")
Expand Down Expand Up @@ -966,7 +966,7 @@ export function toFilterRequest(fop: FilterOptionParsed, overridenValue?: Overri
return undefined;
}

if (fop.pinned.active == "Checkbox_StartChecked") {
if (fop.pinned.active == "Checkbox_Checked") {

} else {
return toFilterRequest(fop, { value: fop.value });
Expand Down
12 changes: 12 additions & 0 deletions Signum.React/Scripts/SearchControl/FilterBuilder.css
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,15 @@ select.form-control.sf-group-selector {
.sf-user-filter:hover {
opacity: 1;
}


.sf-pinned-filter-cell {
background-color: rgb(255, 246, 230);
}


.sf-remove-filter-icon {

width: 22px;
text-align: center;
}
Loading

2 comments on commit e970549

@olmobrutall
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Simple Pinned Filters, Split Values and EntityStrip in SearchControl

Pinned Filters Preview and new Table UI

We have made some UI in the SearchControl, mainly related to configuring Pinned Filters.

image

  1. The filter button now is not solid.
  2. The button is now called "Edit pinned filters" and has an edit icon.
  3. Once clicked, pinned filter opttions appear on the right side of the filter (in light orange), extending the table, instead of below like it did before. Hopefully this way is visually simpler.
  4. Pinned filters now have a Pin icon (instead of an Star).
  5. When "Edit pinned filters" is activated, an interactive preview of the pinned filters result is shown above. Hopefully this also makes the behaviour of the different options (Label / Column / Rows) more intuitive since you get instant feedback after a change. The preview panel is fully functional and stays synchronized with the filter table.

Split Values (aka multiple Tags)

When configuring pinned filters, it is common to use Active: WhenHasValue to enable a behaviour where, the more components the user fill, the less results it will get.

The same idea was possible for text boxes with the old splitText option: The words were splitted by space and and all have to be found somewhere, so the more words you write, the less results you get.

{token : "Entity.Notes", operation: "Contains", values: "sales degree" } // Searches by "sales degree" together

{token : "Entity.Notes", operation: "Contains", values: "sales degree", pinned: { splitText: true } } //Searches "sales" AND "degree" independently

Until now, this idea was not supported for list of elements. For example in the previous screenshot we filter by one Territory.... how can we filter by many?

If we do something like:

{token : "Employee.Territories.Any", operation: "IsIn", values: [Atlanta, Tampa] } //Searches any Employee with Atlanta OR Tampa in the territories

But with the new splitValue (splitText generalized) now we can do:

{token : "Employee.Territories.Any", operation: "IsIn", values: [Atlanta, Tampa] } //Searches any Employee with  Atlanta AND Tampa in the territories

This way the more elements you add to the collection, the less results you get!

EntityStrip in SearchControl

Finally to make the previous scenario even better, we have replaced the polyvalent MultiValueLine with many EntityLine inside that was used when selecting the operation IsIn / IsNotIn with a token of type Entity / Lite for a propper EntityStrip.

This makes autocomplete faster with the keyboard and allows so select multiple entity when clicking in the magnified button (Find).

But this has the drawback that null is not allowed anymore as a value in the collection used in IsIn.

For example if your code has:

{token : "Country", operation: "IsIn", values: [null, Spain, Germany] }

You need to replace it for:

{ 
  groupOperation: "Or", 
  filters: [
    {token : "Country", operation: "Equals", value: null }, 
    {token : "Country", operation: "IsIn", value: [Spain, Germany] },
  ]
}

Is not a very common pattern but I have used once or twice.

Conclusion

I hope this UI improvements will make the live of the end-users and the power-users of your application a little bit easier when using the SearchControl.

@mehdy-karimpour
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great Job 👍

Please sign in to comment.