diff --git a/cmd/filterList.go b/cmd/filterList.go new file mode 100644 index 0000000..3e2dc61 --- /dev/null +++ b/cmd/filterList.go @@ -0,0 +1,142 @@ +// Copyright (c) 2024 Parseable, Inc +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package cmd + +import ( + "fmt" + "os" + "pb/pkg/model" + "strings" + "time" + + "github.com/spf13/cobra" +) + +var FilterList = &cobra.Command{ + Use: "list", + Example: "pb query list ", + Short: "List of saved filter for a stream", + Long: "\nShow a list of saved filter for a stream ", + PreRunE: PreRunDefaultProfile, + Run: func(command *cobra.Command, args []string) { + client := DefaultClient() + + p := model.UiApp() + _, err := p.Run() + if err != nil { + os.Exit(1) + } + + a := model.FilterToApply() + d := model.FilterToDelete() + if a.Stream() != "" { + filterToPbQuery(a.Stream(), a.StartTime(), a.EndTime()) + } + if d.FilterId() != "" { + deleteFilter(&client, d.FilterId()) + } + }, +} + +// Delete a saved filter from the list of filter +func deleteFilter(client *HTTPClient, filterID string) { + deleteUrl := `filters/filter/` + filterID + req, err := client.NewRequest("DELETE", deleteUrl, nil) + if err != nil { + fmt.Println("Error deleting the filter") + } + + resp, err := client.client.Do(req) + if err != nil { + return + } + defer resp.Body.Close() + + if resp.StatusCode == 200 { + fmt.Printf("\n\nFilter Deleted") + } +} + +// Convert a filter to executable pb query +func filterToPbQuery(query string, start string, end string) { + var timeStamps string + if start == "" || end == "" { + timeStamps = `` + } else { + startFormatted := formatToRFC3339(start) + endFormatted := formatToRFC3339(end) + timeStamps = ` --from=` + startFormatted + ` --to=` + endFormatted + } + queryTemplate := `pb query run ` + query + timeStamps + fmt.Printf("\nCopy and paste the command") + fmt.Printf("\n\n%s\n\n", queryTemplate) +} + +// Parses all UTC time format from string to time interface +func parseTimeToFormat(input string) (time.Time, error) { + // List of possible formats + formats := []string{ + time.RFC3339, + "2006-01-02 15:04:05", + "2006-01-02", + "01/02/2006 15:04:05", + "02-Jan-2006 15:04:05 MST", + "2006-01-02T15:04:05Z", + "02-Jan-2006", + } + + var err error + var t time.Time + + for _, format := range formats { + t, err = time.Parse(format, input) + if err == nil { + return t, nil + } + } + + return t, fmt.Errorf("unable to parse time: %s", input) +} + +// Converts to RFC3339 +func convertTime(input string) (string, error) { + t, err := parseTimeToFormat(input) + if err != nil { + return "", err + } + + return t.Format(time.RFC3339), nil +} + +// Converts User inputted time to string type RFC3339 time +func formatToRFC3339(time string) string { + var formattedTime string + if len(strings.Fields(time)) > 1 { + newTime := strings.Fields(time)[0:2] + rfc39990time, err := convertTime(strings.Join(newTime, " ")) + if err != nil { + fmt.Println("error formatting time") + } + formattedTime = rfc39990time + } else { + rfc39990time, err := convertTime(time) + if err != nil { + fmt.Println("error formatting time") + } + formattedTime = rfc39990time + } + return formattedTime +} diff --git a/cmd/query.go b/cmd/query.go index dbc2948..4db92e3 100644 --- a/cmd/query.go +++ b/cmd/query.go @@ -21,11 +21,11 @@ import ( "io" "os" "pb/pkg/config" - "pb/pkg/model" + // "pb/pkg/model" "strings" "time" - - tea "github.com/charmbracelet/bubbletea" + //! This dependancy is required by the interactive flag Do not remove + // tea "github.com/charmbracelet/bubbletea" "github.com/spf13/cobra" ) @@ -45,8 +45,8 @@ var ( saveFilterTimeFlag = "with-time" saveFilterTimeShort = "w" - interactiveFlag = "interactive" - interactiveFlagShort = "i" + // interactiveFlag = "interactive" + // interactiveFlagShort = "i" ) var query = &cobra.Command{ @@ -65,9 +65,9 @@ var query = &cobra.Command{ fmt.Println("please enter your query") fmt.Printf("Example:\n pb query run \"select * from frontend\" --from=10m --to=now\n") return nil - } else { - query = args[0] } + + query = args[0] start, err := command.Flags().GetString(startFlag) if err != nil { @@ -85,15 +85,16 @@ var query = &cobra.Command{ end = defaultEnd } - interactive, err := command.Flags().GetBool(interactiveFlag) - if err != nil { - return err - } + //TODO: Interactive Flag disabled + // interactive, err := command.Flags().GetBool(interactiveFlag) + // if err != nil { + // return err + // } - startTime, endTime, err := parseTime(start, end) - if err != nil { - return err - } + // startTime, endTime, err := parseTime(start, end) + // if err != nil { + // return err + // } keepTime, err := command.Flags().GetBool(saveFilterTimeFlag) if err != nil { @@ -106,14 +107,15 @@ var query = &cobra.Command{ } filterNameTrimmed := strings.Trim(filterName, " ") - if interactive { - p := tea.NewProgram(model.NewQueryModel(DefaultProfile, query, startTime, endTime), tea.WithAltScreen()) - if _, err := p.Run(); err != nil { - fmt.Printf("there's been an error: %v", err) - os.Exit(1) - } - return nil - } + //TODO: Interactive Flag disabled + // if interactive { + // p := tea.NewProgram(model.NewQueryModel(DefaultProfile, query, startTime, endTime), tea.WithAltScreen()) + // if _, err := p.Run(); err != nil { + // fmt.Printf("there's been an error: %v", err) + // os.Exit(1) + // } + // return nil + // } // Checks if there is filter name which is not empty. Empty filter name wont be allowed if command.Flags().Changed(saveFilterFlag) { @@ -143,7 +145,7 @@ var query = &cobra.Command{ var QueryCmd = func() *cobra.Command { query.Flags().BoolP(saveFilterTimeFlag, saveFilterTimeShort, false, "Save the time range associated in the query to the filter") // save time for a filter flag; default value = false (boolean type) - query.Flags().BoolP(interactiveFlag, interactiveFlagShort, false, "open the query result in interactive mode") + // query.Flags().BoolP(interactiveFlag, interactiveFlagShort, false, "open the query result in interactive mode") query.Flags().StringP(startFlag, startFlagShort, defaultStart, "Start time for query. Takes date as '2024-10-12T07:20:50.52Z' or string like '10m', '1hr'") query.Flags().StringP(endFlag, endFlagShort, defaultEnd, "End time for query. Takes date as '2024-10-12T07:20:50.52Z' or 'now'") query.Flags().StringP(saveFilterFlag, saveFilterShort, "", "Save a query filter") // save filter flag. Default value = FILTER_NAME (type string) diff --git a/go.mod b/go.mod index f0f3e42..42f5166 100644 --- a/go.mod +++ b/go.mod @@ -4,8 +4,9 @@ go 1.22 require ( github.com/apache/arrow/go/v13 v13.0.0 - github.com/charmbracelet/bubbles v0.16.1 - github.com/charmbracelet/bubbletea v0.26.4 + github.com/charmbracelet/bubbles v0.18.0 + github.com/charmbracelet/bubbletea v0.26.6 + github.com/charmbracelet/lipgloss v0.12.1 github.com/dustin/go-humanize v1.0.1 golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8 golang.org/x/term v0.21.0 @@ -13,7 +14,7 @@ require ( ) require ( - github.com/charmbracelet/x/ansi v0.1.2 // indirect + github.com/charmbracelet/x/ansi v0.1.4 // indirect github.com/charmbracelet/x/input v0.1.0 // indirect github.com/charmbracelet/x/term v0.1.1 // indirect github.com/charmbracelet/x/windows v0.1.0 // indirect @@ -37,17 +38,16 @@ require ( require ( github.com/atotto/clipboard v0.1.4 // indirect - github.com/charmbracelet/lipgloss v0.7.1 github.com/evertras/bubble-table v0.15.2 github.com/muesli/termenv v0.15.2 github.com/pelletier/go-toml/v2 v2.0.9 - github.com/sahilm/fuzzy v0.1.0 // indirect + github.com/sahilm/fuzzy v0.1.1-0.20230530133925-c48e322e2a8f // indirect ) require ( github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect - github.com/mattn/go-isatty v0.0.19 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-localereader v0.0.1 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect diff --git a/go.sum b/go.sum index 859bae7..2094cee 100644 --- a/go.sum +++ b/go.sum @@ -4,14 +4,14 @@ github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= -github.com/charmbracelet/bubbles v0.16.1 h1:6uzpAAaT9ZqKssntbvZMlksWHruQLNxg49H5WdeuYSY= -github.com/charmbracelet/bubbles v0.16.1/go.mod h1:2QCp9LFlEsBQMvIYERr7Ww2H2bA7xen1idUDIzm/+Xc= -github.com/charmbracelet/bubbletea v0.26.4 h1:2gDkkzLZaTjMl/dQBpNVtnvcCxsh/FCkimep7FC9c40= -github.com/charmbracelet/bubbletea v0.26.4/go.mod h1:P+r+RRA5qtI1DOHNFn0otoNwB4rn+zNAzSj/EXz6xU0= -github.com/charmbracelet/lipgloss v0.7.1 h1:17WMwi7N1b1rVWOjMT+rCh7sQkvDU75B2hbZpc5Kc1E= -github.com/charmbracelet/lipgloss v0.7.1/go.mod h1:yG0k3giv8Qj8edTCbbg6AlQ5e8KNWpFujkNawKNhE2c= -github.com/charmbracelet/x/ansi v0.1.2 h1:6+LR39uG8DE6zAmbu023YlqjJHkYXDF1z36ZwzO4xZY= -github.com/charmbracelet/x/ansi v0.1.2/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw= +github.com/charmbracelet/bubbles v0.18.0 h1:PYv1A036luoBGroX6VWjQIE9Syf2Wby2oOl/39KLfy0= +github.com/charmbracelet/bubbles v0.18.0/go.mod h1:08qhZhtIwzgrtBjAcJnij1t1H0ZRjwHyGsy6AL11PSw= +github.com/charmbracelet/bubbletea v0.26.6 h1:zTCWSuST+3yZYZnVSvbXwKOPRSNZceVeqpzOLN2zq1s= +github.com/charmbracelet/bubbletea v0.26.6/go.mod h1:dz8CWPlfCCGLFbBlTY4N7bjLiyOGDJEnd2Muu7pOWhk= +github.com/charmbracelet/lipgloss v0.12.1 h1:/gmzszl+pedQpjCOH+wFkZr/N90Snz40J/NR7A0zQcs= +github.com/charmbracelet/lipgloss v0.12.1/go.mod h1:V2CiwIuhx9S1S1ZlADfOj9HmxeMAORuz5izHb0zGbB8= +github.com/charmbracelet/x/ansi v0.1.4 h1:IEU3D6+dWwPSgZ6HBH+v6oUuZ/nVawMiWj5831KfiLM= +github.com/charmbracelet/x/ansi v0.1.4/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw= github.com/charmbracelet/x/input v0.1.0 h1:TEsGSfZYQyOtp+STIjyBq6tpRaorH0qpwZUj8DavAhQ= github.com/charmbracelet/x/input v0.1.0/go.mod h1:ZZwaBxPF7IG8gWWzPUVqHEtWhc1+HXJPNuerJGRGZ28= github.com/charmbracelet/x/term v0.1.1 h1:3cosVAiPOig+EV4X9U+3LDgtwwAoEzJjNdwbXDjF6yI= @@ -46,8 +46,8 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0 github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= -github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= -github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= @@ -72,8 +72,8 @@ github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sahilm/fuzzy v0.1.0 h1:FzWGaw2Opqyu+794ZQ9SYifWv2EIXpwP4q8dY1kDAwI= -github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= +github.com/sahilm/fuzzy v0.1.1-0.20230530133925-c48e322e2a8f h1:MvTmaQdww/z0Q4wrYjDSCcZ78NoftLQyHBSLW/Cx79Y= +github.com/sahilm/fuzzy v0.1.1-0.20230530133925-c48e322e2a8f/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= diff --git a/main.go b/main.go index 5985d38..e974f11 100644 --- a/main.go +++ b/main.go @@ -113,6 +113,7 @@ func main() { stream.AddCommand(cmd.StatStreamCmd) query.AddCommand(cmd.QueryCmd) + query.AddCommand(cmd.FilterList) cli.AddCommand(profile) cli.AddCommand(query) diff --git a/pkg/model/savedFilters.go b/pkg/model/savedFilters.go new file mode 100644 index 0000000..e40f65e --- /dev/null +++ b/pkg/model/savedFilters.go @@ -0,0 +1,267 @@ +// Copyright (c) 2024 Parseable, Inc +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +package model + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "pb/pkg/config" + "strings" + "time" + + "github.com/charmbracelet/bubbles/key" + "github.com/charmbracelet/bubbles/list" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" +) + +const ( + applyFilterButton = "a" + deleteFilterButton = "d" +) + +var docStyle = lipgloss.NewStyle().Margin(1, 2) + +// FilterDetails represents the structure of filter data +type FilterDetails struct { + FilterId string `json:"filter_id"` + FilterName string `json:"filter_name"` + StreamName string `json:"stream_name"` + QueryField map[string]interface{} `json:"query"` + TimeFilter map[string]interface{} `json:"time_filter"` +} + +type item struct { + id, title, stream, desc, from, to string +} + +var ( + titleStyles = lipgloss.NewStyle().PaddingLeft(0).Bold(true).Foreground(lipgloss.Color("9")) + queryStyle = lipgloss.NewStyle().PaddingLeft(0).Foreground(lipgloss.Color("7")) + itemStyle = lipgloss.NewStyle().PaddingLeft(4).Foreground(lipgloss.Color("8")) + // selectedItemStyle = lipgloss.NewStyle().PaddingLeft(4).Foreground(lipgloss.Color("170")) + selectedItemStyle = lipgloss.NewStyle().PaddingLeft(1).Foreground(lipgloss.AdaptiveColor{Light: "16", Dark: "226"}) +) + +type itemDelegate struct{} + +func (d itemDelegate) Height() int { return 4 } +func (d itemDelegate) Spacing() int { return 1 } +func (d itemDelegate) Update(_ tea.Msg, _ *list.Model) tea.Cmd { return nil } +func (d itemDelegate) Render(w io.Writer, m list.Model, index int, listItem list.Item) { + i, ok := listItem.(item) + if !ok { + return + } + var str string + + if i.from != "" || i.to != "" { + str = fmt.Sprintf("From: %s\nTo: %s", i.from, i.to) + } else { + str = "" + } + + fn := itemStyle.Render + tr := titleStyles.Render + qr := queryStyle.Render + if index == m.Index() { + tr = func(s ...string) string { + return selectedItemStyle.Render("> " + strings.Join(s, " ")) + } + } + + fmt.Fprint(w, fn(tr(i.title)+"\n"+qr(i.desc)+"\n"+str)) +} + +func (d itemDelegate) ShortHelp() []key.Binding { + return []key.Binding{ + key.NewBinding( + key.WithKeys(applyFilterButton), + key.WithHelp(applyFilterButton, "apply"), + ), + key.NewBinding( + key.WithKeys(deleteFilterButton), + key.WithHelp(deleteFilterButton, "delete"), + ), + } +} + +// FullHelp returns the extended list of keybindings. +func (d itemDelegate) FullHelp() [][]key.Binding { + return [][]key.Binding{ + { + key.NewBinding( + key.WithKeys(applyFilterButton), + key.WithHelp(applyFilterButton, "apply"), + ), + key.NewBinding( + key.WithKeys(deleteFilterButton), + key.WithHelp(deleteFilterButton, "delete"), + ), + }, + } +} + +var selectedFilterApply item +var selectedFilterDelete item + +func (i item) Title() string { return fmt.Sprintf("Filter:%s, Query:%s", i.title, i.desc) } + +func (i item) Description() string { + if i.to == "" || i.from == "" { + return "" + } + return fmt.Sprintf("From:%s To:%s", i.from, i.to) +} + +func (i item) FilterValue() string { return i.title } +func (i item) FilterId() string { return i.id } +func (i item) Stream() string { return i.desc } +func (i item) StartTime() string { return i.from } +func (i item) EndTime() string { return i.to } + +type modelFilter struct { + list list.Model +} + +func (m modelFilter) Init() tea.Cmd { + return nil +} + +func (m modelFilter) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + switch msg := msg.(type) { + case tea.KeyMsg: + if msg.String() == "ctrl+c" { + return m, tea.Quit + } + if msg.String() == "a" || msg.Type == tea.KeyEnter { + selectedFilterApply = m.list.SelectedItem().(item) + return m, tea.Quit + } + if msg.String() == "d" { + selectedFilterDelete = m.list.SelectedItem().(item) + return m, tea.Quit + + } + case tea.WindowSizeMsg: + h, v := docStyle.GetFrameSize() + m.list.SetSize(msg.Width-h, msg.Height-v) + } + + var cmd tea.Cmd + m.list, cmd = m.list.Update(msg) + return m, cmd +} + +func (m modelFilter) View() string { + return docStyle.Render(m.list.View()) +} + +// Interactive list for the user to display all the available filters (only saved SQL filters ) +func UiApp() *tea.Program { + + userConfig, err := config.ReadConfigFromFile() + if err != nil { + fmt.Println("Error reading Default Profile") + } + var userProfile config.Profile + if profile, ok := userConfig.Profiles[userConfig.DefaultProfile]; ok { + userProfile = profile + } + + client := &http.Client{ + Timeout: time.Second * 60, + } + userFilters := fetchFilters(client, &userProfile) + + m := modelFilter{list: list.New(userFilters, itemDelegate{}, 0, 0)} + m.list.Title = fmt.Sprintf("Saved Filters for User: %s", userProfile.Username) + + return tea.NewProgram(m, tea.WithAltScreen()) + +} + +// fetchFilters fetches filters from the server and sends them to the channel +func fetchFilters(client *http.Client, profile *config.Profile) []list.Item { + + endpoint := fmt.Sprintf("%s/%s/%s", profile.URL, "api/v1/filters", profile.Username) + req, err := http.NewRequest("GET", endpoint, nil) + if err != nil { + fmt.Println("Error creating request:", err) + return nil + } + + req.SetBasicAuth(profile.Username, profile.Password) + req.Header.Add("Content-Type", "application/json") + resp, err := client.Do(req) + if err != nil { + fmt.Println("Error making request:", err) + return nil + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + fmt.Println("Error reading response body:", err) + return nil + } + + var filters []FilterDetails + err = json.Unmarshal(body, &filters) + if err != nil { + fmt.Println("Error unmarshaling response:", err) + return nil + } + var userFilters []list.Item + for _, filter := range filters { + var userFilter item + queryBytes, _ := json.Marshal(filter.QueryField["filter_query"]) + + // Extract "from" and "to" from time_filter + var from, to string + if fromValue, exists := filter.TimeFilter["from"]; exists { + from = fmt.Sprintf("%v", fromValue) + } + if toValue, exists := filter.TimeFilter["to"]; exists { + to = fmt.Sprintf("%v", toValue) + } + // filtering only SQL type filters Filter_name is tile and Stream Name is desc + if string(queryBytes) != "null" { + userFilter = item{ + id: filter.FilterId, + title: filter.FilterName, + stream: filter.StreamName, + desc: string(queryBytes), + from: from, + to: to, + } + userFilters = append(userFilters, userFilter) + } + } + return userFilters + +} + +// returns the selected filter by user in the iteractive list +func FilterToApply() item { + return selectedFilterApply +} + +// returns the selected filter by user in the iteractive list +func FilterToDelete() item { + return selectedFilterDelete +}