From 499d4a33c18dc42374c371f49640d37ab1d8c288 Mon Sep 17 00:00:00 2001 From: Conor Mongey Date: Wed, 9 Jan 2019 11:42:02 +0000 Subject: [PATCH 01/10] Replace the settings page with a preferences window Autocorrect swiftlint --- SeaEye.xcodeproj/project.pbxproj | 54 +- SeaEye/AppDelegate.swift | 4 +- .../controllers/SeaEyeBuildsController.swift | 6 +- SeaEye/controllers/SeaEyeIconController.swift | 12 +- .../controllers/SeaEyePopoverController.swift | 17 +- .../SeaEyeSettingsController.swift | 54 +- .../CurrentProjectsController.swift | 194 ++++++ .../preferences/PreferencesWindow.swift | 171 ++++++ .../PreferencesWindowController.swift | 32 + .../controllers/preferences/ServerTable.swift | 81 +++ .../UnfollowedProjectsController.swift | 79 +++ SeaEye/listeners/NotificationListener.swift | 22 +- SeaEye/models/Project.swift | 3 +- SeaEye/models/SettingsV0.swift | 149 +++++ SeaEye/networking/CircleCIClient.swift | 26 + SeaEye/views/FallbackView.swift | 19 +- SeaEye/views/PreferencesWindow.xib | 569 ++++++++++++++++++ SeaEyeTests/NotificationListenerTest.swift | 3 +- SeaEyeTests/SettingsTest.swift | 14 +- ...overContollerBuildUpdateListenerTest.swift | 3 +- .../SeaEyeStatusBarListenerTest.swift | 3 +- 21 files changed, 1444 insertions(+), 71 deletions(-) create mode 100644 SeaEye/controllers/preferences/CurrentProjectsController.swift create mode 100644 SeaEye/controllers/preferences/PreferencesWindow.swift create mode 100644 SeaEye/controllers/preferences/PreferencesWindowController.swift create mode 100644 SeaEye/controllers/preferences/ServerTable.swift create mode 100644 SeaEye/controllers/preferences/UnfollowedProjectsController.swift create mode 100644 SeaEye/models/SettingsV0.swift create mode 100644 SeaEye/views/PreferencesWindow.xib diff --git a/SeaEye.xcodeproj/project.pbxproj b/SeaEye.xcodeproj/project.pbxproj index b9dae63..c6d750e 100644 --- a/SeaEye.xcodeproj/project.pbxproj +++ b/SeaEye.xcodeproj/project.pbxproj @@ -56,8 +56,8 @@ F422C54B20D48CF2002A5897 /* VersionNumberTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F422C54A20D48CF2002A5897 /* VersionNumberTests.swift */; }; F4472C0C216D72B700ECD077 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F4472C0B216D72B700ECD077 /* Main.storyboard */; }; F4B672D821B60A7C003E30A9 /* ApplicationStartupManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4B672D721B60A7C003E30A9 /* ApplicationStartupManager.swift */; }; - F4D5A43221E3F6B3006140DE /* Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4D5A43121E3F6B3006140DE /* Settings.swift */; }; - F4D5A43321E3F6B3006140DE /* Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4D5A43121E3F6B3006140DE /* Settings.swift */; }; + F4D5A43221E3F6B3006140DE /* SettingsV0.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4D5A43121E3F6B3006140DE /* SettingsV0.swift */; }; + F4D5A43321E3F6B3006140DE /* SettingsV0.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4D5A43121E3F6B3006140DE /* SettingsV0.swift */; }; F4D5A43521E3F782006140DE /* FallbackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4D5A43421E3F782006140DE /* FallbackView.swift */; }; F4D5A43621E3F782006140DE /* FallbackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4D5A43421E3F782006140DE /* FallbackView.swift */; }; F4D5A43821E3FE32006140DE /* BuildSummary.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4D5A43721E3FE32006140DE /* BuildSummary.swift */; }; @@ -96,6 +96,17 @@ F4F4807221E46CE90038DC09 /* NotificationListenerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4F4807121E46CE90038DC09 /* NotificationListenerTest.swift */; }; F4F4807421E471DE0038DC09 /* SettingsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4F4807321E471DE0038DC09 /* SettingsTest.swift */; }; F4F4807621E4DD6A0038DC09 /* PopoverContollerBuildUpdateListenerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4F4807521E4DD6A0038DC09 /* PopoverContollerBuildUpdateListenerTest.swift */; }; + F4F4807821E518CE0038DC09 /* PreferencesWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = F4F4807721E518CE0038DC09 /* PreferencesWindow.xib */; }; + F4F4807A21E518F20038DC09 /* PreferencesWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4F4807921E518F20038DC09 /* PreferencesWindow.swift */; }; + F4F4807C21E5191D0038DC09 /* CurrentProjectsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4F4807B21E5191D0038DC09 /* CurrentProjectsController.swift */; }; + F4F4807E21E519350038DC09 /* PreferencesWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4F4807D21E519350038DC09 /* PreferencesWindowController.swift */; }; + F4F4808021E5194A0038DC09 /* ServerTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4F4807F21E5194A0038DC09 /* ServerTable.swift */; }; + F4F4808221E5196B0038DC09 /* UnfollowedProjectsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4F4808121E5196B0038DC09 /* UnfollowedProjectsController.swift */; }; + F4F4808321E51B1B0038DC09 /* PreferencesWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4F4807D21E519350038DC09 /* PreferencesWindowController.swift */; }; + F4F4808421E51B2C0038DC09 /* PreferencesWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4F4807921E518F20038DC09 /* PreferencesWindow.swift */; }; + F4F4808621E51B4D0038DC09 /* CurrentProjectsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4F4807B21E5191D0038DC09 /* CurrentProjectsController.swift */; }; + F4F4808721E51B510038DC09 /* ServerTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4F4807F21E5194A0038DC09 /* ServerTable.swift */; }; + F4F4808821E51B530038DC09 /* UnfollowedProjectsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4F4808121E5196B0038DC09 /* UnfollowedProjectsController.swift */; }; F4FFD80421CA897F00E8C82E /* circleci-workflow.json in Resources */ = {isa = PBXBuildFile; fileRef = F4FFD80321CA897F00E8C82E /* circleci-workflow.json */; }; /* End PBXBuildFile section */ @@ -155,7 +166,7 @@ F422C54A20D48CF2002A5897 /* VersionNumberTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionNumberTests.swift; sourceTree = ""; }; F4472C0B216D72B700ECD077 /* Main.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = ""; }; F4B672D721B60A7C003E30A9 /* ApplicationStartupManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationStartupManager.swift; sourceTree = ""; }; - F4D5A43121E3F6B3006140DE /* Settings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Settings.swift; sourceTree = ""; }; + F4D5A43121E3F6B3006140DE /* SettingsV0.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsV0.swift; sourceTree = ""; }; F4D5A43421E3F782006140DE /* FallbackView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FallbackView.swift; sourceTree = ""; }; F4D5A43721E3FE32006140DE /* BuildSummary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BuildSummary.swift; sourceTree = ""; }; F4D5A43A21E40E4C006140DE /* Notifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Notifications.swift; sourceTree = ""; }; @@ -173,6 +184,12 @@ F4F4807121E46CE90038DC09 /* NotificationListenerTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationListenerTest.swift; sourceTree = ""; }; F4F4807321E471DE0038DC09 /* SettingsTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsTest.swift; sourceTree = ""; }; F4F4807521E4DD6A0038DC09 /* PopoverContollerBuildUpdateListenerTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = PopoverContollerBuildUpdateListenerTest.swift; path = listeners/PopoverContollerBuildUpdateListenerTest.swift; sourceTree = ""; }; + F4F4807721E518CE0038DC09 /* PreferencesWindow.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = PreferencesWindow.xib; sourceTree = ""; }; + F4F4807921E518F20038DC09 /* PreferencesWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesWindow.swift; sourceTree = ""; }; + F4F4807B21E5191D0038DC09 /* CurrentProjectsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrentProjectsController.swift; sourceTree = ""; }; + F4F4807D21E519350038DC09 /* PreferencesWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesWindowController.swift; sourceTree = ""; }; + F4F4807F21E5194A0038DC09 /* ServerTable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerTable.swift; sourceTree = ""; }; + F4F4808121E5196B0038DC09 /* UnfollowedProjectsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnfollowedProjectsController.swift; sourceTree = ""; }; F4FFD80321CA897F00E8C82E /* circleci-workflow.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "circleci-workflow.json"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -217,6 +234,7 @@ 6008690D1A1D3E5E00600E4C /* SeaEyeBuildsController.xib */, 6008690F1A1D3E6F00600E4C /* SeaEyeSettingsController.xib */, 600869111A1D3E9200600E4C /* SeaEyePopoverController.xib */, + F4F4807721E518CE0038DC09 /* PreferencesWindow.xib */, ); path = views; sourceTree = ""; @@ -242,7 +260,7 @@ F4D5A44921E427BB006140DE /* Filter.swift */, 6014AD981A09874D00865329 /* Project.swift */, 607C78251A190C580040FD5C /* SeaEyeStatus.swift */, - F4D5A43121E3F6B3006140DE /* Settings.swift */, + F4D5A43121E3F6B3006140DE /* SettingsV0.swift */, ); path = models; sourceTree = ""; @@ -347,6 +365,7 @@ 60C73C1319FD7ECB0067CDCA /* SeaEyeBuildsController.swift */, 60C73C1719FD7EE10067CDCA /* SeaEyeSettingsController.swift */, 60F0232B1A1A95930067C0A0 /* SeaEyeUpdatesController.swift */, + F4F4808521E51B390038DC09 /* preferences */, ); path = controllers; sourceTree = ""; @@ -392,6 +411,18 @@ name = listeners; sourceTree = ""; }; + F4F4808521E51B390038DC09 /* preferences */ = { + isa = PBXGroup; + children = ( + F4F4807921E518F20038DC09 /* PreferencesWindow.swift */, + F4F4807B21E5191D0038DC09 /* CurrentProjectsController.swift */, + F4F4807D21E519350038DC09 /* PreferencesWindowController.swift */, + F4F4807F21E5194A0038DC09 /* ServerTable.swift */, + F4F4808121E5196B0038DC09 /* UnfollowedProjectsController.swift */, + ); + path = preferences; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -481,6 +512,7 @@ 601D48CC1A0A841F007EA297 /* icon_256x256.png in Resources */, 600869101A1D3E6F00600E4C /* SeaEyeSettingsController.xib in Resources */, 607C77F21A1860070040FD5C /* build-failed@2x.png in Resources */, + F4F4807821E518CE0038DC09 /* PreferencesWindow.xib in Resources */, 602BB1C11A1A93E900E50B78 /* update@2x.png in Resources */, 601D48CF1A0A841F007EA297 /* icon_512x512.png in Resources */, 601D48CD1A0A841F007EA297 /* icon_32x32.png in Resources */, @@ -527,6 +559,8 @@ F4D5A43D21E41FEB006140DE /* SeaEyeStatusBar.swift in Sources */, 60C73C1519FD7ECB0067CDCA /* SeaEyeBuildsController.swift in Sources */, F4D5A45721E451BA006140DE /* TextPrinter.swift in Sources */, + F4F4808221E5196B0038DC09 /* UnfollowedProjectsController.swift in Sources */, + F4F4807E21E519350038DC09 /* PreferencesWindowController.swift in Sources */, 60C73C0619FC16FE0067CDCA /* SeaEyePopoverController.swift in Sources */, 60C73C1819FD7EE10067CDCA /* SeaEyeSettingsController.swift in Sources */, F4D5A43B21E40E4C006140DE /* Notifications.swift in Sources */, @@ -539,8 +573,11 @@ F4D5A45A21E45257006140DE /* SeaEyeStatusBarListener.swift in Sources */, F412CA9721D3E3530069C7C7 /* HTTPRequest.swift in Sources */, F4D5A46021E45613006140DE /* PopoverContollerBuildUpdateListener.swift in Sources */, - F4D5A43221E3F6B3006140DE /* Settings.swift in Sources */, + F4D5A43221E3F6B3006140DE /* SettingsV0.swift in Sources */, + F4F4808021E5194A0038DC09 /* ServerTable.swift in Sources */, F4D5A45021E44BA6006140DE /* ClientBuildUpdater.swift in Sources */, + F4F4807C21E5191D0038DC09 /* CurrentProjectsController.swift in Sources */, + F4F4807A21E518F20038DC09 /* PreferencesWindow.swift in Sources */, F4D5A44A21E427BB006140DE /* Filter.swift in Sources */, F4F1A61D20D84CD1008B8C04 /* GithubClient.swift in Sources */, F4D5A45321E44BF1006140DE /* NewBuildFilter.swift in Sources */, @@ -557,9 +594,12 @@ F412CA9A21D3E3C90069C7C7 /* HTTPRequest.swift in Sources */, F4D5A45821E451BA006140DE /* TextPrinter.swift in Sources */, F4D5A45121E44BA6006140DE /* ClientBuildUpdater.swift in Sources */, + F4F4808321E51B1B0038DC09 /* PreferencesWindowController.swift in Sources */, F4D5A44421E4208A006140DE /* NSImage.swift in Sources */, + F4F4808721E51B510038DC09 /* ServerTable.swift in Sources */, F4F4807221E46CE90038DC09 /* NotificationListenerTest.swift in Sources */, F4D5A44221E42076006140DE /* SeaEyeBuildsController.swift in Sources */, + F4F4808421E51B2C0038DC09 /* PreferencesWindow.swift in Sources */, F4D5A43F21E4201F006140DE /* SeaEyeIconController.swift in Sources */, F4F4807421E471DE0038DC09 /* SettingsTest.swift in Sources */, F4F1A61E20D84DBE008B8C04 /* GithubClient.swift in Sources */, @@ -568,12 +608,14 @@ F422C54B20D48CF2002A5897 /* VersionNumberTests.swift in Sources */, F4D5A44621E420A4006140DE /* BuildDecorator.swift in Sources */, F412CA9B21D3E3D50069C7C7 /* SeaEyeDecoder.swift in Sources */, + F4F4808621E51B4D0038DC09 /* CurrentProjectsController.swift in Sources */, F4D5A43621E3F782006140DE /* FallbackView.swift in Sources */, F412CAA021D3EFEF0069C7C7 /* DateFormatter.swift in Sources */, F4D5A44321E4207C006140DE /* SeaEyeUpdatesController.swift in Sources */, F4D5A45B21E45257006140DE /* SeaEyeStatusBarListener.swift in Sources */, F4D5A44021E42057006140DE /* SeaEyePopoverController.swift in Sources */, - F4D5A43321E3F6B3006140DE /* Settings.swift in Sources */, + F4F4808821E51B530038DC09 /* UnfollowedProjectsController.swift in Sources */, + F4D5A43321E3F6B3006140DE /* SettingsV0.swift in Sources */, F422C54920D48CDD002A5897 /* VersionNumber.swift in Sources */, F4F4807621E4DD6A0038DC09 /* PopoverContollerBuildUpdateListenerTest.swift in Sources */, F4D5A44521E42099006140DE /* BuildView.swift in Sources */, diff --git a/SeaEye/AppDelegate.swift b/SeaEye/AppDelegate.swift index 4f50189..1306e30 100644 --- a/SeaEye/AppDelegate.swift +++ b/SeaEye/AppDelegate.swift @@ -40,8 +40,8 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele fileprivate func initialSetup() { let userDefaults = UserDefaults.standard if userDefaults.bool(forKey: "SeaEyePerformedFirstSetup") == false { - userDefaults.set(true, forKey: Settings.Keys.notify.rawValue) - userDefaults.set(true, forKey: "SeaEyePerformedFirstSetup") +// userDefaults.set(true, forKey: SettingsV0.Keys.notify.rawValue) +// userDefaults.set(true, forKey: "SeaEyePerformedFirstSetup") ApplicationStartupManager.toggleLaunchAtStartup() } } diff --git a/SeaEye/controllers/SeaEyeBuildsController.swift b/SeaEye/controllers/SeaEyeBuildsController.swift index 509b541..8495b26 100644 --- a/SeaEye/controllers/SeaEyeBuildsController.swift +++ b/SeaEye/controllers/SeaEyeBuildsController.swift @@ -30,9 +30,13 @@ class SeaEyeBuildsController: NSViewController, NSTableViewDelegate, NSTableView override func viewDidAppear() { self.reloadBuilds() } + func resetBuilds() { + buildsDict = Dictionary() + regenBuilds() + } func reloadBuilds(_: Any? = nil) { - print("Reload builds!") +// print("Reload builds!") buildsTable?.reloadData() if fallbackView != nil { setupFallBackViews() diff --git a/SeaEye/controllers/SeaEyeIconController.swift b/SeaEye/controllers/SeaEyeIconController.swift index fd9a288..7270d84 100644 --- a/SeaEye/controllers/SeaEyeIconController.swift +++ b/SeaEye/controllers/SeaEyeIconController.swift @@ -42,11 +42,21 @@ class SeaEyeIconController: NSViewController { setup() } + func reset() { + popoverController.buildsViewController.resetBuilds() + for timer in self.timers { + timer.invalidate() + } + setup() + } + func setup() { + self.popoverController.iconController = self let secondsToRefreshBuilds = 30 let popoverControllerBuildListener = PopoverContollerBuildUpdateListener(buildSetter: popoverController) - let listeners: [BuildUpdateListener] = [TextPrinter(), + let listeners: [BuildUpdateListener] = [ +// TextPrinter(), popoverControllerBuildListener, SeaEyeStatusBarListener.init(statusBar: self.statusBarItem), NotificationListener()] diff --git a/SeaEye/controllers/SeaEyePopoverController.swift b/SeaEye/controllers/SeaEyePopoverController.swift index 829ba1f..af0a57e 100644 --- a/SeaEye/controllers/SeaEyePopoverController.swift +++ b/SeaEye/controllers/SeaEyePopoverController.swift @@ -20,6 +20,7 @@ class SeaEyePopoverController: NSViewController, BuildSetter { var updatesViewController: SeaEyeUpdatesController! var applicationStatus: SeaEyeStatus! var appDelegate: NSApplicationDelegate? = NSApplication.shared.delegate + var iconController: SeaEyeIconController? var heldBuilds = [CircleCIBuild]() override init(nibName nibNameOrNil: NSNib.Name?, bundle nibBundleOrNil: Bundle?) { @@ -75,12 +76,16 @@ class SeaEyePopoverController: NSViewController, BuildSetter { } @IBAction func openSettings(_ sender: NSButton) { - openSettingsButton.isHidden = true - openUpdatesButton.isHidden = true - shutdownButton.isHidden = true - openBuildsButton.isHidden = false - buildsViewController.view.removeFromSuperview() - subcontrollerView.addSubview(settingsViewController.view) + let prefrencesWindowVC = PreferencesWindowController() + prefrencesWindowVC.iconController = iconController + prefrencesWindowVC.showWindow(self) + iconController?.closePopover(nil) +// openSettingsButton.isHidden = true +// openUpdatesButton.isHidden = true +// shutdownButton.isHidden = true +// openBuildsButton.isHidden = false +// buildsViewController.view.removeFromSuperview() +// subcontrollerView.addSubview(settingsViewController.view) } @IBAction func openBuilds(_ sender: NSButton) { diff --git a/SeaEye/controllers/SeaEyeSettingsController.swift b/SeaEye/controllers/SeaEyeSettingsController.swift index 1401f20..63a206c 100644 --- a/SeaEye/controllers/SeaEyeSettingsController.swift +++ b/SeaEye/controllers/SeaEyeSettingsController.swift @@ -24,7 +24,7 @@ class SeaEyeSettingsController: NSViewController { @IBOutlet weak var versionString: NSTextField! let appDelegate = NSApplication.shared.delegate - let settings = Settings.load() +// let settings = Settings.load() override init(nibName nibNameOrNil: NSNib.Name?, bundle nibBundleOrNil: Bundle?) { super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) @@ -48,21 +48,21 @@ class SeaEyeSettingsController: NSViewController { } @IBAction func saveUserData(_ sender: NSButton) { - let notify = showNotifications.state == .on - UserDefaults.standard.set(notify, forKey: Settings.Keys.notify.rawValue) - setUserDefaultsFromField(apiKeyField, key: Settings.Keys.apiKey.rawValue) - setUserDefaultsFromField(organizationField, key: Settings.Keys.organisation.rawValue) - setUserDefaultsFromField(projectsField, key: Settings.Keys.projects.rawValue) - setUserDefaultsFromField(branchesField, key: Settings.Keys.branchFilter.rawValue) - setUserDefaultsFromField(usersField, key: Settings.Keys.userFilter.rawValue) - NotificationCenter.default.post(name: Notification.Name(rawValue: "SeaEyeSettingsChanged"), object: nil) - parentController.openBuilds(sender) +// let notify = showNotifications.state == .on +// UserDefaults.standard.set(notify, forKey: SettingsV0.Keys.notify.rawValue) +// setUserDefaultsFromField(apiKeyField, key: SettingsV0.Keys.apiKey.rawValue) +// setUserDefaultsFromField(organizationField, key: SettingsV0.Keys.organisation.rawValue) +// setUserDefaultsFromField(projectsField, key: SettingsV0.Keys.projects.rawValue) +// setUserDefaultsFromField(branchesField, key: SettingsV0.Keys.branchFilter.rawValue) +// setUserDefaultsFromField(usersField, key: SettingsV0.Keys.userFilter.rawValue) +// NotificationCenter.default.post(name: Notification.Name(rawValue: "SeaEyeSettingsChanged"), object: nil) +// parentController.openBuilds(sender) } @IBAction func saveNotificationPreferences(_ sender: NSButton) { let notify = showNotifications.state == .on print("Notificaiton Preference: \(notify)") - UserDefaults.standard.set(notify, forKey: Settings.Keys.notify.rawValue) +// UserDefaults.standard.set(notify, forKey: SettingsV0.Keys.notify.rawValue) } @IBAction func saveRunOnStartupPreferences(_ sender: NSButton) { @@ -81,22 +81,22 @@ class SeaEyeSettingsController: NSViewController { } fileprivate func setupInputFields() { - if settings.notify { - showNotifications.state = NSControl.StateValue.on - } else { - showNotifications.state = NSControl.StateValue.off - } - let hasRunOnStartup = ApplicationStartupManager.applicationIsInStartUpItems() - if hasRunOnStartup { - runOnStartup.state = NSControl.StateValue.on - } else { - runOnStartup.state = NSControl.StateValue.off - } - setupFieldFromUserDefaults(apiKeyField, key: Settings.Keys.apiKey.rawValue) - setupFieldFromUserDefaults(organizationField, key: Settings.Keys.organisation.rawValue) - setupFieldFromUserDefaults(projectsField, key: Settings.Keys.projects.rawValue) - setupFieldFromUserDefaults(branchesField, key: Settings.Keys.branchFilter.rawValue) - setupFieldFromUserDefaults(usersField, key: Settings.Keys.userFilter.rawValue) +// if settings.notify { +// showNotifications.state = NSControl.StateValue.on +// } else { +// showNotifications.state = NSControl.StateValue.off +// } +// let hasRunOnStartup = ApplicationStartupManager.applicationIsInStartUpItems() +// if hasRunOnStartup { +// runOnStartup.state = NSControl.StateValue.on +// } else { +// runOnStartup.state = NSControl.StateValue.off +// } +// setupFieldFromUserDefaults(apiKeyField, key: SettingsV0.Keys.apiKey.rawValue) +// setupFieldFromUserDefaults(organizationField, key: SettingsV0.Keys.organisation.rawValue) +// setupFieldFromUserDefaults(projectsField, key: SettingsV0.Keys.projects.rawValue) +// setupFieldFromUserDefaults(branchesField, key: SettingsV0.Keys.branchFilter.rawValue) +// setupFieldFromUserDefaults(usersField, key: SettingsV0.Keys.userFilter.rawValue) } fileprivate func setupFieldFromUserDefaults(_ field: NSTextField, key: String) { diff --git a/SeaEye/controllers/preferences/CurrentProjectsController.swift b/SeaEye/controllers/preferences/CurrentProjectsController.swift new file mode 100644 index 0000000..ace5069 --- /dev/null +++ b/SeaEye/controllers/preferences/CurrentProjectsController.swift @@ -0,0 +1,194 @@ +import Cocoa +import Foundation + +class CurrentProjectsController: NSObject, NSTextFieldDelegate { + fileprivate enum CellIdentifiers { + static let ProjectNameCell = "projectCell" + } + + private var projects: [Project] + private var selectedProjectIndex: Int? + + let cellIdentifer = NSUserInterfaceItemIdentifier(rawValue: CellIdentifiers.ProjectNameCell) + let tableView: NSTableView + let delegate: ProjectUnfollowedDelegate + let pUdelegate: ProjectUpdatedDelegate + + init(tableView: NSTableView, projects: [Project], delegate: ProjectUnfollowedDelegate, pUdelegate: ProjectUpdatedDelegate) { + self.projects = projects + self.tableView = tableView + self.delegate = delegate + self.pUdelegate = pUdelegate + + super.init() + tableView.delegate = self + tableView.dataSource = self + } + + func set(projects: [Project]) { + self.projects = projects + tableView.reloadData() + } + + func tableViewSelectionDidChange(_: Notification) { + print("hello") + updateStatus() + } + + func updateStatus() { + let itemsSelected = tableView.selectedRow + if itemsSelected >= 0 { + selectedProjectIndex = itemsSelected + print("Probably editing \(projects[itemsSelected]) \(tableView.selectedColumn)") + } else { + print("\(itemsSelected) no row is selected") + } + } + + func controlTextDidEndEditing(_ obj: Notification) { +// print(obj) + print("Editing \(tableView.editedRow) - \(tableView.editedColumn)") + print("Clicked \(tableView.clickedRow) - \(tableView.clickedColumn)") + print("Selected \(tableView.selectedRow) - \(tableView.selectedColumn)") + + if let textField = obj.object as? NSTextField { + print("just set some value \(textField.tag) to \(textField.stringValue)") + let branchFilterTag = 1 + let userFilterTag = 2 + + if let index = self.selectedProjectIndex { + var project = projects[index] + if project.filter == nil { + project.filter = Filter(userFilter: nil, branchFilter: nil) + } + if textField.tag == branchFilterTag { + project.filter?.branchFilter = textField.stringValue + } + if textField.tag == userFilterTag { + project.filter?.userFilter = textField.stringValue + } + print(project) + projects[index] = project + pUdelegate.projectUpdated(projectIndex: index, project: project) + } else { + print("[WARN] there is no selectedProjectIndex") + } + } + tableView.reloadData() + } + + @objc func removeProject(_ sender: NSButton) { + let projectIndex = sender.tag + delegate.removeProject(projectIndex: projectIndex) + } + + @objc func notifyToggledSuccess(_ sender: NSButton) { + var project = projects[sender.tag] + project.notifySuccess = (sender.state == .on) + pUdelegate.projectUpdated(projectIndex: sender.tag, project: project) + + print("Success Notifications \(sender.tag) -> \(sender.state) \(project.notifySuccess)") + } + + @objc func notifyToggledFailure(_ sender: NSButton) { + var project = projects[sender.tag] + project.notifyFailure = (sender.state == .on) + pUdelegate.projectUpdated(projectIndex: sender.tag, project: project) + + print("Failure Notifications \(sender.tag) -> \(sender.state) \(project.notifyFailure)") + } +} + +extension CurrentProjectsController: NSTableViewDelegate { + func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? { + if let cell = tableView.makeView(withIdentifier: cellIdentifer, owner: nil) as? NSTableCellView { + return tableViewForProjectCell(tableView, viewFor: tableColumn, row: row, cell: cell) + } + + return nil + } + + private func tableViewForProjectCell(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int, cell: NSTableCellView) -> NSView? { + var text: String = "unknown" + + let project = projects[row] + + if tableColumn == tableView.tableColumns[0] { + text = project.description + } + + if tableColumn == tableView.tableColumns[1] { + text = project.filter?.branchFilter ?? "*" + cell.textField?.tag = 1 + cell.textField?.isEditable = true + cell.textField?.delegate = self + } + if tableColumn == tableView.tableColumns[2] { + text = project.filter?.userFilter ?? "*" + cell.textField?.tag = 2 + + cell.textField?.isEditable = true + cell.textField?.delegate = self + } + if tableColumn == tableView.tableColumns[3] { + if #available(OSX 10.12, *) { + let button = NSButton(checkboxWithTitle: "", target: self, action: #selector(notifyToggledSuccess)) + button.tag = row + button.state = project.notifySuccess ? .on : .off + return button + + } else { + // Fallback on earlier versions + } + } + if tableColumn == tableView.tableColumns[4] { + if #available(OSX 10.12, *) { + let button = NSButton(checkboxWithTitle: "", target: self, action: #selector(notifyToggledFailure)) + button.tag = row + button.state = project.notifyFailure ? .on : .off + return button + + } else { + // Fallback on earlier versions + } + } + if tableColumn == tableView.tableColumns[5] { + if #available(OSX 10.12, *) { + let button = NSButton(title: "Remove", target: self, action: #selector(removeProject)) + button.setButtonType(NSButton.ButtonType.momentaryPushIn) + button.tag = row + return button + } + } + + cell.textField?.stringValue = text + return cell + } +} + +extension CurrentProjectsController: NSTableViewDataSource { + func numberOfRows(in _: NSTableView) -> Int { + return projects.count + } + + func tableView(_ view: NSTableView, + objectValueFor col: NSTableColumn?, + row index: Int) -> Any? { +// print("\(index) \(col) \(col?.identifier.rawValue)") + let project = projects[index] + switch col?.identifier.rawValue { + case "projectName": + return project.description + case "branchFilter": + return project.filter?.branchFilter + case "userRegex": + return project.filter?.userFilter + case "notifySuccess": + return project.notifySuccess + case "notifyFailure": + return project.notifyFailure + default: + return nil + } + } +} diff --git a/SeaEye/controllers/preferences/PreferencesWindow.swift b/SeaEye/controllers/preferences/PreferencesWindow.swift new file mode 100644 index 0000000..071b569 --- /dev/null +++ b/SeaEye/controllers/preferences/PreferencesWindow.swift @@ -0,0 +1,171 @@ +import Cocoa +import Foundation + +struct SelectedServerView { + var apiServerApiPath: NSTextField! + var apiServerAuthToken: NSTextField! + var apiServerTestButton: NSButton! + + func fill(client: CircleCIClient) { + apiServerApiPath.stringValue = client.baseURL + apiServerAuthToken.stringValue = client.token + apiServerTestButton.isEnabled = !client.token.isEmpty + } +} + +protocol ProjectFollowedDelegate { + func addProject(project: Project) +} + +protocol ProjectUnfollowedDelegate { + func removeProject(projectIndex: Int) +} + +protocol ProjectUpdatedDelegate { + func projectUpdated(projectIndex: Int, project: Project) +} + +// This is a kind-of-controller, that is used to +class PreferencesWindow: NSWindow, NSWindowDelegate, NSTabViewDelegate, ProjectFollowedDelegate, ProjectUnfollowedDelegate, ProjectUpdatedDelegate { + // Preferences window + @IBOutlet var projectsTable: NSTableView! + @IBOutlet var versionNumber: NSTextField! + @IBOutlet var launchAtStartup: NSButton! + @IBOutlet var knownProjectsTable: NSTableView! + + var serverListView: ServerTable? + var currentProjectsView: CurrentProjectsController! + var unfollowedProjectsTableView: UnfollowedProjectsController? + + // Servers + @IBOutlet var serverList: NSTableView! + @IBOutlet var apiServerApiPath: NSTextField! + @IBOutlet var apiServerAuthToken: NSSecureTextField! + @IBOutlet var apiServerTestButton: NSButton! + @IBOutlet var apiServerDeleteButton: NSButton! + + var settings = Settings.load() + var iconController: SeaEyeIconController? + + @IBAction func toggleStartup(_ sender: Any) { + print("Toggle Startup \(sender)") + if let button = sender as? NSButton { + print(button.state) + } + } + + func windowWillClose(_ notification: Notification) { + print("Closing window") + iconController?.reset() + } + + override func awakeFromNib() { + versionNumber.stringValue = VersionNumber.current().description + let selectedServerView = SelectedServerView(apiServerApiPath: apiServerApiPath, + apiServerAuthToken: apiServerAuthToken, + apiServerTestButton: apiServerTestButton) + + serverListView = ServerTable(tableView: serverList!, + clients: settings.clients(), + view: selectedServerView) + self.delegate = self + super.awakeFromNib() + } + + func addProject(project: Project) { + if let client = self.serverListView?.selectedClient() { + print("Add \(project) to \(client)") + + if let index = self.serverListView?.selectedIndex { + print("Add Project \(index)") + settings.clientProjects[index].projects.append(project) + settings.save() + currentProjectsView.set(projects: settings.clientProjects[index].projects) + serverListView?.reloadFromSettings() + iconController?.reset() + } + } + } + + func removeProject(projectIndex: Int) { + if var client = self.serverListView?.selectedClient() { + print("Remove Project \(projectIndex)") + +// print("Remove \(client.projects[projectIndex]) to \(client)") +// + if let index = self.serverListView?.selectedIndex { + settings.clientProjects[index].projects.remove(at: projectIndex) + settings.save() + serverListView?.reloadFromSettings() + currentProjectsView.set(projects: settings.clientProjects[index].projects) + iconController?.reset() + } + } + } + + func projectUpdated(projectIndex: Int, project: Project) { + if var client = self.serverListView?.selectedClient() { + print("Project @ \(projectIndex) updated") + +// print("Update \(client.clientProjects[projectIndex]) to \n \(project)") +// + if let index = self.serverListView?.selectedIndex { + settings.clientProjects[index].projects[projectIndex] = project + settings.save() + currentProjectsView.set(projects: settings.clientProjects[index].projects) + iconController?.reset() + } + + } else {} + } + + func tabView(_: NSTabView, willSelect: NSTabViewItem?) { + if let item = willSelect { + if item.label == "Projects" { + if let client = self.serverListView?.selectedClient() { + currentProjectsView = CurrentProjectsController(tableView: projectsTable, + projects: settings.projects(), + delegate: self, + pUdelegate: self) + + unfollowedProjectsTableView = UnfollowedProjectsController(tableView: knownProjectsTable, + client: client, + knownProjects: settings.projects(), + delegate: self) + + } else { + print("There's no clients .... you can't select projects") + } + unfollowedProjectsTableView?.reload() + } + } + } + + @IBAction func testApiServerSelected(_: NSButton) { + print("test the api server") + serverListView?.testServer() + } + + @IBAction func addNewApiServerSelected(_: NSButton) { + let url = apiServerApiPath.stringValue + let apiKey = apiServerAuthToken.stringValue + var client = CircleCIClient.init(apiKey: apiKey) + client.baseURL = url + let clientp = ClientProject(client: client, projects: []) + + print("Actually adding now \(client)") + serverListView?.clients.append(client) + settings.clientProjects.append(clientp) + settings.save() + serverList.reloadData() + } + + @IBAction func deleteButtonPressed(_: Any) { + let selected = serverList.selectedRow + if selected >= 0 { + settings.clientProjects.remove(at: selected) + settings.save() + serverListView?.reloadFromSettings() + } + } +} diff --git a/SeaEye/controllers/preferences/PreferencesWindowController.swift b/SeaEye/controllers/preferences/PreferencesWindowController.swift new file mode 100644 index 0000000..519c04a --- /dev/null +++ b/SeaEye/controllers/preferences/PreferencesWindowController.swift @@ -0,0 +1,32 @@ +import Cocoa +import Foundation + +class PreferencesWindowController: NSWindowController { + var iconController: SeaEyeIconController? + + override init(window: NSWindow?) { + super.init(window: window) + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + } + + init(window: PreferencesWindow?, controller: SeaEyeIconController) { + self.iconController = controller + super.init(window: window) + window?.iconController = controller + } + + override var windowNibName: NSNib.Name { + return "PreferencesWindow" + } + + override func windowDidLoad() { + print("Prefrences window opened") + if let preferencesWindow = self.window as? PreferencesWindow { + preferencesWindow.iconController = iconController + } + super.windowDidLoad() + } +} diff --git a/SeaEye/controllers/preferences/ServerTable.swift b/SeaEye/controllers/preferences/ServerTable.swift new file mode 100644 index 0000000..c05cb05 --- /dev/null +++ b/SeaEye/controllers/preferences/ServerTable.swift @@ -0,0 +1,81 @@ +import Cocoa +import Foundation + +class ServerTable: NSObject, NSTableViewDelegate, NSTableViewDataSource { + let cellIdentifier = NSUserInterfaceItemIdentifier(rawValue: "serverCell") + var selectedIndex: Int = 0 + var clients: [CircleCIClient] + var view: SelectedServerView + var tableView: NSTableView + + init(tableView: NSTableView, clients: [CircleCIClient], view: SelectedServerView) { + self.clients = clients + self.view = view + self.tableView = tableView + super.init() + tableView.delegate = self + tableView.dataSource = self + selectFirstClient() + } + + func testServer() { + if let client = selectedClient() { + client.getMe { response in + let alert = NSAlert() + alert.addButton(withTitle: "OK") + switch response { + case let .success(user): + alert.messageText = "This API server seems OK!" + alert.informativeText = "\(user.name)" + + case let .failure(err): + alert.messageText = "The test failed for \(client.baseURL)" + alert.informativeText = err.localizedDescription + } + + alert.runModal() + } + } + } + + func selectedClient() -> CircleCIClient? { + reloadFromSettings() + if clients.count > 0 && selectedIndex < clients.count { + return clients[self.selectedIndex] + } + return nil + } + + func selectFirstClient() { + tableView.selectRowIndexes([0], byExtendingSelection: false) + + if let mclient = selectedClient() { + if clients.count > 0 { + view.fill(client: mclient) + } + } + } + + func reloadFromSettings() { + clients = Settings.load().clients() + tableView.reloadData() + } + + func tableView(_ tableView: NSTableView, viewFor _: NSTableColumn?, row: Int) -> NSView? { + if let cell = tableView.makeView(withIdentifier: cellIdentifier, owner: nil) as? NSTableCellView { + cell.textField?.stringValue = clients[row].baseURL + return cell + } + return nil + } + + func tableView(_: NSTableView, shouldSelectRow row: Int) -> Bool { + print("\(row) was \(clients[row])") + view.fill(client: clients[row]) + return true + } + + func numberOfRows(in _: NSTableView) -> Int { + return clients.count + } +} diff --git a/SeaEye/controllers/preferences/UnfollowedProjectsController.swift b/SeaEye/controllers/preferences/UnfollowedProjectsController.swift new file mode 100644 index 0000000..34ab97b --- /dev/null +++ b/SeaEye/controllers/preferences/UnfollowedProjectsController.swift @@ -0,0 +1,79 @@ +import Cocoa +import Foundation + +class UnfollowedProjectsController: NSObject, NSTableViewDelegate, NSTableViewDataSource { + private let cellIdentifier = NSUserInterfaceItemIdentifier(rawValue: "AddProjectName") + + private var client: CircleCIClient + private var projects: [Project] + private let tableView: NSTableView + private let delegate: ProjectFollowedDelegate + private var projectsAlreadyFollwed: [Project] + + init(tableView: NSTableView, client: CircleCIClient, knownProjects: [Project], delegate: ProjectFollowedDelegate) { + self.client = client + self.projectsAlreadyFollwed = knownProjects + projects = [] + self.delegate = delegate + self.tableView = tableView + super.init() + tableView.delegate = self + tableView.dataSource = self + reload() + } + + func reload() { + client.getProjects { response in + switch response { + case let .success(cip): + self.projects = cip.map { return $0.toProject() }.filter({ + for followedProject in self.projectsAlreadyFollwed { + if $0.description == followedProject.description { + return false + } + } + + return true + }) + self.tableView.reloadData() + case let .failure(err): + print(err.localizedDescription) + } + } + } + + func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? { + if let cell = tableView.makeView(withIdentifier: cellIdentifier, owner: nil) as? NSTableCellView { + if tableColumn == tableView.tableColumns[0] { + if row < projects.count { + cell.textField?.stringValue = projects[row].description + cell.textField?.tag = row + } + } + + if tableColumn == tableView.tableColumns[1] { + if #available(OSX 10.12, *) { + let button = NSButton(title: "Add project", target: self, action: #selector(addAProject)) + button.tag = row + return button + } else { + // Fallback on earlier versions + } + } + return cell + } + + return nil + } + + func numberOfRows(in _: NSTableView) -> Int { + return projects.count + } + + @objc func addAProject(_ sender: NSButton) { + let project = projects[sender.tag] + delegate.addProject(project: project) + projects.remove(at: sender.tag) + self.tableView.reloadData() + } +} diff --git a/SeaEye/listeners/NotificationListener.swift b/SeaEye/listeners/NotificationListener.swift index fd180af..ca7cf7a 100644 --- a/SeaEye/listeners/NotificationListener.swift +++ b/SeaEye/listeners/NotificationListener.swift @@ -9,21 +9,25 @@ class NotificationListener: BuildUpdateListener { func notify(project: Project, builds: [CircleCIBuild]) { let filteredBuilds = builds.filter {$0.lastUpdateTime() > initDate} - if project.notify { - if let summary = BuildSummary.generate(builds: filteredBuilds) { - switch summary.status { - case .running: - print("Running build ... skipping notify") - case .failed: + if let summary = BuildSummary.generate(builds: filteredBuilds) { + switch summary.status { + case .running: + print("Running build ... skipping notify") + case .failed: + if project.notifyFailure { print("Notify of a failed build") - let notification = BuildsNotification(summary.build!, summary.count).toNotification() notificationCenter.deliver(notification) - case .success: + } else { + print("Failed build, but project has failure notifications off") + } + case .success: + if project.notifySuccess { print("Notifiy of a success build") - let notification = BuildsNotification(summary.build!, summary.count).toNotification() notificationCenter.deliver(notification) + } else { + print("Success build, but project has success notifications off") } } } diff --git a/SeaEye/models/Project.swift b/SeaEye/models/Project.swift index 07e12a9..06a031f 100644 --- a/SeaEye/models/Project.swift +++ b/SeaEye/models/Project.swift @@ -5,7 +5,8 @@ struct Project: Codable, CustomStringConvertible { let organisation: String let name: String var filter: Filter? - var notify: Bool + var notifySuccess: Bool + var notifyFailure: Bool func path() -> String { return "\(vcsProvider)/\(organisation)/\(name)" diff --git a/SeaEye/models/SettingsV0.swift b/SeaEye/models/SettingsV0.swift new file mode 100644 index 0000000..53b3c1a --- /dev/null +++ b/SeaEye/models/SettingsV0.swift @@ -0,0 +1,149 @@ +import Foundation + +struct Settings: Codable { + private static let defaultsKey: String = "SeaEyeSettings2" + + var clientProjects: [ClientProject] + + static func load(userDefaults: UserDefaults = UserDefaults.standard) -> Settings { + let oldSettings = SettingsV0.load(userDefaults: userDefaults) + + var settings = Settings(clientProjects: []) + if let settingsString = userDefaults.string(forKey: self.defaultsKey) { + let decoder = JSONDecoder() + if let data = settingsString.data(using: .utf8) { + do { + settings = try decoder.decode(Settings.self, from: data) + return settings + } catch let error { + print(error.localizedDescription) + } + } + } + print("Migrating old settings to new settings format") + settings = oldSettings.toSettings() + settings.save(userDefaults: userDefaults) + return settings + } + + func save(userDefaults: UserDefaults = UserDefaults.standard) { + for client in clientProjects { + print("Saving \(client)") + } + + let jsonEncoder = JSONEncoder() + + do { + let jsonData = try jsonEncoder.encode(self) + let json = String(data: jsonData, encoding: String.Encoding.utf8) + userDefaults.setValue(json, forKey: Settings.defaultsKey) + } catch let error { + print(error.localizedDescription) + } + } + + func clientBuildUpdateListeners(listeners: [BuildUpdateListener]) -> [ClientBuildUpdater] { + return clientProjects.flatMap { (cp) -> [ClientBuildUpdater] in + return cp.projects.map({ + ClientBuildUpdater(listeners: listeners, + client: cp.client, + project: $0) + }) + } + } + + func clients() -> [CircleCIClient] { + return self.clientProjects.map { $0.client} + } + func projects() -> [Project] { + return Array(clientProjects.map {$0.projects}.joined()) + } + func numberOfProjects() -> Int { + return clientProjects.compactMap { $0.projects.count }.reduce(0, { $0 + $1 }) + } +} + +private struct SettingsV0: Codable { + enum Keys: String { + case apiKey = "SeaEyeAPIKey" + case organisation = "SeaEyeOrganization" + case projects = "SeaEyeProjects" + case branchFilter = "SeaEyeBranches" + case userFilter = "SeaEyeUsers" + case notify = "SeaEyeNotify" + } + + var apiKey: String? + var organization: String? + var projectsString: String? + var branchFilter: String? + var userFilter: String? + var notify: Bool = true + + static func load(userDefaults: UserDefaults = UserDefaults.standard) -> SettingsV0 { + var settings = self.init() + settings.apiKey = userDefaults.string(forKey: Keys.apiKey.rawValue) + settings.organization = userDefaults.string(forKey: Keys.organisation.rawValue) + settings.projectsString = userDefaults.string(forKey: Keys.projects.rawValue) + settings.branchFilter = userDefaults.string(forKey: Keys.branchFilter.rawValue) + settings.userFilter = userDefaults.string(forKey: Keys.userFilter.rawValue) + settings.notify = userDefaults.bool(forKey: Keys.notify.rawValue) + + return settings + } + + func toSettings() -> Settings { + return Settings(clientProjects: self.clientProjects()) + } + + func clientProjects() -> [ClientProject] { + let projectNames = projectsString?.components(separatedBy: CharacterSet.whitespaces) + let client = CircleCIClient.init(apiKey: apiKey!) + if !valid() { + return [] + } + var projects = [Project]() + + if let projectNames = projectNames { + projects = projectNames.map { + let filter = Filter.init(userFilter: userFilter, branchFilter: branchFilter) + // github is hardcoded, as it was assumed + return Project.init(vcsProvider: "github", + organisation: self.organization!, + name: $0, + filter: filter, + notifySuccess: self.notify, + notifyFailure: self.notify + ) + } + } + return [ClientProject.init(client: client, projects: projects)] + } + + func clients() -> [CircleCIClient] { + return self.clientProjects().map { $0.client} + } + func projects() -> [Project] { + return Array(clientProjects().map {$0.projects}.joined()) + } + + func clientBuildUpdateListeners(listeners: [BuildUpdateListener]) -> [ClientBuildUpdater] { + let clientProjects = self.clientProjects() + return clientProjects.flatMap { (cp) -> [ClientBuildUpdater] in + cp.projects.map { + return ClientBuildUpdater(listeners: listeners, + client: cp.client, + project: $0) + } + } + } + + func valid() -> Bool { + return apiKey != nil && organization != nil && projectsString != nil + } +} + +struct ClientProject: Codable { + var client: CircleCIClient + var projects: [Project] +} diff --git a/SeaEye/networking/CircleCIClient.swift b/SeaEye/networking/CircleCIClient.swift index 2b9593f..6ab96d6 100644 --- a/SeaEye/networking/CircleCIClient.swift +++ b/SeaEye/networking/CircleCIClient.swift @@ -3,6 +3,27 @@ import Foundation protocol BuildClient { func getProject(name: String, completion: ((Result<[CircleCIBuild]>) -> Void)?) } +struct CircleCIProject: Decodable { + let username: String + let reponame: String + let vcsUrl: String + let following: Bool + + func description() -> String { + return "\(username)/\(reponame)" + } + + func toProject() -> Project { + return Project( + vcsProvider: "github", + organisation: username, + name: reponame, + filter: nil, + notifySuccess: true, + notifyFailure: true + ) + } +} struct CircleCIClient: Codable, BuildClient { var baseURL: String = "https://circleci.com" @@ -11,6 +32,11 @@ struct CircleCIClient: Codable, BuildClient { init(apiKey: String) { token = apiKey } + + func getProjects(completion: ((Result<[CircleCIProject]>) -> Void)?) { + request(get(path: "projects"), of: [CircleCIProject].self, completion: completion) + } + func getProject(name: String, completion: ((Result<[CircleCIBuild]>) -> Void)?) { request(get(path: "project/\(name)"), of: [CircleCIBuild].self, completion: completion) } diff --git a/SeaEye/views/FallbackView.swift b/SeaEye/views/FallbackView.swift index 54f5885..e4c344d 100644 --- a/SeaEye/views/FallbackView.swift +++ b/SeaEye/views/FallbackView.swift @@ -5,15 +5,18 @@ struct FallbackView { let builds: [CircleCIBuild] func description() -> String? { - if settings.apiKey == nil { - return "You have not set an API key" - } - if settings.organization == nil { - return "You have not set an organization name" - } - if settings.projectsString == nil { - return "You have not added any projects" + if settings.numberOfProjects() == 0 { + return "Add some projects, mo chara" } +// if settings.apiKey == nil { +// return "You have not set an API key" +// } +// if settings.organization == nil { +// return "You have not set an organization name" +// } +// if settings.projectsString == nil { +// return "You have not added any projects" +// } if builds.count == 0 { return "No Recent Builds Found" } diff --git a/SeaEye/views/PreferencesWindow.xib b/SeaEye/views/PreferencesWindow.xib new file mode 100644 index 0000000..cfc9669 --- /dev/null +++ b/SeaEye/views/PreferencesWindow.xib @@ -0,0 +1,569 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Personal +Access +Token + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You can use 'Test' to verify your settings and move to 'Projects' from above, or if you are an advanced user, you can add more servers to the left. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SeaEyeTests/NotificationListenerTest.swift b/SeaEyeTests/NotificationListenerTest.swift index 0aaf9c7..398c314 100644 --- a/SeaEyeTests/NotificationListenerTest.swift +++ b/SeaEyeTests/NotificationListenerTest.swift @@ -25,7 +25,8 @@ class NotificationListenerTest: XCTestCase { organisation: "nolaneo", name: "SeaEye", filter: nil, - notify: true) + notifySuccess: true, + notifyFailure: true) sut.notify(project: project, builds: [failed]) } diff --git a/SeaEyeTests/SettingsTest.swift b/SeaEyeTests/SettingsTest.swift index 255dade..5b32caf 100644 --- a/SeaEyeTests/SettingsTest.swift +++ b/SeaEyeTests/SettingsTest.swift @@ -12,12 +12,12 @@ import XCTest class SettingsTest: XCTestCase { func testDefaults() { - var settings = Settings.load(userDefaults: UserDefaults.init(suiteName: "testing")!) - XCTAssertFalse(settings.notify) - XCTAssertFalse(settings.valid()) - settings.apiKey = "abc" - settings.organization = "nolaneo" - settings.projectsString = "SeaEye" - XCTAssertTrue(settings.valid()) +// var settings = SettingsV0.load(userDefaults: UserDefaults.init(suiteName: "testing")!) +// XCTAssertFalse(settings.notify) +// XCTAssertFalse(settings.valid()) +// settings.apiKey = "abc" +// settings.organization = "nolaneo" +// settings.projectsString = "SeaEye" +// XCTAssertTrue(settings.valid()) } } diff --git a/SeaEyeTests/listeners/PopoverContollerBuildUpdateListenerTest.swift b/SeaEyeTests/listeners/PopoverContollerBuildUpdateListenerTest.swift index 279453f..3fde63b 100644 --- a/SeaEyeTests/listeners/PopoverContollerBuildUpdateListenerTest.swift +++ b/SeaEyeTests/listeners/PopoverContollerBuildUpdateListenerTest.swift @@ -36,7 +36,8 @@ class PopoverContollerBuildUpdateListenerTest: XCTestCase { organisation: "nolaneo", name: "SeaEye", filter: nil, - notify: true) + notifySuccess: true, + notifyFailure: true) sut.notify(project: project, builds: [old, new]) diff --git a/SeaEyeTests/listeners/SeaEyeStatusBarListenerTest.swift b/SeaEyeTests/listeners/SeaEyeStatusBarListenerTest.swift index 8a75524..d0f6101 100644 --- a/SeaEyeTests/listeners/SeaEyeStatusBarListenerTest.swift +++ b/SeaEyeTests/listeners/SeaEyeStatusBarListenerTest.swift @@ -31,7 +31,8 @@ class SeaEyeStatusBarListenerTest: XCTestCase { organisation: "nolaneo", name: "SeaEye", filter: nil, - notify: true) + notifySuccess: true, + notifyFailure: true) sut.notify(project: project, builds: [failed]) XCTAssertEqual(sut.statusBar.state, .idle, "Old builds should not effect the status") From a320a5dc6911b685d8c549301c4dd9d6a0ff18d3 Mon Sep 17 00:00:00 2001 From: Conor Mongey Date: Wed, 9 Jan 2019 17:13:31 +0000 Subject: [PATCH 02/10] Actually remove old settings --- SeaEye.xcodeproj/project.pbxproj | 10 - .../controllers/SeaEyePopoverController.swift | 10 - .../SeaEyeSettingsController.swift | 112 ----------- SeaEye/views/SeaEyeSettingsController.xib | 182 ------------------ 4 files changed, 314 deletions(-) delete mode 100644 SeaEye/controllers/SeaEyeSettingsController.swift delete mode 100644 SeaEye/views/SeaEyeSettingsController.xib diff --git a/SeaEye.xcodeproj/project.pbxproj b/SeaEye.xcodeproj/project.pbxproj index c6d750e..146d063 100644 --- a/SeaEye.xcodeproj/project.pbxproj +++ b/SeaEye.xcodeproj/project.pbxproj @@ -16,7 +16,6 @@ 5394798D1FCB909600D6FA19 /* CircleCIClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 539479741FCB639300D6FA19 /* CircleCIClient.swift */; }; 53A3BE8D1FD34D85006961C6 /* circleci2-project.json in Resources */ = {isa = PBXBuildFile; fileRef = 53A3BE8C1FD34D85006961C6 /* circleci2-project.json */; }; 6008690E1A1D3E5E00600E4C /* SeaEyeBuildsController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6008690D1A1D3E5E00600E4C /* SeaEyeBuildsController.xib */; }; - 600869101A1D3E6F00600E4C /* SeaEyeSettingsController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6008690F1A1D3E6F00600E4C /* SeaEyeSettingsController.xib */; }; 600869121A1D3E9200600E4C /* SeaEyePopoverController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 600869111A1D3E9200600E4C /* SeaEyePopoverController.xib */; }; 600869141A1D3EA000600E4C /* SeaEyeUpdatesController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 600869131A1D3EA000600E4C /* SeaEyeUpdatesController.xib */; }; 6008691D1A1D4F7400600E4C /* Main.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6008691C1A1D4F7400600E4C /* Main.xib */; }; @@ -41,7 +40,6 @@ 60C73C0619FC16FE0067CDCA /* SeaEyePopoverController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60C73C0519FC16FE0067CDCA /* SeaEyePopoverController.swift */; }; 60C73C0919FC23F60067CDCA /* SeaEyeIconController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60C73C0719FC23F60067CDCA /* SeaEyeIconController.swift */; }; 60C73C1519FD7ECB0067CDCA /* SeaEyeBuildsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60C73C1319FD7ECB0067CDCA /* SeaEyeBuildsController.swift */; }; - 60C73C1819FD7EE10067CDCA /* SeaEyeSettingsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60C73C1719FD7EE10067CDCA /* SeaEyeSettingsController.swift */; }; 60F0232C1A1A95930067C0A0 /* SeaEyeUpdatesController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60F0232B1A1A95930067C0A0 /* SeaEyeUpdatesController.swift */; }; F412CA9221D3DF050069C7C7 /* NSImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = F412CA9121D3DF050069C7C7 /* NSImage.swift */; }; F412CA9721D3E3530069C7C7 /* HTTPRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = F412CA9621D3E3530069C7C7 /* HTTPRequest.swift */; }; @@ -67,7 +65,6 @@ F4D5A43E21E41FEB006140DE /* SeaEyeStatusBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4D5A43C21E41FEB006140DE /* SeaEyeStatusBar.swift */; }; F4D5A43F21E4201F006140DE /* SeaEyeIconController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60C73C0719FC23F60067CDCA /* SeaEyeIconController.swift */; }; F4D5A44021E42057006140DE /* SeaEyePopoverController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60C73C0519FC16FE0067CDCA /* SeaEyePopoverController.swift */; }; - F4D5A44121E4206D006140DE /* SeaEyeSettingsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60C73C1719FD7EE10067CDCA /* SeaEyeSettingsController.swift */; }; F4D5A44221E42076006140DE /* SeaEyeBuildsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60C73C1319FD7ECB0067CDCA /* SeaEyeBuildsController.swift */; }; F4D5A44321E4207C006140DE /* SeaEyeUpdatesController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60F0232B1A1A95930067C0A0 /* SeaEyeUpdatesController.swift */; }; F4D5A44421E4208A006140DE /* NSImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = F412CA9121D3DF050069C7C7 /* NSImage.swift */; }; @@ -126,7 +123,6 @@ 5394797C1FCB8C5200D6FA19 /* CircleCIBuild.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircleCIBuild.swift; sourceTree = ""; }; 53A3BE8C1FD34D85006961C6 /* circleci2-project.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "circleci2-project.json"; sourceTree = ""; }; 6008690D1A1D3E5E00600E4C /* SeaEyeBuildsController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = SeaEyeBuildsController.xib; sourceTree = ""; }; - 6008690F1A1D3E6F00600E4C /* SeaEyeSettingsController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = SeaEyeSettingsController.xib; sourceTree = ""; }; 600869111A1D3E9200600E4C /* SeaEyePopoverController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = SeaEyePopoverController.xib; sourceTree = ""; }; 600869131A1D3EA000600E4C /* SeaEyeUpdatesController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = SeaEyeUpdatesController.xib; sourceTree = ""; }; 6008691C1A1D4F7400600E4C /* Main.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = Main.xib; sourceTree = ""; }; @@ -155,7 +151,6 @@ 60C73C0519FC16FE0067CDCA /* SeaEyePopoverController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SeaEyePopoverController.swift; sourceTree = ""; }; 60C73C0719FC23F60067CDCA /* SeaEyeIconController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SeaEyeIconController.swift; sourceTree = ""; }; 60C73C1319FD7ECB0067CDCA /* SeaEyeBuildsController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SeaEyeBuildsController.swift; sourceTree = ""; }; - 60C73C1719FD7EE10067CDCA /* SeaEyeSettingsController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SeaEyeSettingsController.swift; sourceTree = ""; }; 60F0232B1A1A95930067C0A0 /* SeaEyeUpdatesController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SeaEyeUpdatesController.swift; sourceTree = ""; }; F412CA9121D3DF050069C7C7 /* NSImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSImage.swift; sourceTree = ""; }; F412CA9621D3E3530069C7C7 /* HTTPRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTTPRequest.swift; sourceTree = ""; }; @@ -232,7 +227,6 @@ F4D5A43C21E41FEB006140DE /* SeaEyeStatusBar.swift */, 600869131A1D3EA000600E4C /* SeaEyeUpdatesController.xib */, 6008690D1A1D3E5E00600E4C /* SeaEyeBuildsController.xib */, - 6008690F1A1D3E6F00600E4C /* SeaEyeSettingsController.xib */, 600869111A1D3E9200600E4C /* SeaEyePopoverController.xib */, F4F4807721E518CE0038DC09 /* PreferencesWindow.xib */, ); @@ -363,7 +357,6 @@ 60C73C0719FC23F60067CDCA /* SeaEyeIconController.swift */, 60C73C0519FC16FE0067CDCA /* SeaEyePopoverController.swift */, 60C73C1319FD7ECB0067CDCA /* SeaEyeBuildsController.swift */, - 60C73C1719FD7EE10067CDCA /* SeaEyeSettingsController.swift */, 60F0232B1A1A95930067C0A0 /* SeaEyeUpdatesController.swift */, F4F4808521E51B390038DC09 /* preferences */, ); @@ -510,7 +503,6 @@ 600869141A1D3EA000600E4C /* SeaEyeUpdatesController.xib in Resources */, 601D48CE1A0A841F007EA297 /* icon_32x32@2x.png in Resources */, 601D48CC1A0A841F007EA297 /* icon_256x256.png in Resources */, - 600869101A1D3E6F00600E4C /* SeaEyeSettingsController.xib in Resources */, 607C77F21A1860070040FD5C /* build-failed@2x.png in Resources */, F4F4807821E518CE0038DC09 /* PreferencesWindow.xib in Resources */, 602BB1C11A1A93E900E50B78 /* update@2x.png in Resources */, @@ -562,7 +554,6 @@ F4F4808221E5196B0038DC09 /* UnfollowedProjectsController.swift in Sources */, F4F4807E21E519350038DC09 /* PreferencesWindowController.swift in Sources */, 60C73C0619FC16FE0067CDCA /* SeaEyePopoverController.swift in Sources */, - 60C73C1819FD7EE10067CDCA /* SeaEyeSettingsController.swift in Sources */, F4D5A43B21E40E4C006140DE /* Notifications.swift in Sources */, F412CA9D21D3ED0D0069C7C7 /* BuildDecorator.swift in Sources */, F412CA9F21D3EFEF0069C7C7 /* DateFormatter.swift in Sources */, @@ -624,7 +615,6 @@ 5394798A1FCB908E00D6FA19 /* SeaEyeStatus.swift in Sources */, F4D5A44721E420B5006140DE /* ApplicationStartupManager.swift in Sources */, F4D5A43921E3FE32006140DE /* BuildSummary.swift in Sources */, - F4D5A44121E4206D006140DE /* SeaEyeSettingsController.swift in Sources */, 539479891FCB908E00D6FA19 /* Project.swift in Sources */, F4D5A43E21E41FEB006140DE /* SeaEyeStatusBar.swift in Sources */, F4D5A46421E4619C006140DE /* SeaEyeStatusBarListenerTest.swift in Sources */, diff --git a/SeaEye/controllers/SeaEyePopoverController.swift b/SeaEye/controllers/SeaEyePopoverController.swift index af0a57e..d7f38b6 100644 --- a/SeaEye/controllers/SeaEyePopoverController.swift +++ b/SeaEye/controllers/SeaEyePopoverController.swift @@ -15,7 +15,6 @@ class SeaEyePopoverController: NSViewController, BuildSetter { @IBOutlet weak var openUpdatesButton: NSButton! @IBOutlet weak var shutdownButton: NSButton! - var settingsViewController: SeaEyeSettingsController! var buildsViewController: SeaEyeBuildsController! var updatesViewController: SeaEyeUpdatesController! var applicationStatus: SeaEyeStatus! @@ -58,14 +57,12 @@ class SeaEyePopoverController: NSViewController, BuildSetter { fileprivate func setupViewControllers() { setupNibControllers() - settingsViewController.parentController = self updatesViewController.applicationStatus = self.applicationStatus openBuildsButton.isHidden = true subcontrollerView.addSubview(buildsViewController.view) } fileprivate func setupNibControllers() { - settingsViewController = SeaEyeSettingsController(nibName: "SeaEyeSettingsController", bundle: nil) buildsViewController = SeaEyeBuildsController(nibName: "SeaEyeBuildsController", bundle: nil) // If we have builds from client, we hold them until we can push them into the buildViewsController if heldBuilds.count > 0 { @@ -80,12 +77,6 @@ class SeaEyePopoverController: NSViewController, BuildSetter { prefrencesWindowVC.iconController = iconController prefrencesWindowVC.showWindow(self) iconController?.closePopover(nil) -// openSettingsButton.isHidden = true -// openUpdatesButton.isHidden = true -// shutdownButton.isHidden = true -// openBuildsButton.isHidden = false -// buildsViewController.view.removeFromSuperview() -// subcontrollerView.addSubview(settingsViewController.view) } @IBAction func openBuilds(_ sender: NSButton) { @@ -93,7 +84,6 @@ class SeaEyePopoverController: NSViewController, BuildSetter { openBuildsButton.isHidden = true shutdownButton.isHidden = false openSettingsButton.isHidden = false - settingsViewController.view.removeFromSuperview() updatesViewController.view.removeFromSuperview() subcontrollerView.addSubview(buildsViewController.view) } diff --git a/SeaEye/controllers/SeaEyeSettingsController.swift b/SeaEye/controllers/SeaEyeSettingsController.swift deleted file mode 100644 index 63a206c..0000000 --- a/SeaEye/controllers/SeaEyeSettingsController.swift +++ /dev/null @@ -1,112 +0,0 @@ -// -// SeaEyeSettingsController.swift -// SeaEye -// -// Created by Eoin Nolan on 26/10/2014. -// Copyright (c) 2014 Nolaneo. All rights reserved. -// - -import Cocoa - -class SeaEyeSettingsController: NSViewController { - - var parentController: SeaEyePopoverController! - - @IBOutlet weak var runOnStartup: NSButton! - @IBOutlet weak var showNotifications: NSButton! - - @IBOutlet weak var apiKeyField: NSTextField! - @IBOutlet weak var organizationField: NSTextField! - @IBOutlet weak var projectsField: NSTextField! - @IBOutlet weak var usersField: NSTextField! - @IBOutlet weak var branchesField: NSTextField! - - @IBOutlet weak var versionString: NSTextField! - - let appDelegate = NSApplication.shared.delegate -// let settings = Settings.load() - - override init(nibName nibNameOrNil: NSNib.Name?, bundle nibBundleOrNil: Bundle?) { - super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) - } - - required init?(coder: NSCoder) { - super.init(coder: coder) - } - - override func viewDidLoad() { - super.viewDidLoad() - setupVersionNumber() - } - - override func viewWillAppear() { - setupInputFields() - } - - @IBAction func openAPIPage(_ sender: NSButton) { - NSWorkspace.shared.open(URL(string: "https://circleci.com/account/api")!) - } - - @IBAction func saveUserData(_ sender: NSButton) { -// let notify = showNotifications.state == .on -// UserDefaults.standard.set(notify, forKey: SettingsV0.Keys.notify.rawValue) -// setUserDefaultsFromField(apiKeyField, key: SettingsV0.Keys.apiKey.rawValue) -// setUserDefaultsFromField(organizationField, key: SettingsV0.Keys.organisation.rawValue) -// setUserDefaultsFromField(projectsField, key: SettingsV0.Keys.projects.rawValue) -// setUserDefaultsFromField(branchesField, key: SettingsV0.Keys.branchFilter.rawValue) -// setUserDefaultsFromField(usersField, key: SettingsV0.Keys.userFilter.rawValue) -// NotificationCenter.default.post(name: Notification.Name(rawValue: "SeaEyeSettingsChanged"), object: nil) -// parentController.openBuilds(sender) - } - - @IBAction func saveNotificationPreferences(_ sender: NSButton) { - let notify = showNotifications.state == .on - print("Notificaiton Preference: \(notify)") -// UserDefaults.standard.set(notify, forKey: SettingsV0.Keys.notify.rawValue) - } - - @IBAction func saveRunOnStartupPreferences(_ sender: NSButton) { - print("Changing launch on startup") - ApplicationStartupManager.toggleLaunchAtStartup() - } - - fileprivate func setUserDefaultsFromField(_ field: NSTextField, key: String) { - let userDefaults = UserDefaults.standard - let fieldValue = field.stringValue - if fieldValue.isEmpty { - userDefaults.removeObject(forKey: key) - } else { - userDefaults.setValue(fieldValue, forKey: key) - } - } - - fileprivate func setupInputFields() { -// if settings.notify { -// showNotifications.state = NSControl.StateValue.on -// } else { -// showNotifications.state = NSControl.StateValue.off -// } -// let hasRunOnStartup = ApplicationStartupManager.applicationIsInStartUpItems() -// if hasRunOnStartup { -// runOnStartup.state = NSControl.StateValue.on -// } else { -// runOnStartup.state = NSControl.StateValue.off -// } -// setupFieldFromUserDefaults(apiKeyField, key: SettingsV0.Keys.apiKey.rawValue) -// setupFieldFromUserDefaults(organizationField, key: SettingsV0.Keys.organisation.rawValue) -// setupFieldFromUserDefaults(projectsField, key: SettingsV0.Keys.projects.rawValue) -// setupFieldFromUserDefaults(branchesField, key: SettingsV0.Keys.branchFilter.rawValue) -// setupFieldFromUserDefaults(usersField, key: SettingsV0.Keys.userFilter.rawValue) - } - - fileprivate func setupFieldFromUserDefaults(_ field: NSTextField, key: String) { - let userDefaults = UserDefaults.standard - if let savedValue = userDefaults.string(forKey: key) { - field.stringValue = savedValue - } - } - - fileprivate func setupVersionNumber() { - versionString.stringValue = VersionNumber.current().description - } -} diff --git a/SeaEye/views/SeaEyeSettingsController.xib b/SeaEye/views/SeaEyeSettingsController.xib deleted file mode 100644 index 9d59557..0000000 --- a/SeaEye/views/SeaEyeSettingsController.xib +++ /dev/null @@ -1,182 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - NSAllRomanInputSourcesLocaleIdentifier - - - - - - - - - - - - - - - - - - From 2bae30ad4aee1fed7ded9341dc05e29f51b36e32 Mon Sep 17 00:00:00 2001 From: Conor Mongey Date: Thu, 10 Jan 2019 14:07:10 +0000 Subject: [PATCH 03/10] Remove unused icons --- SeaEye/circleci-normal-alt.png | Bin 3075 -> 0 bytes SeaEye/circleci-normal.png | Bin 3046 -> 0 bytes 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 SeaEye/circleci-normal-alt.png delete mode 100644 SeaEye/circleci-normal.png diff --git a/SeaEye/circleci-normal-alt.png b/SeaEye/circleci-normal-alt.png deleted file mode 100644 index b79ecb23d2120ae1396d8d67c7279256ac0db93a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3075 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 Ro^}8L002ovPDHLkV1g=3!?XYZ diff --git a/SeaEye/circleci-normal.png b/SeaEye/circleci-normal.png deleted file mode 100644 index ba1a9c36cc13c325e1f57d13072504106c34dad6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3046 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 From b558f70ee181b19cf8dd82d1c356a1d84a1cf50f Mon Sep 17 00:00:00 2001 From: Conor Mongey Date: Mon, 28 Jan 2019 18:08:15 +0000 Subject: [PATCH 04/10] Ensure prefrences window appears at the front --- SeaEye/controllers/SeaEyePopoverController.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/SeaEye/controllers/SeaEyePopoverController.swift b/SeaEye/controllers/SeaEyePopoverController.swift index d7f38b6..96ae646 100644 --- a/SeaEye/controllers/SeaEyePopoverController.swift +++ b/SeaEye/controllers/SeaEyePopoverController.swift @@ -73,6 +73,8 @@ class SeaEyePopoverController: NSViewController, BuildSetter { } @IBAction func openSettings(_ sender: NSButton) { + NSApp.activate(ignoringOtherApps: true) // force the settings window to the front + let prefrencesWindowVC = PreferencesWindowController() prefrencesWindowVC.iconController = iconController prefrencesWindowVC.showWindow(self) From 3547e9e2f0bc45b599852c55819bef88fd895ec9 Mon Sep 17 00:00:00 2001 From: Conor Mongey Date: Tue, 19 Feb 2019 14:49:24 +0000 Subject: [PATCH 05/10] Extract structs and extensions --- SeaEye.xcodeproj/project.pbxproj | 18 ++++ .../controllers/SeaEyeBuildsController.swift | 1 - .../CurrentProjectsController.swift | 10 +- .../preferences/PreferencesWindow.swift | 78 ++++++--------- .../controllers/preferences/ServerTable.swift | 16 ++-- SeaEye/models/ClientProject.swift | 6 ++ SeaEye/models/Settings.swift | 96 ++++++++----------- SeaEye/models/SettingsV0.swift | 70 +------------- SeaEye/views/SelectedServerView.swift | 14 +++ 9 files changed, 121 insertions(+), 188 deletions(-) create mode 100644 SeaEye/models/ClientProject.swift create mode 100644 SeaEye/views/SelectedServerView.swift diff --git a/SeaEye.xcodeproj/project.pbxproj b/SeaEye.xcodeproj/project.pbxproj index 146d063..2c9a0d0 100644 --- a/SeaEye.xcodeproj/project.pbxproj +++ b/SeaEye.xcodeproj/project.pbxproj @@ -53,6 +53,12 @@ F422C54920D48CDD002A5897 /* VersionNumber.swift in Sources */ = {isa = PBXBuildFile; fileRef = F422C54720D48CC2002A5897 /* VersionNumber.swift */; }; F422C54B20D48CF2002A5897 /* VersionNumberTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F422C54A20D48CF2002A5897 /* VersionNumberTests.swift */; }; F4472C0C216D72B700ECD077 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F4472C0B216D72B700ECD077 /* Main.storyboard */; }; + F4917465221C4B5300CAF9C1 /* SelectedServerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4917464221C4B5300CAF9C1 /* SelectedServerView.swift */; }; + F4917466221C4CA600CAF9C1 /* SelectedServerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4917464221C4B5300CAF9C1 /* SelectedServerView.swift */; }; + F4917468221C4E0F00CAF9C1 /* ClientProject.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4917467221C4E0F00CAF9C1 /* ClientProject.swift */; }; + F4917469221C4E0F00CAF9C1 /* ClientProject.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4917467221C4E0F00CAF9C1 /* ClientProject.swift */; }; + F491746B221C4E4000CAF9C1 /* Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = F491746A221C4E4000CAF9C1 /* Settings.swift */; }; + F491746C221C4E4000CAF9C1 /* Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = F491746A221C4E4000CAF9C1 /* Settings.swift */; }; F4B672D821B60A7C003E30A9 /* ApplicationStartupManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4B672D721B60A7C003E30A9 /* ApplicationStartupManager.swift */; }; F4D5A43221E3F6B3006140DE /* SettingsV0.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4D5A43121E3F6B3006140DE /* SettingsV0.swift */; }; F4D5A43321E3F6B3006140DE /* SettingsV0.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4D5A43121E3F6B3006140DE /* SettingsV0.swift */; }; @@ -160,6 +166,9 @@ F422C54720D48CC2002A5897 /* VersionNumber.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionNumber.swift; sourceTree = ""; }; F422C54A20D48CF2002A5897 /* VersionNumberTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionNumberTests.swift; sourceTree = ""; }; F4472C0B216D72B700ECD077 /* Main.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = ""; }; + F4917464221C4B5300CAF9C1 /* SelectedServerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectedServerView.swift; sourceTree = ""; }; + F4917467221C4E0F00CAF9C1 /* ClientProject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientProject.swift; sourceTree = ""; }; + F491746A221C4E4000CAF9C1 /* Settings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Settings.swift; sourceTree = ""; }; F4B672D721B60A7C003E30A9 /* ApplicationStartupManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationStartupManager.swift; sourceTree = ""; }; F4D5A43121E3F6B3006140DE /* SettingsV0.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsV0.swift; sourceTree = ""; }; F4D5A43421E3F782006140DE /* FallbackView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FallbackView.swift; sourceTree = ""; }; @@ -225,6 +234,7 @@ 6014AD8D19FED4C400865329 /* BuildView.swift */, F4D5A43421E3F782006140DE /* FallbackView.swift */, F4D5A43C21E41FEB006140DE /* SeaEyeStatusBar.swift */, + F4917464221C4B5300CAF9C1 /* SelectedServerView.swift */, 600869131A1D3EA000600E4C /* SeaEyeUpdatesController.xib */, 6008690D1A1D3E5E00600E4C /* SeaEyeBuildsController.xib */, 600869111A1D3E9200600E4C /* SeaEyePopoverController.xib */, @@ -250,11 +260,13 @@ 603314A419FDCBD90073EAED /* models */ = { isa = PBXGroup; children = ( + F4917467221C4E0F00CAF9C1 /* ClientProject.swift */, 5394797C1FCB8C5200D6FA19 /* CircleCIBuild.swift */, F4D5A44921E427BB006140DE /* Filter.swift */, 6014AD981A09874D00865329 /* Project.swift */, 607C78251A190C580040FD5C /* SeaEyeStatus.swift */, F4D5A43121E3F6B3006140DE /* SettingsV0.swift */, + F491746A221C4E4000CAF9C1 /* Settings.swift */, ); path = models; sourceTree = ""; @@ -551,13 +563,16 @@ F4D5A43D21E41FEB006140DE /* SeaEyeStatusBar.swift in Sources */, 60C73C1519FD7ECB0067CDCA /* SeaEyeBuildsController.swift in Sources */, F4D5A45721E451BA006140DE /* TextPrinter.swift in Sources */, + F4917465221C4B5300CAF9C1 /* SelectedServerView.swift in Sources */, F4F4808221E5196B0038DC09 /* UnfollowedProjectsController.swift in Sources */, F4F4807E21E519350038DC09 /* PreferencesWindowController.swift in Sources */, 60C73C0619FC16FE0067CDCA /* SeaEyePopoverController.swift in Sources */, + F491746B221C4E4000CAF9C1 /* Settings.swift in Sources */, F4D5A43B21E40E4C006140DE /* Notifications.swift in Sources */, F412CA9D21D3ED0D0069C7C7 /* BuildDecorator.swift in Sources */, F412CA9F21D3EFEF0069C7C7 /* DateFormatter.swift in Sources */, 5394797D1FCB8C5200D6FA19 /* CircleCIBuild.swift in Sources */, + F4917468221C4E0F00CAF9C1 /* ClientProject.swift in Sources */, F4D5A45D21E454F4006140DE /* NotificationListener.swift in Sources */, 60F0232C1A1A95930067C0A0 /* SeaEyeUpdatesController.swift in Sources */, F4D5A43821E3FE32006140DE /* BuildSummary.swift in Sources */, @@ -595,6 +610,7 @@ F4F4807421E471DE0038DC09 /* SettingsTest.swift in Sources */, F4F1A61E20D84DBE008B8C04 /* GithubClient.swift in Sources */, F4D5A44821E420C5006140DE /* Notifications.swift in Sources */, + F4917469221C4E0F00CAF9C1 /* ClientProject.swift in Sources */, 5389EF4A1FCBBA49001C0D5F /* DecodingTests.swift in Sources */, F422C54B20D48CF2002A5897 /* VersionNumberTests.swift in Sources */, F4D5A44621E420A4006140DE /* BuildDecorator.swift in Sources */, @@ -617,8 +633,10 @@ F4D5A43921E3FE32006140DE /* BuildSummary.swift in Sources */, 539479891FCB908E00D6FA19 /* Project.swift in Sources */, F4D5A43E21E41FEB006140DE /* SeaEyeStatusBar.swift in Sources */, + F4917466221C4CA600CAF9C1 /* SelectedServerView.swift in Sources */, F4D5A46421E4619C006140DE /* SeaEyeStatusBarListenerTest.swift in Sources */, F4D5A45421E44BF1006140DE /* NewBuildFilter.swift in Sources */, + F491746C221C4E4000CAF9C1 /* Settings.swift in Sources */, F4D5A45E21E454F4006140DE /* NotificationListener.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/SeaEye/controllers/SeaEyeBuildsController.swift b/SeaEye/controllers/SeaEyeBuildsController.swift index 8495b26..76ae801 100644 --- a/SeaEye/controllers/SeaEyeBuildsController.swift +++ b/SeaEye/controllers/SeaEyeBuildsController.swift @@ -36,7 +36,6 @@ class SeaEyeBuildsController: NSViewController, NSTableViewDelegate, NSTableView } func reloadBuilds(_: Any? = nil) { -// print("Reload builds!") buildsTable?.reloadData() if fallbackView != nil { setupFallBackViews() diff --git a/SeaEye/controllers/preferences/CurrentProjectsController.swift b/SeaEye/controllers/preferences/CurrentProjectsController.swift index ace5069..2c57f59 100644 --- a/SeaEye/controllers/preferences/CurrentProjectsController.swift +++ b/SeaEye/controllers/preferences/CurrentProjectsController.swift @@ -44,15 +44,10 @@ class CurrentProjectsController: NSObject, NSTextFieldDelegate { print("\(itemsSelected) no row is selected") } } - - func controlTextDidEndEditing(_ obj: Notification) { -// print(obj) - print("Editing \(tableView.editedRow) - \(tableView.editedColumn)") - print("Clicked \(tableView.clickedRow) - \(tableView.clickedColumn)") - print("Selected \(tableView.selectedRow) - \(tableView.selectedColumn)") + func controlTextDidEndEditing(_ obj: Notification) { if let textField = obj.object as? NSTextField { - print("just set some value \(textField.tag) to \(textField.stringValue)") +// print("just set some value \(textField.tag) to \(textField.stringValue)") let branchFilterTag = 1 let userFilterTag = 2 @@ -174,7 +169,6 @@ extension CurrentProjectsController: NSTableViewDataSource { func tableView(_ view: NSTableView, objectValueFor col: NSTableColumn?, row index: Int) -> Any? { -// print("\(index) \(col) \(col?.identifier.rawValue)") let project = projects[index] switch col?.identifier.rawValue { case "projectName": diff --git a/SeaEye/controllers/preferences/PreferencesWindow.swift b/SeaEye/controllers/preferences/PreferencesWindow.swift index 071b569..d00890c 100644 --- a/SeaEye/controllers/preferences/PreferencesWindow.swift +++ b/SeaEye/controllers/preferences/PreferencesWindow.swift @@ -1,17 +1,6 @@ import Cocoa import Foundation -struct SelectedServerView { - var apiServerApiPath: NSTextField! - var apiServerAuthToken: NSTextField! - var apiServerTestButton: NSButton! - - func fill(client: CircleCIClient) { - apiServerApiPath.stringValue = client.baseURL - apiServerAuthToken.stringValue = client.token - apiServerTestButton.isEnabled = !client.token.isEmpty - } -} protocol ProjectFollowedDelegate { func addProject(project: Project) @@ -25,7 +14,6 @@ protocol ProjectUpdatedDelegate { func projectUpdated(projectIndex: Int, project: Project) } -// This is a kind-of-controller, that is used to class PreferencesWindow: NSWindow, NSWindowDelegate, NSTabViewDelegate, ProjectFollowedDelegate, ProjectUnfollowedDelegate, ProjectUpdatedDelegate { // Preferences window @IBOutlet var projectsTable: NSTableView! @@ -33,9 +21,9 @@ class PreferencesWindow: NSWindow, NSWindowDelegate, NSTabViewDelegate, ProjectF @IBOutlet var launchAtStartup: NSButton! @IBOutlet var knownProjectsTable: NSTableView! - var serverListView: ServerTable? - var currentProjectsView: CurrentProjectsController! - var unfollowedProjectsTableView: UnfollowedProjectsController? + private var serverListView: ServerTable? + private var currentProjectsView: CurrentProjectsController! + private var unfollowedProjectsTableView: UnfollowedProjectsController? // Servers @IBOutlet var serverList: NSTableView! @@ -73,50 +61,40 @@ class PreferencesWindow: NSWindow, NSWindowDelegate, NSTabViewDelegate, ProjectF } func addProject(project: Project) { - if let client = self.serverListView?.selectedClient() { - print("Add \(project) to \(client)") - - if let index = self.serverListView?.selectedIndex { - print("Add Project \(index)") - settings.clientProjects[index].projects.append(project) - settings.save() - currentProjectsView.set(projects: settings.clientProjects[index].projects) - serverListView?.reloadFromSettings() - iconController?.reset() - } + print("Add \(project)") + + if let index = self.serverListView?.selectedIndex { + print("Add Project \(index)") + settings.clientProjects[index].projects.append(project) + settings.save() + currentProjectsView.set(projects: settings.clientProjects[index].projects) + serverListView?.reloadFromSettings() + iconController?.reset() } } func removeProject(projectIndex: Int) { - if var client = self.serverListView?.selectedClient() { - print("Remove Project \(projectIndex)") - -// print("Remove \(client.projects[projectIndex]) to \(client)") -// - if let index = self.serverListView?.selectedIndex { - settings.clientProjects[index].projects.remove(at: projectIndex) - settings.save() - serverListView?.reloadFromSettings() - currentProjectsView.set(projects: settings.clientProjects[index].projects) - iconController?.reset() - } + print("Remove Project \(projectIndex)") + + if let index = self.serverListView?.selectedIndex { + settings.clientProjects[index].projects.remove(at: projectIndex) + settings.save() + serverListView?.reloadFromSettings() + currentProjectsView.set(projects: settings.clientProjects[index].projects) + iconController?.reset() } } func projectUpdated(projectIndex: Int, project: Project) { - if var client = self.serverListView?.selectedClient() { - print("Project @ \(projectIndex) updated") - -// print("Update \(client.clientProjects[projectIndex]) to \n \(project)") -// - if let index = self.serverListView?.selectedIndex { - settings.clientProjects[index].projects[projectIndex] = project - settings.save() - currentProjectsView.set(projects: settings.clientProjects[index].projects) - iconController?.reset() - } + print("Project @ \(projectIndex) updated") + + if let index = self.serverListView?.selectedIndex { + settings.clientProjects[index].projects[projectIndex] = project + settings.save() + currentProjectsView.set(projects: settings.clientProjects[index].projects) + iconController?.reset() + } - } else {} } func tabView(_: NSTabView, willSelect: NSTabViewItem?) { diff --git a/SeaEye/controllers/preferences/ServerTable.swift b/SeaEye/controllers/preferences/ServerTable.swift index c05cb05..28c54d6 100644 --- a/SeaEye/controllers/preferences/ServerTable.swift +++ b/SeaEye/controllers/preferences/ServerTable.swift @@ -1,7 +1,7 @@ import Cocoa import Foundation -class ServerTable: NSObject, NSTableViewDelegate, NSTableViewDataSource { +class ServerTable: NSObject { let cellIdentifier = NSUserInterfaceItemIdentifier(rawValue: "serverCell") var selectedIndex: Int = 0 var clients: [CircleCIClient] @@ -46,7 +46,7 @@ class ServerTable: NSObject, NSTableViewDelegate, NSTableViewDataSource { return nil } - func selectFirstClient() { + private func selectFirstClient() { tableView.selectRowIndexes([0], byExtendingSelection: false) if let mclient = selectedClient() { @@ -60,7 +60,15 @@ class ServerTable: NSObject, NSTableViewDelegate, NSTableViewDataSource { clients = Settings.load().clients() tableView.reloadData() } +} + +extension ServerTable : NSTableViewDataSource { + func numberOfRows(in _: NSTableView) -> Int { + return clients.count + } +} +extension ServerTable : NSTableViewDelegate { func tableView(_ tableView: NSTableView, viewFor _: NSTableColumn?, row: Int) -> NSView? { if let cell = tableView.makeView(withIdentifier: cellIdentifier, owner: nil) as? NSTableCellView { cell.textField?.stringValue = clients[row].baseURL @@ -74,8 +82,4 @@ class ServerTable: NSObject, NSTableViewDelegate, NSTableViewDataSource { view.fill(client: clients[row]) return true } - - func numberOfRows(in _: NSTableView) -> Int { - return clients.count - } } diff --git a/SeaEye/models/ClientProject.swift b/SeaEye/models/ClientProject.swift new file mode 100644 index 0000000..ef356b5 --- /dev/null +++ b/SeaEye/models/ClientProject.swift @@ -0,0 +1,6 @@ +import Foundation + +struct ClientProject: Codable { + var client: CircleCIClient + var projects: [Project] +} diff --git a/SeaEye/models/Settings.swift b/SeaEye/models/Settings.swift index 01b03f8..3289574 100644 --- a/SeaEye/models/Settings.swift +++ b/SeaEye/models/Settings.swift @@ -1,76 +1,64 @@ import Foundation struct Settings: Codable { - enum Keys: String { - case apiKey = "SeaEyeAPIKey" - case organisation = "SeaEyeOrganization" - case projects = "SeaEyeProjects" - case branchFilter = "SeaEyeBranches" - case userFilter = "SeaEyeUsers" - case notify = "SeaEyeNotify" - } + private static let defaultsKey: String = "SeaEyeSettings2" - var apiKey: String? - var organization: String? - var projectsString: String? - var branchFilter: String? - var userFilter: String? - var notify: Bool = true + var clientProjects: [ClientProject] static func load(userDefaults: UserDefaults = UserDefaults.standard) -> Settings { - var settings = self.init() - settings.apiKey = userDefaults.string(forKey: Keys.apiKey.rawValue) - settings.organization = userDefaults.string(forKey: Keys.organisation.rawValue) - settings.projectsString = userDefaults.string(forKey: Keys.projects.rawValue) - settings.branchFilter = userDefaults.string(forKey: Keys.branchFilter.rawValue) - settings.userFilter = userDefaults.string(forKey: Keys.userFilter.rawValue) - settings.notify = userDefaults.bool(forKey: Keys.notify.rawValue) + let oldSettings = SettingsV0.load(userDefaults: userDefaults) + var settings = Settings(clientProjects: []) + if let settingsString = userDefaults.string(forKey: self.defaultsKey) { + let decoder = JSONDecoder() + if let data = settingsString.data(using: .utf8) { + do { + settings = try decoder.decode(Settings.self, from: data) + return settings + } catch let error { + print(error.localizedDescription) + } + } + } + print("Migrating old settings to new settings format") + settings = oldSettings.toSettings() + settings.save(userDefaults: userDefaults) return settings } - func projects() -> [ClientProject] { - let projectNames = projectsString?.components(separatedBy: CharacterSet.whitespaces) - if (apiKey == nil) { - return [] + func save(userDefaults: UserDefaults = UserDefaults.standard) { + for client in clientProjects { + print("Saving \(client)") } - let client = CircleCIClient.init(apiKey: apiKey!) - if !valid() { - return [] - } - var projects = [Project]() - if let projectNames = projectNames { - projects = projectNames.map { - let filter = Filter.init(userFilter: userFilter, branchFilter: branchFilter) - // github is hardcoded, as it was assumed - return Project.init(vcsProvider: "github", - organisation: self.organization!, - name: $0, - filter: filter, - notify: self.notify) - } + let jsonEncoder = JSONEncoder() + + do { + let jsonData = try jsonEncoder.encode(self) + let json = String(data: jsonData, encoding: String.Encoding.utf8) + userDefaults.setValue(json, forKey: Settings.defaultsKey) + } catch let error { + print(error.localizedDescription) } - return [ClientProject.init(client: client, projects: projects)] } func clientBuildUpdateListeners(listeners: [BuildUpdateListener]) -> [ClientBuildUpdater] { - let clientProjects = projects() return clientProjects.flatMap { (cp) -> [ClientBuildUpdater] in - cp.projects.map { - return ClientBuildUpdater(listeners: listeners, - client: cp.client, - project: $0) - } + return cp.projects.map({ + ClientBuildUpdater(listeners: listeners, + client: cp.client, + project: $0) + }) } } - func valid() -> Bool { - return apiKey != nil && organization != nil && projectsString != nil + func clients() -> [CircleCIClient] { + return self.clientProjects.map { $0.client} + } + func projects() -> [Project] { + return Array(clientProjects.map {$0.projects}.joined()) + } + func numberOfProjects() -> Int { + return clientProjects.compactMap { $0.projects.count }.reduce(0, { $0 + $1 }) } -} - -struct ClientProject: Codable { - let client: CircleCIClient - let projects: [Project] } diff --git a/SeaEye/models/SettingsV0.swift b/SeaEye/models/SettingsV0.swift index 53b3c1a..40b0aad 100644 --- a/SeaEye/models/SettingsV0.swift +++ b/SeaEye/models/SettingsV0.swift @@ -1,69 +1,6 @@ import Foundation -struct Settings: Codable { - private static let defaultsKey: String = "SeaEyeSettings2" - - var clientProjects: [ClientProject] - - static func load(userDefaults: UserDefaults = UserDefaults.standard) -> Settings { - let oldSettings = SettingsV0.load(userDefaults: userDefaults) - - var settings = Settings(clientProjects: []) - if let settingsString = userDefaults.string(forKey: self.defaultsKey) { - let decoder = JSONDecoder() - if let data = settingsString.data(using: .utf8) { - do { - settings = try decoder.decode(Settings.self, from: data) - return settings - } catch let error { - print(error.localizedDescription) - } - } - } - print("Migrating old settings to new settings format") - settings = oldSettings.toSettings() - settings.save(userDefaults: userDefaults) - return settings - } - - func save(userDefaults: UserDefaults = UserDefaults.standard) { - for client in clientProjects { - print("Saving \(client)") - } - - let jsonEncoder = JSONEncoder() - - do { - let jsonData = try jsonEncoder.encode(self) - let json = String(data: jsonData, encoding: String.Encoding.utf8) - userDefaults.setValue(json, forKey: Settings.defaultsKey) - } catch let error { - print(error.localizedDescription) - } - } - - func clientBuildUpdateListeners(listeners: [BuildUpdateListener]) -> [ClientBuildUpdater] { - return clientProjects.flatMap { (cp) -> [ClientBuildUpdater] in - return cp.projects.map({ - ClientBuildUpdater(listeners: listeners, - client: cp.client, - project: $0) - }) - } - } - - func clients() -> [CircleCIClient] { - return self.clientProjects.map { $0.client} - } - func projects() -> [Project] { - return Array(clientProjects.map {$0.projects}.joined()) - } - func numberOfProjects() -> Int { - return clientProjects.compactMap { $0.projects.count }.reduce(0, { $0 + $1 }) - } -} - -private struct SettingsV0: Codable { +struct SettingsV0: Codable { enum Keys: String { case apiKey = "SeaEyeAPIKey" case organisation = "SeaEyeOrganization" @@ -142,8 +79,3 @@ private struct SettingsV0: Codable { return apiKey != nil && organization != nil && projectsString != nil } } - -struct ClientProject: Codable { - var client: CircleCIClient - var projects: [Project] -} diff --git a/SeaEye/views/SelectedServerView.swift b/SeaEye/views/SelectedServerView.swift new file mode 100644 index 0000000..473ca30 --- /dev/null +++ b/SeaEye/views/SelectedServerView.swift @@ -0,0 +1,14 @@ +import Foundation +import Cocoa + +struct SelectedServerView { + var apiServerApiPath: NSTextField! + var apiServerAuthToken: NSTextField! + var apiServerTestButton: NSButton! + + func fill(client: CircleCIClient) { + apiServerApiPath.stringValue = client.baseURL + apiServerAuthToken.stringValue = client.token + apiServerTestButton.isEnabled = !client.token.isEmpty + } +} From 84fde875a0ba5a33399fe6e6bebea3163cdd7500 Mon Sep 17 00:00:00 2001 From: Conor Mongey Date: Tue, 19 Feb 2019 14:57:51 +0000 Subject: [PATCH 06/10] Ensure bundler is installed on CI --- .circleci/config.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index edc9963..f2bac7c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -11,6 +11,7 @@ jobs: shell: /bin/bash --login -eo pipefail steps: - checkout + - run: gem install bundler -v 2.0.1 - run: bundle install - run: bundle exec fastlane gym - run: bundle exec fastlane test From 06983b88c30ab44c105337cd731f850b4c1b1958 Mon Sep 17 00:00:00 2001 From: Conor Mongey Date: Tue, 19 Feb 2019 14:53:48 +0000 Subject: [PATCH 07/10] Update fastlane --- Gemfile.lock | 89 ++++++++++++++++++++++++++++------------------------ 1 file changed, 48 insertions(+), 41 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index ee8134f..646f62d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -2,9 +2,9 @@ GEM remote: https://rubygems.org/ specs: CFPropertyList (3.0.0) - addressable (2.5.2) + addressable (2.6.0) public_suffix (>= 2.0.2, < 4.0) - atomos (0.1.2) + atomos (0.1.3) babosa (1.0.2) claide (1.0.2) colored (1.2) @@ -13,35 +13,37 @@ GEM highline (~> 1.7.2) declarative (0.0.10) declarative-option (0.1.0) + digest-crc (0.4.1) domain_name (0.5.20180417) unf (>= 0.0.5, < 1.0.0) - dotenv (2.4.0) - emoji_regex (0.1.1) + dotenv (2.6.0) + emoji_regex (1.0.1) excon (0.62.0) - faraday (0.15.2) + faraday (0.15.4) multipart-post (>= 1.2, < 3) faraday-cookie_jar (0.0.6) faraday (>= 0.7.4) http-cookie (~> 1.0.0) - faraday_middleware (0.12.2) + faraday_middleware (0.13.1) faraday (>= 0.7.4, < 1.0) - fastimage (2.1.3) - fastlane (2.97.0) + fastimage (2.1.5) + fastlane (2.116.0) CFPropertyList (>= 2.3, < 4.0.0) addressable (>= 2.3, < 3.0.0) babosa (>= 1.0.2, < 2.0.0) - bundler (>= 1.12.0, < 2.0.0) + bundler (>= 1.12.0, < 3.0.0) colored commander-fastlane (>= 4.4.6, < 5.0.0) dotenv (>= 2.1.1, < 3.0.0) - emoji_regex (~> 0.1) + emoji_regex (>= 0.1, < 2.0) excon (>= 0.45.0, < 1.0.0) faraday (~> 0.9) faraday-cookie_jar (~> 0.0.6) faraday_middleware (~> 0.9) fastimage (>= 2.1.0, < 3.0.0) gh_inspector (>= 1.1.2, < 2.0.0) - google-api-client (>= 0.21.2, < 0.22.0) + google-api-client (>= 0.21.2, < 0.24.0) + google-cloud-storage (>= 1.15.0, < 2.0.0) highline (>= 1.7.2, < 2.0.0) json (< 3.0.0) mini_magick (~> 4.5.1) @@ -50,7 +52,7 @@ GEM multipart-post (~> 2.0.0) plist (>= 3.1.0, < 4.0.0) public_suffix (~> 2.0.0) - rubyzip (>= 1.2.1, < 2.0.0) + rubyzip (>= 1.2.2, < 2.0.0) security (= 0.1.3) simctl (~> 1.6.3) slack-notifier (>= 2.0.0, < 3.0.0) @@ -59,24 +61,33 @@ GEM tty-screen (>= 0.6.3, < 1.0.0) tty-spinner (>= 0.8.0, < 1.0.0) word_wrap (~> 1.0.0) - xcodeproj (>= 1.5.7, < 2.0.0) - xcpretty (>= 0.2.4, < 1.0.0) + xcodeproj (>= 1.6.0, < 2.0.0) + xcpretty (~> 0.3.0) xcpretty-travis-formatter (>= 0.0.3) gh_inspector (1.1.3) - google-api-client (0.21.2) + google-api-client (0.23.9) addressable (~> 2.5, >= 2.5.1) googleauth (>= 0.5, < 0.7.0) httpclient (>= 2.8.1, < 3.0) mime-types (~> 3.0) representable (~> 3.0) retriable (>= 2.0, < 4.0) - googleauth (0.6.2) + signet (~> 0.9) + google-cloud-core (1.3.0) + google-cloud-env (~> 1.0) + google-cloud-env (1.0.5) + faraday (~> 0.11) + google-cloud-storage (1.16.0) + digest-crc (~> 0.4) + google-api-client (~> 0.23) + google-cloud-core (~> 1.2) + googleauth (>= 0.6.2, < 0.10.0) + googleauth (0.6.7) faraday (~> 0.12) jwt (>= 1.4, < 3.0) - logging (~> 2.0) - memoist (~> 0.12) + memoist (~> 0.16) multi_json (~> 1.11) - os (~> 0.9) + os (>= 0.9, < 2.0) signet (~> 0.7) highline (1.7.10) http-cookie (1.0.3) @@ -84,22 +95,18 @@ GEM httpclient (2.8.3) json (2.1.0) jwt (2.1.0) - little-plugger (1.1.4) - logging (2.2.2) - little-plugger (~> 1.1) - multi_json (~> 1.10) memoist (0.16.0) - mime-types (3.1) + mime-types (3.2.2) mime-types-data (~> 3.2015) - mime-types-data (3.2016.0521) + mime-types-data (3.2018.0812) mini_magick (4.5.1) multi_json (1.13.1) multi_xml (0.6.0) multipart-post (2.0.0) - nanaimo (0.2.5) + nanaimo (0.2.6) naturally (2.2.0) - os (0.9.6) - plist (3.4.0) + os (1.0.0) + plist (3.5.0) public_suffix (2.0.5) representable (3.0.4) declarative (< 0.1.0) @@ -107,37 +114,37 @@ GEM uber (< 0.2.0) retriable (3.1.2) rouge (2.0.7) - rubyzip (1.2.1) + rubyzip (1.2.2) security (0.1.3) - signet (0.8.1) + signet (0.11.0) addressable (~> 2.3) faraday (~> 0.9) jwt (>= 1.5, < 3.0) multi_json (~> 1.10) - simctl (1.6.4) + simctl (1.6.5) CFPropertyList naturally slack-notifier (2.3.2) terminal-notifier (1.8.0) terminal-table (1.8.0) unicode-display_width (~> 1.1, >= 1.1.1) - tty-cursor (0.5.0) - tty-screen (0.6.4) - tty-spinner (0.8.0) - tty-cursor (>= 0.5.0) + tty-cursor (0.6.0) + tty-screen (0.6.5) + tty-spinner (0.9.0) + tty-cursor (~> 0.6.0) uber (0.1.0) unf (0.1.4) unf_ext unf_ext (0.0.7.5) - unicode-display_width (1.4.0) + unicode-display_width (1.4.1) word_wrap (1.0.0) - xcodeproj (1.5.9) + xcodeproj (1.8.0) CFPropertyList (>= 2.3.3, < 4.0) - atomos (~> 0.1.2) + atomos (~> 0.1.3) claide (>= 1.0.2, < 2.0) colored2 (~> 3.1) - nanaimo (~> 0.2.5) - xcpretty (0.2.8) + nanaimo (~> 0.2.6) + xcpretty (0.3.0) rouge (~> 2.0.7) xcpretty-travis-formatter (1.0.0) xcpretty (~> 0.2, >= 0.0.7) @@ -149,4 +156,4 @@ DEPENDENCIES fastlane BUNDLED WITH - 1.15.4 + 2.0.1 From c4e80fceacb42ce97f6d78f2b3f7b1c01d04df7a Mon Sep 17 00:00:00 2001 From: Conor Mongey Date: Tue, 2 Apr 2019 22:08:23 +0100 Subject: [PATCH 08/10] Add a tests for migrating SettingsV0 to Settings --- SeaEye/models/Settings.swift | 13 +++++----- SeaEye/models/SettingsV0.swift | 9 +++++++ SeaEyeTests/SettingsTest.swift | 47 ++++++++++++++++++++++++++++------ 3 files changed, 55 insertions(+), 14 deletions(-) diff --git a/SeaEye/models/Settings.swift b/SeaEye/models/Settings.swift index 3289574..dcc189f 100644 --- a/SeaEye/models/Settings.swift +++ b/SeaEye/models/Settings.swift @@ -1,13 +1,11 @@ import Foundation struct Settings: Codable { - private static let defaultsKey: String = "SeaEyeSettings2" + static let defaultsKey: String = "SeaEyeSettings2" var clientProjects: [ClientProject] static func load(userDefaults: UserDefaults = UserDefaults.standard) -> Settings { - let oldSettings = SettingsV0.load(userDefaults: userDefaults) - var settings = Settings(clientProjects: []) if let settingsString = userDefaults.string(forKey: self.defaultsKey) { let decoder = JSONDecoder() @@ -19,10 +17,13 @@ struct Settings: Codable { print(error.localizedDescription) } } + } else { + let oldSettings = SettingsV0.load(userDefaults: userDefaults) + print("Migrating old settings to new settings format") + settings = oldSettings.toSettings() + settings.save(userDefaults: userDefaults) } - print("Migrating old settings to new settings format") - settings = oldSettings.toSettings() - settings.save(userDefaults: userDefaults) + return settings } diff --git a/SeaEye/models/SettingsV0.swift b/SeaEye/models/SettingsV0.swift index 40b0aad..caa7574 100644 --- a/SeaEye/models/SettingsV0.swift +++ b/SeaEye/models/SettingsV0.swift @@ -29,6 +29,15 @@ struct SettingsV0: Codable { return settings } + func save(userDefaults: UserDefaults = UserDefaults.standard) { + userDefaults.set(self.apiKey, forKey: Keys.apiKey.rawValue) + userDefaults.set(self.organization, forKey: Keys.organisation.rawValue) + userDefaults.set(self.projectsString, forKey: Keys.projects.rawValue) + userDefaults.set(self.branchFilter, forKey: Keys.branchFilter.rawValue) + userDefaults.set(self.userFilter, forKey: Keys.userFilter.rawValue) + userDefaults.set(self.notify, forKey: Keys.notify.rawValue) + } + func toSettings() -> Settings { return Settings(clientProjects: self.clientProjects()) } diff --git a/SeaEyeTests/SettingsTest.swift b/SeaEyeTests/SettingsTest.swift index 5b32caf..bf26ed3 100644 --- a/SeaEyeTests/SettingsTest.swift +++ b/SeaEyeTests/SettingsTest.swift @@ -10,14 +10,45 @@ import Foundation import XCTest class SettingsTest: XCTestCase { + func testMigrationofV0toV1WhenV1DoesNotExist() { + let ud = UserDefaults.init(suiteName: UUID().uuidString)! + let oldSettings = SettingsV0.init(apiKey: "abc123", + organization: "nolaneo", + projectsString: "SeaEye foobar", + branchFilter: "master", + userFilter: nil, + notify: true) + oldSettings.save(userDefaults: ud) + XCTAssertNil(ud.string(forKey: Settings.defaultsKey)) - func testDefaults() { -// var settings = SettingsV0.load(userDefaults: UserDefaults.init(suiteName: "testing")!) -// XCTAssertFalse(settings.notify) -// XCTAssertFalse(settings.valid()) -// settings.apiKey = "abc" -// settings.organization = "nolaneo" -// settings.projectsString = "SeaEye" -// XCTAssertTrue(settings.valid()) + let result = Settings.load(userDefaults: ud) + XCTAssertEqual(result.clientProjects.count, 1, "Should have been a migration") + + // we now expect to see a value in Settings + XCTAssertNotNil(ud.string(forKey: Settings.defaultsKey)) + + XCTAssertEqual(result.clientProjects[0].client.baseURL, "https://circleci.com") + XCTAssertEqual(result.clientProjects[0].client.token, "abc123") + XCTAssertEqual(result.clientProjects[0].projects[0].organisation, "nolaneo") + XCTAssertEqual(result.clientProjects[0].projects[0].name, "SeaEye") + XCTAssertEqual(result.clientProjects[0].projects[1].name, "foobar") + } + + func testLoadingOfSettingsWhenV0AndV1Exist() { + let ud = UserDefaults.init(suiteName: UUID().uuidString)! + let oldSettings = SettingsV0.init(apiKey: "abc123", + organization: "nolaneo", + projectsString: "SeaEye foobar", + branchFilter: "master", + userFilter: nil, + notify: true) + + oldSettings.save(userDefaults: ud) + let newSettings = Settings.init(clientProjects: []) + newSettings.save(userDefaults: ud) + XCTAssertNotNil(ud.string(forKey: Settings.defaultsKey)) + let result = Settings.load(userDefaults: ud) + // we should ignore v0's key, we have v1 + XCTAssertEqual(result.clientProjects.count, 0) } } From 4153a9b9a76f4f4d016e619e1fb1c54d95c6bfd7 Mon Sep 17 00:00:00 2001 From: Conor Mongey Date: Tue, 2 Apr 2019 22:27:27 +0100 Subject: [PATCH 09/10] Shuffle around valid check --- SeaEye/models/SettingsV0.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SeaEye/models/SettingsV0.swift b/SeaEye/models/SettingsV0.swift index caa7574..dd1f08a 100644 --- a/SeaEye/models/SettingsV0.swift +++ b/SeaEye/models/SettingsV0.swift @@ -43,11 +43,11 @@ struct SettingsV0: Codable { } func clientProjects() -> [ClientProject] { - let projectNames = projectsString?.components(separatedBy: CharacterSet.whitespaces) - let client = CircleCIClient.init(apiKey: apiKey!) if !valid() { return [] } + let projectNames = projectsString?.components(separatedBy: CharacterSet.whitespaces) + let client = CircleCIClient.init(apiKey: apiKey!) var projects = [Project]() if let projectNames = projectNames { From e316fa66bc5b83755126098312317808584b2e39 Mon Sep 17 00:00:00 2001 From: Conor Mongey Date: Tue, 2 Apr 2019 23:07:56 +0100 Subject: [PATCH 10/10] Backfill some tests --- SeaEye.xcodeproj/project.pbxproj | 20 ++++++- SeaEyeTests/{models => }/FilterTest.swift | 2 +- SeaEyeTests/NewBuildFilterTest.swift | 57 +++++++++++++++++++ SeaEyeTests/SettingsTest.swift | 8 +++ .../SeaEyeStatusBarListenerTest.swift | 11 ++++ SeaEyeTests/models/ProjectTest.swift | 15 +++++ 6 files changed, 110 insertions(+), 3 deletions(-) rename SeaEyeTests/{models => }/FilterTest.swift (99%) create mode 100644 SeaEyeTests/NewBuildFilterTest.swift create mode 100644 SeaEyeTests/models/ProjectTest.swift diff --git a/SeaEye.xcodeproj/project.pbxproj b/SeaEye.xcodeproj/project.pbxproj index 2c9a0d0..2e5d6c1 100644 --- a/SeaEye.xcodeproj/project.pbxproj +++ b/SeaEye.xcodeproj/project.pbxproj @@ -53,6 +53,8 @@ F422C54920D48CDD002A5897 /* VersionNumber.swift in Sources */ = {isa = PBXBuildFile; fileRef = F422C54720D48CC2002A5897 /* VersionNumber.swift */; }; F422C54B20D48CF2002A5897 /* VersionNumberTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F422C54A20D48CF2002A5897 /* VersionNumberTests.swift */; }; F4472C0C216D72B700ECD077 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F4472C0B216D72B700ECD077 /* Main.storyboard */; }; + F47EA511225410480008FE25 /* ProjectTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = F47EA510225410480008FE25 /* ProjectTest.swift */; }; + F47EA5132254119B0008FE25 /* NewBuildFilterTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = F47EA5122254119B0008FE25 /* NewBuildFilterTest.swift */; }; F4917465221C4B5300CAF9C1 /* SelectedServerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4917464221C4B5300CAF9C1 /* SelectedServerView.swift */; }; F4917466221C4CA600CAF9C1 /* SelectedServerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4917464221C4B5300CAF9C1 /* SelectedServerView.swift */; }; F4917468221C4E0F00CAF9C1 /* ClientProject.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4917467221C4E0F00CAF9C1 /* ClientProject.swift */; }; @@ -166,6 +168,8 @@ F422C54720D48CC2002A5897 /* VersionNumber.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionNumber.swift; sourceTree = ""; }; F422C54A20D48CF2002A5897 /* VersionNumberTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionNumberTests.swift; sourceTree = ""; }; F4472C0B216D72B700ECD077 /* Main.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = ""; }; + F47EA510225410480008FE25 /* ProjectTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ProjectTest.swift; path = models/ProjectTest.swift; sourceTree = ""; }; + F47EA5122254119B0008FE25 /* NewBuildFilterTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewBuildFilterTest.swift; sourceTree = ""; }; F4917464221C4B5300CAF9C1 /* SelectedServerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectedServerView.swift; sourceTree = ""; }; F4917467221C4E0F00CAF9C1 /* ClientProject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientProject.swift; sourceTree = ""; }; F491746A221C4E4000CAF9C1 /* Settings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Settings.swift; sourceTree = ""; }; @@ -176,7 +180,7 @@ F4D5A43A21E40E4C006140DE /* Notifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Notifications.swift; sourceTree = ""; }; F4D5A43C21E41FEB006140DE /* SeaEyeStatusBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeaEyeStatusBar.swift; sourceTree = ""; }; F4D5A44921E427BB006140DE /* Filter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Filter.swift; sourceTree = ""; }; - F4D5A44C21E42A77006140DE /* FilterTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = FilterTest.swift; path = models/FilterTest.swift; sourceTree = ""; }; + F4D5A44C21E42A77006140DE /* FilterTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterTest.swift; sourceTree = ""; }; F4D5A44F21E44BA6006140DE /* ClientBuildUpdater.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientBuildUpdater.swift; sourceTree = ""; }; F4D5A45221E44BF1006140DE /* NewBuildFilter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewBuildFilter.swift; sourceTree = ""; }; F4D5A45621E451BA006140DE /* TextPrinter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextPrinter.swift; sourceTree = ""; }; @@ -336,7 +340,8 @@ 60C73BEF19FC025A0067CDCA /* Supporting Files */, 5389EF491FCBBA49001C0D5F /* DecodingTests.swift */, F422C54A20D48CF2002A5897 /* VersionNumberTests.swift */, - F4D5A44C21E42A77006140DE /* FilterTest.swift */, + F47EA5122254119B0008FE25 /* NewBuildFilterTest.swift */, + F47EA50F225410330008FE25 /* models */, F4F4807321E471DE0038DC09 /* SettingsTest.swift */, ); path = SeaEyeTests; @@ -395,6 +400,15 @@ path = networking; sourceTree = ""; }; + F47EA50F225410330008FE25 /* models */ = { + isa = PBXGroup; + children = ( + F4D5A44C21E42A77006140DE /* FilterTest.swift */, + F47EA510225410480008FE25 /* ProjectTest.swift */, + ); + name = models; + sourceTree = ""; + }; F4D5A45521E45196006140DE /* listeners */ = { isa = PBXGroup; children = ( @@ -597,6 +611,7 @@ 5394798D1FCB909600D6FA19 /* CircleCIClient.swift in Sources */, 539479871FCB8EA500D6FA19 /* CircleCIBuild.swift in Sources */, F4D5A44B21E427BB006140DE /* Filter.swift in Sources */, + F47EA5132254119B0008FE25 /* NewBuildFilterTest.swift in Sources */, F412CA9A21D3E3C90069C7C7 /* HTTPRequest.swift in Sources */, F4D5A45821E451BA006140DE /* TextPrinter.swift in Sources */, F4D5A45121E44BA6006140DE /* ClientBuildUpdater.swift in Sources */, @@ -619,6 +634,7 @@ F4D5A43621E3F782006140DE /* FallbackView.swift in Sources */, F412CAA021D3EFEF0069C7C7 /* DateFormatter.swift in Sources */, F4D5A44321E4207C006140DE /* SeaEyeUpdatesController.swift in Sources */, + F47EA511225410480008FE25 /* ProjectTest.swift in Sources */, F4D5A45B21E45257006140DE /* SeaEyeStatusBarListener.swift in Sources */, F4D5A44021E42057006140DE /* SeaEyePopoverController.swift in Sources */, F4F4808821E51B530038DC09 /* UnfollowedProjectsController.swift in Sources */, diff --git a/SeaEyeTests/models/FilterTest.swift b/SeaEyeTests/FilterTest.swift similarity index 99% rename from SeaEyeTests/models/FilterTest.swift rename to SeaEyeTests/FilterTest.swift index 144c4fc..401880f 100644 --- a/SeaEyeTests/models/FilterTest.swift +++ b/SeaEyeTests/FilterTest.swift @@ -1,4 +1,4 @@ -import Cocoa +import Foundation import XCTest class FilterTest: XCTestCase { diff --git a/SeaEyeTests/NewBuildFilterTest.swift b/SeaEyeTests/NewBuildFilterTest.swift new file mode 100644 index 0000000..ebe059b --- /dev/null +++ b/SeaEyeTests/NewBuildFilterTest.swift @@ -0,0 +1,57 @@ +import XCTest +class NewBuildFilterTest: XCTestCase { + func testItAlsoSortsBuilds() { + var filter = NewBuildFilter() + let project = Project.init(vcsProvider: "github", + organisation: "nolaneo", + name: "SeaEye", + filter: nil, + notifySuccess: false, + notifyFailure: false) + var updatedBuilds = filter.newBuilds(project: project, builds: []) + let build = CircleCIBuild.init(branch: "master", + project: "ugh", + status: .failed, + subject: "You done goofed", + user: "Mongey", + buildNum: 1, + url: URL.init(string: "http://google.com")!, + date: Date.init(timeIntervalSinceNow: 0)) + let newerBuild = CircleCIBuild.init(branch: "master", + project: "ugh", + status: .success, + subject: "You done goofed", + user: "Mongey", + buildNum: 1, + url: URL.init(string: "http://google.com")!, + date: Date.init(timeIntervalSinceNow: 1)) + + updatedBuilds = filter.newBuilds(project: project, builds: [build, newerBuild]) + XCTAssertEqual(updatedBuilds[0].status, newerBuild.status) + XCTAssertEqual(updatedBuilds[1].status, build.status) + } + + func testItFiltersBuildsSoWeOnlySeeNewOnes() { + var filter = NewBuildFilter() + let project = Project.init(vcsProvider: "github", + organisation: "nolaneo", + name: "SeaEye", + filter: nil, + notifySuccess: false, + notifyFailure: false) + var updatedBuilds = filter.newBuilds(project: project, builds: []) + let build = CircleCIBuild.init(branch: "master", + project: "ugh", + status: .failed, + subject: "You done goofed", + user: "Mongey", + buildNum: 1, + url: URL.init(string: "http://google.com")!, + date: Date.init(timeIntervalSinceNow: 0)) + updatedBuilds = filter.newBuilds(project: project, builds: [build]) + XCTAssertEqual(updatedBuilds.count, 1) + + updatedBuilds = filter.newBuilds(project: project, builds: [build]) + XCTAssertEqual(updatedBuilds.count, 0) + } +} diff --git a/SeaEyeTests/SettingsTest.swift b/SeaEyeTests/SettingsTest.swift index bf26ed3..a4a6f21 100644 --- a/SeaEyeTests/SettingsTest.swift +++ b/SeaEyeTests/SettingsTest.swift @@ -51,4 +51,12 @@ class SettingsTest: XCTestCase { // we should ignore v0's key, we have v1 XCTAssertEqual(result.clientProjects.count, 0) } + + func testInvalidJSONInUserDefaults() { + let ud = UserDefaults.init(suiteName: UUID().uuidString)! + XCTAssertNil(ud.string(forKey: Settings.defaultsKey)) + ud.setValue("not a json string", forKey: Settings.defaultsKey) + let newSettings = Settings.load(userDefaults: ud) + XCTAssertEqual(newSettings.clientProjects.count, 0, "Settings should ignore invalid json") + } } diff --git a/SeaEyeTests/listeners/SeaEyeStatusBarListenerTest.swift b/SeaEyeTests/listeners/SeaEyeStatusBarListenerTest.swift index d0f6101..2944352 100644 --- a/SeaEyeTests/listeners/SeaEyeStatusBarListenerTest.swift +++ b/SeaEyeTests/listeners/SeaEyeStatusBarListenerTest.swift @@ -61,5 +61,16 @@ class SeaEyeStatusBarListenerTest: XCTestCase { sut.notify(project: project, builds: []) XCTAssertEqual(sut.statusBar.state, .success, "The Icon should remain set if there are no builds") + + let runningBuild = CircleCIBuild(branch: "master", + project: "foo/bar", + status: .running, + subject: "Got some tests", + user: "Homer", + buildNum: 100, + url: URL.init(string: "https://google.com")!, + date: Date()) + sut.notify(project: project, builds: [runningBuild]) + XCTAssertEqual(sut.statusBar.state, .running, "The Icon should set to running, when there's a running build") } } diff --git a/SeaEyeTests/models/ProjectTest.swift b/SeaEyeTests/models/ProjectTest.swift new file mode 100644 index 0000000..1b883b9 --- /dev/null +++ b/SeaEyeTests/models/ProjectTest.swift @@ -0,0 +1,15 @@ +import Foundation +import XCTest + +class ProjectTest: XCTestCase { + func testProjectPath() { + let project = Project.init(vcsProvider: "github", + organisation: "nolaneo", + name: "SeaEye", + filter: nil, + notifySuccess: false, + notifyFailure: false) + + XCTAssertEqual(project.path(), "github/nolaneo/SeaEye") + } +}