Skip to content

Commit baacc53

Browse files
Merge pull request #19 from matejsemancik/feature/issue-link
Clickable issue key links
2 parents bbdf547 + 5c0c2b1 commit baacc53

File tree

12 files changed

+300
-17
lines changed

12 files changed

+300
-17
lines changed

gradle.properties

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
#Kotlin
22
kotlin.code.style=official
33
kotlin.daemon.jvmargs=-Xmx2048M
4+
45
#Gradle
5-
org.gradle.jvmargs=-Xmx2048M -Dfile.encoding=UTF-8
6+
org.gradle.jvmargs=-Xmx2048M -Dfile.encoding=UTF-8
7+
org.gradle.configuration-cache=true

gradle/libs.versions.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[versions]
22
androidx-lifecycle = "2.8.4"
33
compose-multiplatform = "1.7.3"
4-
compose-hotReload = "1.0.0-dev-65"
4+
compose-hotReload = "1.0.0-alpha01"
55
junit = "4.13.2"
66
kotlin = "2.1.20-RC"
77
ksp = "2.1.20-RC-1.0.31"
@@ -14,7 +14,7 @@ koin = "4.1.0-Beta5"
1414
ktor = "3.0.1"
1515
coil = "3.0.4"
1616
dataStore = "1.1.2"
17-
androidx-room = "2.7.0-alpha13"
17+
androidx-room = "2.7.0-rc01"
1818
androidx-sqlite = "2.5.0-alpha13"
1919
appDirs = "1.3.0"
2020
markdownRenderer = "0.31.0"
@@ -60,4 +60,4 @@ kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref =
6060
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
6161
ktorfit = { id = "de.jensklingenberg.ktorfit", version.ref = "ktorfit" }
6262
kotlinx-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlinx-serialization" }
63-
androidx-room = { id = "androidx.room", version.ref = "androidx-room" }
63+
androidx-room = { id = "androidx.room", version.ref = "androidx-room" }
Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
{
2+
"formatVersion": 1,
3+
"database": {
4+
"version": 7,
5+
"identityHash": "4292c45fa37b76e2a7393c0d57817d1d",
6+
"entities": [
7+
{
8+
"tableName": "user",
9+
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`account_id` TEXT NOT NULL, `email` TEXT NOT NULL, `display_name` TEXT NOT NULL, `avatar_url` TEXT NOT NULL, PRIMARY KEY(`account_id`))",
10+
"fields": [
11+
{
12+
"fieldPath": "accountId",
13+
"columnName": "account_id",
14+
"affinity": "TEXT",
15+
"notNull": true
16+
},
17+
{
18+
"fieldPath": "email",
19+
"columnName": "email",
20+
"affinity": "TEXT",
21+
"notNull": true
22+
},
23+
{
24+
"fieldPath": "displayName",
25+
"columnName": "display_name",
26+
"affinity": "TEXT",
27+
"notNull": true
28+
},
29+
{
30+
"fieldPath": "avatarUrl",
31+
"columnName": "avatar_url",
32+
"affinity": "TEXT",
33+
"notNull": true
34+
}
35+
],
36+
"primaryKey": {
37+
"autoGenerate": false,
38+
"columnNames": [
39+
"account_id"
40+
]
41+
}
42+
},
43+
{
44+
"tableName": "jira_issue",
45+
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `key` TEXT NOT NULL, `summary` TEXT NOT NULL, `icon_url` TEXT NOT NULL, `browse_url` TEXT NOT NULL DEFAULT '', PRIMARY KEY(`id`))",
46+
"fields": [
47+
{
48+
"fieldPath": "id",
49+
"columnName": "id",
50+
"affinity": "INTEGER",
51+
"notNull": true
52+
},
53+
{
54+
"fieldPath": "key",
55+
"columnName": "key",
56+
"affinity": "TEXT",
57+
"notNull": true
58+
},
59+
{
60+
"fieldPath": "sumary",
61+
"columnName": "summary",
62+
"affinity": "TEXT",
63+
"notNull": true
64+
},
65+
{
66+
"fieldPath": "iconUrl",
67+
"columnName": "icon_url",
68+
"affinity": "TEXT",
69+
"notNull": true
70+
},
71+
{
72+
"fieldPath": "browseUrl",
73+
"columnName": "browse_url",
74+
"affinity": "TEXT",
75+
"notNull": true,
76+
"defaultValue": "''"
77+
}
78+
],
79+
"primaryKey": {
80+
"autoGenerate": false,
81+
"columnNames": [
82+
"id"
83+
]
84+
}
85+
},
86+
{
87+
"tableName": "favourite_issue",
88+
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `jira_issue_id` INTEGER NOT NULL, FOREIGN KEY(`jira_issue_id`) REFERENCES `jira_issue`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
89+
"fields": [
90+
{
91+
"fieldPath": "id",
92+
"columnName": "id",
93+
"affinity": "INTEGER",
94+
"notNull": true
95+
},
96+
{
97+
"fieldPath": "jiraIssueId",
98+
"columnName": "jira_issue_id",
99+
"affinity": "INTEGER",
100+
"notNull": true
101+
}
102+
],
103+
"primaryKey": {
104+
"autoGenerate": true,
105+
"columnNames": [
106+
"id"
107+
]
108+
},
109+
"indices": [
110+
{
111+
"name": "index_favourite_issue_jira_issue_id",
112+
"unique": false,
113+
"columnNames": [
114+
"jira_issue_id"
115+
],
116+
"orders": [],
117+
"createSql": "CREATE INDEX IF NOT EXISTS `index_favourite_issue_jira_issue_id` ON `${TABLE_NAME}` (`jira_issue_id`)"
118+
}
119+
],
120+
"foreignKeys": [
121+
{
122+
"table": "jira_issue",
123+
"onDelete": "CASCADE",
124+
"onUpdate": "NO ACTION",
125+
"columns": [
126+
"jira_issue_id"
127+
],
128+
"referencedColumns": [
129+
"id"
130+
]
131+
}
132+
]
133+
},
134+
{
135+
"tableName": "timer",
136+
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `jira_issue_id` INTEGER NOT NULL, `accumulation` INTEGER NOT NULL, `created_at` TEXT NOT NULL, `last_started_at` TEXT, FOREIGN KEY(`jira_issue_id`) REFERENCES `jira_issue`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
137+
"fields": [
138+
{
139+
"fieldPath": "id",
140+
"columnName": "id",
141+
"affinity": "INTEGER",
142+
"notNull": true
143+
},
144+
{
145+
"fieldPath": "jiraIssueId",
146+
"columnName": "jira_issue_id",
147+
"affinity": "INTEGER",
148+
"notNull": true
149+
},
150+
{
151+
"fieldPath": "accumulationMs",
152+
"columnName": "accumulation",
153+
"affinity": "INTEGER",
154+
"notNull": true
155+
},
156+
{
157+
"fieldPath": "createdAt",
158+
"columnName": "created_at",
159+
"affinity": "TEXT",
160+
"notNull": true
161+
},
162+
{
163+
"fieldPath": "lastStartedAt",
164+
"columnName": "last_started_at",
165+
"affinity": "TEXT"
166+
}
167+
],
168+
"primaryKey": {
169+
"autoGenerate": true,
170+
"columnNames": [
171+
"id"
172+
]
173+
},
174+
"indices": [
175+
{
176+
"name": "index_timer_jira_issue_id",
177+
"unique": false,
178+
"columnNames": [
179+
"jira_issue_id"
180+
],
181+
"orders": [],
182+
"createSql": "CREATE INDEX IF NOT EXISTS `index_timer_jira_issue_id` ON `${TABLE_NAME}` (`jira_issue_id`)"
183+
}
184+
],
185+
"foreignKeys": [
186+
{
187+
"table": "jira_issue",
188+
"onDelete": "CASCADE",
189+
"onUpdate": "NO ACTION",
190+
"columns": [
191+
"jira_issue_id"
192+
],
193+
"referencedColumns": [
194+
"id"
195+
]
196+
}
197+
]
198+
}
199+
],
200+
"setupQueries": [
201+
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
202+
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '4292c45fa37b76e2a7393c0d57817d1d')"
203+
]
204+
}
205+
}

