From 0739ea7e09064899ed3c6a4b43e4aaa0f633f55e Mon Sep 17 00:00:00 2001 From: Keepers <104464746+ryanfkeepers@users.noreply.github.com> Date: Fri, 22 Jul 2022 13:23:16 -0600 Subject: [PATCH] add info-based selector flags to backup details (#380) * filter backup details by flags `backup details` should have its output filtered by the flags provided by the user. In addition, the selector's FilterDetails should maintain information (esp service info) about the entries, rather than slicing them down to only the path reference. --- src/cli/backup/exchange.go | 118 ++++++++++++---- src/cli/backup/exchange_test.go | 227 ++++++++++++++++++++++--------- src/internal/common/time_test.go | 3 +- src/pkg/backup/backup.go | 2 +- 4 files changed, 257 insertions(+), 93 deletions(-) diff --git a/src/cli/backup/exchange.go b/src/cli/backup/exchange.go index 0e3113734..f16934718 100644 --- a/src/cli/backup/exchange.go +++ b/src/cli/backup/exchange.go @@ -20,15 +20,19 @@ import ( // exchange bucket info from flags var ( - backupID string - exchangeAll bool - exchangeData []string - contact []string - contactFolder []string - email []string - emailFolder []string - event []string - user []string + backupID string + exchangeAll bool + exchangeData []string + contact []string + contactFolder []string + email []string + emailFolder []string + emailReceivedAfter []string + emailReceivedBefore []string + emailSender []string + emailSubject []string + event []string + user []string ) const ( @@ -66,6 +70,8 @@ func addExchangeCommands(parent *cobra.Command) *cobra.Command { c, fs = utils.AddCommand(parent, exchangeDetailsCmd) fs.StringVar(&backupID, "backup", "", "ID of the backup containing the details to be shown") cobra.CheckErr(c.MarkFlagRequired("backup")) + + // per-data-type flags fs.StringArrayVar(&contact, "contact", nil, "Select backup details by contact ID; accepts "+utils.Wildcard+" to select all contacts") fs.StringArrayVar( &contactFolder, @@ -85,6 +91,12 @@ func addExchangeCommands(parent *cobra.Command) *cobra.Command { cobra.CheckErr(fs.MarkHidden("contact")) cobra.CheckErr(fs.MarkHidden("contact-folder")) cobra.CheckErr(fs.MarkHidden("event")) + + // exchange-info flags + fs.StringArrayVar(&emailReceivedAfter, "email-received-after", nil, "Select backup details where the email was received after this datetime") + fs.StringArrayVar(&emailReceivedBefore, "email-received-before", nil, "Select backup details where the email was received before this datetime") + fs.StringArrayVar(&emailSender, "email-sender", nil, "Select backup details where the email sender matches this user id") + fs.StringArrayVar(&emailSubject, "email-subject", nil, "Select backup details where the email subject lines contain this value") } return c @@ -295,48 +307,60 @@ func detailsExchangeCmd(cmd *cobra.Command, args []string) error { return errors.Wrap(err, "Failed to get backup details in the repository") } - sel := exchangeBackupDetailSelectors(contact, contactFolder, email, emailFolder, event, user) - erSel, err := sel.ToExchangeRestore() - if err != nil { - return err + sel := selectors.NewExchangeRestore() + includeExchangeBackupDetailDataSelectors( + sel, + contact, + contactFolder, + email, + emailFolder, + event, + user) + includeExchangeBackupDetailInfoSelectors( + sel, + emailReceivedAfter, + emailReceivedBefore, + emailSender, + emailSubject) + + // if no selector flags were specified, get all data in the service. + if len(sel.Scopes()) == 0 { + sel.Include(sel.Users(selectors.Any())) } - ds := erSel.FilterDetails(d) + ds := sel.FilterDetails(d) print.Entries(ds.Entries) return nil } -func exchangeBackupDetailSelectors( +// builds the data-selector inclusions for `backup details exchange` +func includeExchangeBackupDetailDataSelectors( + sel *selectors.ExchangeRestore, contacts, contactFolders, emails, emailFolders, events, users []string, -) selectors.Selector { - sel := selectors.NewExchangeBackup() +) { lc, lcf := len(contacts), len(contactFolders) le, lef := len(emails), len(emailFolders) lev := len(events) lu := len(users) - // if only the backupID is provided, treat that as an --all query if lc+lcf+le+lef+lev+lu == 0 { - sel.Include(sel.Users(selectors.Any())) - return sel.Selector + return } // if only users are provided, we only get one selector - if lc+lcf+le+lef+lev == 0 { + if lu > 0 && lc+lcf+le+lef+lev == 0 { sel.Include(sel.Users(users)) - return sel.Selector + return } // otherwise, add selectors for each type of data includeExchangeContacts(sel, users, contactFolders, contacts) includeExchangeEmails(sel, users, emailFolders, email) includeExchangeEvents(sel, users, events) - - return sel.Selector } -func includeExchangeContacts(sel *selectors.ExchangeBackup, users, contactFolders, contacts []string) { +func includeExchangeContacts(sel *selectors.ExchangeRestore, users, contactFolders, contacts []string) { if len(contactFolders) == 0 { return } @@ -347,7 +371,7 @@ func includeExchangeContacts(sel *selectors.ExchangeBackup, users, contactFolder } } -func includeExchangeEmails(sel *selectors.ExchangeBackup, users, emailFolders, emails []string) { +func includeExchangeEmails(sel *selectors.ExchangeRestore, users, emailFolders, emails []string) { if len(emailFolders) == 0 { return } @@ -358,13 +382,53 @@ func includeExchangeEmails(sel *selectors.ExchangeBackup, users, emailFolders, e } } -func includeExchangeEvents(sel *selectors.ExchangeBackup, users, events []string) { +func includeExchangeEvents(sel *selectors.ExchangeRestore, users, events []string) { if len(events) == 0 { return } sel.Include(sel.Events(users, events)) } +// builds the info-selector inclusions for `backup details exchange` +func includeExchangeBackupDetailInfoSelectors( + sel *selectors.ExchangeRestore, + emailReceivedAfter, emailReceivedBefore, emailSender, emailSubject []string, +) { + includeExchangeInfoMailReceivedAfter(sel, emailReceivedAfter) + includeExchangeInfoMailReceivedBefore(sel, emailReceivedBefore) + includeExchangeInfoMailSender(sel, emailSender) + includeExchangeInfoMailSubject(sel, emailSubject) +} + +func includeExchangeInfoMailReceivedAfter(sel *selectors.ExchangeRestore, receivedAfter []string) { + if len(receivedAfter) == 0 { + return + } + sel.Include(sel.MailReceivedAfter(receivedAfter)) +} + +func includeExchangeInfoMailReceivedBefore(sel *selectors.ExchangeRestore, receivedBefore []string) { + if len(receivedBefore) == 0 { + return + } + sel.Include(sel.MailReceivedBefore(receivedBefore)) +} + +func includeExchangeInfoMailSender(sel *selectors.ExchangeRestore, sender []string) { + if len(sender) == 0 { + return + } + sel.Include(sel.MailSender(sender)) +} + +func includeExchangeInfoMailSubject(sel *selectors.ExchangeRestore, subject []string) { + if len(subject) == 0 { + return + } + sel.Include(sel.MailSubject(subject)) +} + +// checks all flags for correctness and interdependencies func validateExchangeBackupDetailFlags( contacts, contactFolders, emails, emailFolders, events, users []string, backupID string, diff --git a/src/cli/backup/exchange_test.go b/src/cli/backup/exchange_test.go index c951b5b45..616f7e80a 100644 --- a/src/cli/backup/exchange_test.go +++ b/src/cli/backup/exchange_test.go @@ -10,6 +10,7 @@ import ( "github.com/alcionai/corso/cli/utils" ctesting "github.com/alcionai/corso/internal/testing" + "github.com/alcionai/corso/pkg/selectors" ) type ExchangeSuite struct { @@ -54,17 +55,17 @@ func (suite *ExchangeSuite) TestAddExchangeCommands() { func (suite *ExchangeSuite) TestValidateBackupCreateFlags() { table := []struct { name string - all bool + any bool user, data []string expect assert.ErrorAssertionFunc }{ { - name: "no users, not all", + name: "no users, not any", expect: assert.Error, }, { - name: "all and data", - all: true, + name: "any and data", + any: true, data: []string{dataEmail}, expect: assert.Error, }, @@ -75,25 +76,25 @@ func (suite *ExchangeSuite) TestValidateBackupCreateFlags() { expect: assert.Error, }, { - name: "users, not all", + name: "users, not any", user: []string{"fnord"}, expect: assert.NoError, }, { - name: "no users, all", - all: true, + name: "no users, any", + any: true, expect: assert.NoError, }, { - name: "users, all", - all: true, + name: "users, any", + any: true, user: []string{"fnord"}, expect: assert.NoError, }, } for _, test := range table { suite.T().Run(test.name, func(t *testing.T) { - test.expect(t, validateExchangeBackupCreateFlags(test.all, test.user, test.data)) + test.expect(t, validateExchangeBackupCreateFlags(test.any, test.user, test.data)) }) } } @@ -101,17 +102,17 @@ func (suite *ExchangeSuite) TestValidateBackupCreateFlags() { func (suite *ExchangeSuite) TestExchangeBackupCreateSelectors() { table := []struct { name string - all bool + any bool user, data []string expectIncludeLen int }{ { - name: "all", - all: true, + name: "any", + any: true, expectIncludeLen: 1, }, { - name: "all users, no data", + name: "any users, no data", user: []string{utils.Wildcard}, expectIncludeLen: 3, }, @@ -121,7 +122,7 @@ func (suite *ExchangeSuite) TestExchangeBackupCreateSelectors() { expectIncludeLen: 3, }, { - name: "all users, contacts", + name: "any users, contacts", user: []string{utils.Wildcard}, data: []string{dataContacts}, expectIncludeLen: 1, @@ -133,7 +134,7 @@ func (suite *ExchangeSuite) TestExchangeBackupCreateSelectors() { expectIncludeLen: 1, }, { - name: "all users, email", + name: "any users, email", user: []string{utils.Wildcard}, data: []string{dataEmail}, expectIncludeLen: 1, @@ -145,7 +146,7 @@ func (suite *ExchangeSuite) TestExchangeBackupCreateSelectors() { expectIncludeLen: 1, }, { - name: "all users, events", + name: "any users, events", user: []string{utils.Wildcard}, data: []string{dataEvents}, expectIncludeLen: 1, @@ -157,7 +158,7 @@ func (suite *ExchangeSuite) TestExchangeBackupCreateSelectors() { expectIncludeLen: 1, }, { - name: "all users, contacts + email", + name: "any users, contacts + email", user: []string{utils.Wildcard}, data: []string{dataContacts, dataEmail}, expectIncludeLen: 2, @@ -169,7 +170,7 @@ func (suite *ExchangeSuite) TestExchangeBackupCreateSelectors() { expectIncludeLen: 2, }, { - name: "all users, email + events", + name: "any users, email + events", user: []string{utils.Wildcard}, data: []string{dataEmail, dataEvents}, expectIncludeLen: 2, @@ -181,7 +182,7 @@ func (suite *ExchangeSuite) TestExchangeBackupCreateSelectors() { expectIncludeLen: 2, }, { - name: "all users, events + contacts", + name: "any users, events + contacts", user: []string{utils.Wildcard}, data: []string{dataEvents, dataContacts}, expectIncludeLen: 2, @@ -207,7 +208,7 @@ func (suite *ExchangeSuite) TestExchangeBackupCreateSelectors() { } for _, test := range table { suite.T().Run(test.name, func(t *testing.T) { - sel := exchangeBackupCreateSelectors(test.all, test.user, test.data) + sel := exchangeBackupCreateSelectors(test.any, test.user, test.data) assert.Equal(t, test.expectIncludeLen, len(sel.Includes)) }) } @@ -227,7 +228,7 @@ func (suite *ExchangeSuite) TestValidateBackupDetailFlags() { expect: assert.NoError, }, { - name: "all values populated", + name: "any values populated", backupID: "bid", contacts: stub, contactFolders: stub, @@ -297,9 +298,9 @@ func (suite *ExchangeSuite) TestValidateBackupDetailFlags() { } } -func (suite *ExchangeSuite) TestExchangeBackupDetailSelectors() { +func (suite *ExchangeSuite) TestIncludeExchangeBackupDetailDataSelectors() { stub := []string{"id-stub"} - all := []string{utils.Wildcard} + any := []string{utils.Wildcard} table := []struct { name string contacts, contactFolders, emails, emailFolders, events, users []string @@ -307,11 +308,11 @@ func (suite *ExchangeSuite) TestExchangeBackupDetailSelectors() { }{ { name: "no selectors", - expectIncludeLen: 1, + expectIncludeLen: 0, }, { - name: "all users", - users: all, + name: "any users", + users: any, expectIncludeLen: 1, }, { @@ -325,20 +326,20 @@ func (suite *ExchangeSuite) TestExchangeBackupDetailSelectors() { expectIncludeLen: 1, }, { - name: "all users, all data", - contacts: all, - contactFolders: all, - emails: all, - emailFolders: all, - events: all, - users: all, + name: "any users, any data", + contacts: any, + contactFolders: any, + emails: any, + emailFolders: any, + events: any, + users: any, expectIncludeLen: 3, }, { - name: "all users, all folders", - contactFolders: all, - emailFolders: all, - users: all, + name: "any users, any folders", + contactFolders: any, + emailFolders: any, + users: any, expectIncludeLen: 2, }, { @@ -359,10 +360,10 @@ func (suite *ExchangeSuite) TestExchangeBackupDetailSelectors() { expectIncludeLen: 2, }, { - name: "all users, contacts", - contacts: all, + name: "any users, contacts", + contacts: any, contactFolders: stub, - users: all, + users: any, expectIncludeLen: 1, }, { @@ -373,10 +374,10 @@ func (suite *ExchangeSuite) TestExchangeBackupDetailSelectors() { expectIncludeLen: 1, }, { - name: "all users, emails", - emails: all, + name: "any users, emails", + emails: any, emailFolders: stub, - users: all, + users: any, expectIncludeLen: 1, }, { @@ -387,9 +388,9 @@ func (suite *ExchangeSuite) TestExchangeBackupDetailSelectors() { expectIncludeLen: 1, }, { - name: "all users, events", - events: all, - users: all, + name: "any users, events", + events: any, + users: any, expectIncludeLen: 1, }, { @@ -399,12 +400,12 @@ func (suite *ExchangeSuite) TestExchangeBackupDetailSelectors() { expectIncludeLen: 1, }, { - name: "all users, contacts + email", - contacts: all, - contactFolders: all, - emails: all, - emailFolders: all, - users: all, + name: "any users, contacts + email", + contacts: any, + contactFolders: any, + emails: any, + emailFolders: any, + users: any, expectIncludeLen: 2, }, { @@ -417,11 +418,11 @@ func (suite *ExchangeSuite) TestExchangeBackupDetailSelectors() { expectIncludeLen: 2, }, { - name: "all users, email + event", - emails: all, - emailFolders: all, - events: all, - users: all, + name: "any users, email + event", + emails: any, + emailFolders: any, + events: any, + users: any, expectIncludeLen: 2, }, { @@ -433,11 +434,11 @@ func (suite *ExchangeSuite) TestExchangeBackupDetailSelectors() { expectIncludeLen: 2, }, { - name: "all users, event + contact", - contacts: all, - contactFolders: all, - events: all, - users: all, + name: "any users, event + contact", + contacts: any, + contactFolders: any, + events: any, + users: any, expectIncludeLen: 2, }, { @@ -465,7 +466,9 @@ func (suite *ExchangeSuite) TestExchangeBackupDetailSelectors() { } for _, test := range table { suite.T().Run(test.name, func(t *testing.T) { - sel := exchangeBackupDetailSelectors( + sel := selectors.NewExchangeRestore() + includeExchangeBackupDetailDataSelectors( + sel, test.contacts, test.contactFolders, test.emails, @@ -476,3 +479,99 @@ func (suite *ExchangeSuite) TestExchangeBackupDetailSelectors() { }) } } + +func (suite *ExchangeSuite) TestIncludeExchangeBackupDetailInfoSelectors() { + stub := []string{"id-stub"} + twoStubs := []string{"smarfs", "fnords"} + any := []string{utils.Wildcard} + table := []struct { + name string + after, before, sender, subject []string + expectIncludeLen int + }{ + { + name: "no selectors", + expectIncludeLen: 0, + }, + { + name: "any receivedAfter", + after: any, + expectIncludeLen: 1, + }, + { + name: "single receivedAfter", + after: stub, + expectIncludeLen: 1, + }, + { + name: "multiple receivedAfter", + after: twoStubs, + expectIncludeLen: 1, + }, + { + name: "any receivedBefore", + before: any, + expectIncludeLen: 1, + }, + { + name: "single receivedBefore", + before: stub, + expectIncludeLen: 1, + }, + { + name: "multiple receivedBefore", + before: twoStubs, + expectIncludeLen: 1, + }, + { + name: "any sender", + sender: any, + expectIncludeLen: 1, + }, + { + name: "single sender", + sender: stub, + expectIncludeLen: 1, + }, + { + name: "multiple senders", + sender: twoStubs, + expectIncludeLen: 1, + }, + { + name: "any subject", + subject: any, + expectIncludeLen: 1, + }, + { + name: "single subject", + subject: stub, + expectIncludeLen: 1, + }, + { + name: "multiple subjects", + subject: twoStubs, + expectIncludeLen: 1, + }, + { + name: "one of each", + after: stub, + before: stub, + sender: stub, + subject: stub, + expectIncludeLen: 4, + }, + } + for _, test := range table { + suite.T().Run(test.name, func(t *testing.T) { + sel := selectors.NewExchangeRestore() + includeExchangeBackupDetailInfoSelectors( + sel, + test.after, + test.before, + test.sender, + test.subject) + assert.Equal(t, test.expectIncludeLen, len(sel.Includes)) + }) + } +} diff --git a/src/internal/common/time_test.go b/src/internal/common/time_test.go index d291d6e9c..b92ec88b7 100644 --- a/src/internal/common/time_test.go +++ b/src/internal/common/time_test.go @@ -4,10 +4,11 @@ import ( "testing" "time" - "github.com/alcionai/corso/internal/common" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" + + "github.com/alcionai/corso/internal/common" ) type CommonTimeUnitSuite struct { diff --git a/src/pkg/backup/backup.go b/src/pkg/backup/backup.go index 0bff0e077..d16e93af7 100644 --- a/src/pkg/backup/backup.go +++ b/src/pkg/backup/backup.go @@ -77,7 +77,7 @@ type DetailsEntry struct { ItemInfo } -// Paths returns the list of Paths extracted from the Entriess slice. +// Paths returns the list of Paths extracted from the Entries slice. func (dm DetailsModel) Paths() []string { ents := dm.Entries r := make([]string, len(ents))