Moodle
  1. Moodle
  2. MDL-32102

restore creates invalid course completion records

    Details

    • Testing Instructions:
      Hide

      Enable Course completion inside a course
      Make sure some users are set to have a complete course (manual completion options work well)
      run cron a few times to make sure completion status is set correctly.
      Backup the course.
      Restore the course by using the "merge" into existing course option.
      Run cron - fatal error is shown.

      NOTE TO QA/tester: I had issues using the merge course under Postgres - threw some index errors related to grades - I did my testing on mysql which restored the course fine. (probably another bug there somewhere that needs further investigation.)

      Show
      Enable Course completion inside a course Make sure some users are set to have a complete course (manual completion options work well) run cron a few times to make sure completion status is set correctly. Backup the course. Restore the course by using the "merge" into existing course option. Run cron - fatal error is shown. NOTE TO QA/tester: I had issues using the merge course under Postgres - threw some index errors related to grades - I did my testing on mysql which restored the course fine. (probably another bug there somewhere that needs further investigation.)
    • Affected Branches:
      MOODLE_22_STABLE
    • Fixed Branches:
      MOODLE_21_STABLE, MOODLE_22_STABLE
    • Pull Master Branch:
      master_MDL-32102
    • Rank:
      38810

      Description

      If you merge a course that has existing course completion records you can end up with multiple course completion records in the db which throws this error in cron:

      !!! Found more than one record in fetch() ! !!!
      !! Stack trace: * line 429 of /lib/setuplib.php: moodle_exception thrown

      line 130 of /lib/completion/data_object.php: call to print_error()
      line 83 of /lib/completion/completion_aggregation.php: call to data_object::fetch_helper()
      line 65 of /lib/completion/data_object.php: call to completion_aggregation::fetch()
      line 374 of /lib/completionlib.php: call to data_object->__construct()
      line 308 of /lib/completion/cron.php: call to completion_info->get_aggregation_method()
      line 45 of /lib/completion/cron.php: call to completion_cron_completions()
      line 173 of /lib/cronlib.php: call to completion_cron()
      line 85 of /admin/cron.php: call to cron_run()

        Issue Links

          Activity

          Hide
          Dan Poltawski added a comment -

          Hey Andrew,

          I wonder if you could peer review this for Dan since you've been working on a related issue in MDL-31914

          If you are not able to please assign to me to peer review.

          Show
          Dan Poltawski added a comment - Hey Andrew, I wonder if you could peer review this for Dan since you've been working on a related issue in MDL-31914 If you are not able to please assign to me to peer review.
          Hide
          Andrew Nicols added a comment -

          I wasn't able to get cron to throw any errors with the instructions provided, but the records were duplicated in the course_completions table (without the patch).

          A few comments on the patch:

          1. I'm not sure why you perform the select with the deleted <> 1 clause. If the deleted field has been set in the existing data, but the data being restored is not deleted, then a duplicate record will still be created. It does look like completion_completion.php::fetch() does ignore deleted records so this might be okay, but just want to make sure that you're aware of this. You may want to add a comment explaining why existing but deleted records are ignored.
          2. The indentation for $existing->timecompleted < $data->timecompleted is not consistent with the coding guidelines - IIRC it should be intended another 4 spaces
          3. $params is an array so $params->id should be $params['id']. This threw errors for me when testing.

          Otherwise it looks good though the testing instructions could do with a bit more detail.

          Show
          Andrew Nicols added a comment - I wasn't able to get cron to throw any errors with the instructions provided, but the records were duplicated in the course_completions table (without the patch). A few comments on the patch: I'm not sure why you perform the select with the deleted <> 1 clause. If the deleted field has been set in the existing data, but the data being restored is not deleted, then a duplicate record will still be created. It does look like completion_completion.php::fetch() does ignore deleted records so this might be okay, but just want to make sure that you're aware of this. You may want to add a comment explaining why existing but deleted records are ignored. The indentation for $existing->timecompleted < $data->timecompleted is not consistent with the coding guidelines - IIRC it should be intended another 4 spaces $params is an array so $params->id should be $params ['id'] . This threw errors for me when testing. Otherwise it looks good though the testing instructions could do with a bit more detail.
          Hide
          Dan Marsden added a comment -

          Hi Andrew,

          I don't do anything with deleted records as these don't appear to be an issue - if the teacher elects to restore completion data - I figure it's their own problem?

          also - the "check" for duplicate deleted data is quite different from the check for "real" completion records. I guess if this was a problem someone else could write a separate patch on a different tracker issue.

          there can only be one "real" record with user/course but there can be multiple deleted records with user/course - have fixed spacing and array - whoops, thanks.

          feel free to add more detailed testing instructions yourself...

          Show
          Dan Marsden added a comment - Hi Andrew, I don't do anything with deleted records as these don't appear to be an issue - if the teacher elects to restore completion data - I figure it's their own problem? also - the "check" for duplicate deleted data is quite different from the check for "real" completion records. I guess if this was a problem someone else could write a separate patch on a different tracker issue. there can only be one "real" record with user/course but there can be multiple deleted records with user/course - have fixed spacing and array - whoops, thanks. feel free to add more detailed testing instructions yourself...
          Hide
          Andrew Nicols added a comment -

          Hi Dan,

          I also meant to mention that this patch doesn't remove the existing duplicate data. I know that the duplicates are removed when saving the Course Completion settings page, but do you think that there may be some merit in adding an upgrade step to remove the duplicates?

          Andrew

          Show
          Andrew Nicols added a comment - Hi Dan, I also meant to mention that this patch doesn't remove the existing duplicate data. I know that the duplicates are removed when saving the Course Completion settings page, but do you think that there may be some merit in adding an upgrade step to remove the duplicates? Andrew
          Hide
          Dan Marsden added a comment -

          clean-up of duplicate data is a separate patch on MDL-27368

          Show
          Dan Marsden added a comment - clean-up of duplicate data is a separate patch on MDL-27368
          Hide
          Andrew Nicols added a comment -

          Looks good to me. Feel free to submit for Integration Review when ready

          Show
          Andrew Nicols added a comment - Looks good to me. Feel free to submit for Integration Review when ready
          Hide
          Andrew Nicols added a comment -

          Hi Dan,

          Just picked up a bug on Postgres - the default value of the deleted field is null. The deleted <> 1 clause doesn't pick up null entries so entries are still duplicated in Postgres.

          Show
          Andrew Nicols added a comment - Hi Dan, Just picked up a bug on Postgres - the default value of the deleted field is null. The deleted <> 1 clause doesn't pick up null entries so entries are still duplicated in Postgres.
          Hide
          Andrew Nicols added a comment -

          Also, worth noting but get_record_select() complains when more than one row is returned – however this will be fixed by the db cleanup in MDL-27638.

          Show
          Andrew Nicols added a comment - Also, worth noting but get_record_select() complains when more than one row is returned – however this will be fixed by the db cleanup in MDL-27638 .
          Hide
          Dan Marsden added a comment -

          grrr - rookie mistake there! - thanks Andrew... I thought about just changing to check for NULL records but I don't know enough about the process that sets the deleted flag - I would have thought it should have a default val of 0 ?

          Show
          Dan Marsden added a comment - grrr - rookie mistake there! - thanks Andrew... I thought about just changing to check for NULL records but I don't know enough about the process that sets the deleted flag - I would have thought it should have a default val of 0 ?
          Hide
          Andrew Nicols added a comment -

          I can't see anywhere that it's used at all - there are references to it being used to mark a record as deleted, but the delete code deletes the record.

          I'm going to open a new bug to investigate the delete flag - personally I think it should be kept and the records not deleted to allow for the future potential of restoring course completion data.

          Show
          Andrew Nicols added a comment - I can't see anywhere that it's used at all - there are references to it being used to mark a record as deleted, but the delete code deletes the record. I'm going to open a new bug to investigate the delete flag - personally I think it should be kept and the records not deleted to allow for the future potential of restoring course completion data.
          Hide
          Dan Poltawski added a comment -

          Hi Dan,

          Just had a look at this and I notice that in the other issue that the oldest completion record is kept rather than the newest record as is the case here.

          So just want to confirm that this disparity is intended?

          Also, just thinking a bit further out of the box .. these course completion records - do they make sense when a course has been restored into another one?

          The course could be completely different but the student would be marked as having completed it? Doesn't seem to make sense that course completion records should be retained in any scenario but if the course is kept completely intact?

          Show
          Dan Poltawski added a comment - Hi Dan, Just had a look at this and I notice that in the other issue that the oldest completion record is kept rather than the newest record as is the case here. So just want to confirm that this disparity is intended? Also, just thinking a bit further out of the box .. these course completion records - do they make sense when a course has been restored into another one? The course could be completely different but the student would be marked as having completed it? Doesn't seem to make sense that course completion records should be retained in any scenario but if the course is kept completely intact?
          Hide
          Dan Marsden added a comment -

          yeah - this seems a bit inconsistent but from what I understand this is how it's supposed to work - the other process is really a clean up to remove bad records and from all the bad data we should keep the "original" completion dates.

          There is a setting to allow the user to restore completion data during the restore - so I guess if they select to restore it - it makes sense? - If not I'd guess that should be a different tracker issue.

          thanks!

          Show
          Dan Marsden added a comment - yeah - this seems a bit inconsistent but from what I understand this is how it's supposed to work - the other process is really a clean up to remove bad records and from all the bad data we should keep the "original" completion dates. There is a setting to allow the user to restore completion data during the restore - so I guess if they select to restore it - it makes sense? - If not I'd guess that should be a different tracker issue. thanks!
          Hide
          Dan Poltawski added a comment -

          Hi Dan,

          Been discussing with Sam and i'm still not sure about this.

          It doesn't seem like it'd be clear to the teacher the impact of what they would be doing by restoring completion records as the records would be conditionally updated.

          So you could imagine a teacher being lured into a false sense of security that restoring completion records will not update existing records if restoring from a old course. Then later restoring from a new course and ending up with a situation where half the students completion records are from the original dates and half from the newly restored dates. It seems it could be confusing.

          We discussed 3 potential solutions:
          a) Hard code it to use either the existing or new completion records - updating all or leaving all in place
          b) Prevent restore of completion information if there is a 'merge conflict'
          c) Ask the user to make a bulk decision about how to handle duplicates in the case of a merge conflict.

          However i'm also aware that this is causing a big problem in the duplicate records killing cron so the other solution is that we integrate this for the short term and then consider the wider solution later. Given that we are not close to the point releases it would seem good to get this sorted once and for all.

          I'd like to get Eloy's opinion when he comes online later so we can consider the best way forward.

          thanks

          Dan

          Show
          Dan Poltawski added a comment - Hi Dan, Been discussing with Sam and i'm still not sure about this. It doesn't seem like it'd be clear to the teacher the impact of what they would be doing by restoring completion records as the records would be conditionally updated. So you could imagine a teacher being lured into a false sense of security that restoring completion records will not update existing records if restoring from a old course. Then later restoring from a new course and ending up with a situation where half the students completion records are from the original dates and half from the newly restored dates. It seems it could be confusing. We discussed 3 potential solutions: a) Hard code it to use either the existing or new completion records - updating all or leaving all in place b) Prevent restore of completion information if there is a 'merge conflict' c) Ask the user to make a bulk decision about how to handle duplicates in the case of a merge conflict. However i'm also aware that this is causing a big problem in the duplicate records killing cron so the other solution is that we integrate this for the short term and then consider the wider solution later. Given that we are not close to the point releases it would seem good to get this sorted once and for all. I'd like to get Eloy's opinion when he comes online later so we can consider the best way forward. thanks Dan
          Hide
          Eloy Lafuente (stronk7) added a comment - - edited

          Ho,

          I think this is a lot like the grade_categories restoration, something that is not easy to fit if there is pre-existing data.

          So, based in that thought... I'd propose to restrict when course_completion information is handled on restore if there are chances of having course completion information already defined in the target course.

          So, here there are 3 solutions, from radical (but clean and simpler) to softer (and I'm not really sure if it will cover all possibilities):

          1) Restrict course_completion restore to operations restoring to new course (backup::TARGET_NEW_COURSE). Can be achieved in the task: https://github.com/stronk7/moodle/compare/master...MDL-32102

          2) Restrict course_completion restore to operations restoring non-adding (exclude backup::TARGET_CURRENT_ADDING and backup::TARGET_EXISTING_ADDING). We must ensure that delete_course_contents() is properly deleting all the course_completion information to accept this. Can be also be achieved in the task, like 1) above.

          3) When restoring the information, verify that there is not course_completion information in the course at all. It should be implemented @ restore_course_completion_structure_step::execute_condition(). We are doing something similar @ restore_gradebook_structure_step::execute_condition(). But we need to be 100% adamant about the queries to use in order to detect any previous (potentially conflicting) information.

          My personal vote goes to 2) (if we can ensure that "deleting" information also deletes all course_completion information) and then to 1) (the simplest and cleanest). Of course 3) is the best, but I'm not sure if we are able to determine 100% when restore should happen or no easily. If we are, go for it, np here.

          For your consideration...ciao

          Show
          Eloy Lafuente (stronk7) added a comment - - edited Ho, I think this is a lot like the grade_categories restoration, something that is not easy to fit if there is pre-existing data. So, based in that thought... I'd propose to restrict when course_completion information is handled on restore if there are chances of having course completion information already defined in the target course. So, here there are 3 solutions, from radical (but clean and simpler) to softer (and I'm not really sure if it will cover all possibilities): 1) Restrict course_completion restore to operations restoring to new course (backup::TARGET_NEW_COURSE). Can be achieved in the task: https://github.com/stronk7/moodle/compare/master...MDL-32102 2) Restrict course_completion restore to operations restoring non-adding (exclude backup::TARGET_CURRENT_ADDING and backup::TARGET_EXISTING_ADDING). We must ensure that delete_course_contents() is properly deleting all the course_completion information to accept this. Can be also be achieved in the task, like 1) above. 3) When restoring the information, verify that there is not course_completion information in the course at all. It should be implemented @ restore_course_completion_structure_step::execute_condition(). We are doing something similar @ restore_gradebook_structure_step::execute_condition(). But we need to be 100% adamant about the queries to use in order to detect any previous (potentially conflicting) information. My personal vote goes to 2) (if we can ensure that "deleting" information also deletes all course_completion information) and then to 1) (the simplest and cleanest). Of course 3) is the best, but I'm not sure if we are able to determine 100% when restore should happen or no easily. If we are, go for it, np here. For your consideration...ciao
          Hide
          Dan Marsden added a comment -

          my vote goes to 2)

          Show
          Dan Marsden added a comment - my vote goes to 2)
          Hide
          Dan Marsden added a comment -

          might need to delay this until next week - there's another fix coming in from MDL-32203 that will also help prevent some duplicates - although I may not have time to implement the options Eloy has suggested - Eloy do you have time to fix this with one of the options suggested for next week?

          Show
          Dan Marsden added a comment - might need to delay this until next week - there's another fix coming in from MDL-32203 that will also help prevent some duplicates - although I may not have time to implement the options Eloy has suggested - Eloy do you have time to fix this with one of the options suggested for next week?
          Hide
          Dan Poltawski added a comment -

          Reopening this as we agreed on delaying this for a week. Thanks

          Show
          Dan Poltawski added a comment - Reopening this as we agreed on delaying this for a week. Thanks
          Hide
          Dan Marsden added a comment -

          implemented option 2 as mentioned by Eloy - if someone wants to implement option 3 at some point in future they can - at the moment we need to prevent the corrupt data from being generated asap.

          Show
          Dan Marsden added a comment - implemented option 2 as mentioned by Eloy - if someone wants to implement option 3 at some point in future they can - at the moment we need to prevent the corrupt data from being generated asap.
          Hide
          Dan Poltawski added a comment -

          Looks like delete_course_content() is calling the completion libraries to delete stuff, so this looks right to me.

          Show
          Dan Poltawski added a comment - Looks like delete_course_content() is calling the completion libraries to delete stuff, so this looks right to me.
          Hide
          Dan Poltawski added a comment -

          Thanks, have integrated this now

          Show
          Dan Poltawski added a comment - Thanks, have integrated this now
          Hide
          Rajesh Taneja added a comment -

          Sorry Dan,
          I am failing this. While restoring course on existing one (merge option selected), I am getting following error (db: MySql)

          Debug info: Duplicate entry '4-33' for key 'mdl_gradgrad_useite_uix'
          INSERT INTO mdl_grade_grades (userid,rawgrade,rawgrademax,rawgrademin,rawscaleid,usermodified,finalgrade,hidden,locked,locktime,exported,overridden,excluded,feedback,feedbackformat,information,informationformat,timecreated,timemodified,itemid) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
          [array (
          0 => '4',
          1 => NULL,
          2 => '100.00000',
          3 => '0.00000',
          4 => NULL,
          5 => NULL,
          6 => '24.00000',
          7 => '0',
          8 => '0',
          9 => '0',
          10 => '0',
          11 => '0',
          12 => '0',
          13 => NULL,
          14 => '0',
          15 => NULL,
          16 => '0',
          17 => NULL,
          18 => NULL,
          19 => '33',
          )]
          Stack trace:
          
              line 416 of /lib/dml/moodle_database.php: dml_write_exception thrown
              line 955 of /lib/dml/mysqli_native_moodle_database.php: call to moodle_database->query_end()
              line 997 of /lib/dml/mysqli_native_moodle_database.php: call to mysqli_native_moodle_database->insert_record_raw()
              line 222 of /backup/moodle2/restore_stepslib.php: call to mysqli_native_moodle_database->insert_record()
              line 131 of /backup/util/plan/restore_structure_step.class.php: call to restore_gradebook_structure_step->process_grade_grade()
              line 103 of /backup/util/helper/restore_structure_parser_processor.class.php: call to restore_structure_step->process()
              line 125 of /backup/util/xml/parser/processors/grouped_parser_processor.class.php: call to restore_structure_parser_processor->dispatch_chunk()
              line 91 of /backup/util/helper/restore_structure_parser_processor.class.php: call to grouped_parser_processor->postprocess_chunk()
              line 148 of /backup/util/xml/parser/processors/simplified_parser_processor.class.php: call to restore_structure_parser_processor->postprocess_chunk()
              line 92 of /backup/util/xml/parser/processors/progressive_parser_processor.class.php: call to simplified_parser_processor->process_chunk()
              line 169 of /backup/util/xml/parser/progressive_parser.class.php: call to progressive_parser_processor->receive_chunk()
              line 253 of /backup/util/xml/parser/progressive_parser.class.php: call to progressive_parser->publish()
              line ? of unknownfile: call to progressive_parser->end_tag()
              line 158 of /backup/util/xml/parser/progressive_parser.class.php: call to xml_parse()
              line 137 of /backup/util/xml/parser/progressive_parser.class.php: call to progressive_parser->parse()
              line 105 of /backup/util/plan/restore_structure_step.class.php: call to progressive_parser->process()
              line 153 of /backup/util/plan/base_task.class.php: call to restore_structure_step->execute()
              line 148 of /backup/util/plan/base_plan.class.php: call to base_task->execute()
              line 157 of /backup/util/plan/restore_plan.class.php: call to base_plan->execute()
              line 315 of /backup/controller/restore_controller.class.php: call to restore_plan->execute()
              line 147 of /backup/util/ui/restore_ui.class.php: call to restore_controller->execute_plan()
              line 46 of /backup/restore.php: call to restore_ui->execute()
          

          Not sure if this is related to one which you observed on postgres. Although, restoring the course on one without grades works fine.

          FYI:
          Both courses are attached for your reference.

          Show
          Rajesh Taneja added a comment - Sorry Dan, I am failing this. While restoring course on existing one (merge option selected), I am getting following error (db: MySql) Debug info: Duplicate entry '4-33' for key 'mdl_gradgrad_useite_uix' INSERT INTO mdl_grade_grades (userid,rawgrade,rawgrademax,rawgrademin,rawscaleid,usermodified,finalgrade,hidden,locked,locktime,exported,overridden,excluded,feedback,feedbackformat,information,informationformat,timecreated,timemodified,itemid) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) [array ( 0 => '4', 1 => NULL, 2 => '100.00000', 3 => '0.00000', 4 => NULL, 5 => NULL, 6 => '24.00000', 7 => '0', 8 => '0', 9 => '0', 10 => '0', 11 => '0', 12 => '0', 13 => NULL, 14 => '0', 15 => NULL, 16 => '0', 17 => NULL, 18 => NULL, 19 => '33', )] Stack trace: line 416 of /lib/dml/moodle_database.php: dml_write_exception thrown line 955 of /lib/dml/mysqli_native_moodle_database.php: call to moodle_database->query_end() line 997 of /lib/dml/mysqli_native_moodle_database.php: call to mysqli_native_moodle_database->insert_record_raw() line 222 of /backup/moodle2/restore_stepslib.php: call to mysqli_native_moodle_database->insert_record() line 131 of /backup/util/plan/restore_structure_step.class.php: call to restore_gradebook_structure_step->process_grade_grade() line 103 of /backup/util/helper/restore_structure_parser_processor.class.php: call to restore_structure_step->process() line 125 of /backup/util/xml/parser/processors/grouped_parser_processor.class.php: call to restore_structure_parser_processor->dispatch_chunk() line 91 of /backup/util/helper/restore_structure_parser_processor.class.php: call to grouped_parser_processor->postprocess_chunk() line 148 of /backup/util/xml/parser/processors/simplified_parser_processor.class.php: call to restore_structure_parser_processor->postprocess_chunk() line 92 of /backup/util/xml/parser/processors/progressive_parser_processor.class.php: call to simplified_parser_processor->process_chunk() line 169 of /backup/util/xml/parser/progressive_parser.class.php: call to progressive_parser_processor->receive_chunk() line 253 of /backup/util/xml/parser/progressive_parser.class.php: call to progressive_parser->publish() line ? of unknownfile: call to progressive_parser->end_tag() line 158 of /backup/util/xml/parser/progressive_parser.class.php: call to xml_parse() line 137 of /backup/util/xml/parser/progressive_parser.class.php: call to progressive_parser->parse() line 105 of /backup/util/plan/restore_structure_step.class.php: call to progressive_parser->process() line 153 of /backup/util/plan/base_task.class.php: call to restore_structure_step->execute() line 148 of /backup/util/plan/base_plan.class.php: call to base_task->execute() line 157 of /backup/util/plan/restore_plan.class.php: call to base_plan->execute() line 315 of /backup/controller/restore_controller.class.php: call to restore_plan->execute() line 147 of /backup/util/ui/restore_ui.class.php: call to restore_controller->execute_plan() line 46 of /backup/restore.php: call to restore_ui->execute() Not sure if this is related to one which you observed on postgres. Although, restoring the course on one without grades works fine. FYI: Both courses are attached for your reference.
          Hide
          Rajesh Taneja added a comment -

          Course on which test was executed.
          backup-for-MDL-32102.mbz is the course which contains completion status and has grades as well.

          Existing course on which...mbz is the one on which I tried to restore backup-for-MDL-32102.mbz

          FYI:
          Restore works fine if grades are removed from Existing course on which....mbz

          Show
          Rajesh Taneja added a comment - Course on which test was executed. backup-for- MDL-32102 .mbz is the course which contains completion status and has grades as well. Existing course on which...mbz is the one on which I tried to restore backup-for- MDL-32102 .mbz FYI: Restore works fine if grades are removed from Existing course on which....mbz
          Hide
          Dan Marsden added a comment -

          ummm not sure why you're failing this because you found a completely different bug?

          this bug fixes course completion records - doesn't have anything to do with grade records. please create a new bug for your new one and re-test this one.

          Show
          Dan Marsden added a comment - ummm not sure why you're failing this because you found a completely different bug? this bug fixes course completion records - doesn't have anything to do with grade records. please create a new bug for your new one and re-test this one.
          Hide
          Rajesh Taneja added a comment -

          Was not sure, if this was related. Will open another bug for this.
          Thanks Dan.

          Show
          Rajesh Taneja added a comment - Was not sure, if this was related. Will open another bug for this. Thanks Dan.
          Hide
          Michael de Raadt added a comment -

          Raj asked me to reset testing.

          Show
          Michael de Raadt added a comment - Raj asked me to reset testing.
          Hide
          Dan Marsden added a comment -

          thanks Raj - you're right it could be the same bug I saw under my postgres install.

          Show
          Dan Marsden added a comment - thanks Raj - you're right it could be the same bug I saw under my postgres install.
          Hide
          Rajesh Taneja added a comment -

          Thanks Michael and Dan,

          Have created new issue for mentioned error. All works as expected

          Show
          Rajesh Taneja added a comment - Thanks Michael and Dan, Have created new issue for mentioned error. All works as expected
          Hide
          Eloy Lafuente (stronk7) added a comment -

          This has been near becoming rejected, because it's not the best code you are able to produce.

          But, luckily, at the end, it has landed and has been spread to all repos out there.

          Many thanks and, don't forget it, keep improving your skills, you can!

          Closing, ciao

          Show
          Eloy Lafuente (stronk7) added a comment - This has been near becoming rejected, because it's not the best code you are able to produce. But, luckily, at the end, it has landed and has been spread to all repos out there. Many thanks and, don't forget it, keep improving your skills, you can! Closing, ciao

            People

            • Votes:
              1 Vote for this issue
              Watchers:
              4 Start watching this issue

              Dates

              • Created:
                Updated:
                Resolved: