Commit 6e45e9f8 authored by johnnyjoy's avatar johnnyjoy

refactor: deprecate old filter

parent 70987216
......@@ -310,10 +310,6 @@ message ListMemosRequest {
// Optional. If true, show deleted memos in the response.
bool show_deleted = 7 [(google.api.field_behavior) = OPTIONAL];
// [Deprecated] Old filter contains some specific conditions to filter memos.
// Format: "creator == 'users/{user}' && visibilities == ['PUBLIC', 'PROTECTED']"
string old_filter = 8;
}
message ListMemosResponse {
......
......@@ -575,10 +575,7 @@ type ListMemosRequest struct {
// Refer to `Shortcut.filter`.
Filter string `protobuf:"bytes,6,opt,name=filter,proto3" json:"filter,omitempty"`
// Optional. If true, show deleted memos in the response.
ShowDeleted bool `protobuf:"varint,7,opt,name=show_deleted,json=showDeleted,proto3" json:"show_deleted,omitempty"`
// [Deprecated] Old filter contains some specific conditions to filter memos.
// Format: "creator == 'users/{user}' && visibilities == ['PUBLIC', 'PROTECTED']"
OldFilter string `protobuf:"bytes,8,opt,name=old_filter,json=oldFilter,proto3" json:"old_filter,omitempty"`
ShowDeleted bool `protobuf:"varint,7,opt,name=show_deleted,json=showDeleted,proto3" json:"show_deleted,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
......@@ -662,13 +659,6 @@ func (x *ListMemosRequest) GetShowDeleted() bool {
return false
}
func (x *ListMemosRequest) GetOldFilter() string {
if x != nil {
return x.OldFilter
}
return ""
}
type ListMemosResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
// The list of memos.
......@@ -2074,7 +2064,7 @@ const file_api_v1_memo_service_proto_rawDesc = "" +
"\amemo_id\x18\x02 \x01(\tB\x03\xe0A\x01R\x06memoId\x12(\n" +
"\rvalidate_only\x18\x03 \x01(\bB\x03\xe0A\x01R\fvalidateOnly\x12\"\n" +
"\n" +
"request_id\x18\x04 \x01(\tB\x03\xe0A\x01R\trequestId\"\xbf\x02\n" +
"request_id\x18\x04 \x01(\tB\x03\xe0A\x01R\trequestId\"\xa0\x02\n" +
"\x10ListMemosRequest\x121\n" +
"\x06parent\x18\x01 \x01(\tB\x19\xe0A\x01\xfaA\x13\n" +
"\x11memos.api.v1/UserR\x06parent\x12 \n" +
......@@ -2084,9 +2074,7 @@ const file_api_v1_memo_service_proto_rawDesc = "" +
"\x05state\x18\x04 \x01(\x0e2\x13.memos.api.v1.StateB\x03\xe0A\x01R\x05state\x12\x1e\n" +
"\border_by\x18\x05 \x01(\tB\x03\xe0A\x01R\aorderBy\x12\x1b\n" +
"\x06filter\x18\x06 \x01(\tB\x03\xe0A\x01R\x06filter\x12&\n" +
"\fshow_deleted\x18\a \x01(\bB\x03\xe0A\x01R\vshowDeleted\x12\x1d\n" +
"\n" +
"old_filter\x18\b \x01(\tR\toldFilter\"\x84\x01\n" +
"\fshow_deleted\x18\a \x01(\bB\x03\xe0A\x01R\vshowDeleted\"\x84\x01\n" +
"\x11ListMemosResponse\x12(\n" +
"\x05memos\x18\x01 \x03(\v2\x12.memos.api.v1.MemoR\x05memos\x12&\n" +
"\x0fnext_page_token\x18\x02 \x01(\tR\rnextPageToken\x12\x1d\n" +
......
......@@ -680,13 +680,6 @@ paths:
description: Optional. If true, show deleted memos in the response.
schema:
type: boolean
- name: oldFilter
in: query
description: |-
[Deprecated] Old filter contains some specific conditions to filter memos.
Format: "creator == 'users/{user}' && visibilities == ['PUBLIC', 'PROTECTED']"
schema:
type: string
responses:
"200":
description: OK
......@@ -1667,13 +1660,6 @@ paths:
description: Optional. If true, show deleted memos in the response.
schema:
type: boolean
- name: oldFilter
in: query
description: |-
[Deprecated] Old filter contains some specific conditions to filter memos.
Format: "creator == 'users/{user}' && visibilities == ['PUBLIC', 'PROTECTED']"
schema:
type: string
responses:
"200":
description: OK
......
......@@ -99,13 +99,6 @@ func (s *APIV1Service) ListMemos(ctx context.Context, request *v1pb.ListMemosReq
// Exclude comments by default.
ExcludeComments: true,
}
// Handle deprecated old_filter for backward compatibility
if request.OldFilter != "" && request.Filter == "" {
//nolint:staticcheck // SA1019: Using deprecated field for backward compatibility
if err := s.buildMemoFindWithFilter(ctx, memoFind, request.OldFilter); err != nil {
return nil, status.Errorf(codes.InvalidArgument, "failed to build find memos with filter: %v", err)
}
}
if request.Parent != "" && request.Parent != "users/-" {
userID, err := ExtractUserIDFromName(request.Parent)
if err != nil {
......
package v1
import (
"context"
"github.com/google/cel-go/cel"
"github.com/pkg/errors"
exprv1 "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"github.com/usememos/memos/store"
)
func (s *APIV1Service) buildMemoFindWithFilter(ctx context.Context, find *store.FindMemo, filter string) error {
if find.PayloadFind == nil {
find.PayloadFind = &store.FindMemoPayload{}
}
if filter != "" {
filterExpr, err := parseMemoFilter(filter)
if err != nil {
return status.Errorf(codes.InvalidArgument, "invalid filter: %v", err)
}
if len(filterExpr.ContentSearch) > 0 {
find.ContentSearch = filterExpr.ContentSearch
}
if filterExpr.TagSearch != nil {
if find.PayloadFind == nil {
find.PayloadFind = &store.FindMemoPayload{}
}
find.PayloadFind.TagSearch = filterExpr.TagSearch
}
if filterExpr.DisplayTimeAfter != nil {
workspaceMemoRelatedSetting, err := s.Store.GetWorkspaceMemoRelatedSetting(ctx)
if err != nil {
return status.Errorf(codes.Internal, "failed to get workspace memo related setting")
}
if workspaceMemoRelatedSetting.DisplayWithUpdateTime {
find.UpdatedTsAfter = filterExpr.DisplayTimeAfter
} else {
find.CreatedTsAfter = filterExpr.DisplayTimeAfter
}
}
if filterExpr.DisplayTimeBefore != nil {
workspaceMemoRelatedSetting, err := s.Store.GetWorkspaceMemoRelatedSetting(ctx)
if err != nil {
return status.Errorf(codes.Internal, "failed to get workspace memo related setting")
}
if workspaceMemoRelatedSetting.DisplayWithUpdateTime {
find.UpdatedTsBefore = filterExpr.DisplayTimeBefore
} else {
find.CreatedTsBefore = filterExpr.DisplayTimeBefore
}
}
if filterExpr.Pinned {
pinned := true
find.Pinned = &pinned
}
if filterExpr.HasLink {
find.PayloadFind.HasLink = true
}
if filterExpr.HasTaskList {
find.PayloadFind.HasTaskList = true
}
if filterExpr.HasCode {
find.PayloadFind.HasCode = true
}
if filterExpr.HasIncompleteTasks {
find.PayloadFind.HasIncompleteTasks = true
}
}
return nil
}
// MemoFilterCELAttributes are the CEL attributes.
var MemoFilterCELAttributes = []cel.EnvOption{
cel.Variable("content_search", cel.ListType(cel.StringType)),
cel.Variable("tag_search", cel.ListType(cel.StringType)),
cel.Variable("display_time_before", cel.IntType),
cel.Variable("display_time_after", cel.IntType),
cel.Variable("pinned", cel.BoolType),
cel.Variable("has_link", cel.BoolType),
cel.Variable("has_task_list", cel.BoolType),
cel.Variable("has_code", cel.BoolType),
cel.Variable("has_incomplete_tasks", cel.BoolType),
}
type MemoFilter struct {
ContentSearch []string
TagSearch []string
DisplayTimeBefore *int64
DisplayTimeAfter *int64
Pinned bool
HasLink bool
HasTaskList bool
HasCode bool
HasIncompleteTasks bool
}
func parseMemoFilter(expression string) (*MemoFilter, error) {
e, err := cel.NewEnv(MemoFilterCELAttributes...)
if err != nil {
return nil, err
}
ast, issues := e.Compile(expression)
if issues != nil {
return nil, errors.Errorf("found issue %v", issues)
}
filter := &MemoFilter{}
parsedExpr, err := cel.AstToParsedExpr(ast)
if err != nil {
return nil, err
}
callExpr := parsedExpr.GetExpr().GetCallExpr()
findMemoField(callExpr, filter)
return filter, nil
}
func findMemoField(callExpr *exprv1.Expr_Call, filter *MemoFilter) {
if len(callExpr.Args) == 2 {
idExpr := callExpr.Args[0].GetIdentExpr()
if idExpr != nil {
if idExpr.Name == "content_search" {
contentSearch := []string{}
for _, expr := range callExpr.Args[1].GetListExpr().GetElements() {
value := expr.GetConstExpr().GetStringValue()
contentSearch = append(contentSearch, value)
}
filter.ContentSearch = contentSearch
} else if idExpr.Name == "tag_search" {
tagSearch := []string{}
for _, expr := range callExpr.Args[1].GetListExpr().GetElements() {
value := expr.GetConstExpr().GetStringValue()
tagSearch = append(tagSearch, value)
}
filter.TagSearch = tagSearch
} else if idExpr.Name == "display_time_before" {
displayTimeBefore := callExpr.Args[1].GetConstExpr().GetInt64Value()
filter.DisplayTimeBefore = &displayTimeBefore
} else if idExpr.Name == "display_time_after" {
displayTimeAfter := callExpr.Args[1].GetConstExpr().GetInt64Value()
filter.DisplayTimeAfter = &displayTimeAfter
} else if idExpr.Name == "pinned" {
value := callExpr.Args[1].GetConstExpr().GetBoolValue()
filter.Pinned = value
} else if idExpr.Name == "has_link" {
value := callExpr.Args[1].GetConstExpr().GetBoolValue()
filter.HasLink = value
} else if idExpr.Name == "has_task_list" {
value := callExpr.Args[1].GetConstExpr().GetBoolValue()
filter.HasTaskList = value
} else if idExpr.Name == "has_code" {
value := callExpr.Args[1].GetConstExpr().GetBoolValue()
filter.HasCode = value
} else if idExpr.Name == "has_incomplete_tasks" {
value := callExpr.Args[1].GetConstExpr().GetBoolValue()
filter.HasIncompleteTasks = value
}
return
}
}
for _, arg := range callExpr.Args {
callExpr := arg.GetCallExpr()
if callExpr != nil {
findMemoField(callExpr, filter)
}
}
}
......@@ -48,12 +48,12 @@ const AddMemoRelationPopover = (props: Props) => {
try {
const conditions = [];
if (searchText) {
conditions.push(`content_search == [${JSON.stringify(searchText)}]`);
conditions.push(`content.contains("${searchText}")`);
}
const { memos } = await memoServiceClient.listMemos({
parent: user.name,
filter: conditions.length > 0 ? conditions.join(" AND ") : undefined,
pageSize: DEFAULT_LIST_MEMOS_PAGE_SIZE,
oldFilter: conditions.length > 0 ? conditions.join(" && ") : undefined,
});
setFetchedMemos(memos);
} catch (error: any) {
......
......@@ -22,7 +22,6 @@ interface Props {
state?: State;
orderBy?: string;
filter?: string;
oldFilter?: string;
pageSize?: number;
}
......@@ -53,7 +52,6 @@ const PagedMemoList = observer((props: Props) => {
state: props.state || State.NORMAL,
orderBy: props.orderBy || "display_time desc",
filter: props.filter || "",
oldFilter: props.oldFilter || "",
pageSize: props.pageSize || DEFAULT_LIST_MEMOS_PAGE_SIZE,
pageToken,
});
......@@ -103,7 +101,7 @@ const PagedMemoList = observer((props: Props) => {
// Initial load and reload when props change
useEffect(() => {
refreshList();
}, [props.owner, props.state, props.orderBy, props.filter, props.oldFilter, props.pageSize]);
}, [props.owner, props.state, props.orderBy, props.filter, props.pageSize]);
// Auto-fetch more content when list changes and page isn't full
useEffect(() => {
......
......@@ -12,25 +12,17 @@ import { Memo } from "@/types/proto/api/v1/memo_service";
const Archived = observer(() => {
const user = useCurrentUser();
const memoListFilter = useMemo(() => {
const memoFitler = useMemo(() => {
const conditions = [];
const contentSearch: string[] = [];
const tagSearch: string[] = [];
for (const filter of memoFilterStore.filters) {
if (filter.factor === "contentSearch") {
contentSearch.push(`"${filter.value}"`);
conditions.push(`content.contains("${filter.value}")`);
} else if (filter.factor === "tagSearch") {
tagSearch.push(`"${filter.value}"`);
conditions.push(`tag in ["${filter.value}"]`);
}
}
if (contentSearch.length > 0) {
conditions.push(`content_search == [${contentSearch.join(", ")}]`);
}
if (tagSearch.length > 0) {
conditions.push(`tag_search == [${tagSearch.join(", ")}]`);
}
return conditions.join(" && ");
}, [user, memoFilterStore.filters]);
return conditions.length > 0 ? conditions.join(" && ") : undefined;
}, [memoFilterStore.filters]);
return (
<PagedMemoList
......@@ -47,7 +39,7 @@ const Archived = observer(() => {
owner={user.name}
state={State.ARCHIVED}
orderBy={viewStore.state.orderByTimeAsc ? "display_time asc" : "display_time desc"}
oldFilter={memoListFilter}
filter={memoFitler}
/>
);
});
......
......@@ -20,39 +20,33 @@ const Home = observer(() => {
const user = useCurrentUser();
const selectedShortcut = userStore.state.shortcuts.find((shortcut) => getShortcutId(shortcut.name) === memoFilterStore.shortcut);
const memoListFilter = useMemo(() => {
const memoFilter = useMemo(() => {
const conditions = [];
const contentSearch: string[] = [];
const tagSearch: string[] = [];
if (selectedShortcut?.filter) {
conditions.push(selectedShortcut.filter);
}
for (const filter of memoFilterStore.filters) {
if (filter.factor === "contentSearch") {
contentSearch.push(`"${filter.value}"`);
conditions.push(`content.contains("${filter.value}")`);
} else if (filter.factor === "tagSearch") {
tagSearch.push(`"${filter.value}"`);
conditions.push(`tag in ["${filter.value}"]`);
} else if (filter.factor === "pinned") {
conditions.push(`pinned == true`);
conditions.push(`pinned`);
} else if (filter.factor === "property.hasLink") {
conditions.push(`has_link == true`);
conditions.push(`has_link`);
} else if (filter.factor === "property.hasTaskList") {
conditions.push(`has_task_list == true`);
conditions.push(`has_task_list`);
} else if (filter.factor === "property.hasCode") {
conditions.push(`has_code == true`);
conditions.push(`has_code`);
} else if (filter.factor === "displayTime") {
const filterDate = new Date(filter.value);
const filterUtcTimestamp = filterDate.getTime() + filterDate.getTimezoneOffset() * 60 * 1000;
const timestampAfter = filterUtcTimestamp / 1000;
conditions.push(`display_time_after == ${timestampAfter}`);
conditions.push(`display_time_before == ${timestampAfter + 60 * 60 * 24}`);
conditions.push(`created_ts >= ${timestampAfter} AND created_ts < ${timestampAfter + 60 * 60 * 24}`);
}
}
if (contentSearch.length > 0) {
conditions.push(`content_search == [${contentSearch.join(", ")}]`);
}
if (tagSearch.length > 0) {
conditions.push(`tag_search == [${tagSearch.join(", ")}]`);
}
return conditions.join(" && ");
}, [user, memoFilterStore.filters, viewStore.state.orderByTimeAsc]);
return conditions.length > 0 ? conditions.join(" && ") : undefined;
}, [memoFilterStore.filters, selectedShortcut?.filter]);
return (
<div className="w-full min-h-full bg-background text-foreground">
......@@ -69,8 +63,7 @@ const Home = observer(() => {
}
owner={user.name}
orderBy={viewStore.state.orderByTimeAsc ? "display_time asc" : "display_time desc"}
filter={selectedShortcut?.filter || ""}
oldFilter={memoListFilter}
filter={memoFilter}
/>
</div>
);
......
......@@ -41,28 +41,20 @@ const UserProfile = observer(() => {
});
}, [params.username]);
const memoListFilter = useMemo(() => {
const memoFilter = useMemo(() => {
if (!user) {
return "";
return undefined;
}
const conditions = [];
const contentSearch: string[] = [];
const tagSearch: string[] = [];
for (const filter of memoFilterStore.filters) {
if (filter.factor === "contentSearch") {
contentSearch.push(`"${filter.value}"`);
conditions.push(`content.contains("${filter.value}")`);
} else if (filter.factor === "tagSearch") {
tagSearch.push(`"${filter.value}"`);
conditions.push(`tag in ["${filter.value}"]`);
}
}
if (contentSearch.length > 0) {
conditions.push(`content_search == [${contentSearch.join(", ")}]`);
}
if (tagSearch.length > 0) {
conditions.push(`tag_search == [${tagSearch.join(", ")}]`);
}
return conditions.join(" && ");
return conditions.length > 0 ? conditions.join(" && ") : undefined;
}, [user, memoFilterStore.filters]);
const handleCopyProfileLink = () => {
......@@ -110,7 +102,7 @@ const UserProfile = observer(() => {
}
owner={user.name}
orderBy={viewStore.state.orderByTimeAsc ? "display_time asc" : "display_time desc"}
oldFilter={memoListFilter}
filter={memoFilter}
/>
</>
) : (
......
......@@ -212,11 +212,6 @@ export interface ListMemosRequest {
filter: string;
/** Optional. If true, show deleted memos in the response. */
showDeleted: boolean;
/**
* [Deprecated] Old filter contains some specific conditions to filter memos.
* Format: "creator == 'users/{user}' && visibilities == ['PUBLIC', 'PROTECTED']"
*/
oldFilter: string;
}
export interface ListMemosResponse {
......@@ -1103,7 +1098,6 @@ function createBaseListMemosRequest(): ListMemosRequest {
orderBy: "",
filter: "",
showDeleted: false,
oldFilter: "",
};
}
......@@ -1130,9 +1124,6 @@ export const ListMemosRequest: MessageFns<ListMemosRequest> = {
if (message.showDeleted !== false) {
writer.uint32(56).bool(message.showDeleted);
}
if (message.oldFilter !== "") {
writer.uint32(66).string(message.oldFilter);
}
return writer;
},
......@@ -1199,14 +1190,6 @@ export const ListMemosRequest: MessageFns<ListMemosRequest> = {
message.showDeleted = reader.bool();
continue;
}
case 8: {
if (tag !== 66) {
break;
}
message.oldFilter = reader.string();
continue;
}
}
if ((tag & 7) === 4 || tag === 0) {
break;
......@@ -1228,7 +1211,6 @@ export const ListMemosRequest: MessageFns<ListMemosRequest> = {
message.orderBy = object.orderBy ?? "";
message.filter = object.filter ?? "";
message.showDeleted = object.showDeleted ?? false;
message.oldFilter = object.oldFilter ?? "";
return message;
},
};
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment