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

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