Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Implement option to enable / disable sync of declined events #62

Merged
merged 21 commits into from
Oct 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
914cb00
fmt: make the linter happy and add document starts to yamls
Aug 12, 2023
b266ae9
feat: add Accepted field to models.Event
Aug 12, 2023
ee2b4be
feat(outlook): add ResponseStatus field and map to models.Event.Accepted
Aug 12, 2023
7a547e4
feat(google): check for Event Response and map to models.Event.Accepted
Aug 12, 2023
cf05cca
feat: add sync_declined_events field to config
Aug 12, 2023
01355ec
feat: add SyncDeclinedEvents field to controller struct
Aug 12, 2023
7c23a11
test: modify tests for the newly introduced accepted field and contro…
Aug 12, 2023
1ad213e
feat: add function to remove declined events if config parameter is set
Aug 12, 2023
fc70f5f
docs: add sync_declined_fields parameter to readme.md
Aug 12, 2023
c0f7c2b
feat: bump go version to 1.21
Aug 12, 2023
0f56847
feat: bump golangci-lint version to 1.54.0
Aug 12, 2023
42a71d5
fix: remove SyncAllDayEvents field and add filters to the config file
Sep 20, 2023
ecd9f35
feat: add Filter Interface, autoconfigure, etc
Sep 20, 2023
ab8b72c
feat: use filters in the new controller
Sep 20, 2023
ca5c521
feat: add filters "AllDayEvents" and "DeclinedEvents"
Sep 20, 2023
bcf96b4
feat: add filter logic to SynchroniseTimeframe
Sep 20, 2023
9d55da9
docs: add filters to example config
Sep 20, 2023
e957eab
fix: move autoConfigure to its own file and allow any object to be pa…
Sep 23, 2023
17dfea0
refactor: filter events one by one
Sep 26, 2023
6460e55
test: modify filter test to the new format
Sep 26, 2023
45de524
docs: update readme.md with filter mechanism
MichaelEischer Sep 26, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
---
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ name: Build
on: [ push ]

env:
GO_VERSION: '1.20.4'
GO_VERSION_SHORT: '1.20'
GOLANGCI_VERSION: 'v1.52.2'
GO_VERSION: '1.21.0'
GO_VERSION_SHORT: '1.21'
GOLANGCI_VERSION: 'v1.54.0'
STATICCHECK_VERSION: '2023.1.3'

permissions:
Expand Down
5 changes: 5 additions & 0 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ on:
tags:
- 'v*'

env:
GO_VERSION: '1.21.0'

permissions:
contents: write
packages: write
Expand All @@ -19,6 +22,8 @@ jobs:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: ${{ env.GO_VERSION }}

- name: Log in to GitHub container registry
uses: docker/login-action@v2
Expand Down
1 change: 1 addition & 0 deletions .goreleaser.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
---
before:
hooks:
- go mod tidy
Expand Down
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,21 @@ transformations:
The transformers are applied in a specific order. The order is defined here:
[`internal/sync/transformer.go`](./internal/sync/transformer.go)

## Filters

In some cases events should not be synced. For example, declined events might
create too much noise in the target calendar. These can be filtered by enabling
the corresponding filter.

```yaml
# Filters remove events from being synced due to different criteria
filters:
# Events where you declined the invitation aren't synced
- name: DeclinedEvents
# Events which cover the full day aren't synced
- name: AllDayEvents
```

# Cleaning Up

You just synced a lot of events in your calendar and decide you want to use a
Expand Down
2 changes: 1 addition & 1 deletion cmd/calendarsync/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ func Run(c *cli.Context) error {
}
}

controller := sync.NewController(log.Default(), sourceAdapter, sinkAdapter, sync.TransformerFactory(cfg.Transformations)...)
controller := sync.NewController(log.Default(), sourceAdapter, sinkAdapter, sync.TransformerFactory(cfg.Transformations), sync.FilterFactory(cfg.Filters))
if cfg.UpdateConcurrency != 0 {
controller.SetConcurrency(cfg.UpdateConcurrency)
}
Expand Down
1 change: 1 addition & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
---
version: '3.4'

