Skip to content

Commit 62df1ee

Browse files
authored
feat: add mls group id in developer tools - WPB-16160 (#2553)
1 parent 363b2a9 commit 62df1ee

File tree

11 files changed

+185
-43
lines changed

11 files changed

+185
-43
lines changed

wire-ios-data-model/Source/MLS/MLSGroupID.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,6 @@ extension MLSGroupID: CustomStringConvertible {
5252
extension MLSGroupID: SafeForLoggingStringConvertible {
5353

5454
public var safeForLoggingDescription: String {
55-
data.readableHash
55+
description.redactedAndTruncated()
5656
}
5757
}

wire-ios/Wire-iOS UnitTests/Developer/DeveloperTools/DebugActions/DeveloperDebugActionsViewModelTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ final class DeveloperDebugActionsViewModelTests: XCTestCase {
2727

2828
// when
2929
// then
30-
XCTAssertEqual(viewModel.buttons.count, 8)
30+
XCTAssertEqual(viewModel.buttons.count, 9)
3131
}
3232

3333
// MARK: - Helpers

wire-ios/Wire-iOS.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
01BC68512CE3AF9E00445243 /* EmptyConversationSearchResultsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BC68502CE3AF9E00445243 /* EmptyConversationSearchResultsView.swift */; };
4040
01BC68652CE496A500445243 /* EmptyPlaceholderContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BC68642CE496A500445243 /* EmptyPlaceholderContainerView.swift */; };
4141
01C1A7C72A54C45A0058D578 /* SnapshotTesting in Frameworks */ = {isa = PBXBuildFile; productRef = 01C1A7C62A54C45A0058D578 /* SnapshotTesting */; };
42+
01D2B13C2D63E8E90030D28D /* TextItemCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01D2B13B2D63E8E90030D28D /* TextItemCell.swift */; };
4243
01D2B7A52D64A9190030D28D /* WireCoreCrypto in Frameworks */ = {isa = PBXBuildFile; productRef = 01D2B7A42D64A9190030D28D /* WireCoreCrypto */; };
4344
01D2C78F2CECC41D00F05E5E /* QRCodeScannerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01D2C78E2CECC41D00F05E5E /* QRCodeScannerViewController.swift */; };
4445
01D8E71A2BA39CE900B71CB7 /* PrivacyWarningChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01D8E7192BA39CE900B71CB7 /* PrivacyWarningChecker.swift */; };
@@ -1963,6 +1964,7 @@
19631964
01A5E048297FDAB500624B65 /* copyExtraAudioNotifications */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = copyExtraAudioNotifications; sourceTree = "<group>"; };
19641965
01BC68502CE3AF9E00445243 /* EmptyConversationSearchResultsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyConversationSearchResultsView.swift; sourceTree = "<group>"; };
19651966
01BC68642CE496A500445243 /* EmptyPlaceholderContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyPlaceholderContainerView.swift; sourceTree = "<group>"; };
1967+
01D2B13B2D63E8E90030D28D /* TextItemCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextItemCell.swift; sourceTree = "<group>"; };
19661968
01D2C78E2CECC41D00F05E5E /* QRCodeScannerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeScannerViewController.swift; sourceTree = "<group>"; };
19671969
01D8E7192BA39CE900B71CB7 /* PrivacyWarningChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacyWarningChecker.swift; sourceTree = "<group>"; };
19681970
01F5EAE32B712DD6009FD25D /* LogFileDestination.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogFileDestination.swift; sourceTree = "<group>"; };
@@ -7743,6 +7745,7 @@
77437745
children = (
77447746
01874DD52C18579300208716 /* ContextItemsProviders */,
77457747
EE742AB5284DE57900A6B5F3 /* DeveloperToolsView.swift */,
7748+
01D2B13B2D63E8E90030D28D /* TextItemCell.swift */,
77467749
013638F32BF6A23200A68F89 /* DeveloperToolsPresenter.swift */,
77477750
EE742AB7284DE66400A6B5F3 /* DeveloperToolsViewModel.swift */,
77487751
63238C692B860DE300951467 /* DeveloperE2ei */,
@@ -9386,6 +9389,7 @@
93869389
06DF42D12757DA56009C8A99 /* GuestLinkInfoCell.swift in Sources */,
93879390
0665DF7328F01D3B0094482B /* ProfileImagePickerManager.swift in Sources */,
93889391
5E8FFC0321ECC5CF0052DF03 /* AuthenticationFeatureProvider.swift in Sources */,
9392+
01D2B13C2D63E8E90030D28D /* TextItemCell.swift in Sources */,
93899393
EF4C5D4E23391C7E0092CA38 /* ConversationListCell+ZMConversationObserver.swift in Sources */,
93909394
EF51C2BF211B2CAF0099A599 /* Data+HEIF.swift in Sources */,
93919395
BF2A74711CEB466A002608BF /* ConversationInputBarViewController+Audio.swift in Sources */,

