Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
6d96ddf
Fetching feature flag
suhaibabsi-inst Nov 9, 2025
7210c7b
Add handler to present immersive view in a dedicated modal.
vargaat Oct 14, 2025
a49f33c
Fix merge conflicts
suhaibabsi-inst Nov 9, 2025
2aac275
Full functional requirements
suhaibabsi-inst Nov 9, 2025
7a2ea28
Fix issues with flag loading
suhaibabsi-inst Nov 10, 2025
7c07632
Remove context from delegate
suhaibabsi-inst Nov 10, 2025
92c5282
Organising things
suhaibabsi-inst Nov 10, 2025
2b4c697
Fix title extraction for expand button, enhanced separation of concerns
suhaibabsi-inst Nov 11, 2025
7c48f36
Fix feature removal issues
suhaibabsi-inst Nov 11, 2025
1844e19
Update InsertStudioOpenDetailViewButton.swift
suhaibabsi-inst Nov 11, 2025
7b262c8
Button with icon
suhaibabsi-inst Nov 11, 2025
a5daf3b
Unit tests
suhaibabsi-inst Nov 12, 2025
6e4cd3b
Address review comments
suhaibabsi-inst Nov 16, 2025
304d865
Naming methods
suhaibabsi-inst Nov 16, 2025
d459e0a
Resolve memory leak
suhaibabsi-inst Nov 16, 2025
84abcf4
Revert "Resolve memory leak"
suhaibabsi-inst Nov 16, 2025
6a0059f
Address code review comments
suhaibabsi-inst Nov 17, 2025
f7ea7b5
Merge branch 'master' into feature/MBL-19480-Immersive-VideoPlayer-Ca…
suhaibabsi-inst Nov 17, 2025
b3bda43
Update CoreWebViewTests.swift
suhaibabsi-inst Nov 17, 2025
243d795
Few fixes
suhaibabsi-inst Nov 17, 2025
5366c88
Update InsertStudioOpenDetailViewButton.swift
suhaibabsi-inst Nov 18, 2025
d2c3ae7
Address AI feedback
suhaibabsi-inst Nov 18, 2025
8b887fe
Merge branch 'master' into feature/MBL-19480-Immersive-VideoPlayer-Ca…
suhaibabsi-inst Nov 23, 2025
cca614d
Address AI feedback
suhaibabsi-inst Nov 24, 2025
9dfb196
Update unit tests
suhaibabsi-inst Nov 25, 2025
ba73d63
Address code review comments
suhaibabsi-inst Nov 26, 2025
a84594c
Fix issue with link forming
suhaibabsi-inst Nov 26, 2025
c6d99a2
Update CoreWebViewTests.swift
suhaibabsi-inst Nov 26, 2025
15a0c49
Update InsertStudioOpenDetailViewButtonTests.swift
suhaibabsi-inst Nov 26, 2025
e18ad06
Update CoreWebViewTests.swift
suhaibabsi-inst Nov 26, 2025
cf6fedd
Fix font size.
vargaat Nov 27, 2025
b045bb9
Add better name.
vargaat Nov 27, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
//
// This file is part of Canvas.
// Copyright (C) 2025-present Instructure, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//

import Foundation