services:
Expand Down
7 changes: 7 additions & 0 deletions example.sync.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,13 @@ transformations:
config:
UseEmailAsDisplayName: true

# Filters remove events from being synced due to different criteria
filters:
# Events where you declined the invitation aren't synced
- name: DeclinedEvents
# Events which cover the full day aren't synced
- name: AllDayEvents

# Perform multiple calendar updates concurrently
# Defaults to 1 if not set
updateConcurrency: 3
5 changes: 5 additions & 0 deletions internal/adapter/google/event.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ func calendarEventToEvent(e *calendar.Event, adapterSourceID string) models.Even
metadata := ensureMetadata(e, adapterSourceID)

var attendees []models.Attendee
var hasEventAccepted bool = true
for _, eventAttendee := range e.Attendees {
if eventAttendee.Self && eventAttendee.ResponseStatus == "declined" {
hasEventAccepted = false
}
attendees = append(attendees, models.Attendee{
Email: eventAttendee.Email,
DisplayName: eventAttendee.DisplayName,
Expand Down Expand Up @@ -47,6 +51,7 @@ func calendarEventToEvent(e *calendar.Event, adapterSourceID string) models.Even
Attendees: attendees,
Reminders: reminders,
MeetingLink: e.HangoutLink,
Accepted: hasEventAccepted,
}
}

Expand Down
5 changes: 5 additions & 0 deletions internal/adapter/outlook_http/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,10 @@ func (o OutlookClient) outlookEventToEvent(oe Event, adapterSourceID string) (e
},
})
}
var hasEventAccepted bool = true
if oe.ResponseStatus.Response == "declined" {
hasEventAccepted = false
}

