diff --git a/README.ja.md b/README.ja.md index 4b10265..1e9c226 100644 --- a/README.ja.md +++ b/README.ja.md @@ -20,7 +20,7 @@ CSVファイルに記載された情報を読み込んで、Redmineにチケッ 入手したjarファイルを指定してアプリケーションを実行します。 ``` -java -jar redmine-issue-loader-2.4.1-all.jar config.json issues.csv +java -jar redmine-issue-loader-2.5.0-all.jar config.json issues.csv ``` 第1引数が設定ファイル、第2引数がチケットの情報が書かれたCSVファイルとなります。 @@ -85,6 +85,15 @@ Processing is completed. 3 issues were loaded. "type": "CUSTOM_FIELD", "customFieldId": 2, "multipleItemSeparator": ";" + }, + { + "headerName": "Watchers", + "type": "WATCHER_USER_IDS", + "multipleItemSeparator": ";", + "mappings": { + "User A": 5, + "User B": 6 + } } ] } @@ -93,10 +102,10 @@ Processing is completed. 3 issues were loaded. 上記に対応するCSVファイルの例です。 ```csv -Project,Tracker,Subject,Description,Field1,Field2,Field3 -Project A,Bug,xxxx,yyyy,A,1;2,C +Project,Tracker,Subject,Description,Field1,Field2,Watchers +Project A,Bug,xxxx,yyyy,A,1;2,User A;User B Project B,Feature,aaaa,bbbb,,, -Project B,Bug,zzzz,zzzz,1,2,3 +Project B,Bug,zzzz,zzzz,1,2,User B ``` ### 例: 更新時 @@ -158,7 +167,7 @@ Project B,Bug,zzzz,zzzz,1,2,3 * `headerName` : CSV内のヘッダ名。 * `type` : 種別。種別として指定可能なものは後述。 * `customFieldId` : カスタムフィールドのID。種別が`CUSTOM_FIELD`の場合に設定する。 - * `multipleItemSeparator` : 値を分割する文字。複数選択のカスタムフィールドの場合に設定する。 + * `multipleItemSeparator` : 値を分割する文字。種別が`WATCHER_USER_IDS`、または`CUSTOM_FIELD`で複数選択の場合に設定する。 * `primaryKey` : プライマリーキーか。更新時のみ有効な項目であり、`true`となっているフィールドの情報を使って更新対象のチケットを検索し、`false`となっているフィールドが更新されることとなる。 * `mappings` : CSV上の値とRedmine上での値のマッピングを記載することによって、CSVの内容を変換して登録できる。たとえば、プロジェクト名をプロジェクトIDに変換する場合など。 @@ -183,6 +192,7 @@ Project B,Bug,zzzz,zzzz,1,2,3 |`IS_PRIVATE`|○|○|プライベートか。`true`または`false`を指定。|-| |`ESTIMATED_HOURS`|○|○|予定工数。|-| |`CUSTOM_FIELD`|○|○|カスタムフィールド。更新時のプライマリーキーとしても利用できる。
この種別を指定する際には、`customFieldId`として対応するカスタムフィールドのIDを指定する必要がある。|`/custom_fields.xml`| +|`WATCHER_USER_IDS`|○|×|ウォッチャーのID。更新には対応していない。|`/users.xml`| IDとして指定するものは、上記表のID確認URLでIDを確認することができます。 @@ -241,3 +251,13 @@ gradlew shadowJar ``` `build/libs/redmine-issue-loader-x.x.x-all.jar`という実行ファイルが出来上がります。(`x.x.x`はバージョン番号) + +## ライセンス + +MIT + +## 作者 + +[onozaty](https://github.com/onozaty) + +[スポンサー](https://github.com/sponsors/onozaty) となり、本プロジェクトを維持することに貢献していただける方を募集しています。 diff --git a/README.md b/README.md index ae2d9af..03c4e51 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,15 @@ An example of a configuration file when creating a new issue. "type": "CUSTOM_FIELD", "customFieldId": 2, "multipleItemSeparator": ";" + }, + { + "headerName": "Watchers", + "type": "WATCHER_USER_IDS", + "multipleItemSeparator": ";", + "mappings": { + "User A": 5, + "User B": 6 + } } ] } @@ -92,10 +101,10 @@ An example of a configuration file when creating a new issue. An example of a CSV file corresponding to the above configuration file. ```csv -Project,Tracker,Subject,Description,Field1,Field2,Field3 -Project A,Bug,xxxx,yyyy,A,1;2,C +Project,Tracker,Subject,Description,Field1,Field2,Watchers +Project A,Bug,xxxx,yyyy,A,1;2,User A;User B Project B,Feature,aaaa,bbbb,,, -Project B,Bug,zzzz,zzzz,1,2,3 +Project B,Bug,zzzz,zzzz,1,2,User B ``` ### Ex: update issue @@ -157,7 +166,7 @@ The contents of each item are as follows. * `headerName` : Header name in CSV. * `type` : Type. What can be specified as a type is described later. * `customFieldId` : ID of the custom field. Set if the type is `CUSTOM_FIELD`. - * `multipleItemSeparator` : The character to separate the values. Set for multiple selection custom fields. + * `multipleItemSeparator` : The character to separate the values. Set if the type is `WATCHER_USER_IDS` or `CUSTOM_FIELD` and multiple selection. * `primaryKey` : Primary key? Search for issues to be updated using the information of the field set to `true`, and the field` false` will be updated. It is not necessary to specify when mode is `CREATE`. * `mappings` : By describing the mapping between the value on CSV and the value on Redmine, contents of CSV can be converted and registered. For example, to convert a project name to a project ID. @@ -182,6 +191,7 @@ Items that can be specified as a type of field are as follows. |`IS_PRIVATE`|○|○|Private. `true` or `false`.|-| |`ESTIMATED_HOURS`|○|○|Estimated time.|-| |`CUSTOM_FIELD`|○|○|Custom field. It can also be used as a primary key for updating.
When specifying this type, you need to specify the ID of the corresponding custom field as `customFieldId`.|`/custom_fields.xml`| +|`WATCHER_USER_IDS`|○|×|Watcher ID. It does not support update mode.|`/users.xml`| Items specified as ID can be confirmed with the ID confirmation URL in the table above. @@ -240,3 +250,13 @@ gradlew shadowJar ``` `build/libs/redmine-issue-loader-x.x.x-all.jar` will be created. (`x.x.x` is version number) + +## License + +MIT + +## Author + +[onozaty](https://github.com/onozaty) + +I am looking for people who are willing to become [sponsors](https://github.com/sponsors/onozaty) and contribute to maintaining this project. diff --git a/sample/config-create.json b/sample/config-create.json index 0637181..60dcf33 100644 --- a/sample/config-create.json +++ b/sample/config-create.json @@ -104,6 +104,15 @@ "headerName": "Field1", "type": "CUSTOM_FIELD", "customFieldId": 1 + }, + { + "headerName": "Watchers", + "type": "WATCHER_USER_IDS", + "multipleItemSeparator": ";", + "mappings": { + "User A": 5, + "User B": 6 + } } ] } diff --git a/sample/issues.csv b/sample/issues.csv index 0ee4b87..b63d6fd 100644 --- a/sample/issues.csv +++ b/sample/issues.csv @@ -1,4 +1,4 @@ -#,Project,Tracker,Status,Priority,Assignee,Category,Target version,Parent #,Subject,Description,Start date,Due date,% Done,Private,Estimated time,Field1,Field2,Field3 -1,Project A,Bug,New,Normal,User A,Category1,v1.0,,xxx,aaa,2019/2/1,2019/2/20,10,true,2.5,A,1,A -2,Project B,Bug,In Progress,Low,User B,,,,yyy,,2019/3/2,,,false,,B,1;2,B -3,Project A,Support,Closed,High,,Category2,v2.0,1,zzz,ccc,,2019/10/30,90,false,10,C,,C +#,Project,Tracker,Status,Priority,Assignee,Category,Target version,Parent #,Subject,Description,Start date,Due date,% Done,Private,Estimated time,Field1,Field2,Field3,Watchers +1,Project A,Bug,New,Normal,User A,Category1,v1.0,,xxx,aaa,2019/2/1,2019/2/20,10,true,2.5,A,1,A,User B +2,Project B,Bug,In Progress,Low,User B,,,,yyy,,2019/3/2,,,false,,B,1;2,B, +3,Project A,Support,Closed,High,,Category2,v2.0,1,zzz,ccc,,2019/10/30,90,false,10,C,,C,User A;User B diff --git a/src/main/java/com/github/onozaty/redmine/issue/loader/input/FieldType.java b/src/main/java/com/github/onozaty/redmine/issue/loader/input/FieldType.java index bde57a8..8b08d9d 100644 --- a/src/main/java/com/github/onozaty/redmine/issue/loader/input/FieldType.java +++ b/src/main/java/com/github/onozaty/redmine/issue/loader/input/FieldType.java @@ -46,7 +46,9 @@ public enum FieldType { ESTIMATED_HOURS("estimated_hours"), - CUSTOM_FIELD("custom_field"); + CUSTOM_FIELD("custom_field"), + + WATCHER_USER_IDS("watcher_user_ids"); @Getter private final String fieldName; diff --git a/src/main/java/com/github/onozaty/redmine/issue/loader/input/IssueRecords.java b/src/main/java/com/github/onozaty/redmine/issue/loader/input/IssueRecords.java index 9e084aa..f954f37 100644 --- a/src/main/java/com/github/onozaty/redmine/issue/loader/input/IssueRecords.java +++ b/src/main/java/com/github/onozaty/redmine/issue/loader/input/IssueRecords.java @@ -7,7 +7,10 @@ import java.nio.charset.Charset; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Arrays; +import java.util.Collections; import java.util.Iterator; +import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -111,6 +114,33 @@ private IssueRecord toIssueRecord(CSVRecord csvRecord) { break; + case WATCHER_USER_IDS: + + // ウォッチャーはリスト + + List watcherUserIds; + + if (StringUtils.isEmpty(value)) { + + watcherUserIds = Collections.emptyList(); + + } else if (StringUtils.isNotEmpty(fieldSetting.getMultipleItemSeparator())) { + + watcherUserIds = Stream.of(StringUtils.split(value, fieldSetting.getMultipleItemSeparator())) + .map(v -> convertValue(v, fieldSetting)) + .map(Integer::valueOf) + .collect(Collectors.toList()); + + } else { + + // 区切り文字が無い場合、1ユーザとして登録 + watcherUserIds = Arrays.asList(Integer.valueOf(convertValue(value, fieldSetting))); + } + + targetFieldsBuilder.field(fieldType, watcherUserIds); + + break; + default: // その他の項目は更新対象フィールドとして利用 value = convertValue(value, fieldSetting); diff --git a/src/main/java/com/github/onozaty/redmine/issue/loader/input/IssueTargetFieldsBuilder.java b/src/main/java/com/github/onozaty/redmine/issue/loader/input/IssueTargetFieldsBuilder.java index 8fd2c07..3da1880 100644 --- a/src/main/java/com/github/onozaty/redmine/issue/loader/input/IssueTargetFieldsBuilder.java +++ b/src/main/java/com/github/onozaty/redmine/issue/loader/input/IssueTargetFieldsBuilder.java @@ -9,7 +9,7 @@ public class IssueTargetFieldsBuilder { private Map updateTargetFields = new LinkedHashMap<>(); // テスト時に順序を保証したいので - public IssueTargetFieldsBuilder field(FieldType type, String value) { + public IssueTargetFieldsBuilder field(FieldType type, Object value) { updateTargetFields.put(type.getFieldName(), value); return this; diff --git a/src/test/java/com/github/onozaty/redmine/issue/loader/IssueLoadRunnerTest.java b/src/test/java/com/github/onozaty/redmine/issue/loader/IssueLoadRunnerTest.java index 7672c89..cf18020 100644 --- a/src/test/java/com/github/onozaty/redmine/issue/loader/IssueLoadRunnerTest.java +++ b/src/test/java/com/github/onozaty/redmine/issue/loader/IssueLoadRunnerTest.java @@ -51,7 +51,7 @@ public class IssueLoadRunnerTest { assertThat(request.getHeader("X-Redmine-API-Key")).isEqualTo("apikey1234567890"); assertThat(request.getPath()).isEqualTo("/issues.json"); assertThat(request.getBody().readUtf8()).isEqualTo( - "{\"issue\":{\"project_id\":\"1\",\"tracker_id\":\"2\",\"status_id\":\"1\",\"priority_id\":\"2\",\"assigned_to_id\":\"5\",\"category_id\":\"2\",\"fixed_version_id\":\"2\",\"parent_issue_id\":\"\",\"subject\":\"xxx\",\"description\":\"説明1\",\"start_date\":\"2019-02-01\",\"due_date\":\"2019-02-20\",\"done_ratio\":\"10\",\"is_private\":\"true\",\"estimated_hours\":\"2.5\",\"custom_fields\":[{\"id\":1,\"value\":\"A\"},{\"id\":2,\"value\":\"a\"},{\"id\":3,\"value\":[\"C\"]}]}}"); + "{\"issue\":{\"project_id\":\"1\",\"tracker_id\":\"2\",\"status_id\":\"1\",\"priority_id\":\"2\",\"assigned_to_id\":\"5\",\"category_id\":\"2\",\"fixed_version_id\":\"2\",\"parent_issue_id\":\"\",\"subject\":\"xxx\",\"description\":\"説明1\",\"start_date\":\"2019-02-01\",\"due_date\":\"2019-02-20\",\"done_ratio\":\"10\",\"is_private\":\"true\",\"estimated_hours\":\"2.5\",\"custom_fields\":[{\"id\":1,\"value\":\"A\"},{\"id\":2,\"value\":\"a\"},{\"id\":3,\"value\":[\"C\"]}],\"watcher_user_ids\":[6]}}"); } // 2レコード目 @@ -61,7 +61,7 @@ public class IssueLoadRunnerTest { assertThat(request.getHeader("X-Redmine-API-Key")).isEqualTo("apikey1234567890"); assertThat(request.getPath()).isEqualTo("/issues.json"); assertThat(request.getBody().readUtf8()).isEqualTo( - "{\"issue\":{\"project_id\":\"2\",\"tracker_id\":\"2\",\"status_id\":\"2\",\"priority_id\":\"1\",\"assigned_to_id\":\"\",\"category_id\":\"2\",\"fixed_version_id\":\"\",\"parent_issue_id\":\"\",\"subject\":\"yyy\",\"description\":\"説明2\",\"start_date\":\"2019-03-02\",\"due_date\":\"\",\"done_ratio\":\"\",\"is_private\":\"false\",\"estimated_hours\":\"\",\"custom_fields\":[{\"id\":1,\"value\":\"B\"},{\"id\":2,\"value\":\"b\"},{\"id\":3,\"value\":[\"A\",\"B\"]}]}}"); + "{\"issue\":{\"project_id\":\"2\",\"tracker_id\":\"2\",\"status_id\":\"2\",\"priority_id\":\"1\",\"assigned_to_id\":\"\",\"category_id\":\"2\",\"fixed_version_id\":\"\",\"parent_issue_id\":\"\",\"subject\":\"yyy\",\"description\":\"説明2\",\"start_date\":\"2019-03-02\",\"due_date\":\"\",\"done_ratio\":\"\",\"is_private\":\"false\",\"estimated_hours\":\"\",\"custom_fields\":[{\"id\":1,\"value\":\"B\"},{\"id\":2,\"value\":\"b\"},{\"id\":3,\"value\":[\"A\",\"B\"]}],\"watcher_user_ids\":[5,6]}}"); } // 3レコード目 @@ -71,7 +71,7 @@ public class IssueLoadRunnerTest { assertThat(request.getHeader("X-Redmine-API-Key")).isEqualTo("apikey1234567890"); assertThat(request.getPath()).isEqualTo("/issues.json"); assertThat(request.getBody().readUtf8()).isEqualTo( - "{\"issue\":{\"project_id\":\"1\",\"tracker_id\":\"3\",\"status_id\":\"3\",\"priority_id\":\"3\",\"assigned_to_id\":\"6\",\"category_id\":\"1\",\"fixed_version_id\":\"1\",\"parent_issue_id\":\"1\",\"subject\":\"zzz\",\"description\":\"説明3\",\"start_date\":\"2019-03-12\",\"due_date\":\"2019-10-30\",\"done_ratio\":\"90\",\"is_private\":\"false\",\"estimated_hours\":\"10\",\"custom_fields\":[{\"id\":1,\"value\":\"C\"},{\"id\":2,\"value\":\"c\"},{\"id\":3,\"value\":[]}]}}"); + "{\"issue\":{\"project_id\":\"1\",\"tracker_id\":\"3\",\"status_id\":\"3\",\"priority_id\":\"3\",\"assigned_to_id\":\"6\",\"category_id\":\"1\",\"fixed_version_id\":\"1\",\"parent_issue_id\":\"1\",\"subject\":\"zzz\",\"description\":\"説明3\",\"start_date\":\"2019-03-12\",\"due_date\":\"2019-10-30\",\"done_ratio\":\"90\",\"is_private\":\"false\",\"estimated_hours\":\"10\",\"custom_fields\":[{\"id\":1,\"value\":\"C\"},{\"id\":2,\"value\":\"c\"},{\"id\":3,\"value\":[]}],\"watcher_user_ids\":[]}}"); } } } @@ -134,14 +134,14 @@ public class IssueLoadRunnerTest { server.start(); Path configPath = - Paths.get(IssueLoadRunnerTest.class.getResource("create-multiple_custom_fields.json").toURI()); + Paths.get(IssueLoadRunnerTest.class.getResource("create-multiple-custom_fields.json").toURI()); Config config = Config.of(configPath); // Mockに対してリクエスト送信するよう設定 config.setReadmineUrl(server.url("/").toString()); Path csvPath = - Paths.get(IssueLoadRunnerTest.class.getResource("issues-multiple_custom_fields.csv").toURI()); + Paths.get(IssueLoadRunnerTest.class.getResource("issues-multiple-custom_fields.csv").toURI()); IssueLoadRunner runner = new IssueLoadRunner(System.out); runner.execute(config, csvPath); @@ -180,6 +180,53 @@ public class IssueLoadRunnerTest { } } + @Test + public void execute_新規作成_ウォッチャー区切り文字無し() throws URISyntaxException, IOException, InterruptedException { + + try (MockWebServer server = new MockWebServer()) { + + server.enqueue(new MockResponse().setBody("{\"issue\":{\"id\":1}}")); + server.enqueue(new MockResponse().setBody("{\"issue\":{\"id\":2}}")); + + server.start(); + + Path configPath = + Paths.get(IssueLoadRunnerTest.class.getResource("create-single-watchers.json").toURI()); + Config config = Config.of(configPath); + + // Mockに対してリクエスト送信するよう設定 + config.setReadmineUrl(server.url("/").toString()); + + Path csvPath = + Paths.get(IssueLoadRunnerTest.class.getResource("issues-single-watchers.csv").toURI()); + + IssueLoadRunner runner = new IssueLoadRunner(System.out); + runner.execute(config, csvPath); + + assertThat(server.getRequestCount()).isEqualTo(2); + + // 1レコード目 + { + RecordedRequest request = server.takeRequest(); + assertThat(request.getMethod()).isEqualTo("POST"); + assertThat(request.getHeader("X-Redmine-API-Key")).isEqualTo("apikey1234567890"); + assertThat(request.getPath()).isEqualTo("/issues.json"); + assertThat(request.getBody().readUtf8()).isEqualTo( + "{\"issue\":{\"project_id\":\"1\",\"subject\":\"xxx\",\"watcher_user_ids\":[1]}}"); + } + + // 2レコード目 + { + RecordedRequest request = server.takeRequest(); + assertThat(request.getMethod()).isEqualTo("POST"); + assertThat(request.getHeader("X-Redmine-API-Key")).isEqualTo("apikey1234567890"); + assertThat(request.getPath()).isEqualTo("/issues.json"); + assertThat(request.getBody().readUtf8()).isEqualTo( + "{\"issue\":{\"project_id\":\"2\",\"subject\":\"yyy\",\"watcher_user_ids\":[]}}"); + } + } + } + @Test public void execute_Basic認証() throws URISyntaxException, IOException, InterruptedException { diff --git a/src/test/resources/com/github/onozaty/redmine/issue/loader/create-all_fields.json b/src/test/resources/com/github/onozaty/redmine/issue/loader/create-all_fields.json index 7f0f051..6ee7da6 100644 --- a/src/test/resources/com/github/onozaty/redmine/issue/loader/create-all_fields.json +++ b/src/test/resources/com/github/onozaty/redmine/issue/loader/create-all_fields.json @@ -110,6 +110,15 @@ "type": "CUSTOM_FIELD", "customFieldId": 3, "multipleItemSeparator": ";" + }, + { + "headerName": "Watchers", + "type": "WATCHER_USER_IDS", + "multipleItemSeparator": ";", + "mappings": { + "ユーザA": 5, + "ユーザB": 6 + } } ] } diff --git a/src/test/resources/com/github/onozaty/redmine/issue/loader/create-multiple_custom_fields.json b/src/test/resources/com/github/onozaty/redmine/issue/loader/create-multiple-custom_fields.json similarity index 100% rename from src/test/resources/com/github/onozaty/redmine/issue/loader/create-multiple_custom_fields.json rename to src/test/resources/com/github/onozaty/redmine/issue/loader/create-multiple-custom_fields.json diff --git a/src/test/resources/com/github/onozaty/redmine/issue/loader/create-single-watchers.json b/src/test/resources/com/github/onozaty/redmine/issue/loader/create-single-watchers.json new file mode 100644 index 0000000..8492a84 --- /dev/null +++ b/src/test/resources/com/github/onozaty/redmine/issue/loader/create-single-watchers.json @@ -0,0 +1,24 @@ +{ + "mode": "CREATE", + "readmineUrl": "http://localhost", + "apiKey": "apikey1234567890", + "csvEncoding": "UTF-8", + "fields": [ + { + "headerName": "Project", + "type": "PROJECT_ID", + "mappings": { + "プロジェクト1": 1, + "プロジェクト2": 2 + } + }, + { + "headerName": "Subject", + "type": "SUBJECT" + }, + { + "headerName": "Watchers", + "type": "WATCHER_USER_IDS" + } + ] +} diff --git a/src/test/resources/com/github/onozaty/redmine/issue/loader/issues-all_fields.csv b/src/test/resources/com/github/onozaty/redmine/issue/loader/issues-all_fields.csv index ba812d5..f9cf0d2 100644 --- a/src/test/resources/com/github/onozaty/redmine/issue/loader/issues-all_fields.csv +++ b/src/test/resources/com/github/onozaty/redmine/issue/loader/issues-all_fields.csv @@ -1,4 +1,4 @@ -#,Project,Tracker,Status,Priority,Assignee,Category,Target version,Parent #,Subject,Description,Start date,Due date,% Done,Private,Estimated time,Field1,Field2,Field3 -1,プロジェクト1,トラッカー2,新規,通常,ユーザA,カテゴリ2,v2.0,,xxx,説明1,2019/02/01,2019/02/20,10,true,2.5,A,a,C -2,プロジェクト2,トラッカー2,進行中,低め,,カテゴリ2,,,yyy,説明2,2019/03/02,,,false,,B,b,A;B -3,プロジェクト1,トラッカー3,解決,高め,ユーザB,カテゴリ1,v1.0,1,zzz,説明3,2019/03/12,2019/10/30,90,false,10,C,c, +#,Project,Tracker,Status,Priority,Assignee,Category,Target version,Parent #,Subject,Description,Start date,Due date,% Done,Private,Estimated time,Field1,Field2,Field3,Watchers +1,プロジェクト1,トラッカー2,新規,通常,ユーザA,カテゴリ2,v2.0,,xxx,説明1,2019/02/01,2019/02/20,10,true,2.5,A,a,C,ユーザB +2,プロジェクト2,トラッカー2,進行中,低め,,カテゴリ2,,,yyy,説明2,2019/03/02,,,false,,B,b,A;B,ユーザA;ユーザB +3,プロジェクト1,トラッカー3,解決,高め,ユーザB,カテゴリ1,v1.0,1,zzz,説明3,2019/03/12,2019/10/30,90,false,10,C,c,, diff --git a/src/test/resources/com/github/onozaty/redmine/issue/loader/issues-multiple_custom_fields.csv b/src/test/resources/com/github/onozaty/redmine/issue/loader/issues-multiple-custom_fields.csv similarity index 100% rename from src/test/resources/com/github/onozaty/redmine/issue/loader/issues-multiple_custom_fields.csv rename to src/test/resources/com/github/onozaty/redmine/issue/loader/issues-multiple-custom_fields.csv diff --git a/src/test/resources/com/github/onozaty/redmine/issue/loader/issues-single-watchers.csv b/src/test/resources/com/github/onozaty/redmine/issue/loader/issues-single-watchers.csv new file mode 100644 index 0000000..0638337 --- /dev/null +++ b/src/test/resources/com/github/onozaty/redmine/issue/loader/issues-single-watchers.csv @@ -0,0 +1,3 @@ +Project,Subject,Watchers +プロジェクト1,xxx,1 +プロジェクト2,yyy,