shared/src/commonMain/kotlin/dev/matsem/bpm/data/database/AppDatabase.kt

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package dev.matsem.bpm.data.database
22

3+
import androidx.room.AutoMigration
34
import androidx.room.ConstructedBy
45
import androidx.room.Database
56
import androidx.room.RoomDatabase
@@ -8,6 +9,7 @@ import androidx.room.TypeConverters
89
import dev.matsem.bpm.data.database.dao.JiraIssueDao
910
import dev.matsem.bpm.data.database.dao.TimerDao
1011
import dev.matsem.bpm.data.database.dao.UserDao
12+
import dev.matsem.bpm.data.database.migration.AutoMigrationSpecFrom6to7
1113
import dev.matsem.bpm.data.database.model.FavouriteIssue
1214
import dev.matsem.bpm.data.database.model.JiraIssue
1315
import dev.matsem.bpm.data.database.model.Timer
@@ -16,7 +18,10 @@ import dev.matsem.bpm.data.database.typeConverter.JsonTypeConverters
1618

1719
@Database(
1820
entities = [User::class, JiraIssue::class, FavouriteIssue::class, Timer::class],
19-
version = 6
21+
version = 7,
22+
autoMigrations = [
23+
AutoMigration(from = 6, to = 7, spec = AutoMigrationSpecFrom6to7::class)
24+
]
2025
)
2126
@TypeConverters(JsonTypeConverters::class)
2227
@ConstructedBy(AppDatabaseConstructor::class)
@@ -30,4 +35,5 @@ internal abstract class AppDatabase : RoomDatabase() {
3035

3136
internal expect object AppDatabaseConstructor : RoomDatabaseConstructor<AppDatabase> {
3237
override fun initialize(): AppDatabase
33-
}
38+
}
39+
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package dev.matsem.bpm.data.database.migration
2+
3+
import androidx.room.migration.AutoMigrationSpec
4+
import androidx.sqlite.SQLiteConnection
5+
import androidx.sqlite.execSQL
6+
import dev.matsem.bpm.data.persistence.ApplicationPersistence
7+
import dev.matsem.bpm.tooling.Constants
8+
import kotlinx.coroutines.MainScope
9+
import kotlinx.coroutines.launch
10+
import org.koin.core.component.KoinComponent
11+
import org.koin.core.component.inject
12+
13+
/**
14+
* A class that handles the automatic migration from version 6 to version 7 of the database schema.
15+
*/
16+
class AutoMigrationSpecFrom6to7 : AutoMigrationSpec, KoinComponent {
17+
18+
private val applicationPersistence: ApplicationPersistence by inject()
19+
private val coroutineScope = MainScope()
20+
21+
/**
22+
* Called after the migration process is complete.
23+
* Updates the 'browse_url' field in the 'jira_issue' table based on the 'key' field.
24+
*
25+
* @param connection The SQLite connection to the database.
26+
*/
27+
override fun onPostMigrate(connection: SQLiteConnection) {
28+
coroutineScope.launch {
29+
val jiraDomain = applicationPersistence.getCredentials()?.jiraDomain ?: run {
30+
println("User is not signed in, no migration of jira_issue is needed")
31+
return@launch
32+
}
33+
34+
val issueBrowseUrlPrefix = "${Constants.JiraApiUrl(jiraDomain)}browse/"
35+
connection.execSQL(
36+
"UPDATE jira_issue SET browse_url = '$issueBrowseUrlPrefix' || key"
37+
)
38+
}
39+
}
40+
}