bufEvent = models.Event{
ICalUID: oe.UID,
Expand All @@ -280,6 +284,7 @@ func (o OutlookClient) outlookEventToEvent(oe Event, adapterSourceID string) (e
Attendees: attendees,
Reminders: reminders,
MeetingLink: oe.OnlineMeetingUrl,
Accepted: hasEventAccepted,
}

if oe.IsAllDay {
Expand Down
37 changes: 22 additions & 15 deletions internal/adapter/outlook_http/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,22 @@ type EventList struct {
}

type Event struct {
ID string `json:"id"`
UID string `json:"iCalUId"`
ChangeKey string `json:"changeKey"`
HtmlLink string `json:"webLink"`
Subject string `json:"subject"`
Start Time `json:"start"`
End Time `json:"end"`
Body Body `json:"body,omitempty"`
Attendees []Attendee `json:"attendees,omitempty"`
Location Location `json:"location"`
IsReminderOn bool `json:"isReminderOn"`
ReminderMinutesBeforeStart int `json:"reminderMinutesBeforeStart"`
Extensions []Extensions `json:"extensions"`
IsAllDay bool `json:"isAllDay"`
OnlineMeetingUrl string `json:"onlineMeetingUrl"`
ID string `json:"id"`
UID string `json:"iCalUId"`
ChangeKey string `json:"changeKey"`
HtmlLink string `json:"webLink"`
Subject string `json:"subject"`
Start Time `json:"start"`
End Time `json:"end"`
Body Body `json:"body,omitempty"`
Attendees []Attendee `json:"attendees,omitempty"`
Location Location `json:"location"`
IsReminderOn bool `json:"isReminderOn"`
ReminderMinutesBeforeStart int `json:"reminderMinutesBeforeStart"`
Extensions []Extensions `json:"extensions"`
IsAllDay bool `json:"isAllDay"`
OnlineMeetingUrl string `json:"onlineMeetingUrl"`
ResponseStatus ResponseStatus `json:"responseStatus,omitempty"`
}

type Extensions struct {
Expand All @@ -36,6 +37,12 @@ type Extensions struct {
models.Metadata
}

type ResponseStatus struct {
Response string `json:"response,omitempty"`
// there's an additional field called `time` which returns date and time when the response was returned
// but we don't need that
}

type Body struct {
ContentType string `json:"contentType,omitempty"`
Content string `json:"content,omitempty"`
Expand Down
17 changes: 12 additions & 5 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ import (
)

type File struct {
Path string
Auth AuthStorage
Source Source `yaml:"source"`
Sink Sink `yaml:"sink"`
// TODO: filters for source events
Path string
Auth AuthStorage
Source Source `yaml:"source"`
Sink Sink `yaml:"sink"`
Filters []Filter `yaml:"filters,omitempty"`
Transformations []Transformer `yaml:"transformations,omitempty"`
Sync Sync `yaml:"sync"`
UpdateConcurrency int `yaml:"updateConcurrency,omitempty"`
Expand Down Expand Up @@ -77,6 +77,13 @@ type Transformer struct {
Config CustomMap `yaml:"config"`
}

type Filter struct {
// Name of the filter
Name string `yaml:"name"`
// Any kind of parameter which can be passed to a filter.
Config CustomMap `yaml:"config"`
}

// Sync configuration
type Sync struct {
StartTime SyncTime `yaml:"start"`
Expand Down
16 changes: 16 additions & 0 deletions internal/filter/allDayEvents.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package filter

import (
"github.com/inovex/CalendarSync/internal/models"
)

type AllDayEvents struct {
}

func (a AllDayEvents) Name() string {
return "AllDayEvents"
}

func (a AllDayEvents) Filter(event models.Event) bool {
return !event.AllDay
}
15 changes: 15 additions & 0 deletions internal/filter/declinedEvents.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package filter

import (
"github.com/inovex/CalendarSync/internal/models"
)

type DeclinedEvents struct{}

func (d DeclinedEvents) Name() string {
return "DeclinedEvents"
}

func (d DeclinedEvents) Filter(event models.Event) bool {
return event.Accepted
}
1 change: 1 addition & 0 deletions internal/models/event.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ type Event struct {
Attendees Attendees
Reminders Reminders
MeetingLink string
Accepted bool
}

type Reminders []Reminder
Expand Down
20 changes: 18 additions & 2 deletions internal/sync/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,19 @@ type Controller struct {
source Source
// transformers are applied in order
transformers []Transformer
filters []Filter
sink Sink
concurrency int
logger *log.Logger
}

// NewController constructs a new Controller.
func NewController(logger *log.Logger, source Source, sink Sink, transformer ...Transformer) Controller {
func NewController(logger *log.Logger, source Source, sink Sink, transformer []Transformer, filters []Filter) Controller {
return Controller{
concurrency: 1,
source: source,
transformers: transformer,
filters: filters,
sink: sink,
logger: logger,
}
Expand Down Expand Up @@ -75,6 +77,20 @@ func (p Controller) SynchroniseTimeframe(ctx context.Context, start time.Time, e
return err
}

filteredEventsInSource := []models.Event{}

for _, filter := range p.filters {
p.logger.Info("loaded filter", "name", filter.Name())
}

for _, event := range eventsInSource {
if FilterEvent(event, p.filters...) {
filteredEventsInSource = append(filteredEventsInSource, event)
} else {
p.logger.Debug("filter rejects event", logFields(event)...)
}
}

// Transform source events before comparing them to the sink events
transformedEventsInSource := []models.Event{}

Expand All @@ -83,7 +99,7 @@ func (p Controller) SynchroniseTimeframe(ctx context.Context, start time.Time, e
p.logger.Info("loaded transformer", "name", trans.Name())
}

for _, event := range eventsInSource {
for _, event := range filteredEventsInSource {
transformedEventsInSource = append(transformedEventsInSource, TransformEvent(event, p.transformers...))
}

Expand Down
Loading
Loading