updates
This commit is contained in:
+22
-1
@@ -218,4 +218,25 @@ sonar-scanner.log
|
||||
# *.xcodeproj
|
||||
# *.xcworkspace
|
||||
# *.xcassets
|
||||
# *.entitlements
|
||||
# *.entitlements
|
||||
|
||||
|
||||
.DS_Store
|
||||
|
||||
**/.DS_Store
|
||||
|
||||
# Xcode per-user state
|
||||
|
||||
xcuserdata/
|
||||
|
||||
*.xcuserstate
|
||||
|
||||
UserInterfaceState.xcuserstate
|
||||
|
||||
# Xcode build output
|
||||
|
||||
DerivedData/
|
||||
|
||||
build/
|
||||
|
||||
.build/
|
||||
Generated
+14
@@ -1,6 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="BackendCodeEditorSettings">
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CommentTypo/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAArrayIndexOutOfBounds/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAConstantFunctionResult/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAConstantParameter/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAEndlessLoop/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAInfiniteRecursion/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFATimeOver/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAUnreachableCode/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAUnreachableFunctionCall/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAUnreadVariable/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAUnusedValue/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppInconsistentNaming/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=IdentifierTypo/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=StringLiteralTypo/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
|
||||
<option name="/Default/CodeStyle/CodeFormatting/CppClangFormat/EnableClangFormatSupport/@EntryValue" value="false" type="bool" />
|
||||
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/ALIGN_MULTILINE_ARGUMENT/@EntryValue" value="true" type="bool" />
|
||||
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/ALIGN_MULTILINE_BINARY_EXPRESSIONS_CHAIN/@EntryValue" value="true" type="bool" />
|
||||
|
||||
BIN
Binary file not shown.
-14
@@ -1,14 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>SchemeUserState</key>
|
||||
<dict>
|
||||
<key>SonarDesk.xcscheme_^#shared#^_</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
Vendored
BIN
Binary file not shown.
@@ -42,6 +42,7 @@ struct IssuesView: View {
|
||||
@State private var filter: IssueFilter
|
||||
@State private var page = 1
|
||||
@State private var pageSize = 25
|
||||
@State private var selectedIssue: Issue?
|
||||
|
||||
init(projectKey: String?, initialFilter: IssueFilter = .all) {
|
||||
self.projectKey = projectKey
|
||||
@@ -64,6 +65,23 @@ struct IssuesView: View {
|
||||
private var requestID: String { "\(projectKey ?? "all")|\(filter.rawValue)|\(page)|\(pageSize)" }
|
||||
|
||||
var body: some View {
|
||||
Group {
|
||||
if let selectedIssue {
|
||||
IssueDetailView(issue: selectedIssue, tint: filter.tint) {
|
||||
self.selectedIssue = nil
|
||||
}
|
||||
} else {
|
||||
issueList
|
||||
}
|
||||
}
|
||||
.background(SonarTheme.canvas)
|
||||
.navigationTitle(selectedIssue == nil ? filter.rawValue : "Issue Details")
|
||||
.task(id: requestID) { await app.loadIssues(projectKey: projectKey, filter: filter, page: page, pageSize: pageSize) }
|
||||
.onChange(of: filter) { _, _ in page = 1; search = ""; selectedIssue = nil }
|
||||
.onChange(of: pageSize) { _, _ in page = 1 }
|
||||
}
|
||||
|
||||
private var issueList: some View {
|
||||
VStack(spacing: 0) {
|
||||
issueHeader
|
||||
Divider().opacity(0.55)
|
||||
@@ -93,11 +111,6 @@ struct IssuesView: View {
|
||||
Divider().opacity(0.55)
|
||||
pagination
|
||||
}
|
||||
.background(SonarTheme.canvas)
|
||||
.navigationTitle(filter.rawValue)
|
||||
.task(id: requestID) { await app.loadIssues(projectKey: projectKey, filter: filter, page: page, pageSize: pageSize) }
|
||||
.onChange(of: filter) { _, _ in page = 1; search = "" }
|
||||
.onChange(of: pageSize) { _, _ in page = 1 }
|
||||
}
|
||||
|
||||
private var issueHeader: some View {
|
||||
@@ -156,6 +169,8 @@ struct IssuesView: View {
|
||||
}
|
||||
.padding(.horizontal, 20).padding(.vertical, 12)
|
||||
.contentShape(Rectangle())
|
||||
.onTapGesture { selectedIssue = issue }
|
||||
.help("Open issue details")
|
||||
}
|
||||
|
||||
private var pagination: some View {
|
||||
@@ -164,12 +179,34 @@ struct IssuesView: View {
|
||||
Text(app.issueTotal == 0 ? "No issues" : "Showing \(start)–\(min(page * pageSize, app.issueTotal)) of \(app.issueTotal)")
|
||||
.font(.caption).foregroundStyle(.secondary)
|
||||
Spacer()
|
||||
Button { page = max(1, page - 1) } label: { Label("Previous", systemImage: "chevron.left") }.disabled(page <= 1 || app.isLoading)
|
||||
Text("Page \(page) of \(pageCount)").font(.caption.monospacedDigit()).frame(minWidth: 92)
|
||||
Button { page = min(pageCount, page + 1) } label: { Label("Next", systemImage: "chevron.right") }.disabled(page >= pageCount || app.isLoading)
|
||||
pagingButton("Previous", systemImage: "chevron.left", disabled: page <= 1 || app.isLoading) {
|
||||
page = max(1, page - 1)
|
||||
}
|
||||
Text("Page \(page) of \(pageCount)").font(.caption.monospacedDigit().weight(.semibold)).frame(minWidth: 92)
|
||||
pagingButton("Next", systemImage: "chevron.right", disabled: page >= pageCount || app.isLoading) {
|
||||
page = min(pageCount, page + 1)
|
||||
}
|
||||
}.padding(.horizontal, 18).padding(.vertical, 12).background(SonarTheme.panel.opacity(0.45))
|
||||
}
|
||||
|
||||
private func pagingButton(_ title: String, systemImage: String, disabled: Bool, action: @escaping () -> Void) -> some View {
|
||||
Button(action: action) {
|
||||
Label(title, systemImage: systemImage)
|
||||
.font(.subheadline.weight(.semibold))
|
||||
.foregroundStyle(disabled ? Color.primary.opacity(0.55) : Color.white)
|
||||
.padding(.horizontal, 14)
|
||||
.padding(.vertical, 7)
|
||||
.background(disabled ? Color.primary.opacity(0.08) : SonarTheme.accent,
|
||||
in: RoundedRectangle(cornerRadius: 8, style: .continuous))
|
||||
.overlay {
|
||||
RoundedRectangle(cornerRadius: 8, style: .continuous)
|
||||
.stroke(disabled ? Color.primary.opacity(0.14) : SonarTheme.accent.opacity(0.8))
|
||||
}
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.allowsHitTesting(!disabled)
|
||||
}
|
||||
|
||||
private func severityBadge(_ severity: String) -> some View {
|
||||
let color: Color = switch severity.uppercased() {
|
||||
case "BLOCKER", "CRITICAL": SonarTheme.danger
|
||||
@@ -184,3 +221,126 @@ struct IssuesView: View {
|
||||
private func displayType(_ value: String?) -> String { (value ?? "—").replacingOccurrences(of: "_", with: " ").capitalized }
|
||||
private func shortComponent(_ value: String?) -> String { guard let value else { return "—" }; return value.split(separator: ":").last.map(String.init) ?? value }
|
||||
}
|
||||
|
||||
|
||||
private struct IssueDetailView: View {
|
||||
let issue: Issue
|
||||
let tint: Color
|
||||
let onBack: () -> Void
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 0) {
|
||||
HStack(spacing: 12) {
|
||||
Button(action: onBack) {
|
||||
Label("Back to Issues", systemImage: "chevron.left")
|
||||
.font(.subheadline.weight(.semibold))
|
||||
.foregroundStyle(Color.white)
|
||||
.padding(.horizontal, 13)
|
||||
.padding(.vertical, 7)
|
||||
.background(SonarTheme.accent, in: RoundedRectangle(cornerRadius: 8, style: .continuous))
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
|
||||
Divider().frame(height: 24)
|
||||
|
||||
Image(systemName: icon)
|
||||
.font(.system(size: 17, weight: .semibold))
|
||||
.foregroundStyle(tint)
|
||||
Text("Issue Details").font(.title2.bold())
|
||||
Spacer()
|
||||
badge(issue.severity ?? "UNKNOWN", color: severityColor)
|
||||
badge(displayType(issue.type), color: tint)
|
||||
}
|
||||
.padding(.horizontal, 22).padding(.vertical, 16)
|
||||
.background(SonarTheme.panel.opacity(0.45))
|
||||
|
||||
ScrollView {
|
||||
VStack(alignment: .leading, spacing: 18) {
|
||||
SonarPanel {
|
||||
VStack(alignment: .leading, spacing: 10) {
|
||||
Text("MESSAGE").font(.caption2.bold()).foregroundStyle(.secondary)
|
||||
Text(issue.message ?? "No issue message supplied by SonarQube.")
|
||||
.font(.title3.weight(.semibold))
|
||||
.textSelection(.enabled)
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.padding(18)
|
||||
}
|
||||
|
||||
SonarPanel {
|
||||
VStack(spacing: 0) {
|
||||
detailRow("Rule", issue.rule ?? "—")
|
||||
Divider().opacity(0.45)
|
||||
detailRow("Status", issue.status ?? "—")
|
||||
Divider().opacity(0.45)
|
||||
detailRow("Type", displayType(issue.type))
|
||||
Divider().opacity(0.45)
|
||||
detailRow("Severity", issue.severity?.capitalized ?? "—")
|
||||
Divider().opacity(0.45)
|
||||
detailRow("Project", issue.project ?? "—")
|
||||
Divider().opacity(0.45)
|
||||
detailRow("Component / File", issue.component ?? "—")
|
||||
Divider().opacity(0.45)
|
||||
detailRow("Line", issue.line.map(String.init) ?? "—")
|
||||
Divider().opacity(0.45)
|
||||
detailRow("Effort", issue.effort ?? "—")
|
||||
Divider().opacity(0.45)
|
||||
detailRow("Created", formattedDate(issue.creationDate))
|
||||
Divider().opacity(0.45)
|
||||
detailRow("Issue Key", issue.key)
|
||||
}
|
||||
.padding(.horizontal, 18)
|
||||
}
|
||||
}
|
||||
.padding(22)
|
||||
.frame(maxWidth: 980)
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var icon: String {
|
||||
switch issue.type?.uppercased() {
|
||||
case "BUG": "ladybug.fill"
|
||||
case "VULNERABILITY": "lock.trianglebadge.exclamationmark.fill"
|
||||
case "CODE_SMELL": "exclamationmark.bubble.fill"
|
||||
default: "exclamationmark.triangle.fill"
|
||||
}
|
||||
}
|
||||
|
||||
private var severityColor: Color {
|
||||
switch issue.severity?.uppercased() {
|
||||
case "BLOCKER", "CRITICAL": SonarTheme.danger
|
||||
case "MAJOR": SonarTheme.warning
|
||||
case "MINOR": SonarTheme.info
|
||||
default: .secondary
|
||||
}
|
||||
}
|
||||
|
||||
private func detailRow(_ label: String, _ value: String) -> some View {
|
||||
HStack(alignment: .top, spacing: 18) {
|
||||
Text(label).font(.caption.weight(.semibold)).foregroundStyle(.secondary).frame(width: 130, alignment: .leading)
|
||||
Text(value).font(.subheadline).textSelection(.enabled).frame(maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
.padding(.vertical, 11)
|
||||
}
|
||||
|
||||
private func badge(_ value: String, color: Color) -> some View {
|
||||
Text(value.replacingOccurrences(of: "_", with: " ").capitalized)
|
||||
.font(.caption.weight(.bold))
|
||||
.foregroundStyle(color)
|
||||
.padding(.horizontal, 9).padding(.vertical, 5)
|
||||
.background(color.opacity(0.13), in: Capsule())
|
||||
}
|
||||
|
||||
private func displayType(_ value: String?) -> String {
|
||||
(value ?? "—").replacingOccurrences(of: "_", with: " ").capitalized
|
||||
}
|
||||
|
||||
private func formattedDate(_ value: String?) -> String {
|
||||
guard let value else { return "—" }
|
||||
let formatter = ISO8601DateFormatter()
|
||||
guard let date = formatter.date(from: value) else { return value }
|
||||
return date.formatted(date: .abbreviated, time: .shortened)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user