diff --git a/.github/actions/asana-add-comment/templates/internal-release-ready-tag-failed copy.yml b/.github/actions/asana-add-comment/templates/internal-release-ready-tag-failed copy.yml
deleted file mode 100644
index f98641fa62..0000000000
--- a/.github/actions/asana-add-comment/templates/internal-release-ready-tag-failed copy.yml
+++ /dev/null
@@ -1,16 +0,0 @@
-data:
- html_text: |
-
- [ACTION NEEDED] Internal release build ${TAG} ready
-
- - 📥 DMG is available from ${DMG_URL}.
- - ❗️ Tagging repository failed.
- - ⚠️ GitHub release creation was skipped.
- - ⚠️ Merging
${BRANCH}
to ${BASE_BRANCH}
was skipped.
-
-
- , please proceed with manual tagging and merging according to instructions.
-
-
- 🔗 Workflow URL: ${WORKFLOW_URL}.
-
diff --git a/.github/workflows/build_hotfix_release.yml b/.github/workflows/build_hotfix_release.yml
index 225608a530..21e856ee13 100644
--- a/.github/workflows/build_hotfix_release.yml
+++ b/.github/workflows/build_hotfix_release.yml
@@ -64,6 +64,8 @@ jobs:
BRANCH: ${{ github.ref_name }}
run: |
version="$(cut -d '/' -f 2 <<< "$BRANCH")"
+ # 'internal', because we start with making a build that still needs to be tested before being published
+ # and we want Asana tasks to be moved to "Validation" and not already to "Done"
./scripts/update_asana_for_release.sh internal ${{ steps.task-id.outputs.task-id }} ${{ vars.MACOS_APP_BOARD_VALIDATION_SECTION_ID }} "${version}"
prepare_release:
@@ -107,7 +109,7 @@ jobs:
asana-task-url: ${{ github.event.inputs.asana-task-url }}
branch: ${{ github.ref_name }}
base-branch: ${{ github.event.inputs.current-internal-release-branch || 'main' }}
- prerelease: true
+ prerelease: true # Pre-release for now, and the actual release will be done as part of publish_dmg_release that's called later
secrets:
ASANA_ACCESS_TOKEN: ${{ secrets.ASANA_ACCESS_TOKEN }}
GHA_ELEVATED_PERMISSIONS_TOKEN: ${{ secrets.GHA_ELEVATED_PERMISSIONS_TOKEN }}
diff --git a/.github/workflows/bump_internal_release.yml b/.github/workflows/bump_internal_release.yml
index 177376d166..5643832cf7 100644
--- a/.github/workflows/bump_internal_release.yml
+++ b/.github/workflows/bump_internal_release.yml
@@ -123,3 +123,16 @@ jobs:
secrets:
ASANA_ACCESS_TOKEN: ${{ secrets.ASANA_ACCESS_TOKEN }}
GHA_ELEVATED_PERMISSIONS_TOKEN: ${{ secrets.GHA_ELEVATED_PERMISSIONS_TOKEN }}
+
+ publish_release:
+ name: Publish DMG Release
+ needs: [ tag_and_merge ]
+ uses: ./.github/workflows/publish_dmg_release.yml
+ with:
+ asana-task-url: ${{ github.event.inputs.asana-task-url }}
+ secrets:
+ ASANA_ACCESS_TOKEN: ${{ secrets.ASANA_ACCESS_TOKEN }}
+ AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID_RELEASE_S3 }}
+ AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY_RELEASE_S3 }}
+ GHA_ELEVATED_PERMISSIONS_TOKEN: ${{ secrets.GHA_ELEVATED_PERMISSIONS_TOKEN }}
+ SPARKLE_PRIVATE_KEY: ${{ secrets.SPARKLE_PRIVATE_KEY }}
diff --git a/.github/workflows/publish_dmg_release.yml b/.github/workflows/publish_dmg_release.yml
index 860306a810..8ead92a918 100644
--- a/.github/workflows/publish_dmg_release.yml
+++ b/.github/workflows/publish_dmg_release.yml
@@ -19,16 +19,33 @@ on:
- internal
- public
- hotfix
+ workflow_call:
+ inputs:
+ asana-task-url:
+ description: "Asana release task URL"
+ required: true
+ type: string
+ secrets:
+ ASANA_ACCESS_TOKEN:
+ required: true
+ AWS_ACCESS_KEY_ID:
+ required: true
+ AWS_SECRET_ACCESS_KEY:
+ required: true
+ GHA_ELEVATED_PERMISSIONS_TOKEN:
+ required: true
+ SPARKLE_PRIVATE_KEY:
+ required: true
jobs:
- # This is only run for public and hotfix releases
+ # This is only run for public and hotfix releases, so only when it's triggered manually.
# Internal release has been tagged as part of code_freeze or bump_interal_release workflows
tag-public-release:
name: Tag public release
- if: ${{ github.event.inputs.release-type != 'internal' }}
+ if: github.event.inputs.release-type != 'internal'
uses: ./.github/workflows/tag_release.yml
with:
@@ -43,6 +60,10 @@ jobs:
name: Publish a release to Sparkle
+ env:
+ RELEASE_TYPE: ${{ github.event.inputs.release-type || 'internal' }}
+ SPARKLE_DIR: ${{ github.workspace }}/sparkle-updates
+
needs: [tag-public-release]
# Allow to run even if the tag-public-release job was skipped (e.g. for internal releases)
@@ -52,15 +73,26 @@ jobs:
runs-on: macos-13-xlarge
timeout-minutes: 10
- env:
- SPARKLE_DIR: ${{ github.workspace }}/sparkle-updates
-
steps:
+ - name: Download tag artifact
+ id: download-tag
+ continue-on-error: true
+ uses: actions/download-artifact@v4
+ with:
+ name: tag
+ path: .github
+
+ - name: Set tag variable
+ run: |
+ if [[ "${{ steps.download-tag.outcome }}" == 'success' ]]; then
+ echo "tag=$(<.github/tag)" >> $GITHUB_ENV
+ else
+ echo "tag=${{ github.event.inputs.tag }}" >> $GITHUB_ENV
+ fi
+
- name: Verify the tag
id: verify-tag
- env:
- tag: ${{ github.event.inputs.tag }}
run: |
tag_regex='^[0-9]+\.[0-9]+\.[0-9]+-[0-9]+$'
@@ -87,12 +119,11 @@ jobs:
- name: Fetch DMG
id: fetch-dmg
- if: ${{ github.event.inputs.release-type != 'public' }}
env:
DMG_NAME: duckduckgo-${{ steps.verify-tag.outputs.release-version }}.dmg
run: |
# Public release doesn't need fetching a DMG (it's already uploaded to S3)
- if [[ "${{ github.event.inputs.release-type }}" != 'public' ]]; then
+ if [[ "${RELEASE_TYPE}" != 'public' ]]; then
DMG_URL="${{ vars.TEST_DMG_URL_ROOT }}${DMG_NAME}"
curl -fLSs -o "$DMG_NAME" "$DMG_URL"
fi
@@ -121,7 +152,6 @@ jobs:
env:
DMG_PATH: ${{ steps.fetch-dmg.outputs.dmg-path }}
SPARKLE_PRIVATE_KEY: ${{ secrets.SPARKLE_PRIVATE_KEY }}
- RELEASE_TYPE: ${{ github.event.inputs.release-type }}
VERSION: ${{ steps.verify-tag.outputs.release-version }}
run: |
echo -n "$SPARKLE_PRIVATE_KEY" > sparkle_private_key
@@ -224,8 +254,6 @@ jobs:
- name: Set up Asana templates
if: always()
id: asana-templates
- env:
- RELEASE_TYPE: ${{ github.event.inputs.release-type }}
run: |
if [[ ${{ steps.upload.outcome }} == "success" ]]; then
if [[ "${RELEASE_TYPE}" == "internal" ]]; then
diff --git a/.github/workflows/tag_release.yml b/.github/workflows/tag_release.yml
index 02d8d45dec..eedb4437d7 100644
--- a/.github/workflows/tag_release.yml
+++ b/.github/workflows/tag_release.yml
@@ -84,6 +84,18 @@ jobs:
prerelease: ${{ env.prerelease }}
github-token: ${{ github.token }}
+ - name: Store created tag in a file artifact
+ if: github.event_name == 'workflow_call'
+ run: echo ${{ steps.create-tag.outputs.tag }} > .github/tag
+
+ - name: Upload tag artifact
+ if: github.event_name == 'workflow_call'
+ uses: actions/upload-artifact@v4
+ with:
+ name: tag
+ path: .github/tag
+ retention-days: 1
+
- name: Merge to base branch
id: merge
if: ${{ env.prerelease == 'true' }}
@@ -106,7 +118,7 @@ jobs:
env:
GH_TOKEN: ${{ github.token }}
run: |
- gh api --method DELETE /repos/${{ github.repository }}/git/refs/heads/${{ env.BRANCH}}
+ gh api --method DELETE /repos/${{ github.repository }}/git/refs/heads/${{ env.BRANCH }}
- name: Set common environment variables
if: always()
diff --git a/DuckDuckGo/Application/AppDelegate.swift b/DuckDuckGo/Application/AppDelegate.swift
index 1fea2a1b84..a0ab3bfe70 100644
--- a/DuckDuckGo/Application/AppDelegate.swift
+++ b/DuckDuckGo/Application/AppDelegate.swift
@@ -104,14 +104,6 @@ final class AppDelegate: NSObject, NSApplicationDelegate, FileDownloadManagerDel
fileStore = EncryptedFileStore()
}
- // keep this on top!
- // disable onboarding for existing users
- let isOnboardingFinished = UserDefaultsWrapper(key: .onboardingFinished, defaultValue: false)
- if !isOnboardingFinished.wrappedValue,
- FileManager.default.fileExists(atPath: URL.sandboxApplicationSupportURL.path) {
- isOnboardingFinished.wrappedValue = true
- }
-
let internalUserDeciderStore = InternalUserDeciderStore(fileStore: fileStore)
internalUserDecider = DefaultInternalUserDecider(store: internalUserDeciderStore)
diff --git a/DuckDuckGo/HomePage/Model/HomePageContinueSetUpModel.swift b/DuckDuckGo/HomePage/Model/HomePageContinueSetUpModel.swift
index 93e8b9ee74..49c3dd9fad 100644
--- a/DuckDuckGo/HomePage/Model/HomePageContinueSetUpModel.swift
+++ b/DuckDuckGo/HomePage/Model/HomePageContinueSetUpModel.swift
@@ -281,6 +281,7 @@ extension HomePage.Models {
}
// Helper Functions
+ @MainActor(unsafe)
@objc private func newTabOpenNotification(_ notification: Notification) {
if !isFirstSession {
listOfFeatures = randomisedFeatures
diff --git a/DuckDuckGo/Onboarding/ViewModel/OnboardingViewModel.swift b/DuckDuckGo/Onboarding/ViewModel/OnboardingViewModel.swift
index 5786b56e9e..e9448763fc 100644
--- a/DuckDuckGo/Onboarding/ViewModel/OnboardingViewModel.swift
+++ b/DuckDuckGo/Onboarding/ViewModel/OnboardingViewModel.swift
@@ -53,7 +53,30 @@ final class OnboardingViewModel: ObservableObject {
}
@UserDefaultsWrapper(key: .onboardingFinished, defaultValue: false)
- private(set) static var isOnboardingFinished: Bool
+ private static var _isOnboardingFinished: Bool
+
+ @MainActor
+ private(set) static var isOnboardingFinished: Bool {
+ get {
+ guard !_isOnboardingFinished else { return true }
+
+ // when there‘s a restored state but Onboarding Finished flag is not set - set it
+ guard WindowsManager.mainWindows.count <= 1 else {
+ OnboardingViewModel.isOnboardingFinished = true
+ return true
+ }
+ guard let tabsContent = (WindowsManager.mainWindows.first?.contentViewController as? MainViewController)?.tabCollectionViewModel.tabs.map(\.content) else { return false }
+ if !tabsContent.isEmpty, tabsContent != [.newtab] {
+ // there‘s some tabs content not equal to the new tab page: it means there‘s a session restored
+ OnboardingViewModel.isOnboardingFinished = true
+ return true
+ }
+ return false
+ }
+ set {
+ _isOnboardingFinished = newValue
+ }
+ }
weak var delegate: OnboardingDelegate?
@@ -79,6 +102,7 @@ final class OnboardingViewModel: ObservableObject {
state = .setDefault
}
+ @MainActor
func onSetDefaultPressed() {
delegate?.onboardingDidRequestSetDefault { [weak self] in
self?.state = .startBrowsing
@@ -87,6 +111,7 @@ final class OnboardingViewModel: ObservableObject {
}
}
+ @MainActor
func onSetDefaultSkipped() {
state = .startBrowsing
Self.isOnboardingFinished = true
@@ -97,6 +122,7 @@ final class OnboardingViewModel: ObservableObject {
skipTypingRequested = true
}
+ @MainActor
func onboardingReshown() {
if Self.isOnboardingFinished {
typingDisabled = true
diff --git a/DuckDuckGo/Windows/View/WindowsManager.swift b/DuckDuckGo/Windows/View/WindowsManager.swift
index d19dad2a42..bfe93e0329 100644
--- a/DuckDuckGo/Windows/View/WindowsManager.swift
+++ b/DuckDuckGo/Windows/View/WindowsManager.swift
@@ -23,7 +23,11 @@ import BrowserServicesKit
final class WindowsManager {
class var windows: [NSWindow] {
- return NSApplication.shared.windows
+ NSApplication.shared.windows
+ }
+
+ class var mainWindows: [MainWindow] {
+ NSApplication.shared.windows.compactMap { $0 as? MainWindow }
}
class func closeWindows(except windows: [NSWindow] = []) {
diff --git a/UnitTests/Onboarding/OnboardingTests.swift b/UnitTests/Onboarding/OnboardingTests.swift
index 24c2dd4eb9..729d97636e 100644
--- a/UnitTests/Onboarding/OnboardingTests.swift
+++ b/UnitTests/Onboarding/OnboardingTests.swift
@@ -68,6 +68,7 @@ class OnboardingTests: XCTestCase {
XCTAssertEqual(0, delegate.hasFinishedCalled)
}
+ @MainActor
func testWhenSetDefaultPressedDelegateIsCalled() {
let model = OnboardingViewModel(delegate: delegate)
XCTAssertEqual(0, delegate.didRequestImportDataCalled)