add counter for skip-collision counting (#3722)
Introduces a counting bus, and threads it into restore operations so that we can count the number of collision skips that occur. --- #### Does this PR need a docs update or release note? - [x] ⛔ No #### Type of change - [x] 🌻 Feature #### Issue(s) * #3562 #### Test Plan - [x] ⚡ Unit test - [x] 💚 E2E
This commit is contained in:
parent
f58cc90958
commit
2a150cc610
@ -1,18 +1,11 @@
|
|||||||
package restore
|
package restore
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/alcionai/clues"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
|
|
||||||
"github.com/alcionai/corso/src/cli/flags"
|
"github.com/alcionai/corso/src/cli/flags"
|
||||||
. "github.com/alcionai/corso/src/cli/print"
|
|
||||||
"github.com/alcionai/corso/src/cli/repo"
|
|
||||||
"github.com/alcionai/corso/src/cli/utils"
|
"github.com/alcionai/corso/src/cli/utils"
|
||||||
"github.com/alcionai/corso/src/internal/common/dttm"
|
|
||||||
"github.com/alcionai/corso/src/internal/data"
|
|
||||||
"github.com/alcionai/corso/src/pkg/path"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// called by restore.go to map subcommands to provider-specific handling.
|
// called by restore.go to map subcommands to provider-specific handling.
|
||||||
@ -94,33 +87,14 @@ func restoreExchangeCmd(cmd *cobra.Command, args []string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
r, _, _, err := utils.GetAccountAndConnect(ctx, path.ExchangeService, repo.S3Overrides(cmd))
|
|
||||||
if err != nil {
|
|
||||||
return Only(ctx, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
defer utils.CloseRepo(ctx, r)
|
|
||||||
|
|
||||||
restoreCfg := utils.MakeRestoreConfig(ctx, opts.RestoreCfg, dttm.HumanReadable)
|
|
||||||
|
|
||||||
sel := utils.IncludeExchangeRestoreDataSelectors(opts)
|
sel := utils.IncludeExchangeRestoreDataSelectors(opts)
|
||||||
utils.FilterExchangeRestoreInfoSelectors(sel, opts)
|
utils.FilterExchangeRestoreInfoSelectors(sel, opts)
|
||||||
|
|
||||||
ro, err := r.NewRestore(ctx, flags.BackupIDFV, sel.Selector, restoreCfg)
|
return runRestore(
|
||||||
if err != nil {
|
ctx,
|
||||||
return Only(ctx, clues.Wrap(err, "Failed to initialize Exchange restore"))
|
cmd,
|
||||||
}
|
opts.RestoreCfg,
|
||||||
|
sel.Selector,
|
||||||
ds, err := ro.Run(ctx)
|
flags.BackupIDFV,
|
||||||
if err != nil {
|
"Exchange")
|
||||||
if errors.Is(err, data.ErrNotFound) {
|
|
||||||
return Only(ctx, clues.New("Backup or backup details missing for id "+flags.BackupIDFV))
|
|
||||||
}
|
|
||||||
|
|
||||||
return Only(ctx, clues.Wrap(err, "Failed to run Exchange restore"))
|
|
||||||
}
|
|
||||||
|
|
||||||
ds.Items().MaybePrintEntries(ctx)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,18 +1,12 @@
|
|||||||
package restore
|
package restore
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/alcionai/clues"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
|
|
||||||
"github.com/alcionai/corso/src/cli/flags"
|
"github.com/alcionai/corso/src/cli/flags"
|
||||||
. "github.com/alcionai/corso/src/cli/print"
|
|
||||||
"github.com/alcionai/corso/src/cli/repo"
|
|
||||||
"github.com/alcionai/corso/src/cli/utils"
|
"github.com/alcionai/corso/src/cli/utils"
|
||||||
"github.com/alcionai/corso/src/internal/common/dttm"
|
"github.com/alcionai/corso/src/internal/common/dttm"
|
||||||
"github.com/alcionai/corso/src/internal/data"
|
|
||||||
"github.com/alcionai/corso/src/pkg/path"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// called by restore.go to map subcommands to provider-specific handling.
|
// called by restore.go to map subcommands to provider-specific handling.
|
||||||
@ -84,6 +78,7 @@ func restoreOneDriveCmd(cmd *cobra.Command, args []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
opts := utils.MakeOneDriveOpts(cmd)
|
opts := utils.MakeOneDriveOpts(cmd)
|
||||||
|
opts.RestoreCfg.DTTMFormat = dttm.HumanReadableDriveItem
|
||||||
|
|
||||||
if flags.RunModeFV == flags.RunModeFlagTest {
|
if flags.RunModeFV == flags.RunModeFlagTest {
|
||||||
return nil
|
return nil
|
||||||
@ -93,33 +88,14 @@ func restoreOneDriveCmd(cmd *cobra.Command, args []string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
r, _, _, err := utils.GetAccountAndConnect(ctx, path.OneDriveService, repo.S3Overrides(cmd))
|
|
||||||
if err != nil {
|
|
||||||
return Only(ctx, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
defer utils.CloseRepo(ctx, r)
|
|
||||||
|
|
||||||
sel := utils.IncludeOneDriveRestoreDataSelectors(opts)
|
sel := utils.IncludeOneDriveRestoreDataSelectors(opts)
|
||||||
utils.FilterOneDriveRestoreInfoSelectors(sel, opts)
|
utils.FilterOneDriveRestoreInfoSelectors(sel, opts)
|
||||||
|
|
||||||
restoreCfg := utils.MakeRestoreConfig(ctx, opts.RestoreCfg, dttm.HumanReadableDriveItem)
|
return runRestore(
|
||||||
|
ctx,
|
||||||
ro, err := r.NewRestore(ctx, flags.BackupIDFV, sel.Selector, restoreCfg)
|
cmd,
|
||||||
if err != nil {
|
opts.RestoreCfg,
|
||||||
return Only(ctx, clues.Wrap(err, "Failed to initialize OneDrive restore"))
|
sel.Selector,
|
||||||
}
|
flags.BackupIDFV,
|
||||||
|
"OneDrive")
|
||||||
ds, err := ro.Run(ctx)
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, data.ErrNotFound) {
|
|
||||||
return Only(ctx, clues.New("Backup or backup details missing for id "+flags.BackupIDFV))
|
|
||||||
}
|
|
||||||
|
|
||||||
return Only(ctx, clues.Wrap(err, "Failed to run OneDrive restore"))
|
|
||||||
}
|
|
||||||
|
|
||||||
ds.Items().MaybePrintEntries(ctx)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,19 @@
|
|||||||
package restore
|
package restore
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/alcionai/clues"
|
||||||
|
"github.com/pkg/errors"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"github.com/alcionai/corso/src/cli/flags"
|
||||||
|
. "github.com/alcionai/corso/src/cli/print"
|
||||||
|
"github.com/alcionai/corso/src/cli/repo"
|
||||||
|
"github.com/alcionai/corso/src/cli/utils"
|
||||||
|
"github.com/alcionai/corso/src/internal/data"
|
||||||
|
"github.com/alcionai/corso/src/pkg/count"
|
||||||
|
"github.com/alcionai/corso/src/pkg/selectors"
|
||||||
)
|
)
|
||||||
|
|
||||||
var restoreCommands = []func(cmd *cobra.Command) *cobra.Command{
|
var restoreCommands = []func(cmd *cobra.Command) *cobra.Command{
|
||||||
@ -39,3 +51,47 @@ func restoreCmd() *cobra.Command {
|
|||||||
func handleRestoreCmd(cmd *cobra.Command, args []string) error {
|
func handleRestoreCmd(cmd *cobra.Command, args []string) error {
|
||||||
return cmd.Help()
|
return cmd.Help()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// common handlers
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
func runRestore(
|
||||||
|
ctx context.Context,
|
||||||
|
cmd *cobra.Command,
|
||||||
|
urco utils.RestoreCfgOpts,
|
||||||
|
sel selectors.Selector,
|
||||||
|
backupID, serviceName string,
|
||||||
|
) error {
|
||||||
|
r, _, _, err := utils.GetAccountAndConnect(ctx, sel.PathService(), repo.S3Overrides(cmd))
|
||||||
|
if err != nil {
|
||||||
|
return Only(ctx, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer utils.CloseRepo(ctx, r)
|
||||||
|
|
||||||
|
ro, err := r.NewRestore(ctx, backupID, sel, utils.MakeRestoreConfig(ctx, urco))
|
||||||
|
if err != nil {
|
||||||
|
return Only(ctx, clues.Wrap(err, "Failed to initialize "+serviceName+" restore"))
|
||||||
|
}
|
||||||
|
|
||||||
|
ds, err := ro.Run(ctx)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, data.ErrNotFound) {
|
||||||
|
return Only(ctx, clues.New("Backup or backup details missing for id "+flags.BackupIDFV))
|
||||||
|
}
|
||||||
|
|
||||||
|
return Only(ctx, clues.Wrap(err, "Failed to run "+serviceName+" restore"))
|
||||||
|
}
|
||||||
|
|
||||||
|
Info(ctx, "Completed Restore:")
|
||||||
|
|
||||||
|
skipped := ro.Counter.Get(count.CollisionSkip)
|
||||||
|
if skipped > 0 {
|
||||||
|
Infof(ctx, "Skipped %d items due to collision", skipped)
|
||||||
|
}
|
||||||
|
|
||||||
|
ds.Items().MaybePrintEntries(ctx)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@ -1,18 +1,12 @@
|
|||||||
package restore
|
package restore
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/alcionai/clues"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
|
|
||||||
"github.com/alcionai/corso/src/cli/flags"
|
"github.com/alcionai/corso/src/cli/flags"
|
||||||
. "github.com/alcionai/corso/src/cli/print"
|
|
||||||
"github.com/alcionai/corso/src/cli/repo"
|
|
||||||
"github.com/alcionai/corso/src/cli/utils"
|
"github.com/alcionai/corso/src/cli/utils"
|
||||||
"github.com/alcionai/corso/src/internal/common/dttm"
|
"github.com/alcionai/corso/src/internal/common/dttm"
|
||||||
"github.com/alcionai/corso/src/internal/data"
|
|
||||||
"github.com/alcionai/corso/src/pkg/path"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// called by restore.go to map subcommands to provider-specific handling.
|
// called by restore.go to map subcommands to provider-specific handling.
|
||||||
@ -90,6 +84,7 @@ func restoreSharePointCmd(cmd *cobra.Command, args []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
opts := utils.MakeSharePointOpts(cmd)
|
opts := utils.MakeSharePointOpts(cmd)
|
||||||
|
opts.RestoreCfg.DTTMFormat = dttm.HumanReadableDriveItem
|
||||||
|
|
||||||
if flags.RunModeFV == flags.RunModeFlagTest {
|
if flags.RunModeFV == flags.RunModeFlagTest {
|
||||||
return nil
|
return nil
|
||||||
@ -99,33 +94,14 @@ func restoreSharePointCmd(cmd *cobra.Command, args []string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
r, _, _, err := utils.GetAccountAndConnect(ctx, path.SharePointService, repo.S3Overrides(cmd))
|
|
||||||
if err != nil {
|
|
||||||
return Only(ctx, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
defer utils.CloseRepo(ctx, r)
|
|
||||||
|
|
||||||
sel := utils.IncludeSharePointRestoreDataSelectors(ctx, opts)
|
sel := utils.IncludeSharePointRestoreDataSelectors(ctx, opts)
|
||||||
utils.FilterSharePointRestoreInfoSelectors(sel, opts)
|
utils.FilterSharePointRestoreInfoSelectors(sel, opts)
|
||||||
|
|
||||||
restoreCfg := utils.MakeRestoreConfig(ctx, opts.RestoreCfg, dttm.HumanReadableDriveItem)
|
return runRestore(
|
||||||
|
ctx,
|
||||||
ro, err := r.NewRestore(ctx, flags.BackupIDFV, sel.Selector, restoreCfg)
|
cmd,
|
||||||
if err != nil {
|
opts.RestoreCfg,
|
||||||
return Only(ctx, clues.Wrap(err, "Failed to initialize SharePoint restore"))
|
sel.Selector,
|
||||||
}
|
flags.BackupIDFV,
|
||||||
|
"SharePoint")
|
||||||
ds, err := ro.Run(ctx)
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, data.ErrNotFound) {
|
|
||||||
return Only(ctx, clues.New("Backup or backup details missing for id "+flags.BackupIDFV))
|
|
||||||
}
|
|
||||||
|
|
||||||
return Only(ctx, clues.Wrap(err, "Failed to run SharePoint restore"))
|
|
||||||
}
|
|
||||||
|
|
||||||
ds.Items().MaybePrintEntries(ctx)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,6 +15,10 @@ import (
|
|||||||
type RestoreCfgOpts struct {
|
type RestoreCfgOpts struct {
|
||||||
Collisions string
|
Collisions string
|
||||||
Destination string
|
Destination string
|
||||||
|
// DTTMFormat is the timestamp format appended
|
||||||
|
// to the default folder name. Defaults to
|
||||||
|
// dttm.HumanReadable.
|
||||||
|
DTTMFormat dttm.TimeFormat
|
||||||
|
|
||||||
Populated flags.PopulatedFlags
|
Populated flags.PopulatedFlags
|
||||||
}
|
}
|
||||||
@ -23,6 +27,7 @@ func makeRestoreCfgOpts(cmd *cobra.Command) RestoreCfgOpts {
|
|||||||
return RestoreCfgOpts{
|
return RestoreCfgOpts{
|
||||||
Collisions: flags.CollisionsFV,
|
Collisions: flags.CollisionsFV,
|
||||||
Destination: flags.DestinationFV,
|
Destination: flags.DestinationFV,
|
||||||
|
DTTMFormat: dttm.HumanReadable,
|
||||||
|
|
||||||
// populated contains the list of flags that appear in the
|
// populated contains the list of flags that appear in the
|
||||||
// command, according to pflags. Use this to differentiate
|
// command, according to pflags. Use this to differentiate
|
||||||
@ -47,9 +52,12 @@ func validateRestoreConfigFlags(fv string, opts RestoreCfgOpts) error {
|
|||||||
func MakeRestoreConfig(
|
func MakeRestoreConfig(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
opts RestoreCfgOpts,
|
opts RestoreCfgOpts,
|
||||||
locationTimeFormat dttm.TimeFormat,
|
|
||||||
) control.RestoreConfig {
|
) control.RestoreConfig {
|
||||||
restoreCfg := control.DefaultRestoreConfig(locationTimeFormat)
|
if len(opts.DTTMFormat) == 0 {
|
||||||
|
opts.DTTMFormat = dttm.HumanReadable
|
||||||
|
}
|
||||||
|
|
||||||
|
restoreCfg := control.DefaultRestoreConfig(opts.DTTMFormat)
|
||||||
|
|
||||||
if _, ok := opts.Populated[flags.CollisionsFN]; ok {
|
if _, ok := opts.Populated[flags.CollisionsFN]; ok {
|
||||||
restoreCfg.OnCollision = control.CollisionPolicy(opts.Collisions)
|
restoreCfg.OnCollision = control.CollisionPolicy(opts.Collisions)
|
||||||
|
|||||||
@ -8,7 +8,6 @@ import (
|
|||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
"github.com/alcionai/corso/src/cli/flags"
|
"github.com/alcionai/corso/src/cli/flags"
|
||||||
"github.com/alcionai/corso/src/internal/common/dttm"
|
|
||||||
"github.com/alcionai/corso/src/internal/tester"
|
"github.com/alcionai/corso/src/internal/tester"
|
||||||
"github.com/alcionai/corso/src/pkg/control"
|
"github.com/alcionai/corso/src/pkg/control"
|
||||||
)
|
)
|
||||||
@ -129,7 +128,7 @@ func (suite *RestoreCfgUnitSuite) TestMakeRestoreConfig() {
|
|||||||
opts := *rco
|
opts := *rco
|
||||||
opts.Populated = test.populated
|
opts.Populated = test.populated
|
||||||
|
|
||||||
result := MakeRestoreConfig(ctx, opts, dttm.HumanReadable)
|
result := MakeRestoreConfig(ctx, opts)
|
||||||
assert.Equal(t, test.expect.OnCollision, result.OnCollision)
|
assert.Equal(t, test.expect.OnCollision, result.OnCollision)
|
||||||
assert.Contains(t, result.Location, test.expect.Location)
|
assert.Contains(t, result.Location, test.expect.Location)
|
||||||
})
|
})
|
||||||
|
|||||||
@ -71,8 +71,8 @@ func AccountConnectAndWriteRepoConfig(
|
|||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// repo config is already set while repo connect and init. This is just to confirm correct values.
|
// repo config gets set during repo connect and init.
|
||||||
// So won't fail is the write fails
|
// This call confirms we have the correct values.
|
||||||
err = config.WriteRepoConfig(ctx, s3Config, m365Config, r.GetID())
|
err = config.WriteRepoConfig(ctx, s3Config, m365Config, r.GetID())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.CtxErr(ctx, err).Info("writing to repository configuration")
|
logger.CtxErr(ctx, err).Info("writing to repository configuration")
|
||||||
|
|||||||
@ -27,6 +27,7 @@ import (
|
|||||||
"github.com/alcionai/corso/src/pkg/backup/details"
|
"github.com/alcionai/corso/src/pkg/backup/details"
|
||||||
"github.com/alcionai/corso/src/pkg/control"
|
"github.com/alcionai/corso/src/pkg/control"
|
||||||
"github.com/alcionai/corso/src/pkg/control/testdata"
|
"github.com/alcionai/corso/src/pkg/control/testdata"
|
||||||
|
"github.com/alcionai/corso/src/pkg/count"
|
||||||
"github.com/alcionai/corso/src/pkg/credentials"
|
"github.com/alcionai/corso/src/pkg/credentials"
|
||||||
"github.com/alcionai/corso/src/pkg/fault"
|
"github.com/alcionai/corso/src/pkg/fault"
|
||||||
"github.com/alcionai/corso/src/pkg/path"
|
"github.com/alcionai/corso/src/pkg/path"
|
||||||
@ -63,6 +64,7 @@ func generateAndRestoreItems(
|
|||||||
dbf dataBuilderFunc,
|
dbf dataBuilderFunc,
|
||||||
opts control.Options,
|
opts control.Options,
|
||||||
errs *fault.Bus,
|
errs *fault.Bus,
|
||||||
|
ctr *count.Bus,
|
||||||
) (*details.Details, error) {
|
) (*details.Details, error) {
|
||||||
items := make([]item, 0, howMany)
|
items := make([]item, 0, howMany)
|
||||||
|
|
||||||
@ -102,7 +104,7 @@ func generateAndRestoreItems(
|
|||||||
|
|
||||||
print.Infof(ctx, "Generating %d %s items in %s\n", howMany, cat, Destination)
|
print.Infof(ctx, "Generating %d %s items in %s\n", howMany, cat, Destination)
|
||||||
|
|
||||||
return ctrl.ConsumeRestoreCollections(ctx, version.Backup, sel, restoreCfg, opts, dataColls, errs)
|
return ctrl.ConsumeRestoreCollections(ctx, version.Backup, sel, restoreCfg, opts, dataColls, errs, ctr)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------
|
||||||
@ -220,8 +222,9 @@ func generateAndRestoreDriveItems(
|
|||||||
cat path.CategoryType,
|
cat path.CategoryType,
|
||||||
sel selectors.Selector,
|
sel selectors.Selector,
|
||||||
tenantID, destFldr string,
|
tenantID, destFldr string,
|
||||||
count int,
|
intCount int,
|
||||||
errs *fault.Bus,
|
errs *fault.Bus,
|
||||||
|
ctr *count.Bus,
|
||||||
) (
|
) (
|
||||||
*details.Details,
|
*details.Details,
|
||||||
error,
|
error,
|
||||||
@ -266,7 +269,7 @@ func generateAndRestoreDriveItems(
|
|||||||
currentTime = fmt.Sprintf("%d-%v-%d-%d-%d-%d", year, mnth, date, hour, min, sec)
|
currentTime = fmt.Sprintf("%d-%v-%d-%d-%d-%d", year, mnth, date, hour, min, sec)
|
||||||
)
|
)
|
||||||
|
|
||||||
for i := 0; i < count; i++ {
|
for i := 0; i < intCount; i++ {
|
||||||
col := []odStub.ColInfo{
|
col := []odStub.ColInfo{
|
||||||
// basic folder and file creation
|
// basic folder and file creation
|
||||||
{
|
{
|
||||||
@ -426,5 +429,5 @@ func generateAndRestoreDriveItems(
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return ctrl.ConsumeRestoreCollections(ctx, version.Backup, sel, restoreCfg, opts, collections, errs)
|
return ctrl.ConsumeRestoreCollections(ctx, version.Backup, sel, restoreCfg, opts, collections, errs, ctr)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import (
|
|||||||
exchMock "github.com/alcionai/corso/src/internal/m365/exchange/mock"
|
exchMock "github.com/alcionai/corso/src/internal/m365/exchange/mock"
|
||||||
"github.com/alcionai/corso/src/internal/m365/resource"
|
"github.com/alcionai/corso/src/internal/m365/resource"
|
||||||
"github.com/alcionai/corso/src/pkg/control"
|
"github.com/alcionai/corso/src/pkg/control"
|
||||||
|
"github.com/alcionai/corso/src/pkg/count"
|
||||||
"github.com/alcionai/corso/src/pkg/fault"
|
"github.com/alcionai/corso/src/pkg/fault"
|
||||||
"github.com/alcionai/corso/src/pkg/logger"
|
"github.com/alcionai/corso/src/pkg/logger"
|
||||||
"github.com/alcionai/corso/src/pkg/path"
|
"github.com/alcionai/corso/src/pkg/path"
|
||||||
@ -72,7 +73,8 @@ func handleExchangeEmailFactory(cmd *cobra.Command, args []string) error {
|
|||||||
now, now, now, now)
|
now, now, now, now)
|
||||||
},
|
},
|
||||||
control.Defaults(),
|
control.Defaults(),
|
||||||
errs)
|
errs,
|
||||||
|
count.New())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Only(ctx, err)
|
return Only(ctx, err)
|
||||||
}
|
}
|
||||||
@ -120,7 +122,8 @@ func handleExchangeCalendarEventFactory(cmd *cobra.Command, args []string) error
|
|||||||
exchMock.NoExceptionOccurrences)
|
exchMock.NoExceptionOccurrences)
|
||||||
},
|
},
|
||||||
control.Defaults(),
|
control.Defaults(),
|
||||||
errs)
|
errs,
|
||||||
|
count.New())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Only(ctx, err)
|
return Only(ctx, err)
|
||||||
}
|
}
|
||||||
@ -170,7 +173,8 @@ func handleExchangeContactFactory(cmd *cobra.Command, args []string) error {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
control.Defaults(),
|
control.Defaults(),
|
||||||
errs)
|
errs,
|
||||||
|
count.New())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Only(ctx, err)
|
return Only(ctx, err)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import (
|
|||||||
. "github.com/alcionai/corso/src/cli/print"
|
. "github.com/alcionai/corso/src/cli/print"
|
||||||
"github.com/alcionai/corso/src/cli/utils"
|
"github.com/alcionai/corso/src/cli/utils"
|
||||||
"github.com/alcionai/corso/src/internal/m365/resource"
|
"github.com/alcionai/corso/src/internal/m365/resource"
|
||||||
|
"github.com/alcionai/corso/src/pkg/count"
|
||||||
"github.com/alcionai/corso/src/pkg/fault"
|
"github.com/alcionai/corso/src/pkg/fault"
|
||||||
"github.com/alcionai/corso/src/pkg/logger"
|
"github.com/alcionai/corso/src/pkg/logger"
|
||||||
"github.com/alcionai/corso/src/pkg/path"
|
"github.com/alcionai/corso/src/pkg/path"
|
||||||
@ -56,7 +57,8 @@ func handleOneDriveFileFactory(cmd *cobra.Command, args []string) error {
|
|||||||
Tenant,
|
Tenant,
|
||||||
Destination,
|
Destination,
|
||||||
Count,
|
Count,
|
||||||
errs)
|
errs,
|
||||||
|
count.New())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Only(ctx, err)
|
return Only(ctx, err)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import (
|
|||||||
. "github.com/alcionai/corso/src/cli/print"
|
. "github.com/alcionai/corso/src/cli/print"
|
||||||
"github.com/alcionai/corso/src/cli/utils"
|
"github.com/alcionai/corso/src/cli/utils"
|
||||||
"github.com/alcionai/corso/src/internal/m365/resource"
|
"github.com/alcionai/corso/src/internal/m365/resource"
|
||||||
|
"github.com/alcionai/corso/src/pkg/count"
|
||||||
"github.com/alcionai/corso/src/pkg/fault"
|
"github.com/alcionai/corso/src/pkg/fault"
|
||||||
"github.com/alcionai/corso/src/pkg/logger"
|
"github.com/alcionai/corso/src/pkg/logger"
|
||||||
"github.com/alcionai/corso/src/pkg/path"
|
"github.com/alcionai/corso/src/pkg/path"
|
||||||
@ -56,7 +57,8 @@ func handleSharePointLibraryFileFactory(cmd *cobra.Command, args []string) error
|
|||||||
Tenant,
|
Tenant,
|
||||||
Destination,
|
Destination,
|
||||||
Count,
|
Count,
|
||||||
errs)
|
errs,
|
||||||
|
count.New())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Only(ctx, err)
|
return Only(ctx, err)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,6 +22,7 @@ require (
|
|||||||
github.com/microsoftgraph/msgraph-sdk-go v1.4.0
|
github.com/microsoftgraph/msgraph-sdk-go v1.4.0
|
||||||
github.com/microsoftgraph/msgraph-sdk-go-core v1.0.0
|
github.com/microsoftgraph/msgraph-sdk-go-core v1.0.0
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
|
github.com/puzpuzpuz/xsync/v2 v2.4.1
|
||||||
github.com/rudderlabs/analytics-go v3.3.3+incompatible
|
github.com/rudderlabs/analytics-go v3.3.3+incompatible
|
||||||
github.com/spatialcurrent/go-lazy v0.0.0-20211115014721-47315cc003d1
|
github.com/spatialcurrent/go-lazy v0.0.0-20211115014721-47315cc003d1
|
||||||
github.com/spf13/cobra v1.7.0
|
github.com/spf13/cobra v1.7.0
|
||||||
|
|||||||
@ -345,6 +345,8 @@ github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsT
|
|||||||
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
|
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
|
||||||
github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI=
|
github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI=
|
||||||
github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY=
|
github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY=
|
||||||
|
github.com/puzpuzpuz/xsync/v2 v2.4.1 h1:aGdE1C/HaR/QC6YAFdtZXi60Df8/qBIrs8PKrzkItcM=
|
||||||
|
github.com/puzpuzpuz/xsync/v2 v2.4.1/go.mod h1:gD2H2krq/w52MfPLE+Uy64TzJDVY7lP2znR9qmR35kU=
|
||||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
github.com/rivo/uniseg v0.4.3 h1:utMvzDsuh3suAEnhH0RdHmoPbU648o6CvXxTx4SBMOw=
|
github.com/rivo/uniseg v0.4.3 h1:utMvzDsuh3suAEnhH0RdHmoPbU648o6CvXxTx4SBMOw=
|
||||||
github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||||
|
|||||||
@ -385,7 +385,7 @@ func (w *conn) writePolicy(
|
|||||||
ctx = clues.Add(ctx, "source_info", si)
|
ctx = clues.Add(ctx, "source_info", si)
|
||||||
|
|
||||||
writeOpts := repo.WriteSessionOptions{Purpose: purpose}
|
writeOpts := repo.WriteSessionOptions{Purpose: purpose}
|
||||||
cb := func(innerCtx context.Context, rw repo.RepositoryWriter) error {
|
ctr := func(innerCtx context.Context, rw repo.RepositoryWriter) error {
|
||||||
if err := policy.SetPolicy(ctx, rw, si, p); err != nil {
|
if err := policy.SetPolicy(ctx, rw, si, p); err != nil {
|
||||||
return clues.Stack(err).WithClues(innerCtx)
|
return clues.Stack(err).WithClues(innerCtx)
|
||||||
}
|
}
|
||||||
@ -393,7 +393,7 @@ func (w *conn) writePolicy(
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := repo.WriteSession(ctx, w.Repository, writeOpts, cb); err != nil {
|
if err := repo.WriteSession(ctx, w.Repository, writeOpts, ctr); err != nil {
|
||||||
return clues.Wrap(err, "updating policy").WithClues(ctx)
|
return clues.Wrap(err, "updating policy").WithClues(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -506,11 +506,11 @@ func (ms *ModelStore) DeleteWithModelStoreID(ctx context.Context, id manifest.ID
|
|||||||
}
|
}
|
||||||
|
|
||||||
opts := repo.WriteSessionOptions{Purpose: "ModelStoreDelete"}
|
opts := repo.WriteSessionOptions{Purpose: "ModelStoreDelete"}
|
||||||
cb := func(innerCtx context.Context, w repo.RepositoryWriter) error {
|
ctr := func(innerCtx context.Context, w repo.RepositoryWriter) error {
|
||||||
return w.DeleteManifest(innerCtx, id)
|
return w.DeleteManifest(innerCtx, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := repo.WriteSession(ctx, ms.c, opts, cb); err != nil {
|
if err := repo.WriteSession(ctx, ms.c, opts, ctr); err != nil {
|
||||||
return clues.Wrap(err, "deleting model").WithClues(ctx)
|
return clues.Wrap(err, "deleting model").WithClues(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -302,7 +302,7 @@ func (cp *corsoProgress) get(k string) *itemDetails {
|
|||||||
|
|
||||||
func collectionEntries(
|
func collectionEntries(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
cb func(context.Context, fs.Entry) error,
|
ctr func(context.Context, fs.Entry) error,
|
||||||
streamedEnts data.BackupCollection,
|
streamedEnts data.BackupCollection,
|
||||||
progress *corsoProgress,
|
progress *corsoProgress,
|
||||||
) (map[string]struct{}, error) {
|
) (map[string]struct{}, error) {
|
||||||
@ -399,7 +399,7 @@ func collectionEntries(
|
|||||||
modTime,
|
modTime,
|
||||||
newBackupStreamReader(serializationVersion, e.ToReader()))
|
newBackupStreamReader(serializationVersion, e.ToReader()))
|
||||||
|
|
||||||
err = cb(ctx, entry)
|
err = ctr(ctx, entry)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Kopia's uploader swallows errors in most cases, so if we see
|
// Kopia's uploader swallows errors in most cases, so if we see
|
||||||
// something here it's probably a big issue and we should return.
|
// something here it's probably a big issue and we should return.
|
||||||
@ -411,7 +411,7 @@ func collectionEntries(
|
|||||||
|
|
||||||
func streamBaseEntries(
|
func streamBaseEntries(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
cb func(context.Context, fs.Entry) error,
|
ctr func(context.Context, fs.Entry) error,
|
||||||
curPath path.Path,
|
curPath path.Path,
|
||||||
prevPath path.Path,
|
prevPath path.Path,
|
||||||
locationPath *path.Builder,
|
locationPath *path.Builder,
|
||||||
@ -501,7 +501,7 @@ func streamBaseEntries(
|
|||||||
progress.put(encodeAsPath(itemPath.PopFront().Elements()...), d)
|
progress.put(encodeAsPath(itemPath.PopFront().Elements()...), d)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := cb(ctx, entry); err != nil {
|
if err := ctr(ctx, entry); err != nil {
|
||||||
return clues.Wrap(err, "executing callback on item").With("item_path", itemPath)
|
return clues.Wrap(err, "executing callback on item").With("item_path", itemPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -527,13 +527,13 @@ func getStreamItemFunc(
|
|||||||
globalExcludeSet prefixmatcher.StringSetReader,
|
globalExcludeSet prefixmatcher.StringSetReader,
|
||||||
progress *corsoProgress,
|
progress *corsoProgress,
|
||||||
) func(context.Context, func(context.Context, fs.Entry) error) error {
|
) func(context.Context, func(context.Context, fs.Entry) error) error {
|
||||||
return func(ctx context.Context, cb func(context.Context, fs.Entry) error) error {
|
return func(ctx context.Context, ctr func(context.Context, fs.Entry) error) error {
|
||||||
ctx, end := diagnostics.Span(ctx, "kopia:getStreamItemFunc")
|
ctx, end := diagnostics.Span(ctx, "kopia:getStreamItemFunc")
|
||||||
defer end()
|
defer end()
|
||||||
|
|
||||||
// Return static entries in this directory first.
|
// Return static entries in this directory first.
|
||||||
for _, d := range staticEnts {
|
for _, d := range staticEnts {
|
||||||
if err := cb(ctx, d); err != nil {
|
if err := ctr(ctx, d); err != nil {
|
||||||
return clues.Wrap(err, "executing callback on static directory").WithClues(ctx)
|
return clues.Wrap(err, "executing callback on static directory").WithClues(ctx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -544,14 +544,14 @@ func getStreamItemFunc(
|
|||||||
locationPath = lp.LocationPath()
|
locationPath = lp.LocationPath()
|
||||||
}
|
}
|
||||||
|
|
||||||
seen, err := collectionEntries(ctx, cb, streamedEnts, progress)
|
seen, err := collectionEntries(ctx, ctr, streamedEnts, progress)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return clues.Wrap(err, "streaming collection entries")
|
return clues.Wrap(err, "streaming collection entries")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := streamBaseEntries(
|
if err := streamBaseEntries(
|
||||||
ctx,
|
ctx,
|
||||||
cb,
|
ctr,
|
||||||
curPath,
|
curPath,
|
||||||
prevPath,
|
prevPath,
|
||||||
locationPath,
|
locationPath,
|
||||||
|
|||||||
@ -24,6 +24,7 @@ import (
|
|||||||
"github.com/alcionai/corso/src/internal/version"
|
"github.com/alcionai/corso/src/internal/version"
|
||||||
"github.com/alcionai/corso/src/pkg/control"
|
"github.com/alcionai/corso/src/pkg/control"
|
||||||
"github.com/alcionai/corso/src/pkg/control/testdata"
|
"github.com/alcionai/corso/src/pkg/control/testdata"
|
||||||
|
"github.com/alcionai/corso/src/pkg/count"
|
||||||
"github.com/alcionai/corso/src/pkg/fault"
|
"github.com/alcionai/corso/src/pkg/fault"
|
||||||
"github.com/alcionai/corso/src/pkg/path"
|
"github.com/alcionai/corso/src/pkg/path"
|
||||||
"github.com/alcionai/corso/src/pkg/selectors"
|
"github.com/alcionai/corso/src/pkg/selectors"
|
||||||
@ -314,7 +315,8 @@ func (suite *ControllerIntegrationSuite) TestRestoreFailsBadService() {
|
|||||||
ToggleFeatures: control.Toggles{},
|
ToggleFeatures: control.Toggles{},
|
||||||
},
|
},
|
||||||
nil,
|
nil,
|
||||||
fault.New(true))
|
fault.New(true),
|
||||||
|
count.New())
|
||||||
assert.Error(t, err, clues.ToCore(err))
|
assert.Error(t, err, clues.ToCore(err))
|
||||||
assert.NotNil(t, deets)
|
assert.NotNil(t, deets)
|
||||||
|
|
||||||
@ -392,7 +394,8 @@ func (suite *ControllerIntegrationSuite) TestEmptyCollections() {
|
|||||||
ToggleFeatures: control.Toggles{},
|
ToggleFeatures: control.Toggles{},
|
||||||
},
|
},
|
||||||
test.col,
|
test.col,
|
||||||
fault.New(true))
|
fault.New(true),
|
||||||
|
count.New())
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
assert.NotNil(t, deets)
|
assert.NotNil(t, deets)
|
||||||
|
|
||||||
@ -432,7 +435,8 @@ func runRestore(
|
|||||||
sci.RestoreCfg,
|
sci.RestoreCfg,
|
||||||
sci.Opts,
|
sci.Opts,
|
||||||
collections,
|
collections,
|
||||||
fault.New(true))
|
fault.New(true),
|
||||||
|
count.New())
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
assert.NotNil(t, deets)
|
assert.NotNil(t, deets)
|
||||||
|
|
||||||
@ -1042,7 +1046,8 @@ func (suite *ControllerIntegrationSuite) TestMultiFolderBackupDifferentNames() {
|
|||||||
ToggleFeatures: control.Toggles{},
|
ToggleFeatures: control.Toggles{},
|
||||||
},
|
},
|
||||||
collections,
|
collections,
|
||||||
fault.New(true))
|
fault.New(true),
|
||||||
|
count.New())
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
require.NotNil(t, deets)
|
require.NotNil(t, deets)
|
||||||
|
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import (
|
|||||||
"github.com/alcionai/corso/src/internal/m365/graph"
|
"github.com/alcionai/corso/src/internal/m365/graph"
|
||||||
"github.com/alcionai/corso/src/pkg/backup/details"
|
"github.com/alcionai/corso/src/pkg/backup/details"
|
||||||
"github.com/alcionai/corso/src/pkg/control"
|
"github.com/alcionai/corso/src/pkg/control"
|
||||||
|
"github.com/alcionai/corso/src/pkg/count"
|
||||||
"github.com/alcionai/corso/src/pkg/fault"
|
"github.com/alcionai/corso/src/pkg/fault"
|
||||||
"github.com/alcionai/corso/src/pkg/logger"
|
"github.com/alcionai/corso/src/pkg/logger"
|
||||||
"github.com/alcionai/corso/src/pkg/path"
|
"github.com/alcionai/corso/src/pkg/path"
|
||||||
@ -71,6 +72,7 @@ func (h contactRestoreHandler) restore(
|
|||||||
collisionKeyToItemID map[string]string,
|
collisionKeyToItemID map[string]string,
|
||||||
collisionPolicy control.CollisionPolicy,
|
collisionPolicy control.CollisionPolicy,
|
||||||
errs *fault.Bus,
|
errs *fault.Bus,
|
||||||
|
ctr *count.Bus,
|
||||||
) (*details.ExchangeInfo, error) {
|
) (*details.ExchangeInfo, error) {
|
||||||
return restoreContact(
|
return restoreContact(
|
||||||
ctx,
|
ctx,
|
||||||
@ -79,7 +81,8 @@ func (h contactRestoreHandler) restore(
|
|||||||
userID, destinationID,
|
userID, destinationID,
|
||||||
collisionKeyToItemID,
|
collisionKeyToItemID,
|
||||||
collisionPolicy,
|
collisionPolicy,
|
||||||
errs)
|
errs,
|
||||||
|
ctr)
|
||||||
}
|
}
|
||||||
|
|
||||||
type contactRestorer interface {
|
type contactRestorer interface {
|
||||||
@ -95,6 +98,7 @@ func restoreContact(
|
|||||||
collisionKeyToItemID map[string]string,
|
collisionKeyToItemID map[string]string,
|
||||||
collisionPolicy control.CollisionPolicy,
|
collisionPolicy control.CollisionPolicy,
|
||||||
errs *fault.Bus,
|
errs *fault.Bus,
|
||||||
|
ctr *count.Bus,
|
||||||
) (*details.ExchangeInfo, error) {
|
) (*details.ExchangeInfo, error) {
|
||||||
contact, err := api.BytesToContactable(body)
|
contact, err := api.BytesToContactable(body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -114,7 +118,9 @@ func restoreContact(
|
|||||||
log.Debug("item collision")
|
log.Debug("item collision")
|
||||||
|
|
||||||
if collisionPolicy == control.Skip {
|
if collisionPolicy == control.Skip {
|
||||||
|
ctr.Inc(count.CollisionSkip)
|
||||||
log.Debug("skipping item with collision")
|
log.Debug("skipping item with collision")
|
||||||
|
|
||||||
return nil, graph.ErrItemAlreadyExistsConflict
|
return nil, graph.ErrItemAlreadyExistsConflict
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -16,6 +16,7 @@ import (
|
|||||||
"github.com/alcionai/corso/src/internal/tester/tconfig"
|
"github.com/alcionai/corso/src/internal/tester/tconfig"
|
||||||
"github.com/alcionai/corso/src/pkg/control"
|
"github.com/alcionai/corso/src/pkg/control"
|
||||||
"github.com/alcionai/corso/src/pkg/control/testdata"
|
"github.com/alcionai/corso/src/pkg/control/testdata"
|
||||||
|
"github.com/alcionai/corso/src/pkg/count"
|
||||||
"github.com/alcionai/corso/src/pkg/fault"
|
"github.com/alcionai/corso/src/pkg/fault"
|
||||||
"github.com/alcionai/corso/src/pkg/path"
|
"github.com/alcionai/corso/src/pkg/path"
|
||||||
"github.com/alcionai/corso/src/pkg/services/m365/api"
|
"github.com/alcionai/corso/src/pkg/services/m365/api"
|
||||||
@ -191,7 +192,8 @@ func (suite *ContactsRestoreIntgSuite) TestRestoreContact() {
|
|||||||
"destination",
|
"destination",
|
||||||
test.collisionMap,
|
test.collisionMap,
|
||||||
test.onCollision,
|
test.onCollision,
|
||||||
fault.New(true))
|
fault.New(true),
|
||||||
|
count.New())
|
||||||
|
|
||||||
test.expectErr(t, err)
|
test.expectErr(t, err)
|
||||||
test.expectMock(t, test.apiMock)
|
test.expectMock(t, test.apiMock)
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import (
|
|||||||
"github.com/alcionai/corso/src/internal/m365/graph"
|
"github.com/alcionai/corso/src/internal/m365/graph"
|
||||||
"github.com/alcionai/corso/src/pkg/backup/details"
|
"github.com/alcionai/corso/src/pkg/backup/details"
|
||||||
"github.com/alcionai/corso/src/pkg/control"
|
"github.com/alcionai/corso/src/pkg/control"
|
||||||
|
"github.com/alcionai/corso/src/pkg/count"
|
||||||
"github.com/alcionai/corso/src/pkg/fault"
|
"github.com/alcionai/corso/src/pkg/fault"
|
||||||
"github.com/alcionai/corso/src/pkg/logger"
|
"github.com/alcionai/corso/src/pkg/logger"
|
||||||
"github.com/alcionai/corso/src/pkg/path"
|
"github.com/alcionai/corso/src/pkg/path"
|
||||||
@ -72,6 +73,7 @@ func (h eventRestoreHandler) restore(
|
|||||||
collisionKeyToItemID map[string]string,
|
collisionKeyToItemID map[string]string,
|
||||||
collisionPolicy control.CollisionPolicy,
|
collisionPolicy control.CollisionPolicy,
|
||||||
errs *fault.Bus,
|
errs *fault.Bus,
|
||||||
|
ctr *count.Bus,
|
||||||
) (*details.ExchangeInfo, error) {
|
) (*details.ExchangeInfo, error) {
|
||||||
return restoreEvent(
|
return restoreEvent(
|
||||||
ctx,
|
ctx,
|
||||||
@ -80,7 +82,8 @@ func (h eventRestoreHandler) restore(
|
|||||||
userID, destinationID,
|
userID, destinationID,
|
||||||
collisionKeyToItemID,
|
collisionKeyToItemID,
|
||||||
collisionPolicy,
|
collisionPolicy,
|
||||||
errs)
|
errs,
|
||||||
|
ctr)
|
||||||
}
|
}
|
||||||
|
|
||||||
type eventRestorer interface {
|
type eventRestorer interface {
|
||||||
@ -96,6 +99,7 @@ func restoreEvent(
|
|||||||
collisionKeyToItemID map[string]string,
|
collisionKeyToItemID map[string]string,
|
||||||
collisionPolicy control.CollisionPolicy,
|
collisionPolicy control.CollisionPolicy,
|
||||||
errs *fault.Bus,
|
errs *fault.Bus,
|
||||||
|
ctr *count.Bus,
|
||||||
) (*details.ExchangeInfo, error) {
|
) (*details.ExchangeInfo, error) {
|
||||||
event, err := api.BytesToEventable(body)
|
event, err := api.BytesToEventable(body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -115,7 +119,9 @@ func restoreEvent(
|
|||||||
log.Debug("item collision")
|
log.Debug("item collision")
|
||||||
|
|
||||||
if collisionPolicy == control.Skip {
|
if collisionPolicy == control.Skip {
|
||||||
|
ctr.Inc(count.CollisionSkip)
|
||||||
log.Debug("skipping item with collision")
|
log.Debug("skipping item with collision")
|
||||||
|
|
||||||
return nil, graph.ErrItemAlreadyExistsConflict
|
return nil, graph.ErrItemAlreadyExistsConflict
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -17,6 +17,7 @@ import (
|
|||||||
"github.com/alcionai/corso/src/internal/tester/tconfig"
|
"github.com/alcionai/corso/src/internal/tester/tconfig"
|
||||||
"github.com/alcionai/corso/src/pkg/control"
|
"github.com/alcionai/corso/src/pkg/control"
|
||||||
"github.com/alcionai/corso/src/pkg/control/testdata"
|
"github.com/alcionai/corso/src/pkg/control/testdata"
|
||||||
|
"github.com/alcionai/corso/src/pkg/count"
|
||||||
"github.com/alcionai/corso/src/pkg/fault"
|
"github.com/alcionai/corso/src/pkg/fault"
|
||||||
"github.com/alcionai/corso/src/pkg/path"
|
"github.com/alcionai/corso/src/pkg/path"
|
||||||
"github.com/alcionai/corso/src/pkg/services/m365/api"
|
"github.com/alcionai/corso/src/pkg/services/m365/api"
|
||||||
@ -239,7 +240,8 @@ func (suite *EventsRestoreIntgSuite) TestRestoreEvent() {
|
|||||||
"destination",
|
"destination",
|
||||||
test.collisionMap,
|
test.collisionMap,
|
||||||
test.onCollision,
|
test.onCollision,
|
||||||
fault.New(true))
|
fault.New(true),
|
||||||
|
count.New())
|
||||||
|
|
||||||
test.expectErr(t, err)
|
test.expectErr(t, err)
|
||||||
test.expectMock(t, test.apiMock)
|
test.expectMock(t, test.apiMock)
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import (
|
|||||||
"github.com/alcionai/corso/src/internal/m365/graph"
|
"github.com/alcionai/corso/src/internal/m365/graph"
|
||||||
"github.com/alcionai/corso/src/pkg/backup/details"
|
"github.com/alcionai/corso/src/pkg/backup/details"
|
||||||
"github.com/alcionai/corso/src/pkg/control"
|
"github.com/alcionai/corso/src/pkg/control"
|
||||||
|
"github.com/alcionai/corso/src/pkg/count"
|
||||||
"github.com/alcionai/corso/src/pkg/fault"
|
"github.com/alcionai/corso/src/pkg/fault"
|
||||||
"github.com/alcionai/corso/src/pkg/path"
|
"github.com/alcionai/corso/src/pkg/path"
|
||||||
"github.com/alcionai/corso/src/pkg/services/m365/api"
|
"github.com/alcionai/corso/src/pkg/services/m365/api"
|
||||||
@ -80,6 +81,7 @@ type itemRestorer interface {
|
|||||||
collisionKeyToItemID map[string]string,
|
collisionKeyToItemID map[string]string,
|
||||||
collisionPolicy control.CollisionPolicy,
|
collisionPolicy control.CollisionPolicy,
|
||||||
errs *fault.Bus,
|
errs *fault.Bus,
|
||||||
|
ctr *count.Bus,
|
||||||
) (*details.ExchangeInfo, error)
|
) (*details.ExchangeInfo, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import (
|
|||||||
"github.com/alcionai/corso/src/internal/m365/graph"
|
"github.com/alcionai/corso/src/internal/m365/graph"
|
||||||
"github.com/alcionai/corso/src/pkg/backup/details"
|
"github.com/alcionai/corso/src/pkg/backup/details"
|
||||||
"github.com/alcionai/corso/src/pkg/control"
|
"github.com/alcionai/corso/src/pkg/control"
|
||||||
|
"github.com/alcionai/corso/src/pkg/count"
|
||||||
"github.com/alcionai/corso/src/pkg/fault"
|
"github.com/alcionai/corso/src/pkg/fault"
|
||||||
"github.com/alcionai/corso/src/pkg/logger"
|
"github.com/alcionai/corso/src/pkg/logger"
|
||||||
"github.com/alcionai/corso/src/pkg/path"
|
"github.com/alcionai/corso/src/pkg/path"
|
||||||
@ -76,6 +77,7 @@ func (h mailRestoreHandler) restore(
|
|||||||
collisionKeyToItemID map[string]string,
|
collisionKeyToItemID map[string]string,
|
||||||
collisionPolicy control.CollisionPolicy,
|
collisionPolicy control.CollisionPolicy,
|
||||||
errs *fault.Bus,
|
errs *fault.Bus,
|
||||||
|
ctr *count.Bus,
|
||||||
) (*details.ExchangeInfo, error) {
|
) (*details.ExchangeInfo, error) {
|
||||||
return restoreMail(
|
return restoreMail(
|
||||||
ctx,
|
ctx,
|
||||||
@ -84,7 +86,8 @@ func (h mailRestoreHandler) restore(
|
|||||||
userID, destinationID,
|
userID, destinationID,
|
||||||
collisionKeyToItemID,
|
collisionKeyToItemID,
|
||||||
collisionPolicy,
|
collisionPolicy,
|
||||||
errs)
|
errs,
|
||||||
|
ctr)
|
||||||
}
|
}
|
||||||
|
|
||||||
type mailRestorer interface {
|
type mailRestorer interface {
|
||||||
@ -101,6 +104,7 @@ func restoreMail(
|
|||||||
collisionKeyToItemID map[string]string,
|
collisionKeyToItemID map[string]string,
|
||||||
collisionPolicy control.CollisionPolicy,
|
collisionPolicy control.CollisionPolicy,
|
||||||
errs *fault.Bus,
|
errs *fault.Bus,
|
||||||
|
ctr *count.Bus,
|
||||||
) (*details.ExchangeInfo, error) {
|
) (*details.ExchangeInfo, error) {
|
||||||
msg, err := api.BytesToMessageable(body)
|
msg, err := api.BytesToMessageable(body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -120,7 +124,9 @@ func restoreMail(
|
|||||||
log.Debug("item collision")
|
log.Debug("item collision")
|
||||||
|
|
||||||
if collisionPolicy == control.Skip {
|
if collisionPolicy == control.Skip {
|
||||||
|
ctr.Inc(count.CollisionSkip)
|
||||||
log.Debug("skipping item with collision")
|
log.Debug("skipping item with collision")
|
||||||
|
|
||||||
return nil, graph.ErrItemAlreadyExistsConflict
|
return nil, graph.ErrItemAlreadyExistsConflict
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -17,6 +17,7 @@ import (
|
|||||||
"github.com/alcionai/corso/src/internal/tester/tconfig"
|
"github.com/alcionai/corso/src/internal/tester/tconfig"
|
||||||
"github.com/alcionai/corso/src/pkg/control"
|
"github.com/alcionai/corso/src/pkg/control"
|
||||||
"github.com/alcionai/corso/src/pkg/control/testdata"
|
"github.com/alcionai/corso/src/pkg/control/testdata"
|
||||||
|
"github.com/alcionai/corso/src/pkg/count"
|
||||||
"github.com/alcionai/corso/src/pkg/fault"
|
"github.com/alcionai/corso/src/pkg/fault"
|
||||||
"github.com/alcionai/corso/src/pkg/path"
|
"github.com/alcionai/corso/src/pkg/path"
|
||||||
"github.com/alcionai/corso/src/pkg/services/m365/api"
|
"github.com/alcionai/corso/src/pkg/services/m365/api"
|
||||||
@ -208,7 +209,8 @@ func (suite *MailRestoreIntgSuite) TestRestoreMail() {
|
|||||||
"destination",
|
"destination",
|
||||||
test.collisionMap,
|
test.collisionMap,
|
||||||
test.onCollision,
|
test.onCollision,
|
||||||
fault.New(true))
|
fault.New(true),
|
||||||
|
count.New())
|
||||||
|
|
||||||
test.expectErr(t, err)
|
test.expectErr(t, err)
|
||||||
test.expectMock(t, test.apiMock)
|
test.expectMock(t, test.apiMock)
|
||||||
|
|||||||
@ -16,6 +16,7 @@ import (
|
|||||||
"github.com/alcionai/corso/src/internal/observe"
|
"github.com/alcionai/corso/src/internal/observe"
|
||||||
"github.com/alcionai/corso/src/pkg/backup/details"
|
"github.com/alcionai/corso/src/pkg/backup/details"
|
||||||
"github.com/alcionai/corso/src/pkg/control"
|
"github.com/alcionai/corso/src/pkg/control"
|
||||||
|
"github.com/alcionai/corso/src/pkg/count"
|
||||||
"github.com/alcionai/corso/src/pkg/fault"
|
"github.com/alcionai/corso/src/pkg/fault"
|
||||||
"github.com/alcionai/corso/src/pkg/logger"
|
"github.com/alcionai/corso/src/pkg/logger"
|
||||||
"github.com/alcionai/corso/src/pkg/path"
|
"github.com/alcionai/corso/src/pkg/path"
|
||||||
@ -31,6 +32,7 @@ func ConsumeRestoreCollections(
|
|||||||
dcs []data.RestoreCollection,
|
dcs []data.RestoreCollection,
|
||||||
deets *details.Builder,
|
deets *details.Builder,
|
||||||
errs *fault.Bus,
|
errs *fault.Bus,
|
||||||
|
ctr *count.Bus,
|
||||||
) (*support.ControllerOperationStatus, error) {
|
) (*support.ControllerOperationStatus, error) {
|
||||||
if len(dcs) == 0 {
|
if len(dcs) == 0 {
|
||||||
return support.CreateStatus(ctx, support.Restore, 0, support.CollectionMetrics{}, ""), nil
|
return support.CreateStatus(ctx, support.Restore, 0, support.CollectionMetrics{}, ""), nil
|
||||||
@ -103,7 +105,8 @@ func ConsumeRestoreCollections(
|
|||||||
collisionKeyToItemID,
|
collisionKeyToItemID,
|
||||||
restoreCfg.OnCollision,
|
restoreCfg.OnCollision,
|
||||||
deets,
|
deets,
|
||||||
errs)
|
errs,
|
||||||
|
ctr.Local())
|
||||||
|
|
||||||
metrics = support.CombineMetrics(metrics, temp)
|
metrics = support.CombineMetrics(metrics, temp)
|
||||||
|
|
||||||
@ -136,6 +139,7 @@ func restoreCollection(
|
|||||||
collisionPolicy control.CollisionPolicy,
|
collisionPolicy control.CollisionPolicy,
|
||||||
deets *details.Builder,
|
deets *details.Builder,
|
||||||
errs *fault.Bus,
|
errs *fault.Bus,
|
||||||
|
ctr *count.Bus,
|
||||||
) (support.CollectionMetrics, error) {
|
) (support.CollectionMetrics, error) {
|
||||||
ctx, end := diagnostics.Span(ctx, "m365:exchange:restoreCollection", diagnostics.Label("path", dc.FullPath()))
|
ctx, end := diagnostics.Span(ctx, "m365:exchange:restoreCollection", diagnostics.Label("path", dc.FullPath()))
|
||||||
defer end()
|
defer end()
|
||||||
@ -185,7 +189,8 @@ func restoreCollection(
|
|||||||
destinationID,
|
destinationID,
|
||||||
collisionKeyToItemID,
|
collisionKeyToItemID,
|
||||||
collisionPolicy,
|
collisionPolicy,
|
||||||
errs)
|
errs,
|
||||||
|
ctr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !graph.IsErrItemAlreadyExistsConflict(err) {
|
if !graph.IsErrItemAlreadyExistsConflict(err) {
|
||||||
el.AddRecoverable(ictx, err)
|
el.AddRecoverable(ictx, err)
|
||||||
|
|||||||
@ -16,6 +16,7 @@ import (
|
|||||||
"github.com/alcionai/corso/src/pkg/account"
|
"github.com/alcionai/corso/src/pkg/account"
|
||||||
"github.com/alcionai/corso/src/pkg/control"
|
"github.com/alcionai/corso/src/pkg/control"
|
||||||
"github.com/alcionai/corso/src/pkg/control/testdata"
|
"github.com/alcionai/corso/src/pkg/control/testdata"
|
||||||
|
"github.com/alcionai/corso/src/pkg/count"
|
||||||
"github.com/alcionai/corso/src/pkg/fault"
|
"github.com/alcionai/corso/src/pkg/fault"
|
||||||
"github.com/alcionai/corso/src/pkg/path"
|
"github.com/alcionai/corso/src/pkg/path"
|
||||||
"github.com/alcionai/corso/src/pkg/services/m365/api"
|
"github.com/alcionai/corso/src/pkg/services/m365/api"
|
||||||
@ -78,7 +79,8 @@ func (suite *RestoreIntgSuite) TestRestoreContact() {
|
|||||||
userID, folderID,
|
userID, folderID,
|
||||||
nil,
|
nil,
|
||||||
control.Copy,
|
control.Copy,
|
||||||
fault.New(true))
|
fault.New(true),
|
||||||
|
count.New())
|
||||||
assert.NoError(t, err, clues.ToCore(err))
|
assert.NoError(t, err, clues.ToCore(err))
|
||||||
assert.NotNil(t, info, "contact item info")
|
assert.NotNil(t, info, "contact item info")
|
||||||
}
|
}
|
||||||
@ -152,7 +154,8 @@ func (suite *RestoreIntgSuite) TestRestoreEvent() {
|
|||||||
userID, calendarID,
|
userID, calendarID,
|
||||||
nil,
|
nil,
|
||||||
control.Copy,
|
control.Copy,
|
||||||
fault.New(true))
|
fault.New(true),
|
||||||
|
count.New())
|
||||||
assert.NoError(t, err, clues.ToCore(err))
|
assert.NoError(t, err, clues.ToCore(err))
|
||||||
assert.NotNil(t, info, "event item info")
|
assert.NotNil(t, info, "event item info")
|
||||||
})
|
})
|
||||||
@ -380,7 +383,8 @@ func (suite *RestoreIntgSuite) TestRestoreExchangeObject() {
|
|||||||
userID, destination,
|
userID, destination,
|
||||||
nil,
|
nil,
|
||||||
control.Copy,
|
control.Copy,
|
||||||
fault.New(true))
|
fault.New(true),
|
||||||
|
count.New())
|
||||||
assert.NoError(t, err, clues.ToCore(err))
|
assert.NoError(t, err, clues.ToCore(err))
|
||||||
assert.NotNil(t, info, "item info was not populated")
|
assert.NotNil(t, info, "item info was not populated")
|
||||||
})
|
})
|
||||||
@ -413,7 +417,8 @@ func (suite *RestoreIntgSuite) TestRestoreAndBackupEvent_recurringInstancesWithA
|
|||||||
userID, calendarID,
|
userID, calendarID,
|
||||||
nil,
|
nil,
|
||||||
control.Copy,
|
control.Copy,
|
||||||
fault.New(true))
|
fault.New(true),
|
||||||
|
count.New())
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
assert.NotNil(t, info, "event item info")
|
assert.NotNil(t, info, "event item info")
|
||||||
|
|
||||||
|
|||||||
@ -384,22 +384,22 @@ func (suite *GraphErrorsUnitSuite) TestIsErrUnauthorized() {
|
|||||||
|
|
||||||
func (suite *GraphErrorsUnitSuite) TestMalwareInfo() {
|
func (suite *GraphErrorsUnitSuite) TestMalwareInfo() {
|
||||||
var (
|
var (
|
||||||
i = models.NewDriveItem()
|
i = models.NewDriveItem()
|
||||||
cb = models.NewUser()
|
createdBy = models.NewUser()
|
||||||
cbID = "created-by"
|
cbID = "created-by"
|
||||||
lm = models.NewUser()
|
lm = models.NewUser()
|
||||||
lmID = "last-mod-by"
|
lmID = "last-mod-by"
|
||||||
ref = models.NewItemReference()
|
ref = models.NewItemReference()
|
||||||
refCID = "container-id"
|
refCID = "container-id"
|
||||||
refCN = "container-name"
|
refCN = "container-name"
|
||||||
refCP = "/drives/b!vF-sdsdsds-sdsdsa-sdsd/root:/Folder/container-name"
|
refCP = "/drives/b!vF-sdsdsds-sdsdsa-sdsd/root:/Folder/container-name"
|
||||||
refCPexp = "/Folder/container-name"
|
refCPexp = "/Folder/container-name"
|
||||||
mal = models.NewMalware()
|
mal = models.NewMalware()
|
||||||
malDesc = "malware-description"
|
malDesc = "malware-description"
|
||||||
)
|
)
|
||||||
|
|
||||||
cb.SetId(&cbID)
|
createdBy.SetId(&cbID)
|
||||||
i.SetCreatedByUser(cb)
|
i.SetCreatedByUser(createdBy)
|
||||||
|
|
||||||
lm.SetId(&lmID)
|
lm.SetId(&lmID)
|
||||||
i.SetLastModifiedByUser(lm)
|
i.SetLastModifiedByUser(lm)
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import (
|
|||||||
"github.com/alcionai/corso/src/internal/operations/inject"
|
"github.com/alcionai/corso/src/internal/operations/inject"
|
||||||
"github.com/alcionai/corso/src/pkg/backup/details"
|
"github.com/alcionai/corso/src/pkg/backup/details"
|
||||||
"github.com/alcionai/corso/src/pkg/control"
|
"github.com/alcionai/corso/src/pkg/control"
|
||||||
|
"github.com/alcionai/corso/src/pkg/count"
|
||||||
"github.com/alcionai/corso/src/pkg/fault"
|
"github.com/alcionai/corso/src/pkg/fault"
|
||||||
"github.com/alcionai/corso/src/pkg/path"
|
"github.com/alcionai/corso/src/pkg/path"
|
||||||
"github.com/alcionai/corso/src/pkg/selectors"
|
"github.com/alcionai/corso/src/pkg/selectors"
|
||||||
@ -64,6 +65,7 @@ func (ctrl Controller) ConsumeRestoreCollections(
|
|||||||
_ control.Options,
|
_ control.Options,
|
||||||
_ []data.RestoreCollection,
|
_ []data.RestoreCollection,
|
||||||
_ *fault.Bus,
|
_ *fault.Bus,
|
||||||
|
_ *count.Bus,
|
||||||
) (*details.Details, error) {
|
) (*details.Details, error) {
|
||||||
return ctrl.Deets, ctrl.Err
|
return ctrl.Deets, ctrl.Err
|
||||||
}
|
}
|
||||||
|
|||||||
@ -25,6 +25,7 @@ import (
|
|||||||
"github.com/alcionai/corso/src/internal/version"
|
"github.com/alcionai/corso/src/internal/version"
|
||||||
"github.com/alcionai/corso/src/pkg/backup/details"
|
"github.com/alcionai/corso/src/pkg/backup/details"
|
||||||
"github.com/alcionai/corso/src/pkg/control"
|
"github.com/alcionai/corso/src/pkg/control"
|
||||||
|
"github.com/alcionai/corso/src/pkg/count"
|
||||||
"github.com/alcionai/corso/src/pkg/fault"
|
"github.com/alcionai/corso/src/pkg/fault"
|
||||||
"github.com/alcionai/corso/src/pkg/logger"
|
"github.com/alcionai/corso/src/pkg/logger"
|
||||||
"github.com/alcionai/corso/src/pkg/path"
|
"github.com/alcionai/corso/src/pkg/path"
|
||||||
@ -75,6 +76,7 @@ func ConsumeRestoreCollections(
|
|||||||
dcs []data.RestoreCollection,
|
dcs []data.RestoreCollection,
|
||||||
deets *details.Builder,
|
deets *details.Builder,
|
||||||
errs *fault.Bus,
|
errs *fault.Bus,
|
||||||
|
ctr *count.Bus,
|
||||||
) (*support.ControllerOperationStatus, error) {
|
) (*support.ControllerOperationStatus, error) {
|
||||||
var (
|
var (
|
||||||
restoreMetrics support.CollectionMetrics
|
restoreMetrics support.CollectionMetrics
|
||||||
@ -113,7 +115,8 @@ func ConsumeRestoreCollections(
|
|||||||
caches,
|
caches,
|
||||||
deets,
|
deets,
|
||||||
opts.RestorePermissions,
|
opts.RestorePermissions,
|
||||||
errs)
|
errs,
|
||||||
|
ctr.Local())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
el.AddRecoverable(ctx, err)
|
el.AddRecoverable(ctx, err)
|
||||||
}
|
}
|
||||||
@ -150,6 +153,7 @@ func RestoreCollection(
|
|||||||
deets *details.Builder,
|
deets *details.Builder,
|
||||||
restorePerms bool, // TODD: move into restoreConfig
|
restorePerms bool, // TODD: move into restoreConfig
|
||||||
errs *fault.Bus,
|
errs *fault.Bus,
|
||||||
|
ctr *count.Bus,
|
||||||
) (support.CollectionMetrics, error) {
|
) (support.CollectionMetrics, error) {
|
||||||
var (
|
var (
|
||||||
metrics = support.CollectionMetrics{}
|
metrics = support.CollectionMetrics{}
|
||||||
@ -303,7 +307,8 @@ func RestoreCollection(
|
|||||||
caches,
|
caches,
|
||||||
restorePerms,
|
restorePerms,
|
||||||
itemData,
|
itemData,
|
||||||
itemPath)
|
itemPath,
|
||||||
|
ctr)
|
||||||
|
|
||||||
// skipped items don't get counted, but they can error
|
// skipped items don't get counted, but they can error
|
||||||
if !skipped {
|
if !skipped {
|
||||||
@ -353,6 +358,7 @@ func restoreItem(
|
|||||||
restorePerms bool,
|
restorePerms bool,
|
||||||
itemData data.Stream,
|
itemData data.Stream,
|
||||||
itemPath path.Path,
|
itemPath path.Path,
|
||||||
|
ctr *count.Bus,
|
||||||
) (details.ItemInfo, bool, error) {
|
) (details.ItemInfo, bool, error) {
|
||||||
itemUUID := itemData.UUID()
|
itemUUID := itemData.UUID()
|
||||||
ctx = clues.Add(ctx, "item_id", itemUUID)
|
ctx = clues.Add(ctx, "item_id", itemUUID)
|
||||||
@ -367,7 +373,8 @@ func restoreItem(
|
|||||||
restoreFolderID,
|
restoreFolderID,
|
||||||
copyBuffer,
|
copyBuffer,
|
||||||
caches.collisionKeyToItemID,
|
caches.collisionKeyToItemID,
|
||||||
itemData)
|
itemData,
|
||||||
|
ctr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, graph.ErrItemAlreadyExistsConflict) && restoreCfg.OnCollision == control.Skip {
|
if errors.Is(err, graph.ErrItemAlreadyExistsConflict) && restoreCfg.OnCollision == control.Skip {
|
||||||
return details.ItemInfo{}, true, nil
|
return details.ItemInfo{}, true, nil
|
||||||
@ -424,7 +431,8 @@ func restoreItem(
|
|||||||
restorePerms,
|
restorePerms,
|
||||||
caches,
|
caches,
|
||||||
itemPath,
|
itemPath,
|
||||||
itemData)
|
itemData,
|
||||||
|
ctr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, graph.ErrItemAlreadyExistsConflict) && restoreCfg.OnCollision == control.Skip {
|
if errors.Is(err, graph.ErrItemAlreadyExistsConflict) && restoreCfg.OnCollision == control.Skip {
|
||||||
return details.ItemInfo{}, true, nil
|
return details.ItemInfo{}, true, nil
|
||||||
@ -449,7 +457,8 @@ func restoreItem(
|
|||||||
restorePerms,
|
restorePerms,
|
||||||
caches,
|
caches,
|
||||||
itemPath,
|
itemPath,
|
||||||
itemData)
|
itemData,
|
||||||
|
ctr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, graph.ErrItemAlreadyExistsConflict) && restoreCfg.OnCollision == control.Skip {
|
if errors.Is(err, graph.ErrItemAlreadyExistsConflict) && restoreCfg.OnCollision == control.Skip {
|
||||||
return details.ItemInfo{}, true, nil
|
return details.ItemInfo{}, true, nil
|
||||||
@ -471,6 +480,7 @@ func restoreV0File(
|
|||||||
copyBuffer []byte,
|
copyBuffer []byte,
|
||||||
collisionKeyToItemID map[string]api.DriveCollisionItem,
|
collisionKeyToItemID map[string]api.DriveCollisionItem,
|
||||||
itemData data.Stream,
|
itemData data.Stream,
|
||||||
|
ctr *count.Bus,
|
||||||
) (details.ItemInfo, error) {
|
) (details.ItemInfo, error) {
|
||||||
_, itemInfo, err := restoreFile(
|
_, itemInfo, err := restoreFile(
|
||||||
ctx,
|
ctx,
|
||||||
@ -482,7 +492,8 @@ func restoreV0File(
|
|||||||
drivePath.DriveID,
|
drivePath.DriveID,
|
||||||
restoreFolderID,
|
restoreFolderID,
|
||||||
collisionKeyToItemID,
|
collisionKeyToItemID,
|
||||||
copyBuffer)
|
copyBuffer,
|
||||||
|
ctr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return itemInfo, clues.Wrap(err, "restoring file")
|
return itemInfo, clues.Wrap(err, "restoring file")
|
||||||
}
|
}
|
||||||
@ -502,6 +513,7 @@ func restoreV1File(
|
|||||||
caches *restoreCaches,
|
caches *restoreCaches,
|
||||||
itemPath path.Path,
|
itemPath path.Path,
|
||||||
itemData data.Stream,
|
itemData data.Stream,
|
||||||
|
ctr *count.Bus,
|
||||||
) (details.ItemInfo, error) {
|
) (details.ItemInfo, error) {
|
||||||
trimmedName := strings.TrimSuffix(itemData.UUID(), metadata.DataFileSuffix)
|
trimmedName := strings.TrimSuffix(itemData.UUID(), metadata.DataFileSuffix)
|
||||||
|
|
||||||
@ -515,7 +527,8 @@ func restoreV1File(
|
|||||||
drivePath.DriveID,
|
drivePath.DriveID,
|
||||||
restoreFolderID,
|
restoreFolderID,
|
||||||
caches.collisionKeyToItemID,
|
caches.collisionKeyToItemID,
|
||||||
copyBuffer)
|
copyBuffer,
|
||||||
|
ctr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return details.ItemInfo{}, err
|
return details.ItemInfo{}, err
|
||||||
}
|
}
|
||||||
@ -561,6 +574,7 @@ func restoreV6File(
|
|||||||
caches *restoreCaches,
|
caches *restoreCaches,
|
||||||
itemPath path.Path,
|
itemPath path.Path,
|
||||||
itemData data.Stream,
|
itemData data.Stream,
|
||||||
|
ctr *count.Bus,
|
||||||
) (details.ItemInfo, error) {
|
) (details.ItemInfo, error) {
|
||||||
trimmedName := strings.TrimSuffix(itemData.UUID(), metadata.DataFileSuffix)
|
trimmedName := strings.TrimSuffix(itemData.UUID(), metadata.DataFileSuffix)
|
||||||
|
|
||||||
@ -598,7 +612,8 @@ func restoreV6File(
|
|||||||
drivePath.DriveID,
|
drivePath.DriveID,
|
||||||
restoreFolderID,
|
restoreFolderID,
|
||||||
caches.collisionKeyToItemID,
|
caches.collisionKeyToItemID,
|
||||||
copyBuffer)
|
copyBuffer,
|
||||||
|
ctr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return details.ItemInfo{}, err
|
return details.ItemInfo{}, err
|
||||||
}
|
}
|
||||||
@ -795,6 +810,7 @@ func restoreFile(
|
|||||||
driveID, parentFolderID string,
|
driveID, parentFolderID string,
|
||||||
collisionKeyToItemID map[string]api.DriveCollisionItem,
|
collisionKeyToItemID map[string]api.DriveCollisionItem,
|
||||||
copyBuffer []byte,
|
copyBuffer []byte,
|
||||||
|
ctr *count.Bus,
|
||||||
) (string, details.ItemInfo, error) {
|
) (string, details.ItemInfo, error) {
|
||||||
ctx, end := diagnostics.Span(ctx, "gc:oneDrive:restoreItem", diagnostics.Label("item_uuid", itemData.UUID()))
|
ctx, end := diagnostics.Span(ctx, "gc:oneDrive:restoreItem", diagnostics.Label("item_uuid", itemData.UUID()))
|
||||||
defer end()
|
defer end()
|
||||||
@ -819,7 +835,9 @@ func restoreFile(
|
|||||||
log.Debug("item collision")
|
log.Debug("item collision")
|
||||||
|
|
||||||
if restoreCfg.OnCollision == control.Skip {
|
if restoreCfg.OnCollision == control.Skip {
|
||||||
|
ctr.Inc(count.CollisionSkip)
|
||||||
log.Debug("skipping item with collision")
|
log.Debug("skipping item with collision")
|
||||||
|
|
||||||
return "", details.ItemInfo{}, graph.ErrItemAlreadyExistsConflict
|
return "", details.ItemInfo{}, graph.ErrItemAlreadyExistsConflict
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -18,6 +18,7 @@ import (
|
|||||||
"github.com/alcionai/corso/src/internal/tester"
|
"github.com/alcionai/corso/src/internal/tester"
|
||||||
"github.com/alcionai/corso/src/internal/version"
|
"github.com/alcionai/corso/src/internal/version"
|
||||||
"github.com/alcionai/corso/src/pkg/control"
|
"github.com/alcionai/corso/src/pkg/control"
|
||||||
|
"github.com/alcionai/corso/src/pkg/count"
|
||||||
"github.com/alcionai/corso/src/pkg/path"
|
"github.com/alcionai/corso/src/pkg/path"
|
||||||
"github.com/alcionai/corso/src/pkg/services/m365/api"
|
"github.com/alcionai/corso/src/pkg/services/m365/api"
|
||||||
)
|
)
|
||||||
@ -492,7 +493,8 @@ func (suite *RestoreUnitSuite) TestRestoreItem_collisionHandling() {
|
|||||||
ID: uuid.NewString(),
|
ID: uuid.NewString(),
|
||||||
Reader: mock.FileRespReadCloser(mock.DriveFilePayloadData),
|
Reader: mock.FileRespReadCloser(mock.DriveFilePayloadData),
|
||||||
},
|
},
|
||||||
nil)
|
nil,
|
||||||
|
count.New())
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
test.expectSkipped(t, skip)
|
test.expectSkipped(t, skip)
|
||||||
test.expectMock(t, rh)
|
test.expectMock(t, rh)
|
||||||
|
|||||||
@ -14,6 +14,7 @@ import (
|
|||||||
"github.com/alcionai/corso/src/internal/m365/support"
|
"github.com/alcionai/corso/src/internal/m365/support"
|
||||||
"github.com/alcionai/corso/src/pkg/backup/details"
|
"github.com/alcionai/corso/src/pkg/backup/details"
|
||||||
"github.com/alcionai/corso/src/pkg/control"
|
"github.com/alcionai/corso/src/pkg/control"
|
||||||
|
"github.com/alcionai/corso/src/pkg/count"
|
||||||
"github.com/alcionai/corso/src/pkg/fault"
|
"github.com/alcionai/corso/src/pkg/fault"
|
||||||
"github.com/alcionai/corso/src/pkg/selectors"
|
"github.com/alcionai/corso/src/pkg/selectors"
|
||||||
)
|
)
|
||||||
@ -29,6 +30,7 @@ func (ctrl *Controller) ConsumeRestoreCollections(
|
|||||||
opts control.Options,
|
opts control.Options,
|
||||||
dcs []data.RestoreCollection,
|
dcs []data.RestoreCollection,
|
||||||
errs *fault.Bus,
|
errs *fault.Bus,
|
||||||
|
ctr *count.Bus,
|
||||||
) (*details.Details, error) {
|
) (*details.Details, error) {
|
||||||
ctx, end := diagnostics.Span(ctx, "m365:restore")
|
ctx, end := diagnostics.Span(ctx, "m365:restore")
|
||||||
defer end()
|
defer end()
|
||||||
@ -44,7 +46,7 @@ func (ctrl *Controller) ConsumeRestoreCollections(
|
|||||||
|
|
||||||
switch sels.Service {
|
switch sels.Service {
|
||||||
case selectors.ServiceExchange:
|
case selectors.ServiceExchange:
|
||||||
status, err = exchange.ConsumeRestoreCollections(ctx, ctrl.AC, restoreCfg, dcs, deets, errs)
|
status, err = exchange.ConsumeRestoreCollections(ctx, ctrl.AC, restoreCfg, dcs, deets, errs, ctr)
|
||||||
case selectors.ServiceOneDrive:
|
case selectors.ServiceOneDrive:
|
||||||
status, err = onedrive.ConsumeRestoreCollections(
|
status, err = onedrive.ConsumeRestoreCollections(
|
||||||
ctx,
|
ctx,
|
||||||
@ -54,7 +56,8 @@ func (ctrl *Controller) ConsumeRestoreCollections(
|
|||||||
opts,
|
opts,
|
||||||
dcs,
|
dcs,
|
||||||
deets,
|
deets,
|
||||||
errs)
|
errs,
|
||||||
|
ctr)
|
||||||
case selectors.ServiceSharePoint:
|
case selectors.ServiceSharePoint:
|
||||||
status, err = sharepoint.ConsumeRestoreCollections(
|
status, err = sharepoint.ConsumeRestoreCollections(
|
||||||
ctx,
|
ctx,
|
||||||
@ -64,7 +67,8 @@ func (ctrl *Controller) ConsumeRestoreCollections(
|
|||||||
opts,
|
opts,
|
||||||
dcs,
|
dcs,
|
||||||
deets,
|
deets,
|
||||||
errs)
|
errs,
|
||||||
|
ctr)
|
||||||
default:
|
default:
|
||||||
err = clues.Wrap(clues.New(sels.Service.String()), "service not supported")
|
err = clues.Wrap(clues.New(sels.Service.String()), "service not supported")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,6 +19,7 @@ import (
|
|||||||
"github.com/alcionai/corso/src/internal/m365/support"
|
"github.com/alcionai/corso/src/internal/m365/support"
|
||||||
"github.com/alcionai/corso/src/pkg/backup/details"
|
"github.com/alcionai/corso/src/pkg/backup/details"
|
||||||
"github.com/alcionai/corso/src/pkg/control"
|
"github.com/alcionai/corso/src/pkg/control"
|
||||||
|
"github.com/alcionai/corso/src/pkg/count"
|
||||||
"github.com/alcionai/corso/src/pkg/fault"
|
"github.com/alcionai/corso/src/pkg/fault"
|
||||||
"github.com/alcionai/corso/src/pkg/logger"
|
"github.com/alcionai/corso/src/pkg/logger"
|
||||||
"github.com/alcionai/corso/src/pkg/path"
|
"github.com/alcionai/corso/src/pkg/path"
|
||||||
@ -35,6 +36,7 @@ func ConsumeRestoreCollections(
|
|||||||
dcs []data.RestoreCollection,
|
dcs []data.RestoreCollection,
|
||||||
deets *details.Builder,
|
deets *details.Builder,
|
||||||
errs *fault.Bus,
|
errs *fault.Bus,
|
||||||
|
ctr *count.Bus,
|
||||||
) (*support.ControllerOperationStatus, error) {
|
) (*support.ControllerOperationStatus, error) {
|
||||||
var (
|
var (
|
||||||
restoreMetrics support.CollectionMetrics
|
restoreMetrics support.CollectionMetrics
|
||||||
@ -74,7 +76,8 @@ func ConsumeRestoreCollections(
|
|||||||
caches,
|
caches,
|
||||||
deets,
|
deets,
|
||||||
opts.RestorePermissions,
|
opts.RestorePermissions,
|
||||||
errs)
|
errs,
|
||||||
|
ctr)
|
||||||
|
|
||||||
case path.ListsCategory:
|
case path.ListsCategory:
|
||||||
metrics, err = RestoreListCollection(
|
metrics, err = RestoreListCollection(
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import (
|
|||||||
"github.com/alcionai/corso/src/pkg/backup/details"
|
"github.com/alcionai/corso/src/pkg/backup/details"
|
||||||
"github.com/alcionai/corso/src/pkg/control"
|
"github.com/alcionai/corso/src/pkg/control"
|
||||||
"github.com/alcionai/corso/src/pkg/control/repository"
|
"github.com/alcionai/corso/src/pkg/control/repository"
|
||||||
|
"github.com/alcionai/corso/src/pkg/count"
|
||||||
"github.com/alcionai/corso/src/pkg/fault"
|
"github.com/alcionai/corso/src/pkg/fault"
|
||||||
"github.com/alcionai/corso/src/pkg/path"
|
"github.com/alcionai/corso/src/pkg/path"
|
||||||
"github.com/alcionai/corso/src/pkg/selectors"
|
"github.com/alcionai/corso/src/pkg/selectors"
|
||||||
@ -41,6 +42,7 @@ type (
|
|||||||
opts control.Options,
|
opts control.Options,
|
||||||
dcs []data.RestoreCollection,
|
dcs []data.RestoreCollection,
|
||||||
errs *fault.Bus,
|
errs *fault.Bus,
|
||||||
|
ctr *count.Bus,
|
||||||
) (*details.Details, error)
|
) (*details.Details, error)
|
||||||
|
|
||||||
Wait() *data.CollectionStats
|
Wait() *data.CollectionStats
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import (
|
|||||||
"github.com/alcionai/corso/src/internal/events"
|
"github.com/alcionai/corso/src/internal/events"
|
||||||
"github.com/alcionai/corso/src/internal/kopia"
|
"github.com/alcionai/corso/src/internal/kopia"
|
||||||
"github.com/alcionai/corso/src/pkg/control"
|
"github.com/alcionai/corso/src/pkg/control"
|
||||||
|
"github.com/alcionai/corso/src/pkg/count"
|
||||||
"github.com/alcionai/corso/src/pkg/fault"
|
"github.com/alcionai/corso/src/pkg/fault"
|
||||||
"github.com/alcionai/corso/src/pkg/store"
|
"github.com/alcionai/corso/src/pkg/store"
|
||||||
)
|
)
|
||||||
@ -49,7 +50,8 @@ const (
|
|||||||
type operation struct {
|
type operation struct {
|
||||||
CreatedAt time.Time `json:"createdAt"`
|
CreatedAt time.Time `json:"createdAt"`
|
||||||
|
|
||||||
Errors *fault.Bus `json:"errors"`
|
Errors *fault.Bus `json:"errors"`
|
||||||
|
Counter *count.Bus
|
||||||
Options control.Options `json:"options"`
|
Options control.Options `json:"options"`
|
||||||
Status OpStatus `json:"status"`
|
Status OpStatus `json:"status"`
|
||||||
|
|
||||||
@ -67,6 +69,7 @@ func newOperation(
|
|||||||
return operation{
|
return operation{
|
||||||
CreatedAt: time.Now(),
|
CreatedAt: time.Now(),
|
||||||
Errors: fault.New(opts.FailureHandling == control.FailFast),
|
Errors: fault.New(opts.FailureHandling == control.FailFast),
|
||||||
|
Counter: count.New(),
|
||||||
Options: opts,
|
Options: opts,
|
||||||
|
|
||||||
bus: bus,
|
bus: bus,
|
||||||
|
|||||||
@ -25,6 +25,7 @@ import (
|
|||||||
"github.com/alcionai/corso/src/pkg/account"
|
"github.com/alcionai/corso/src/pkg/account"
|
||||||
"github.com/alcionai/corso/src/pkg/backup/details"
|
"github.com/alcionai/corso/src/pkg/backup/details"
|
||||||
"github.com/alcionai/corso/src/pkg/control"
|
"github.com/alcionai/corso/src/pkg/control"
|
||||||
|
"github.com/alcionai/corso/src/pkg/count"
|
||||||
"github.com/alcionai/corso/src/pkg/fault"
|
"github.com/alcionai/corso/src/pkg/fault"
|
||||||
"github.com/alcionai/corso/src/pkg/logger"
|
"github.com/alcionai/corso/src/pkg/logger"
|
||||||
"github.com/alcionai/corso/src/pkg/path"
|
"github.com/alcionai/corso/src/pkg/path"
|
||||||
@ -178,6 +179,7 @@ func (op *RestoreOperation) Run(ctx context.Context) (restoreDetails *details.De
|
|||||||
|
|
||||||
finalizeErrorHandling(ctx, op.Options, op.Errors, "running restore")
|
finalizeErrorHandling(ctx, op.Options, op.Errors, "running restore")
|
||||||
LogFaultErrors(ctx, op.Errors.Errors(), "running restore")
|
LogFaultErrors(ctx, op.Errors.Errors(), "running restore")
|
||||||
|
logger.Ctx(ctx).With("total_counts", op.Counter.Values()).Info("restore stats")
|
||||||
|
|
||||||
// -----
|
// -----
|
||||||
// Persistence
|
// Persistence
|
||||||
@ -266,7 +268,8 @@ func (op *RestoreOperation) do(
|
|||||||
op.RestoreCfg,
|
op.RestoreCfg,
|
||||||
op.Options,
|
op.Options,
|
||||||
dcs,
|
dcs,
|
||||||
op.Errors)
|
op.Errors,
|
||||||
|
op.Counter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, clues.Wrap(err, "restoring collections")
|
return nil, clues.Wrap(err, "restoring collections")
|
||||||
}
|
}
|
||||||
@ -324,6 +327,7 @@ func consumeRestoreCollections(
|
|||||||
opts control.Options,
|
opts control.Options,
|
||||||
dcs []data.RestoreCollection,
|
dcs []data.RestoreCollection,
|
||||||
errs *fault.Bus,
|
errs *fault.Bus,
|
||||||
|
ctr *count.Bus,
|
||||||
) (*details.Details, error) {
|
) (*details.Details, error) {
|
||||||
complete := observe.MessageWithCompletion(ctx, "Restoring data")
|
complete := observe.MessageWithCompletion(ctx, "Restoring data")
|
||||||
defer func() {
|
defer func() {
|
||||||
@ -338,7 +342,8 @@ func consumeRestoreCollections(
|
|||||||
restoreCfg,
|
restoreCfg,
|
||||||
opts,
|
opts,
|
||||||
dcs,
|
dcs,
|
||||||
errs)
|
errs,
|
||||||
|
ctr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, clues.Wrap(err, "restoring collections")
|
return nil, clues.Wrap(err, "restoring collections")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -33,6 +33,7 @@ import (
|
|||||||
"github.com/alcionai/corso/src/pkg/backup/details"
|
"github.com/alcionai/corso/src/pkg/backup/details"
|
||||||
"github.com/alcionai/corso/src/pkg/control"
|
"github.com/alcionai/corso/src/pkg/control"
|
||||||
"github.com/alcionai/corso/src/pkg/control/repository"
|
"github.com/alcionai/corso/src/pkg/control/repository"
|
||||||
|
"github.com/alcionai/corso/src/pkg/count"
|
||||||
"github.com/alcionai/corso/src/pkg/fault"
|
"github.com/alcionai/corso/src/pkg/fault"
|
||||||
"github.com/alcionai/corso/src/pkg/path"
|
"github.com/alcionai/corso/src/pkg/path"
|
||||||
"github.com/alcionai/corso/src/pkg/selectors"
|
"github.com/alcionai/corso/src/pkg/selectors"
|
||||||
@ -425,7 +426,8 @@ func generateContainerOfItems(
|
|||||||
restoreCfg,
|
restoreCfg,
|
||||||
opts,
|
opts,
|
||||||
dataColls,
|
dataColls,
|
||||||
fault.New(true))
|
fault.New(true),
|
||||||
|
count.New())
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
// have to wait here, both to ensure the process
|
// have to wait here, both to ensure the process
|
||||||
|
|||||||
108
src/pkg/count/count.go
Normal file
108
src/pkg/count/count.go
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
package count
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/puzpuzpuz/xsync/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Bus handles threadsafe counting of arbitrarily keyed metrics.
|
||||||
|
type Bus struct {
|
||||||
|
parent *Bus
|
||||||
|
stats *xsync.MapOf[string, *xsync.Counter]
|
||||||
|
}
|
||||||
|
|
||||||
|
func New() *Bus {
|
||||||
|
return &Bus{
|
||||||
|
stats: xsync.NewMapOf[*xsync.Counter](),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Local generates a bus with a parent link. Any value added to
|
||||||
|
// the local instance also updates the parent by the same increment.
|
||||||
|
// This allows you to maintain an isolated set of counts for a
|
||||||
|
// bounded context while automatically tallying the global total.
|
||||||
|
func (b *Bus) Local() *Bus {
|
||||||
|
bus := New()
|
||||||
|
bus.parent = b
|
||||||
|
|
||||||
|
return bus
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Bus) getCounter(k key) *xsync.Counter {
|
||||||
|
xc, _ := b.stats.LoadOrStore(string(k), xsync.NewCounter())
|
||||||
|
return xc
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inc increases the count by 1.
|
||||||
|
func (b *Bus) Inc(k key) {
|
||||||
|
if b == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
b.Add(k, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inc increases the count by n.
|
||||||
|
func (b *Bus) Add(k key, n int64) {
|
||||||
|
if b == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
b.getCounter(k).Add(n)
|
||||||
|
|
||||||
|
if b.parent != nil {
|
||||||
|
b.parent.Add(k, n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns the local count.
|
||||||
|
func (b *Bus) Get(k key) int64 {
|
||||||
|
if b == nil {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.getCounter(k).Value()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Total returns the global count.
|
||||||
|
func (b *Bus) Total(k key) int64 {
|
||||||
|
if b == nil {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.parent != nil {
|
||||||
|
return b.parent.Total(k)
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.Get(k)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Values returns a map of all local values.
|
||||||
|
// Not a snapshot, and therefore not threadsafe.
|
||||||
|
func (b *Bus) Values() map[string]int64 {
|
||||||
|
if b == nil {
|
||||||
|
return map[string]int64{}
|
||||||
|
}
|
||||||
|
|
||||||
|
m := make(map[string]int64, b.stats.Size())
|
||||||
|
|
||||||
|
b.stats.Range(func(k string, v *xsync.Counter) bool {
|
||||||
|
m[k] = v.Value()
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// TotalValues returns a map of all global values.
|
||||||
|
// Not a snapshot, and therefore not threadsafe.
|
||||||
|
func (b *Bus) TotalValues() map[string]int64 {
|
||||||
|
if b == nil {
|
||||||
|
return map[string]int64{}
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.parent != nil {
|
||||||
|
return b.parent.TotalValues()
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.Values()
|
||||||
|
}
|
||||||
180
src/pkg/count/count_test.go
Normal file
180
src/pkg/count/count_test.go
Normal file
@ -0,0 +1,180 @@
|
|||||||
|
package count
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
|
"github.com/alcionai/corso/src/internal/tester"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CountUnitSuite struct {
|
||||||
|
tester.Suite
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCountUnitSuite(t *testing.T) {
|
||||||
|
suite.Run(t, &CountUnitSuite{Suite: tester.NewUnitSuite(t)})
|
||||||
|
}
|
||||||
|
|
||||||
|
const testKey = key("just-for-testing")
|
||||||
|
|
||||||
|
func (suite *CountUnitSuite) TestBus_Inc() {
|
||||||
|
newParent := func() *Bus {
|
||||||
|
parent := New()
|
||||||
|
parent.Inc(testKey)
|
||||||
|
|
||||||
|
return parent
|
||||||
|
}
|
||||||
|
|
||||||
|
table := []struct {
|
||||||
|
name string
|
||||||
|
skip bool
|
||||||
|
bus *Bus
|
||||||
|
expect int64
|
||||||
|
expectTotal int64
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "nil",
|
||||||
|
bus: nil,
|
||||||
|
expect: -1,
|
||||||
|
expectTotal: -1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "none",
|
||||||
|
skip: true,
|
||||||
|
bus: newParent().Local(),
|
||||||
|
expect: 0,
|
||||||
|
expectTotal: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "one",
|
||||||
|
bus: newParent().Local(),
|
||||||
|
expect: 1,
|
||||||
|
expectTotal: 2,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range table {
|
||||||
|
suite.Run(test.name, func() {
|
||||||
|
t := suite.T()
|
||||||
|
|
||||||
|
if !test.skip {
|
||||||
|
test.bus.Inc(testKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
result := test.bus.Get(testKey)
|
||||||
|
assert.Equal(t, test.expect, result)
|
||||||
|
|
||||||
|
resultTotal := test.bus.Total(testKey)
|
||||||
|
assert.Equal(t, test.expectTotal, resultTotal)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *CountUnitSuite) TestBus_Add() {
|
||||||
|
newParent := func() *Bus {
|
||||||
|
parent := New()
|
||||||
|
parent.Add(testKey, 2)
|
||||||
|
|
||||||
|
return parent
|
||||||
|
}
|
||||||
|
|
||||||
|
table := []struct {
|
||||||
|
name string
|
||||||
|
skip bool
|
||||||
|
bus *Bus
|
||||||
|
expect int64
|
||||||
|
expectTotal int64
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "nil",
|
||||||
|
bus: nil,
|
||||||
|
expect: -1,
|
||||||
|
expectTotal: -1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "none",
|
||||||
|
skip: true,
|
||||||
|
bus: newParent().Local(),
|
||||||
|
expect: 0,
|
||||||
|
expectTotal: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "some",
|
||||||
|
bus: newParent().Local(),
|
||||||
|
expect: 4,
|
||||||
|
expectTotal: 6,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range table {
|
||||||
|
suite.Run(test.name, func() {
|
||||||
|
t := suite.T()
|
||||||
|
|
||||||
|
if !test.skip {
|
||||||
|
test.bus.Add(testKey, 4)
|
||||||
|
}
|
||||||
|
|
||||||
|
result := test.bus.Get(testKey)
|
||||||
|
assert.Equal(t, test.expect, result)
|
||||||
|
|
||||||
|
resultTotal := test.bus.Total(testKey)
|
||||||
|
assert.Equal(t, test.expectTotal, resultTotal)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *CountUnitSuite) TestBus_Values() {
|
||||||
|
table := []struct {
|
||||||
|
name string
|
||||||
|
bus func() *Bus
|
||||||
|
expect map[string]int64
|
||||||
|
expectTotal map[string]int64
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "nil",
|
||||||
|
bus: func() *Bus { return nil },
|
||||||
|
expect: map[string]int64{},
|
||||||
|
expectTotal: map[string]int64{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "none",
|
||||||
|
bus: func() *Bus {
|
||||||
|
parent := New()
|
||||||
|
parent.Add(testKey, 2)
|
||||||
|
|
||||||
|
l := parent.Local()
|
||||||
|
|
||||||
|
return l
|
||||||
|
},
|
||||||
|
expect: map[string]int64{},
|
||||||
|
expectTotal: map[string]int64{string(testKey): 2},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "some",
|
||||||
|
bus: func() *Bus {
|
||||||
|
parent := New()
|
||||||
|
parent.Add(testKey, 2)
|
||||||
|
|
||||||
|
l := parent.Local()
|
||||||
|
l.Inc(testKey)
|
||||||
|
|
||||||
|
return l
|
||||||
|
},
|
||||||
|
expect: map[string]int64{string(testKey): 1},
|
||||||
|
expectTotal: map[string]int64{string(testKey): 3},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range table {
|
||||||
|
suite.Run(test.name, func() {
|
||||||
|
t := suite.T()
|
||||||
|
|
||||||
|
b := test.bus()
|
||||||
|
|
||||||
|
result := b.Values()
|
||||||
|
assert.Equal(t, test.expect, result)
|
||||||
|
|
||||||
|
resultTotal := b.TotalValues()
|
||||||
|
assert.Equal(t, test.expectTotal, resultTotal)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
7
src/pkg/count/keys.go
Normal file
7
src/pkg/count/keys.go
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
package count
|
||||||
|
|
||||||
|
type key string
|
||||||
|
|
||||||
|
const (
|
||||||
|
CollisionSkip key = "collision-skip"
|
||||||
|
)
|
||||||
@ -22,8 +22,12 @@ const (
|
|||||||
ccRecipients = "ccRecipients"
|
ccRecipients = "ccRecipients"
|
||||||
createdDateTime = "createdDateTime"
|
createdDateTime = "createdDateTime"
|
||||||
displayName = "displayName"
|
displayName = "displayName"
|
||||||
|
emailAddresses = "emailAddresses"
|
||||||
givenName = "givenName"
|
givenName = "givenName"
|
||||||
|
mobilePhone = "mobilePhone"
|
||||||
parentFolderID = "parentFolderId"
|
parentFolderID = "parentFolderId"
|
||||||
|
receivedDateTime = "receivedDateTime"
|
||||||
|
sentDateTime = "sentDateTime"
|
||||||
surname = "surname"
|
surname = "surname"
|
||||||
toRecipients = "toRecipients"
|
toRecipients = "toRecipients"
|
||||||
userPrincipalName = "userPrincipalName"
|
userPrincipalName = "userPrincipalName"
|
||||||
|
|||||||
@ -317,7 +317,7 @@ func ContactInfo(contact models.Contactable) *details.ExchangeInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func contactCollisionKeyProps() []string {
|
func contactCollisionKeyProps() []string {
|
||||||
return idAnd(givenName)
|
return idAnd(givenName, surname, emailAddresses, mobilePhone)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ContactCollisionKey constructs a key from the contactable's creation time and either displayName or given+surname.
|
// ContactCollisionKey constructs a key from the contactable's creation time and either displayName or given+surname.
|
||||||
@ -327,5 +327,17 @@ func ContactCollisionKey(item models.Contactable) string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
return ptr.Val(item.GetId())
|
var (
|
||||||
|
given = ptr.Val(item.GetGivenName())
|
||||||
|
sur = ptr.Val(item.GetSurname())
|
||||||
|
emails = item.GetEmailAddresses()
|
||||||
|
email string
|
||||||
|
phone = ptr.Val(item.GetMobilePhone())
|
||||||
|
)
|
||||||
|
|
||||||
|
for _, em := range emails {
|
||||||
|
email += ptr.Val(em.GetAddress())
|
||||||
|
}
|
||||||
|
|
||||||
|
return given + sur + email + phone
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
|
"golang.org/x/exp/maps"
|
||||||
|
|
||||||
"github.com/alcionai/corso/src/internal/common/ptr"
|
"github.com/alcionai/corso/src/internal/common/ptr"
|
||||||
"github.com/alcionai/corso/src/internal/tester"
|
"github.com/alcionai/corso/src/internal/tester"
|
||||||
@ -52,16 +53,26 @@ func (suite *ContactsPagerIntgSuite) TestContacts_GetItemsInContainerByCollision
|
|||||||
require.NoError(t, err, clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
cs := conts.GetValue()
|
cs := conts.GetValue()
|
||||||
expect := make([]string, 0, len(cs))
|
expectM := map[string]struct{}{}
|
||||||
|
|
||||||
for _, c := range cs {
|
for _, c := range cs {
|
||||||
expect = append(expect, api.ContactCollisionKey(c))
|
expectM[api.ContactCollisionKey(c)] = struct{}{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
expect := maps.Keys(expectM)
|
||||||
|
|
||||||
results, err := suite.its.ac.Contacts().GetItemsInContainerByCollisionKey(ctx, suite.its.userID, "contacts")
|
results, err := suite.its.ac.Contacts().GetItemsInContainerByCollisionKey(ctx, suite.its.userID, "contacts")
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
require.Less(t, 0, len(results), "requires at least one result")
|
require.Less(t, 0, len(results), "requires at least one result")
|
||||||
|
|
||||||
|
for _, k := range expect {
|
||||||
|
t.Log("expects key", k)
|
||||||
|
}
|
||||||
|
|
||||||
|
for k := range results {
|
||||||
|
t.Log("results key", k)
|
||||||
|
}
|
||||||
|
|
||||||
for k, v := range results {
|
for k, v := range results {
|
||||||
assert.NotEmpty(t, k, "all keys should be populated")
|
assert.NotEmpty(t, k, "all keys should be populated")
|
||||||
assert.NotEmpty(t, v, "all values should be populated")
|
assert.NotEmpty(t, v, "all values should be populated")
|
||||||
|
|||||||
@ -54,6 +54,9 @@ func (suite *DrivePagerIntgSuite) TestDrives_GetItemsInContainerByCollisionKey()
|
|||||||
ctx, flush := tester.NewContext(t)
|
ctx, flush := tester.NewContext(t)
|
||||||
defer flush()
|
defer flush()
|
||||||
|
|
||||||
|
t.Log("drive", test.driveID)
|
||||||
|
t.Log("rootFolder", test.rootFolderID)
|
||||||
|
|
||||||
items, err := suite.its.ac.Stable.
|
items, err := suite.its.ac.Stable.
|
||||||
Client().
|
Client().
|
||||||
Drives().
|
Drives().
|
||||||
@ -73,10 +76,20 @@ func (suite *DrivePagerIntgSuite) TestDrives_GetItemsInContainerByCollisionKey()
|
|||||||
"need at least one item to compare in user %s drive %s folder %s",
|
"need at least one item to compare in user %s drive %s folder %s",
|
||||||
suite.its.userID, test.driveID, test.rootFolderID)
|
suite.its.userID, test.driveID, test.rootFolderID)
|
||||||
|
|
||||||
results, err := suite.its.ac.Drives().GetItemsInContainerByCollisionKey(ctx, test.driveID, test.rootFolderID)
|
results, err := suite.its.ac.
|
||||||
|
Drives().
|
||||||
|
GetItemsInContainerByCollisionKey(ctx, test.driveID, test.rootFolderID)
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
require.NotEmpty(t, results)
|
require.NotEmpty(t, results)
|
||||||
|
|
||||||
|
for _, k := range expect {
|
||||||
|
t.Log("expects key", k)
|
||||||
|
}
|
||||||
|
|
||||||
|
for k := range results {
|
||||||
|
t.Log("results key", k)
|
||||||
|
}
|
||||||
|
|
||||||
for k, v := range results {
|
for k, v := range results {
|
||||||
assert.NotEmpty(t, k, "all keys should be populated")
|
assert.NotEmpty(t, k, "all keys should be populated")
|
||||||
assert.NotEmpty(t, v, "all values should be populated")
|
assert.NotEmpty(t, v, "all values should be populated")
|
||||||
|
|||||||
@ -701,7 +701,7 @@ func EventFromMap(ev map[string]any) (models.Eventable, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func eventCollisionKeyProps() []string {
|
func eventCollisionKeyProps() []string {
|
||||||
return idAnd("subject")
|
return idAnd("subject", "type", "start", "end", "attendees", "recurrence")
|
||||||
}
|
}
|
||||||
|
|
||||||
// EventCollisionKey constructs a key from the eventable's creation time, subject, and organizer.
|
// EventCollisionKey constructs a key from the eventable's creation time, subject, and organizer.
|
||||||
@ -711,5 +711,39 @@ func EventCollisionKey(item models.Eventable) string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
return ptr.Val(item.GetSubject())
|
var (
|
||||||
|
subject = ptr.Val(item.GetSubject())
|
||||||
|
attendees = item.GetAttendees()
|
||||||
|
a string
|
||||||
|
oftype = ptr.Val(item.GetType())
|
||||||
|
t = oftype.String()
|
||||||
|
start = item.GetStart()
|
||||||
|
s string
|
||||||
|
end = item.GetEnd()
|
||||||
|
e string
|
||||||
|
recurs = item.GetRecurrence()
|
||||||
|
r string
|
||||||
|
)
|
||||||
|
|
||||||
|
for _, att := range attendees {
|
||||||
|
if att.GetEmailAddress() != nil {
|
||||||
|
a += ptr.Val(att.GetEmailAddress().GetAddress())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if start != nil {
|
||||||
|
s = ptr.Val(start.GetDateTime())
|
||||||
|
}
|
||||||
|
|
||||||
|
if end != nil {
|
||||||
|
e = ptr.Val(end.GetDateTime())
|
||||||
|
}
|
||||||
|
|
||||||
|
if recurs != nil && recurs.GetPattern() != nil {
|
||||||
|
r = ptr.Val(recurs.GetPattern().GetOdataType())
|
||||||
|
}
|
||||||
|
|
||||||
|
// this result gets hashed to ensure that an enormous list of attendees
|
||||||
|
// doesn't generate a multi-kb collision key.
|
||||||
|
return clues.ConcealWith(clues.SHA256, subject+a+t+s+e+r)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
|
"golang.org/x/exp/maps"
|
||||||
|
|
||||||
"github.com/alcionai/corso/src/internal/common/ptr"
|
"github.com/alcionai/corso/src/internal/common/ptr"
|
||||||
"github.com/alcionai/corso/src/internal/tester"
|
"github.com/alcionai/corso/src/internal/tester"
|
||||||
@ -52,13 +53,17 @@ func (suite *EventsPagerIntgSuite) TestEvents_GetItemsInContainerByCollisionKey(
|
|||||||
require.NoError(t, err, clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
es := evts.GetValue()
|
es := evts.GetValue()
|
||||||
expect := make([]string, 0, len(es))
|
expectM := map[string]struct{}{}
|
||||||
|
|
||||||
for _, e := range es {
|
for _, e := range es {
|
||||||
expect = append(expect, api.EventCollisionKey(e))
|
expectM[api.EventCollisionKey(e)] = struct{}{}
|
||||||
}
|
}
|
||||||
|
|
||||||
results, err := suite.its.ac.Events().GetItemsInContainerByCollisionKey(ctx, suite.its.userID, "calendar")
|
expect := maps.Keys(expectM)
|
||||||
|
|
||||||
|
results, err := suite.its.ac.
|
||||||
|
Events().
|
||||||
|
GetItemsInContainerByCollisionKey(ctx, suite.its.userID, "calendar")
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
require.Less(t, 0, len(results), "requires at least one result")
|
require.Less(t, 0, len(results), "requires at least one result")
|
||||||
|
|
||||||
@ -67,6 +72,14 @@ func (suite *EventsPagerIntgSuite) TestEvents_GetItemsInContainerByCollisionKey(
|
|||||||
assert.NotEmpty(t, v, "all values should be populated")
|
assert.NotEmpty(t, v, "all values should be populated")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, k := range expect {
|
||||||
|
t.Log("expects key", k)
|
||||||
|
}
|
||||||
|
|
||||||
|
for k := range results {
|
||||||
|
t.Log("results key", k)
|
||||||
|
}
|
||||||
|
|
||||||
for _, e := range expect {
|
for _, e := range expect {
|
||||||
_, ok := results[e]
|
_, ok := results[e]
|
||||||
assert.Truef(t, ok, "expected results to contain collision key: %s", e)
|
assert.Truef(t, ok, "expected results to contain collision key: %s", e)
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import (
|
|||||||
"github.com/microsoftgraph/msgraph-sdk-go/models"
|
"github.com/microsoftgraph/msgraph-sdk-go/models"
|
||||||
"github.com/microsoftgraph/msgraph-sdk-go/users"
|
"github.com/microsoftgraph/msgraph-sdk-go/users"
|
||||||
|
|
||||||
|
"github.com/alcionai/corso/src/internal/common/dttm"
|
||||||
"github.com/alcionai/corso/src/internal/common/ptr"
|
"github.com/alcionai/corso/src/internal/common/ptr"
|
||||||
"github.com/alcionai/corso/src/internal/m365/graph"
|
"github.com/alcionai/corso/src/internal/m365/graph"
|
||||||
"github.com/alcionai/corso/src/pkg/backup/details"
|
"github.com/alcionai/corso/src/pkg/backup/details"
|
||||||
@ -611,7 +612,7 @@ func UnwrapEmailAddress(contact models.Recipientable) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func mailCollisionKeyProps() []string {
|
func mailCollisionKeyProps() []string {
|
||||||
return idAnd("subject")
|
return idAnd("subject", sentDateTime, receivedDateTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MailCollisionKey constructs a key from the messageable's subject, sender, and recipients (to, cc, bcc).
|
// MailCollisionKey constructs a key from the messageable's subject, sender, and recipients (to, cc, bcc).
|
||||||
@ -621,5 +622,11 @@ func MailCollisionKey(item models.Messageable) string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
return ptr.Val(item.GetSubject())
|
var (
|
||||||
|
subject = ptr.Val(item.GetSubject())
|
||||||
|
sent = ptr.Val(item.GetSentDateTime())
|
||||||
|
received = ptr.Val(item.GetReceivedDateTime())
|
||||||
|
)
|
||||||
|
|
||||||
|
return subject + dttm.Format(sent) + dttm.Format(received)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
|
"golang.org/x/exp/maps"
|
||||||
|
|
||||||
"github.com/alcionai/corso/src/internal/common/ptr"
|
"github.com/alcionai/corso/src/internal/common/ptr"
|
||||||
"github.com/alcionai/corso/src/internal/tester"
|
"github.com/alcionai/corso/src/internal/tester"
|
||||||
@ -52,16 +53,26 @@ func (suite *MailPagerIntgSuite) TestMail_GetItemsInContainerByCollisionKey() {
|
|||||||
require.NoError(t, err, clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
|
|
||||||
ms := msgs.GetValue()
|
ms := msgs.GetValue()
|
||||||
expect := make([]string, 0, len(ms))
|
expectM := map[string]struct{}{}
|
||||||
|
|
||||||
for _, m := range ms {
|
for _, m := range ms {
|
||||||
expect = append(expect, api.MailCollisionKey(m))
|
expectM[api.MailCollisionKey(m)] = struct{}{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
expect := maps.Keys(expectM)
|
||||||
|
|
||||||
results, err := suite.its.ac.Mail().GetItemsInContainerByCollisionKey(ctx, suite.its.userID, "inbox")
|
results, err := suite.its.ac.Mail().GetItemsInContainerByCollisionKey(ctx, suite.its.userID, "inbox")
|
||||||
require.NoError(t, err, clues.ToCore(err))
|
require.NoError(t, err, clues.ToCore(err))
|
||||||
require.Less(t, 0, len(results), "requires at least one result")
|
require.Less(t, 0, len(results), "requires at least one result")
|
||||||
|
|
||||||
|
for _, k := range expect {
|
||||||
|
t.Log("expects key", k)
|
||||||
|
}
|
||||||
|
|
||||||
|
for k := range results {
|
||||||
|
t.Log("results key", k)
|
||||||
|
}
|
||||||
|
|
||||||
for k, v := range results {
|
for k, v := range results {
|
||||||
assert.NotEmpty(t, k, "all keys should be populated")
|
assert.NotEmpty(t, k, "all keys should be populated")
|
||||||
assert.NotEmpty(t, v, "all values should be populated")
|
assert.NotEmpty(t, v, "all values should be populated")
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user