wire-ios/Wire-iOS/Sources/Developer/DeveloperTools/ContextItemsProviders/ConversationDeveloperActionsProvider.swift

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,9 @@ struct ConversationDeveloperActionsProvider: DeveloperToolsContextItemsProvider
3737
var items = [
3838
makeConversationIdItem(),
3939
makeConversationTypeItem(),
40-
makeConversationMessageProtocolItem()
41-
]
40+
makeConversationMessageProtocolItem(),
41+
makeConversationMLSGroupIDItem()
42+
].compactMap { $0 }
4243

4344
if DeveloperFlag.debugDuplicateObjects.isOn {
4445
items.append(makeDuplicateConversationItem())
@@ -72,6 +73,13 @@ struct ConversationDeveloperActionsProvider: DeveloperToolsContextItemsProvider
7273
))
7374
}
7475

76+
private func makeConversationMLSGroupIDItem() -> DeveloperToolsViewModel.Item? {
77+
.text(DeveloperToolsViewModel.TextItem(
78+
title: "MLS Group ID",
79+
value: conversation.mlsGroupID?.description ?? "-"
80+
))
81+
}
82+
7583
private func makeDuplicateConversationItem() -> DeveloperToolsViewModel.Item {
7684
.button(ButtonItem(
7785
title: "Duplicate Conversation",

wire-ios/Wire-iOS/Sources/Developer/DeveloperTools/DebugActions/DeveloperDebugActionsView.swift

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,59 @@ import SwiftUI
2121
struct DeveloperDebugActionsView: View {
2222

2323
@ObservedObject var viewModel: DeveloperDebugActionsViewModel
24+
@State var userInput: String = ""
2425

2526
var body: some View {
2627
List(viewModel.buttons) { button in
2728
Button(action: button.action) {
2829
Text(button.title)
2930
}
3031
}
32+
.sheet(item: $viewModel.mlsGroupSearchItem, content: mlsGroupSearchView)
33+
.presentationDetents([.medium])
34+
.presentationDragIndicator(.visible)
35+
}
36+
37+
@ViewBuilder
38+
func mlsGroupSearchView(_ item: MLSGroupSearchItem) -> some View {
39+
List {
40+
Section("Conversations with MLS Group ID") {
41+
HStack {
42+
TextField("Enter MLS Group ID", text: $userInput)
43+
.onSubmit(submitUserInput)
44+
Button("Search", action: submitUserInput)
45+
.disabled(userInput.isEmpty)
46+
}
47+
}
48+
switch item {
49+
case let .result(results, term):
50+
if results.isEmpty, !term.isEmpty {
51+
Section {
52+
Text("Nothing found")
53+
}
54+
} else {
55+
ForEach(results, id: \.id) { result in
56+
Section {
57+
TextItemCell(title: "Name:", value: result.name) {
58+
UIPasteboard.general.string = result.name
59+
}
60+
TextItemCell(title: "Conversation id:", value: result.id) {
61+
UIPasteboard.general.string = result.id
62+
}
63+
TextItemCell(title: "MLS Group id:", value: result.groupID?.description ?? "-") {
64+
UIPasteboard.general.string = result.groupID?.description ?? "-"
65+
}
66+
}
67+
}
68+
}
69+
}
70+
}
71+
}
72+
73+
private func submitUserInput() {
74+
Task {
75+
await viewModel.findConversations(with: userInput.trim())
76+
}
3177
}
3278
}
3379

wire-ios/Wire-iOS/Sources/Developer/DeveloperTools/DebugActions/DeveloperDebugActionsViewModel.swift

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,31 @@ import WireDataModel
2121
import WireLogging
2222
import WireSyncEngine
2323

24+
struct ConversationResult {
25+
var id: String
26+
var groupID: MLSGroupID?
27+
var name: String
28+
29+
var description: String {
30+
id
31+
}
32+
}
33+
34+
enum MLSGroupSearchItem: Identifiable {
35+
var id: String {
36+
switch self {
37+
case .result:
38+
"result"
39+
}
40+
}
41+
42+
case result([ConversationResult], String)
43+
}
44+
2445
final class DeveloperDebugActionsViewModel: ObservableObject {
2546

2647
@Published var buttons: [DeveloperDebugActionsDisplayModel.ButtonItem] = []
48+
@Published var mlsGroupSearchItem: MLSGroupSearchItem?
2749

2850
private var userSession: ZMUserSession? { ZMUserSession.shared() }
2951

@@ -48,7 +70,8 @@ final class DeveloperDebugActionsViewModel: ObservableObject {
4870
.init(title: "Update Conversation to mixed protocol", action: updateConversationProtocolToMixed),
4971
.init(title: "Update Conversation to MLS protocol", action: updateConversationProtocolToMLS),
5072
.init(title: "Update MLS migration status", action: updateMLSMigrationStatus),
51-
.init(title: "Delete domains in the database", action: deleteDomains)
73+
.init(title: "Delete domains in the database", action: deleteDomains),
74+
.init(title: "Find Conversation with MLS Group", action: showSearchMLSConversations)
5275
]
5376
}
5477

@@ -204,4 +227,49 @@ final class DeveloperDebugActionsViewModel: ObservableObject {
204227
}
205228
}
206229

230+
// MARK: Find conversation
231+
232+
private func showSearchMLSConversations() {
233+
mlsGroupSearchItem = .result([], "")
234+
}
235+
236+
@MainActor
237+
func findConversations(with mlsGroupID: String?) async {
238+
guard let strippedMLSGroupID = mlsGroupID?.replacingOccurrences(of: "*", with: "") else {
239+
showConversationInfo(results: [], term: "")
240+
return
241+
}
242+
243+
guard let syncContext = userSession?.syncContext else {
244+
showConversationInfo(results: [], term: strippedMLSGroupID)
245+
return
246+
}
247+
248+
let results = try? await syncContext.perform {
249+
let fetchRequest = NSFetchRequest<ZMConversation>(entityName: ZMConversation.entityName())
250+
fetchRequest.fetchBatchSize = 50
251+
// as we have a string and MLSGroupID is data we can't fetch with a predicate
252+
let conversations = try syncContext.fetch(fetchRequest)
253+
254+
var matchedConversationInfos = [ConversationResult]()
255+
for conversation in conversations
256+
where conversation.mlsGroupID?.description.starts(with: strippedMLSGroupID) == true {
257+
matchedConversationInfos.append(
258+
ConversationResult(
259+
id: conversation.remoteIdentifier.uuidString,
260+
groupID: conversation.mlsGroupID,
261+
name: conversation.name ?? "-"
262+
)
263+
)
264+
}
265+
return matchedConversationInfos
266+
}
267+
showConversationInfo(results: results ?? [], term: strippedMLSGroupID)
268+
}
269+
270+
@MainActor
271+
private func showConversationInfo(results: [ConversationResult], term: String) {
272+
mlsGroupSearchItem = .result(results, term)
273+
}
274+
207275
}

wire-ios/Wire-iOS/Sources/Developer/DeveloperTools/DeveloperToolsView.swift

Lines changed: 3 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -57,18 +57,9 @@ struct DeveloperToolsView: View {
5757
}
5858

5959
case let .text(textItem):
60-
TextItemCell(title: textItem.title, value: textItem.value)
61-
.contextMenu {
62-
Button(
63-
hapticFeedbackStyle: .success,
64-
action: {
65-
viewModel.handleEvent(.itemCopyRequested(item))
66-
},
67-
label: {
68-
Label("Copy", systemImage: "doc.on.doc")
69-
}
70-
)
71-
}
60+
TextItemCell(title: textItem.title, value: textItem.value) {
61+
viewModel.handleEvent(.itemCopyRequested(item))
62+
}
7263

7364
case let .destination(destinationItem):
7465
NavigationLink(destinationItem.title, destination: destinationItem.makeView)
@@ -84,27 +75,6 @@ struct DeveloperToolsView: View {
8475

8576
}
8677

87-
// MARK: - Subviews
88-
89-
private struct TextItemCell: View {
90-
91-
let title: String
92-
let value: String
93-
94-
var body: some View {
95-
HStack {
96-
Text(title)
97-
98-
Spacer()
99-
100-
Text(value)
101-
.lineLimit(1)
102-
.truncationMode(.middle)
103-
.foregroundColor(.secondary)
104-
}
105-
}
106-
}
107-
10878
// MARK: - Previews
10979

11080
struct DeveloperToolsView_Previews: PreviewProvider {
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
//
2+
// Wire
3+
// Copyright (C) 2025 Wire Swiss GmbH
4+
//
5+
// This program is free software: you can redistribute it and/or modify
6+
// it under the terms of the GNU General Public License as published by
7+
// the Free Software Foundation, either version 3 of the License, or
8+
// (at your option) any later version.
9+
//
10+
// This program is distributed in the hope that it will be useful,
11+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
// GNU General Public License for more details.
14+
//
15+
// You should have received a copy of the GNU General Public License
16+
// along with this program. If not, see http://www.gnu.org/licenses/.
17+
//
18+
19+
import SwiftUI
20+
21+
struct TextItemCell: View {
22+
23+
let title: String
24+
let value: String
25+
let onCopy: () -> Void
26+
27+
var body: some View {
28+
HStack {
29+
Text(title)
30+
31+
Spacer()
32+
33+
Text(value)
34+
.lineLimit(1)
35+
.truncationMode(.middle)
36+
.foregroundColor(.secondary)
37+
}
38+
.contextMenu {
39+
Button(
40+
hapticFeedbackStyle: .success,
41+
action: {
42+
onCopy()
43+
},
44+
label: {
45+
Label("Copy", systemImage: "doc.on.doc")
46+
}
47+
)
48+
}
49+
}
50+
}

wire-ios/Wire-iOS/Sources/UserInterface/GroupDetails/Sections/MessageProtocolSectionController.swift

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ final class MessageProtocolSectionController: GroupDetailsSectionController {
6565
return 1
6666

6767
case .mls, .mixed:
68-
return Bundle.developerModeEnabled ? 3 : 2
68+
return 2
6969
}
7070
}
7171

@@ -92,10 +92,6 @@ final class MessageProtocolSectionController: GroupDetailsSectionController {
9292
cell.status = ciphersuite?.description ?? ""
9393
cell.allowMultilineStatus = true
9494

95-
case (.mls, 2) where Bundle.developerModeEnabled:
96-
cell.title = "Group ID (hashed)"
97-
cell.status = groupID?.safeForLoggingDescription
98-
9995
default:
10096
break
10197
}

0 commit comments

Comments
 (0)