MM-11359: support excluding results from search (#11196)
Этот коммит содержится в:
@@ -14,12 +14,18 @@ var searchTermPuncEnd = regexp.MustCompile(`[^\pL\d\s*"]+$`)
|
||||
|
||||
type SearchParams struct {
|
||||
Terms string
|
||||
ExcludedTerms string
|
||||
IsHashtag bool
|
||||
InChannels []string
|
||||
ExcludedChannels []string
|
||||
FromUsers []string
|
||||
ExcludedUsers []string
|
||||
AfterDate string
|
||||
ExcludedAfterDate string
|
||||
BeforeDate string
|
||||
ExcludedBeforeDate string
|
||||
OnDate string
|
||||
ExcludedDate string
|
||||
OrTerms bool
|
||||
IncludeDeletedChannels bool
|
||||
TimeZoneOffset int
|
||||
@@ -40,6 +46,19 @@ func (p *SearchParams) GetAfterDateMillis() int64 {
|
||||
return GetStartOfDayMillis(afterDate, p.TimeZoneOffset)
|
||||
}
|
||||
|
||||
// Returns the epoch timestamp of the start of the day specified by SearchParams.ExcludedAfterDate
|
||||
func (p *SearchParams) GetExcludedAfterDateMillis() int64 {
|
||||
date, err := time.Parse("2006-01-02", PadDateStringZeros(p.ExcludedAfterDate))
|
||||
if err != nil {
|
||||
date = time.Now()
|
||||
}
|
||||
|
||||
// travel forward 1 day
|
||||
oneDay := time.Hour * 24
|
||||
afterDate := date.Add(oneDay)
|
||||
return GetStartOfDayMillis(afterDate, p.TimeZoneOffset)
|
||||
}
|
||||
|
||||
// Returns the epoch timestamp of the end of the day specified by SearchParams.BeforeDate
|
||||
func (p *SearchParams) GetBeforeDateMillis() int64 {
|
||||
date, err := time.Parse("2006-01-02", PadDateStringZeros(p.BeforeDate))
|
||||
@@ -53,6 +72,19 @@ func (p *SearchParams) GetBeforeDateMillis() int64 {
|
||||
return GetEndOfDayMillis(beforeDate, p.TimeZoneOffset)
|
||||
}
|
||||
|
||||
// Returns the epoch timestamp of the end of the day specified by SearchParams.ExcludedBeforeDate
|
||||
func (p *SearchParams) GetExcludedBeforeDateMillis() int64 {
|
||||
date, err := time.Parse("2006-01-02", PadDateStringZeros(p.ExcludedBeforeDate))
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
// travel back 1 day
|
||||
oneDay := time.Hour * -24
|
||||
beforeDate := date.Add(oneDay)
|
||||
return GetEndOfDayMillis(beforeDate, p.TimeZoneOffset)
|
||||
}
|
||||
|
||||
// Returns the epoch timestamps of the start and end of the day specified by SearchParams.OnDate
|
||||
func (p *SearchParams) GetOnDateMillis() (int64, int64) {
|
||||
date, err := time.Parse("2006-01-02", PadDateStringZeros(p.OnDate))
|
||||
@@ -63,8 +95,29 @@ func (p *SearchParams) GetOnDateMillis() (int64, int64) {
|
||||
return GetStartOfDayMillis(date, p.TimeZoneOffset), GetEndOfDayMillis(date, p.TimeZoneOffset)
|
||||
}
|
||||
|
||||
// Returns the epoch timestamps of the start and end of the day specified by SearchParams.ExcludedDate
|
||||
func (p *SearchParams) GetExcludedDateMillis() (int64, int64) {
|
||||
date, err := time.Parse("2006-01-02", PadDateStringZeros(p.ExcludedDate))
|
||||
if err != nil {
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
return GetStartOfDayMillis(date, p.TimeZoneOffset), GetEndOfDayMillis(date, p.TimeZoneOffset)
|
||||
}
|
||||
|
||||
var searchFlags = [...]string{"from", "channel", "in", "before", "after", "on"}
|
||||
|
||||
type flag struct {
|
||||
name string
|
||||
value string
|
||||
exclude bool
|
||||
}
|
||||
|
||||
type searchWord struct {
|
||||
value string
|
||||
exclude bool
|
||||
}
|
||||
|
||||
func splitWords(text string) []string {
|
||||
words := []string{}
|
||||
|
||||
@@ -79,9 +132,13 @@ func splitWords(text string) []string {
|
||||
foundQuote = false
|
||||
location = i + 1
|
||||
} else {
|
||||
words = append(words, strings.Fields(text[location:i])...)
|
||||
nextStart := i
|
||||
if i > 0 && text[i-1] == '-' {
|
||||
nextStart = i - 1
|
||||
}
|
||||
words = append(words, strings.Fields(text[location:nextStart])...)
|
||||
foundQuote = true
|
||||
location = i
|
||||
location = nextStart
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -91,9 +148,9 @@ func splitWords(text string) []string {
|
||||
return words
|
||||
}
|
||||
|
||||
func parseSearchFlags(input []string) ([]string, [][2]string) {
|
||||
words := []string{}
|
||||
flags := [][2]string{}
|
||||
func parseSearchFlags(input []string) ([]searchWord, []flag) {
|
||||
words := []searchWord{}
|
||||
flags := []flag{}
|
||||
|
||||
skipNextWord := false
|
||||
for i, word := range input {
|
||||
@@ -105,17 +162,34 @@ func parseSearchFlags(input []string) ([]string, [][2]string) {
|
||||
isFlag := false
|
||||
|
||||
if colon := strings.Index(word, ":"); colon != -1 {
|
||||
flag := word[:colon]
|
||||
var flagName string
|
||||
var exclude bool
|
||||
if strings.HasPrefix(word, "-") {
|
||||
flagName = word[1:colon]
|
||||
exclude = true
|
||||
} else {
|
||||
flagName = word[:colon]
|
||||
exclude = false
|
||||
}
|
||||
|
||||
value := word[colon+1:]
|
||||
|
||||
for _, searchFlag := range searchFlags {
|
||||
// check for case insensitive equality
|
||||
if strings.EqualFold(flag, searchFlag) {
|
||||
if strings.EqualFold(flagName, searchFlag) {
|
||||
if value != "" {
|
||||
flags = append(flags, [2]string{searchFlag, value})
|
||||
flags = append(flags, flag{
|
||||
searchFlag,
|
||||
value,
|
||||
exclude,
|
||||
})
|
||||
isFlag = true
|
||||
} else if i < len(input)-1 {
|
||||
flags = append(flags, [2]string{searchFlag, input[i+1]})
|
||||
flags = append(flags, flag{
|
||||
searchFlag,
|
||||
input[i+1],
|
||||
exclude,
|
||||
})
|
||||
skipNextWord = true
|
||||
isFlag = true
|
||||
}
|
||||
@@ -128,6 +202,10 @@ func parseSearchFlags(input []string) ([]string, [][2]string) {
|
||||
}
|
||||
|
||||
if !isFlag {
|
||||
exclude := false
|
||||
if strings.HasPrefix(word, "-") {
|
||||
exclude = true
|
||||
}
|
||||
// trim off surrounding punctuation (note that we leave trailing asterisks to allow wildcards)
|
||||
word = searchTermPuncStart.ReplaceAllString(word, "")
|
||||
word = searchTermPuncEnd.ReplaceAllString(word, "")
|
||||
@@ -136,7 +214,10 @@ func parseSearchFlags(input []string) ([]string, [][2]string) {
|
||||
word = hashtagStart.ReplaceAllString(word, "#")
|
||||
|
||||
if len(word) != 0 {
|
||||
words = append(words, word)
|
||||
words = append(words, searchWord{
|
||||
word,
|
||||
exclude,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -148,81 +229,139 @@ func ParseSearchParams(text string, timeZoneOffset int) []*SearchParams {
|
||||
words, flags := parseSearchFlags(splitWords(text))
|
||||
|
||||
hashtagTermList := []string{}
|
||||
excludedHashtagTermList := []string{}
|
||||
plainTermList := []string{}
|
||||
excludedPlainTermList := []string{}
|
||||
|
||||
for _, word := range words {
|
||||
if validHashtag.MatchString(word) {
|
||||
hashtagTermList = append(hashtagTermList, word)
|
||||
if validHashtag.MatchString(word.value) {
|
||||
if word.exclude {
|
||||
excludedHashtagTermList = append(excludedHashtagTermList, word.value)
|
||||
} else {
|
||||
hashtagTermList = append(hashtagTermList, word.value)
|
||||
}
|
||||
} else {
|
||||
plainTermList = append(plainTermList, word)
|
||||
if word.exclude {
|
||||
excludedPlainTermList = append(excludedPlainTermList, word.value)
|
||||
} else {
|
||||
plainTermList = append(plainTermList, word.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hashtagTerms := strings.Join(hashtagTermList, " ")
|
||||
excludedHashtagTerms := strings.Join(excludedHashtagTermList, " ")
|
||||
plainTerms := strings.Join(plainTermList, " ")
|
||||
excludedPlainTerms := strings.Join(excludedPlainTermList, " ")
|
||||
|
||||
inChannels := []string{}
|
||||
excludedChannels := []string{}
|
||||
fromUsers := []string{}
|
||||
excludedUsers := []string{}
|
||||
afterDate := ""
|
||||
excludedAfterDate := ""
|
||||
beforeDate := ""
|
||||
excludedBeforeDate := ""
|
||||
onDate := ""
|
||||
excludedDate := ""
|
||||
|
||||
for _, flagPair := range flags {
|
||||
flag := flagPair[0]
|
||||
value := flagPair[1]
|
||||
|
||||
if flag == "in" || flag == "channel" {
|
||||
inChannels = append(inChannels, value)
|
||||
} else if flag == "from" {
|
||||
fromUsers = append(fromUsers, value)
|
||||
} else if flag == "after" {
|
||||
afterDate = value
|
||||
} else if flag == "before" {
|
||||
beforeDate = value
|
||||
} else if flag == "on" {
|
||||
onDate = value
|
||||
for _, flag := range flags {
|
||||
if flag.name == "in" || flag.name == "channel" {
|
||||
if flag.exclude {
|
||||
excludedChannels = append(excludedChannels, flag.value)
|
||||
} else {
|
||||
inChannels = append(inChannels, flag.value)
|
||||
}
|
||||
} else if flag.name == "from" {
|
||||
if flag.exclude {
|
||||
excludedUsers = append(excludedUsers, flag.value)
|
||||
} else {
|
||||
fromUsers = append(fromUsers, flag.value)
|
||||
}
|
||||
} else if flag.name == "after" {
|
||||
if flag.exclude {
|
||||
excludedAfterDate = flag.value
|
||||
} else {
|
||||
afterDate = flag.value
|
||||
}
|
||||
} else if flag.name == "before" {
|
||||
if flag.exclude {
|
||||
excludedBeforeDate = flag.value
|
||||
} else {
|
||||
beforeDate = flag.value
|
||||
}
|
||||
} else if flag.name == "on" {
|
||||
if flag.exclude {
|
||||
excludedDate = flag.value
|
||||
} else {
|
||||
onDate = flag.value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
paramsList := []*SearchParams{}
|
||||
|
||||
if len(plainTerms) > 0 {
|
||||
if len(plainTerms) > 0 || len(excludedPlainTerms) > 0 {
|
||||
paramsList = append(paramsList, &SearchParams{
|
||||
Terms: plainTerms,
|
||||
IsHashtag: false,
|
||||
InChannels: inChannels,
|
||||
FromUsers: fromUsers,
|
||||
AfterDate: afterDate,
|
||||
BeforeDate: beforeDate,
|
||||
OnDate: onDate,
|
||||
TimeZoneOffset: timeZoneOffset,
|
||||
Terms: plainTerms,
|
||||
ExcludedTerms: excludedPlainTerms,
|
||||
IsHashtag: false,
|
||||
InChannels: inChannels,
|
||||
ExcludedChannels: excludedChannels,
|
||||
FromUsers: fromUsers,
|
||||
ExcludedUsers: excludedUsers,
|
||||
AfterDate: afterDate,
|
||||
ExcludedAfterDate: excludedAfterDate,
|
||||
BeforeDate: beforeDate,
|
||||
ExcludedBeforeDate: excludedBeforeDate,
|
||||
OnDate: onDate,
|
||||
ExcludedDate: excludedDate,
|
||||
TimeZoneOffset: timeZoneOffset,
|
||||
})
|
||||
}
|
||||
|
||||
if len(hashtagTerms) > 0 {
|
||||
if len(hashtagTerms) > 0 || len(excludedHashtagTerms) > 0 {
|
||||
paramsList = append(paramsList, &SearchParams{
|
||||
Terms: hashtagTerms,
|
||||
IsHashtag: true,
|
||||
InChannels: inChannels,
|
||||
FromUsers: fromUsers,
|
||||
AfterDate: afterDate,
|
||||
BeforeDate: beforeDate,
|
||||
OnDate: onDate,
|
||||
TimeZoneOffset: timeZoneOffset,
|
||||
Terms: hashtagTerms,
|
||||
ExcludedTerms: excludedHashtagTerms,
|
||||
IsHashtag: true,
|
||||
InChannels: inChannels,
|
||||
ExcludedChannels: excludedChannels,
|
||||
FromUsers: fromUsers,
|
||||
ExcludedUsers: excludedUsers,
|
||||
AfterDate: afterDate,
|
||||
ExcludedAfterDate: excludedAfterDate,
|
||||
BeforeDate: beforeDate,
|
||||
ExcludedBeforeDate: excludedBeforeDate,
|
||||
OnDate: onDate,
|
||||
ExcludedDate: excludedDate,
|
||||
TimeZoneOffset: timeZoneOffset,
|
||||
})
|
||||
}
|
||||
|
||||
// special case for when no terms are specified but we still have a filter
|
||||
if len(plainTerms) == 0 && len(hashtagTerms) == 0 && (len(inChannels) != 0 || len(fromUsers) != 0 || len(afterDate) != 0 || len(beforeDate) != 0 || len(onDate) != 0) {
|
||||
if len(plainTerms) == 0 && len(hashtagTerms) == 0 &&
|
||||
len(excludedPlainTerms) == 0 && len(excludedHashtagTerms) == 0 &&
|
||||
(len(inChannels) != 0 || len(fromUsers) != 0 ||
|
||||
len(excludedChannels) != 0 || len(excludedUsers) != 0 ||
|
||||
len(afterDate) != 0 || len(excludedAfterDate) != 0 ||
|
||||
len(beforeDate) != 0 || len(excludedBeforeDate) != 0 ||
|
||||
len(onDate) != 0 || len(excludedDate) != 0) {
|
||||
paramsList = append(paramsList, &SearchParams{
|
||||
Terms: "",
|
||||
IsHashtag: false,
|
||||
InChannels: inChannels,
|
||||
FromUsers: fromUsers,
|
||||
AfterDate: afterDate,
|
||||
BeforeDate: beforeDate,
|
||||
OnDate: onDate,
|
||||
TimeZoneOffset: timeZoneOffset,
|
||||
Terms: "",
|
||||
ExcludedTerms: "",
|
||||
IsHashtag: false,
|
||||
InChannels: inChannels,
|
||||
ExcludedChannels: excludedChannels,
|
||||
FromUsers: fromUsers,
|
||||
ExcludedUsers: excludedUsers,
|
||||
AfterDate: afterDate,
|
||||
ExcludedAfterDate: excludedAfterDate,
|
||||
BeforeDate: beforeDate,
|
||||
ExcludedBeforeDate: excludedBeforeDate,
|
||||
OnDate: onDate,
|
||||
ExcludedDate: excludedDate,
|
||||
TimeZoneOffset: timeZoneOffset,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
Ссылка в новой задаче
Block a user