Uploaded image for project: 'Flink'
  1. Flink
  2. FLINK-5907

RowCsvInputFormat bug on parsing tsv

    Details

    • Type: Bug
    • Status: Closed
    • Priority: Major
    • Resolution: Fixed
    • Affects Version/s: 1.2.0
    • Fix Version/s: 1.3.0, 1.2.1
    • Component/s: Java API
    • Labels:

      Description

      The following snippet reproduce the problem (using the attached file as input):

      char fieldDelim = '\t';
          TypeInformation<?>[] fieldTypes = new TypeInformation<?>[51];
          for (int i = 0; i < fieldTypes.length; i++) {
            fieldTypes[i] = BasicTypeInfo.STRING_TYPE_INFO;
          }
          int[] fieldMask = new int[fieldTypes.length];
          for (int i = 0; i < fieldMask.length; i++) {
            fieldMask[i] = i;
          }
          RowCsvInputFormat csvIF = new RowCsvInputFormat(new Path(testCsv), fieldTypes, "\n", fieldDelim +"", 
             fieldMask, true);
          csvIF.setNestedFileEnumeration(true);
          DataSet<Row> csv = env.createInput(csvIF);
         csv.print()
      
      1. test.tsv
        0.2 kB
        Flavio Pompermaier

        Issue Links

          Activity

          Hide
          f.pompermaier Flavio Pompermaier added a comment -

          Test file

          Show
          f.pompermaier Flavio Pompermaier added a comment - Test file
          Hide
          ykt836 Kurt Young added a comment -

          Looks like RowCsvInputFormat didn't handle rows which ended with field delimiter correctly.

          Show
          ykt836 Kurt Young added a comment - Looks like RowCsvInputFormat didn't handle rows which ended with field delimiter correctly.
          Hide
          ykt836 Kurt Young added a comment -

          You can add one more '\t' at the end of each line to work around first.

          Show
          ykt836 Kurt Young added a comment - You can add one more '\t' at the end of each line to work around first.
          Hide
          githubbot ASF GitHub Bot added a comment -

          GitHub user KurtYoung opened a pull request:

          https://github.com/apache/flink/pull/3417

          FLINK-5907 [java api] Fix trailing empty fields in CsvInputFormat

          If there are 3 fields with field delimiter ",", both these two line should be parsed successfully:
          aaa,bbb,
          aaa,bbb

          You can merge this pull request into a Git repository by running:

          $ git pull https://github.com/KurtYoung/flink flink-5907

          Alternatively you can review and apply these changes as the patch at:

          https://github.com/apache/flink/pull/3417.patch

          To close this pull request, make a commit to your master/trunk branch
          with (at least) the following in the commit message:

          This closes #3417


          commit 4e93fe1376d623eda5f8f68afb54583070968881
          Author: Kurt Young <ykt836@gmail.com>
          Date: 2017-02-25T08:37:37Z

          FLINK-5907 [java api] Fix trailing empty fields in CsvInputFormat


          Show
          githubbot ASF GitHub Bot added a comment - GitHub user KurtYoung opened a pull request: https://github.com/apache/flink/pull/3417 FLINK-5907 [java api] Fix trailing empty fields in CsvInputFormat If there are 3 fields with field delimiter ",", both these two line should be parsed successfully: aaa,bbb, aaa,bbb You can merge this pull request into a Git repository by running: $ git pull https://github.com/KurtYoung/flink flink-5907 Alternatively you can review and apply these changes as the patch at: https://github.com/apache/flink/pull/3417.patch To close this pull request, make a commit to your master/trunk branch with (at least) the following in the commit message: This closes #3417 commit 4e93fe1376d623eda5f8f68afb54583070968881 Author: Kurt Young <ykt836@gmail.com> Date: 2017-02-25T08:37:37Z FLINK-5907 [java api] Fix trailing empty fields in CsvInputFormat
          Hide
          githubbot ASF GitHub Bot added a comment -

          Github user fpompermaier commented on the issue:

          https://github.com/apache/flink/pull/3417

          I've tested your PR against my original CSV that was causing the problem and it works now! Thanks for this fix

          Show
          githubbot ASF GitHub Bot added a comment - Github user fpompermaier commented on the issue: https://github.com/apache/flink/pull/3417 I've tested your PR against my original CSV that was causing the problem and it works now! Thanks for this fix
          Hide
          githubbot ASF GitHub Bot added a comment -

          Github user fhueske commented on a diff in the pull request:

          https://github.com/apache/flink/pull/3417#discussion_r103200986

          — Diff: flink-java/src/main/java/org/apache/flink/api/java/io/RowCsvInputFormat.java —
          @@ -197,6 +197,14 @@ protected boolean parseRecord(Object[] holders, byte[] bytes, int offset, int nu
          if (startPos < 0)

          { throw new ParseException(String.format("Unexpected parser position for column %1$s of row '%2$s'", field, new String(bytes, offset, numBytes))); + }

          else if (startPos == limit
          + && field != fieldIncluded.length - 1
          + && !FieldParser.endsWithDelimiter(bytes, startPos - 1, fieldDelimiter)) {
          + if (isLenient()) {
          — End diff –

          Add a comment: "We are at the end of the record, but not all fields have been read and the end is not a field delimiter indicating an empty last field."

          Show
          githubbot ASF GitHub Bot added a comment - Github user fhueske commented on a diff in the pull request: https://github.com/apache/flink/pull/3417#discussion_r103200986 — Diff: flink-java/src/main/java/org/apache/flink/api/java/io/RowCsvInputFormat.java — @@ -197,6 +197,14 @@ protected boolean parseRecord(Object[] holders, byte[] bytes, int offset, int nu if (startPos < 0) { throw new ParseException(String.format("Unexpected parser position for column %1$s of row '%2$s'", field, new String(bytes, offset, numBytes))); + } else if (startPos == limit + && field != fieldIncluded.length - 1 + && !FieldParser.endsWithDelimiter(bytes, startPos - 1, fieldDelimiter)) { + if (isLenient()) { — End diff – Add a comment: "We are at the end of the record, but not all fields have been read and the end is not a field delimiter indicating an empty last field."
          Hide
          githubbot ASF GitHub Bot added a comment -

          Github user fhueske commented on a diff in the pull request:

          https://github.com/apache/flink/pull/3417#discussion_r103197368

          — Diff: flink-java/src/test/java/org/apache/flink/api/java/io/RowCsvInputFormatTest.java —
          @@ -311,7 +317,7 @@ public void readMixedQuotedStringFields() throws Exception {

          @Test
          public void readStringFieldsWithTrailingDelimiters() throws Exception {

          • String fileContent = "abc|def|-ghijk\nabc||hhg\n|||\n";
            + String fileContent = "abc|def|-ghijk\nabc||hhg\n|||\n||\nabc|-def\n";
              • End diff –

          Can you move the checks for correctly handling empty last fields into a dedicated method?

          Show
          githubbot ASF GitHub Bot added a comment - Github user fhueske commented on a diff in the pull request: https://github.com/apache/flink/pull/3417#discussion_r103197368 — Diff: flink-java/src/test/java/org/apache/flink/api/java/io/RowCsvInputFormatTest.java — @@ -311,7 +317,7 @@ public void readMixedQuotedStringFields() throws Exception { @Test public void readStringFieldsWithTrailingDelimiters() throws Exception { String fileContent = "abc| def|-ghijk\nabc| | hhg\n| | | \n"; + String fileContent = "abc| def|-ghijk\nabc| | hhg\n| | | \n| | \nabc|-def\n"; End diff – Can you move the checks for correctly handling empty last fields into a dedicated method?
          Hide
          githubbot ASF GitHub Bot added a comment -

          Github user fhueske commented on a diff in the pull request:

          https://github.com/apache/flink/pull/3417#discussion_r103203173

          — Diff: flink-core/src/main/java/org/apache/flink/api/common/io/GenericCsvInputFormat.java —
          @@ -358,24 +358,27 @@ protected boolean parseRecord(Object[] holders, byte[] bytes, int offset, int nu
          for (int field = 0, output = 0; field < fieldIncluded.length; field++) {

          // check valid start position

          • if (startPos >= limit) {
            + if (startPos > limit || (startPos == limit && field != fieldIncluded.length - 1))
            Unknown macro: { if (lenient) { return false; } else { throw new ParseException("Row too short: " + new String(bytes, offset, numBytes)); } }
          • +
            if (fieldIncluded[field]) {
            // parse field
            @SuppressWarnings("unchecked")
            FieldParser<Object> parser = (FieldParser<Object>) this.fieldParsers[output];
            Object reuse = holders[output];
            startPos = parser.resetErrorStateAndParse(bytes, startPos, limit, this.fieldDelim, reuse);
            holders[output] = parser.getLastResult();

          • +
            // check parse result

          • if (startPos < 0) {
            + if (startPos < 0 ||
            + (startPos == limit
              • End diff –

          Move this condition into an `else if` branch and give a more detailed error message (row to short).
          Also add a comment that we read the whole records but that there are fields missing.

          Show
          githubbot ASF GitHub Bot added a comment - Github user fhueske commented on a diff in the pull request: https://github.com/apache/flink/pull/3417#discussion_r103203173 — Diff: flink-core/src/main/java/org/apache/flink/api/common/io/GenericCsvInputFormat.java — @@ -358,24 +358,27 @@ protected boolean parseRecord(Object[] holders, byte[] bytes, int offset, int nu for (int field = 0, output = 0; field < fieldIncluded.length; field++) { // check valid start position if (startPos >= limit) { + if (startPos > limit || (startPos == limit && field != fieldIncluded.length - 1)) Unknown macro: { if (lenient) { return false; } else { throw new ParseException("Row too short: " + new String(bytes, offset, numBytes)); } } + if (fieldIncluded [field] ) { // parse field @SuppressWarnings("unchecked") FieldParser<Object> parser = (FieldParser<Object>) this.fieldParsers [output] ; Object reuse = holders [output] ; startPos = parser.resetErrorStateAndParse(bytes, startPos, limit, this.fieldDelim, reuse); holders [output] = parser.getLastResult(); + // check parse result if (startPos < 0) { + if (startPos < 0 || + (startPos == limit End diff – Move this condition into an `else if` branch and give a more detailed error message (row to short). Also add a comment that we read the whole records but that there are fields missing.
          Hide
          githubbot ASF GitHub Bot added a comment -

          Github user fhueske commented on a diff in the pull request:

          https://github.com/apache/flink/pull/3417#discussion_r103197216

          — Diff: flink-java/src/test/java/org/apache/flink/api/java/io/CsvInputFormatTest.java —
          @@ -400,7 +400,7 @@ public void readMixedQuotedStringFields() {
          @Test
          public void readStringFieldsWithTrailingDelimiters() {
          try {

          • final String fileContent = "abc|def|-ghijk\nabc||hhg\n|||\n";
            + final String fileContent = "abc|def|-ghijk\nabc||hhg\n|||\n||\nabc|-def\n";
              • End diff –

          This method tests whether an additional field delimiter at the end is accepted.
          Can you move the checks for correctly identifying empty last fields into a separate `testTailingEmptyFields` method?

          Show
          githubbot ASF GitHub Bot added a comment - Github user fhueske commented on a diff in the pull request: https://github.com/apache/flink/pull/3417#discussion_r103197216 — Diff: flink-java/src/test/java/org/apache/flink/api/java/io/CsvInputFormatTest.java — @@ -400,7 +400,7 @@ public void readMixedQuotedStringFields() { @Test public void readStringFieldsWithTrailingDelimiters() { try { final String fileContent = "abc| def|-ghijk\nabc| | hhg\n| | | \n"; + final String fileContent = "abc| def|-ghijk\nabc| | hhg\n| | | \n| | \nabc|-def\n"; End diff – This method tests whether an additional field delimiter at the end is accepted. Can you move the checks for correctly identifying empty last fields into a separate `testTailingEmptyFields` method?
          Hide
          githubbot ASF GitHub Bot added a comment -

          Github user fhueske commented on a diff in the pull request:

          https://github.com/apache/flink/pull/3417#discussion_r103183251

          — Diff: flink-java/src/test/java/org/apache/flink/api/java/io/CsvInputFormatTest.java —
          @@ -957,6 +964,42 @@ public void testPojoTypeWithPrivateField() throws Exception {
          }

          @Test
          + public void testPojoTypeWithTrailingEmptyFields() throws Exception {
          + File tempFile = File.createTempFile("CsvReaderPojoType", "tmp");
          — End diff –

          Use the tmp file utils as in `readStringFieldsWithTrailingDelimiters()`

          Show
          githubbot ASF GitHub Bot added a comment - Github user fhueske commented on a diff in the pull request: https://github.com/apache/flink/pull/3417#discussion_r103183251 — Diff: flink-java/src/test/java/org/apache/flink/api/java/io/CsvInputFormatTest.java — @@ -957,6 +964,42 @@ public void testPojoTypeWithPrivateField() throws Exception { } @Test + public void testPojoTypeWithTrailingEmptyFields() throws Exception { + File tempFile = File.createTempFile("CsvReaderPojoType", "tmp"); — End diff – Use the tmp file utils as in `readStringFieldsWithTrailingDelimiters()`
          Hide
          githubbot ASF GitHub Bot added a comment -

          Github user fhueske commented on a diff in the pull request:

          https://github.com/apache/flink/pull/3417#discussion_r103203254

          — Diff: flink-core/src/main/java/org/apache/flink/api/common/io/GenericCsvInputFormat.java —
          @@ -392,12 +395,17 @@ protected boolean parseRecord(Object[] holders, byte[] bytes, int offset, int nu
          else {
          // skip field
          startPos = skipFields(bytes, startPos, limit, this.fieldDelim);

          • if (startPos < 0) {
            + if (startPos < 0 ||
              • End diff –

          Same here as above

          Show
          githubbot ASF GitHub Bot added a comment - Github user fhueske commented on a diff in the pull request: https://github.com/apache/flink/pull/3417#discussion_r103203254 — Diff: flink-core/src/main/java/org/apache/flink/api/common/io/GenericCsvInputFormat.java — @@ -392,12 +395,17 @@ protected boolean parseRecord(Object[] holders, byte[] bytes, int offset, int nu else { // skip field startPos = skipFields(bytes, startPos, limit, this.fieldDelim); if (startPos < 0) { + if (startPos < 0 || End diff – Same here as above
          Hide
          githubbot ASF GitHub Bot added a comment -

          Github user KurtYoung commented on a diff in the pull request:

          https://github.com/apache/flink/pull/3417#discussion_r103224286

          — Diff: flink-java/src/test/java/org/apache/flink/api/java/io/CsvInputFormatTest.java —
          @@ -400,7 +400,7 @@ public void readMixedQuotedStringFields() {
          @Test
          public void readStringFieldsWithTrailingDelimiters() {
          try {

          • final String fileContent = "abc|def|-ghijk\nabc||hhg\n|||\n";
            + final String fileContent = "abc|def|-ghijk\nabc||hhg\n|||\n||\nabc|-def\n";
              • End diff –

          sounds good

          Show
          githubbot ASF GitHub Bot added a comment - Github user KurtYoung commented on a diff in the pull request: https://github.com/apache/flink/pull/3417#discussion_r103224286 — Diff: flink-java/src/test/java/org/apache/flink/api/java/io/CsvInputFormatTest.java — @@ -400,7 +400,7 @@ public void readMixedQuotedStringFields() { @Test public void readStringFieldsWithTrailingDelimiters() { try { final String fileContent = "abc| def|-ghijk\nabc| | hhg\n| | | \n"; + final String fileContent = "abc| def|-ghijk\nabc| | hhg\n| | | \n| | \nabc|-def\n"; End diff – sounds good
          Hide
          githubbot ASF GitHub Bot added a comment -

          Github user KurtYoung commented on a diff in the pull request:

          https://github.com/apache/flink/pull/3417#discussion_r103224327

          — Diff: flink-java/src/test/java/org/apache/flink/api/java/io/CsvInputFormatTest.java —
          @@ -957,6 +964,42 @@ public void testPojoTypeWithPrivateField() throws Exception {
          }

          @Test
          + public void testPojoTypeWithTrailingEmptyFields() throws Exception {
          + File tempFile = File.createTempFile("CsvReaderPojoType", "tmp");
          — End diff –

          changed

          Show
          githubbot ASF GitHub Bot added a comment - Github user KurtYoung commented on a diff in the pull request: https://github.com/apache/flink/pull/3417#discussion_r103224327 — Diff: flink-java/src/test/java/org/apache/flink/api/java/io/CsvInputFormatTest.java — @@ -957,6 +964,42 @@ public void testPojoTypeWithPrivateField() throws Exception { } @Test + public void testPojoTypeWithTrailingEmptyFields() throws Exception { + File tempFile = File.createTempFile("CsvReaderPojoType", "tmp"); — End diff – changed
          Hide
          githubbot ASF GitHub Bot added a comment -

          Github user KurtYoung commented on a diff in the pull request:

          https://github.com/apache/flink/pull/3417#discussion_r103225144

          — Diff: flink-java/src/test/java/org/apache/flink/api/java/io/RowCsvInputFormatTest.java —
          @@ -311,7 +317,7 @@ public void readMixedQuotedStringFields() throws Exception {

          @Test
          public void readStringFieldsWithTrailingDelimiters() throws Exception {

          • String fileContent = "abc|def|-ghijk\nabc||hhg\n|||\n";
            + String fileContent = "abc|def|-ghijk\nabc||hhg\n|||\n||\nabc|-def\n";
              • End diff –

          ok

          Show
          githubbot ASF GitHub Bot added a comment - Github user KurtYoung commented on a diff in the pull request: https://github.com/apache/flink/pull/3417#discussion_r103225144 — Diff: flink-java/src/test/java/org/apache/flink/api/java/io/RowCsvInputFormatTest.java — @@ -311,7 +317,7 @@ public void readMixedQuotedStringFields() throws Exception { @Test public void readStringFieldsWithTrailingDelimiters() throws Exception { String fileContent = "abc| def|-ghijk\nabc| | hhg\n| | | \n"; + String fileContent = "abc| def|-ghijk\nabc| | hhg\n| | | \n| | \nabc|-def\n"; End diff – ok
          Hide
          githubbot ASF GitHub Bot added a comment -

          Github user KurtYoung commented on a diff in the pull request:

          https://github.com/apache/flink/pull/3417#discussion_r103225729

          — Diff: flink-java/src/main/java/org/apache/flink/api/java/io/RowCsvInputFormat.java —
          @@ -197,6 +197,14 @@ protected boolean parseRecord(Object[] holders, byte[] bytes, int offset, int nu
          if (startPos < 0)

          { throw new ParseException(String.format("Unexpected parser position for column %1$s of row '%2$s'", field, new String(bytes, offset, numBytes))); + }

          else if (startPos == limit
          + && field != fieldIncluded.length - 1
          + && !FieldParser.endsWithDelimiter(bytes, startPos - 1, fieldDelimiter)) {
          + if (isLenient()) {
          — End diff –

          added

          Show
          githubbot ASF GitHub Bot added a comment - Github user KurtYoung commented on a diff in the pull request: https://github.com/apache/flink/pull/3417#discussion_r103225729 — Diff: flink-java/src/main/java/org/apache/flink/api/java/io/RowCsvInputFormat.java — @@ -197,6 +197,14 @@ protected boolean parseRecord(Object[] holders, byte[] bytes, int offset, int nu if (startPos < 0) { throw new ParseException(String.format("Unexpected parser position for column %1$s of row '%2$s'", field, new String(bytes, offset, numBytes))); + } else if (startPos == limit + && field != fieldIncluded.length - 1 + && !FieldParser.endsWithDelimiter(bytes, startPos - 1, fieldDelimiter)) { + if (isLenient()) { — End diff – added
          Hide
          githubbot ASF GitHub Bot added a comment -

          Github user KurtYoung commented on a diff in the pull request:

          https://github.com/apache/flink/pull/3417#discussion_r103226814

          — Diff: flink-core/src/main/java/org/apache/flink/api/common/io/GenericCsvInputFormat.java —
          @@ -392,12 +395,17 @@ protected boolean parseRecord(Object[] holders, byte[] bytes, int offset, int nu
          else {
          // skip field
          startPos = skipFields(bytes, startPos, limit, this.fieldDelim);

          • if (startPos < 0) {
            + if (startPos < 0 ||
              • End diff –

          done

          Show
          githubbot ASF GitHub Bot added a comment - Github user KurtYoung commented on a diff in the pull request: https://github.com/apache/flink/pull/3417#discussion_r103226814 — Diff: flink-core/src/main/java/org/apache/flink/api/common/io/GenericCsvInputFormat.java — @@ -392,12 +395,17 @@ protected boolean parseRecord(Object[] holders, byte[] bytes, int offset, int nu else { // skip field startPos = skipFields(bytes, startPos, limit, this.fieldDelim); if (startPos < 0) { + if (startPos < 0 || End diff – done
          Hide
          githubbot ASF GitHub Bot added a comment -

          Github user KurtYoung commented on a diff in the pull request:

          https://github.com/apache/flink/pull/3417#discussion_r103226821

          — Diff: flink-core/src/main/java/org/apache/flink/api/common/io/GenericCsvInputFormat.java —
          @@ -358,24 +358,27 @@ protected boolean parseRecord(Object[] holders, byte[] bytes, int offset, int nu
          for (int field = 0, output = 0; field < fieldIncluded.length; field++) {

          // check valid start position

          • if (startPos >= limit) {
            + if (startPos > limit || (startPos == limit && field != fieldIncluded.length - 1))
            Unknown macro: { if (lenient) { return false; } else { throw new ParseException("Row too short: " + new String(bytes, offset, numBytes)); } }
          • +
            if (fieldIncluded[field]) {
            // parse field
            @SuppressWarnings("unchecked")
            FieldParser<Object> parser = (FieldParser<Object>) this.fieldParsers[output];
            Object reuse = holders[output];
            startPos = parser.resetErrorStateAndParse(bytes, startPos, limit, this.fieldDelim, reuse);
            holders[output] = parser.getLastResult();

          • +
            // check parse result

          • if (startPos < 0) {
            + if (startPos < 0 ||
            + (startPos == limit
              • End diff –

          done

          Show
          githubbot ASF GitHub Bot added a comment - Github user KurtYoung commented on a diff in the pull request: https://github.com/apache/flink/pull/3417#discussion_r103226821 — Diff: flink-core/src/main/java/org/apache/flink/api/common/io/GenericCsvInputFormat.java — @@ -358,24 +358,27 @@ protected boolean parseRecord(Object[] holders, byte[] bytes, int offset, int nu for (int field = 0, output = 0; field < fieldIncluded.length; field++) { // check valid start position if (startPos >= limit) { + if (startPos > limit || (startPos == limit && field != fieldIncluded.length - 1)) Unknown macro: { if (lenient) { return false; } else { throw new ParseException("Row too short: " + new String(bytes, offset, numBytes)); } } + if (fieldIncluded [field] ) { // parse field @SuppressWarnings("unchecked") FieldParser<Object> parser = (FieldParser<Object>) this.fieldParsers [output] ; Object reuse = holders [output] ; startPos = parser.resetErrorStateAndParse(bytes, startPos, limit, this.fieldDelim, reuse); holders [output] = parser.getLastResult(); + // check parse result if (startPos < 0) { + if (startPos < 0 || + (startPos == limit End diff – done
          Hide
          githubbot ASF GitHub Bot added a comment -

          Github user fhueske commented on the issue:

          https://github.com/apache/flink/pull/3417

          Thanks for the fast update @KurtYoung.
          +1 to merge

          Show
          githubbot ASF GitHub Bot added a comment - Github user fhueske commented on the issue: https://github.com/apache/flink/pull/3417 Thanks for the fast update @KurtYoung. +1 to merge
          Hide
          githubbot ASF GitHub Bot added a comment -

          Github user fhueske commented on the issue:

          https://github.com/apache/flink/pull/3417

          Merging

          Show
          githubbot ASF GitHub Bot added a comment - Github user fhueske commented on the issue: https://github.com/apache/flink/pull/3417 Merging
          Hide
          githubbot ASF GitHub Bot added a comment -

          Github user asfgit closed the pull request at:

          https://github.com/apache/flink/pull/3417

          Show
          githubbot ASF GitHub Bot added a comment - Github user asfgit closed the pull request at: https://github.com/apache/flink/pull/3417
          Hide
          fhueske Fabian Hueske added a comment -

          Fixed for 1.2.1 with 5168b9f62a05176aca5bd3c094241daaa4d14b2e
          Fixed for 1.3.0 with 1a062b796274c9f63caeb2bf12aad96e34efd0aa

          Show
          fhueske Fabian Hueske added a comment - Fixed for 1.2.1 with 5168b9f62a05176aca5bd3c094241daaa4d14b2e Fixed for 1.3.0 with 1a062b796274c9f63caeb2bf12aad96e34efd0aa

            People

            • Assignee:
              ykt836 Kurt Young
              Reporter:
              f.pompermaier Flavio Pompermaier
            • Votes:
              0 Vote for this issue
              Watchers:
              4 Start watching this issue

              Dates

              • Created:
                Updated:
                Resolved:

                Development