shared/src/commonMain/kotlin/dev/matsem/bpm/data/database/model/JiraIssue.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,7 @@ data class JiraIssue(
1919

2020
@ColumnInfo(name = "icon_url")
2121
val iconUrl: String,
22-
)
22+
23+
@ColumnInfo(name = "browse_url", defaultValue = "")
24+
val browseUrl: String,
25+
)
Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package dev.matsem.bpm.data.mapping
22

3+
import dev.matsem.bpm.tooling.Constants
34
import dev.matsem.bpm.data.database.model.JiraIssue as JiraIssue_Database
45
import dev.matsem.bpm.data.repo.model.Issue as JiraIssue_Domain
56
import dev.matsem.bpm.data.service.jira.model.issuePicker.SuggestedIssue as JiraIssue_Network
@@ -10,20 +11,23 @@ internal object IssueMapping {
1011
id = id,
1112
key = key,
1213
summary = summary,
13-
iconUrl = apiUrl + iconUrlPath.removePrefix("/")
14+
iconUrl = apiUrl + iconUrlPath.removePrefix("/"),
15+
browseUrl = Constants.JiraIssueUrl(apiUrl, key)
1416
)
1517

1618
fun JiraIssue_Domain.toDbModel(): JiraIssue_Database = JiraIssue_Database(
1719
id = id,
1820
key = key,
1921
sumary = summary,
20-
iconUrl = iconUrl
22+
iconUrl = iconUrl,
23+
browseUrl = browseUrl,
2124
)
2225

2326
fun JiraIssue_Database.toDomainModel(): JiraIssue_Domain = JiraIssue_Domain(
2427
id = id,
2528
key = key,
2629
summary = sumary,
27-
iconUrl = iconUrl
30+
iconUrl = iconUrl,
31+
browseUrl = browseUrl
2832
)
29-
}
33+
}

shared/src/commonMain/kotlin/dev/matsem/bpm/data/repo/model/Issue.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ data class Issue(
55
val key: String,
66
val summary: String,
77
val iconUrl: String,
8+
val browseUrl: String,
89
)
910

1011
val MockIssueKeys = (1..100).map { "MTSM-$it" }
@@ -120,6 +121,7 @@ val MockIssues = MockIssueTitles
120121
id = index.toLong(),
121122
key = "MTSM-${index + 1}",
122123
summary = title,
123-
iconUrl = ""
124+
iconUrl = "",
125+
browseUrl = ""
124126
)
125127
}

0 commit comments

Comments
 (0)