public struct APIFeatureFlagState: Codable {

public enum State: String, Codable {
/// (valid only for account context) The feature is `off` in the account, but may be enabled in sub-accounts and courses by setting a feature flag `on` the sub-account or course.
case allowed
/// (valid only for account context) The feature is `on` in the account, but may be disabled in sub-accounts and courses by setting a feature flag `off` the sub-account or course.
case allowed_on
/// The feature is turned `on` unconditionally for the user, course, or account and sub-accounts.
case on
/// The feature is not available for the course, user, or account and sub-accounts.
case off
}

public let feature: String
public let state: State
public let locked: Bool

private let context_id: String
private let context_type: String

init(feature: String, state: State, locked: Bool, context_id: String, context_type: String) {
self.feature = feature
self.state = state
self.locked = locked
self.context_id = context_id
self.context_type = context_type
}

public var contextType: ContextType? {
return ContextType(rawValue: context_type.lowercased())
}

public var canvasContextID: String {
return "\(context_type.lowercased())_\(context_id)"
}

public func overriden(state: State, context: Context) -> Self {
APIFeatureFlagState(
feature: feature,
state: state,
locked: locked,
context_id: context.id,
context_type: context.contextType.rawValue
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,6 @@ import Foundation
import CoreData

public struct APIFeatureFlag {
public enum Key: String {
case assignmentEnhancements = "assignments_2_student"
}
public let key: String
public let isEnabled: Bool
public let canvasContextID: String
Expand Down Expand Up @@ -55,10 +52,24 @@ public final class FeatureFlag: NSManagedObject, WriteableModel {
flag.isEnvironmentFlag = item.isEnvironmentFlag
return flag
}

@discardableResult
public static func save(_ item: APIFeatureFlagState, in context: NSManagedObjectContext) -> FeatureFlag {
let predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [
NSPredicate(format: "%K == %@", #keyPath(FeatureFlag.canvasContextID), item.canvasContextID),
NSPredicate(format: "%K == %@", #keyPath(FeatureFlag.name), item.feature)
])
let flag: FeatureFlag = context.fetch(predicate).first ?? context.insert()
flag.name = item.feature
flag.enabled = item.state == .on
flag.canvasContextID = item.canvasContextID
flag.isEnvironmentFlag = false
return flag
}
}

extension Collection where Element == FeatureFlag {
public func isFeatureFlagEnabled(_ key: APIFeatureFlag.Key) -> Bool {
public func isFeatureFlagEnabled(_ key: FeatureFlagName) -> Bool {
isFeatureFlagEnabled(name: key.rawValue)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ public class GetEnabledFeatureFlags: CollectionUseCase {
}

extension Store where U == GetEnabledFeatureFlags {
public func isFeatureFlagEnabled(_ key: APIFeatureFlag.Key) -> Bool {
public func isFeatureFlagEnabled(_ key: FeatureFlagName) -> Bool {
all.isFeatureFlagEnabled(key)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
//
// This file is part of Canvas.
// Copyright (C) 2025-present Instructure, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//

import Foundation
import CoreData

public class GetFeatureFlagState: CollectionUseCase {
public typealias Model = FeatureFlag

public let featureName: FeatureFlagName
public let context: Context

public var scope: Scope {
let contextPredicate = NSPredicate(format: "%K == %@", #keyPath(FeatureFlag.canvasContextID), context.canvasContextID)
let namePredicate = NSPredicate(format: "%K == %@", #keyPath(FeatureFlag.name), featureName.rawValue)
let predicate = NSCompoundPredicate(
andPredicateWithSubpredicates: [
contextPredicate,
namePredicate
]
)
return Scope(predicate: predicate, order: [NSSortDescriptor(key: #keyPath(FeatureFlag.name), ascending: true)])
}

public var cacheKey: String? {
return "get-\(context.canvasContextID)-\(featureName.rawValue)-feature-flag-state"
}

public var request: GetFeatureFlagStateRequest {
return GetFeatureFlagStateRequest(featureName: featureName, context: context)
}

public init(featureName: FeatureFlagName, context: Context) {
self.featureName = featureName
self.context = context
}

public func write(response: APIFeatureFlagState?, urlResponse: URLResponse?, to client: NSManagedObjectContext) {
guard var item = response else { return }

/// This as workaround for an API limitation, where
/// requesting feature state of a course context,
/// if not set on that course, would return the feature state of
/// the most higher level (which is the account)
if item.contextType != context.contextType && item.contextType == .account {
item = item.overriden(
state: item.state == .allowed_on ? .on : item.state,
context: context
)
}

FeatureFlag.save(item, in: client)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
//
// This file is part of Canvas.
// Copyright (C) 2025-present Instructure, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//

import Foundation

// https://canvas.instructure.com/doc/api/feature_flags.html#method.feature_flags.show
public struct GetFeatureFlagStateRequest: APIRequestable {
public typealias Response = APIFeatureFlagState

public let context: Context
public let featureName: FeatureFlagName

public var path: String {
return "\(context.pathComponent)/features/flags/\(featureName.rawValue)"
}

public init(featureName: FeatureFlagName, context: Context) {
self.featureName = featureName
self.context = context
}
}

// MARK: - Parameters

public enum FeatureFlagName: String {
case assignmentEnhancements = "assignments_2_student"
case studioEmbedImprovements = "rce_studio_embed_improvements"
}
Loading