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))