Skip to content

Organization Validation for Service Inventory - #1668

Open
JVickery-TBS wants to merge 46 commits into
masterfrom
feature/ds-reference-tables
Open

Organization Validation for Service Inventory#1668
JVickery-TBS wants to merge 46 commits into
masterfrom
feature/ds-reference-tables

Conversation

@JVickery-TBS

Copy link
Copy Markdown
Contributor

We are able to consistently use the releases from the SI github to generate the CSV ref data we need. We have commands which load it into the tables which we define in an sql script in here. The part we were tripped on was relation between Service IDs and Program IDs, but there is no relation that is being defined or maintained currently so don't have to worry about that.

Just extended the temp datastore table to add an optional org_name to it, which we then use in the triggers. The Service ID one works fine, need to make the error message for it still.

The Program IDs one is gunna be interesting as Program IDs is a multiple select.

- Started working on reference tables for pd data.
- Continued script to generate service reference data.
- Continued schema for ref tables.
- Improved pd subcommand for loading ref data.
- Finalized script to generate service ref data.
- Added more recombinant schema key/values.
- Made JSONB index.
- Added choices_filter_query.
- Added recombinant org_name to the temp ds table.
- Started writing the database validation.
Comment thread ckanext/canada/plugin/internal_plugin.py Outdated
- Finalized ref value checks and errors in the db func.
- Fixed up some other logic things.
- Started working on fiscal year function.
- Finalized fiscal year db function.
Comment thread bin/service_generate_reference_data.py Outdated
Comment thread ckanext/canada/tables/service.yaml Outdated
Comment thread ckanext/canada/tables/service.yaml Outdated
Comment thread ckanext/canada/tables/service.yaml Outdated
@wardi

wardi commented Apr 18, 2026

Copy link
Copy Markdown
Member

just those minor things otherwise LGTM

- Added change log file.
- Pyright fixes.
- Flake8 fixes.
- Made service name fields computed ones.
- Modified filter scripts for service name lookups.
- Filter script typo.
- Filter script typo.
@codecov

codecov Bot commented Apr 20, 2026

Copy link
Copy Markdown

❌ 2 Tests Failed:

Tests completed Failed Passed Skipped
302 2 300 0
View the top 2 failed test(s) by shortest run time
ckanext/canada/tests/test_webforms.py::TestRecombinantWebForms::test_admin_can_create_single_record
Stack Traces | 7.07s run time
self = <ckanext.canada.tests.test_webforms.TestRecombinantWebForms object at 0x7f6195c2f9a0>
app = <ckan.tests.helpers.CKANTestApp object at 0x7f6193d76640>

    @mock.patch.object(h, 'flash_notice', flashes.mock_flash)
    @mock.patch.object(h, 'get_flashed_messages', flashes.mock_get_flashed_messages)
    @mock.patch.object(h, 'is_registry_domain', mock_is_registry_domain)
    def test_admin_can_create_single_record(self, app):
        self._lc_init_pd()
    
        offset = h.url_for('canada.create_pd_record',
                           resource_name=self.pd_type,
                           owner_org=self.org['name'])
        response = app.get(offset, extra_environ=self.extra_environ_system,
                           environ_overrides=self.environ_overrides_system)
    
>       assert 'Create Record' in response.body
E       assert 'Create Record' in '<!DOCTYPE html><!--[if lt IE 9]>\n  <html class="no-js lt-ie9" lang="en" dir="ltr"><![endif]--> <!--[if gt IE 8]><!-->\n  <html class="no-js" lang="en" dir="ltr"> <!--<![endif]-->\n  <head>\n    \n  <meta charset="utf-8" />\n      <meta name="csrf_field_name" content="_csrf_token" />\n      <meta name="_csrf_token" content="IjlhODBmYjUzMjNjN2NkZWVmYWMwMTY5NzI0MzE0NDMyZjYwYWFmZjIi.amt3uQ.4w11GGEj1i8ZDLsHkp0aEX8XcOw" />\n\n      <meta name="generator" content="ckan 2.10.8" />\n      <meta name="viewport" content="width=device-width, initial-scale=1.0">\n  \n    <meta property="og:title" content="Error 500 - CKAN">\n  \n  \n    \n  \n\n    <title>Error 500 - CKAN</title>\n\n    \n    \n  <link rel="shortcut icon" href="https://www.canada..../wet-boew/assets/favicon.ico" />\n    \n  \n    \n    \n    \n  \n  \n  \n\n  \n  \n      \n      \n      \n    \n  \n\n  \n\n\n    \n      \n      <link rel="stylesheet" href="TEST_TEMPLATE_HEAD_END.css" type="text/css">\n    \n\n    \n    <link href="....../webassets/base/b44cc5bc_main.css" rel="stylesheet" nonce="VWXbD8FHDvnuFDGDgZenPw"/>\n<link href="....../webassets/ckanext-scheming/073d7cfa_scheming_css.css" rel="st...public_analytics.js" type="text/javascript" nonce="VWXbD8FHDvnuFDGDgZenPw"></script>\n<script src="....../webassets/canada_internal_analytics/201f40a5_canada_internal_analytics.js" type="text/javascript" nonce="VWXbD8FHDvnuFDGDgZenPw"></script>\n<script src="....../webassets/vendor/8c3c143a_jquery.js" type="text/javascript" nonce="VWXbD8FHDvnuFDGDgZenPw"></script>\n<script src="....../webassets/vendor/9cf42cfd_vendor.js" type="text/javascript" nonce="VWXbD8FHDvnuFDGDgZenPw"></script>\n<script src="....../webassets/vendor/6d1ad7e6_bootstrap.js" type="text/javascript" nonce="VWXbD8FHDvnuFDGDgZenPw"></script>\n<script src="....../webassets/base/6fddaa3f_main.js" type="text/javascript" nonce="VWXbD8FHDvnuFDGDgZenPw"></script>\n<script src="....../webassets/base/7435ee13_ckan.js" type="text/javascript" nonce="VWXbD8FHDvnuFDGDgZenPw"></script>\n<script src="....../webassets/base/cd2c0272_tracking.js" type="text/javascript" nonce="VWXbD8FHDvnuFDGDgZenPw"></script>\n<script src="....../webassets/activity/0fdb5ee0_activity.js" type="text/javascript" nonce="VWXbD8FHDvnuFDGDgZenPw"></script>\n<script src="....../webassets/canada_modules/d7367692_user_opt_in.js" type="text/javascript" nonce="VWXbD8FHDvnuFDGDgZenPw"></script>\n  </body>\n</html>'
E        +  where '<!DOCTYPE html><!--[if lt IE 9]>\n  <html class="no-js lt-ie9" lang="en" dir="ltr"><![endif]--> <!--[if gt IE 8]><!-->\n  <html class="no-js" lang="en" dir="ltr"> <!--<![endif]-->\n  <head>\n    \n  <meta charset="utf-8" />\n      <meta name="csrf_field_name" content="_csrf_token" />\n      <meta name="_csrf_token" content="IjlhODBmYjUzMjNjN2NkZWVmYWMwMTY5NzI0MzE0NDMyZjYwYWFmZjIi.amt3uQ.4w11GGEj1i8ZDLsHkp0aEX8XcOw" />\n\n      <meta name="generator" content="ckan 2.10.8" />\n      <meta name="viewport" content="width=device-width, initial-scale=1.0">\n  \n    <meta property="og:title" content="Error 500 - CKAN">\n  \n  \n    \n  \n\n    <title>Error 500 - CKAN</title>\n\n    \n    \n  <link rel="shortcut icon" href="https://www.canada..../wet-boew/assets/favicon.ico" />\n    \n  \n    \n    \n    \n  \n  \n  \n\n  \n  \n      \n      \n      \n    \n  \n\n  \n\n\n    \n      \n      <link rel="stylesheet" href="TEST_TEMPLATE_HEAD_END.css" type="text/css">\n    \n\n    \n    <link href="....../webassets/base/b44cc5bc_main.css" rel="stylesheet" nonce="VWXbD8FHDvnuFDGDgZenPw"/>\n<link href="....../webassets/ckanext-scheming/073d7cfa_scheming_css.css" rel="st...public_analytics.js" type="text/javascript" nonce="VWXbD8FHDvnuFDGDgZenPw"></script>\n<script src="....../webassets/canada_internal_analytics/201f40a5_canada_internal_analytics.js" type="text/javascript" nonce="VWXbD8FHDvnuFDGDgZenPw"></script>\n<script src="....../webassets/vendor/8c3c143a_jquery.js" type="text/javascript" nonce="VWXbD8FHDvnuFDGDgZenPw"></script>\n<script src="....../webassets/vendor/9cf42cfd_vendor.js" type="text/javascript" nonce="VWXbD8FHDvnuFDGDgZenPw"></script>\n<script src="....../webassets/vendor/6d1ad7e6_bootstrap.js" type="text/javascript" nonce="VWXbD8FHDvnuFDGDgZenPw"></script>\n<script src="....../webassets/base/6fddaa3f_main.js" type="text/javascript" nonce="VWXbD8FHDvnuFDGDgZenPw"></script>\n<script src="....../webassets/base/7435ee13_ckan.js" type="text/javascript" nonce="VWXbD8FHDvnuFDGDgZenPw"></script>\n<script src="....../webassets/base/cd2c0272_tracking.js" type="text/javascript" nonce="VWXbD8FHDvnuFDGDgZenPw"></script>\n<script src="....../webassets/activity/0fdb5ee0_activity.js" type="text/javascript" nonce="VWXbD8FHDvnuFDGDgZenPw"></script>\n<script src="....../webassets/canada_modules/d7367692_user_opt_in.js" type="text/javascript" nonce="VWXbD8FHDvnuFDGDgZenPw"></script>\n  </body>\n</html>' = <WrapperTestResponse 21557 bytes [500 INTERNAL SERVER ERROR]>.body

.../canada/tests/test_webforms.py:521: AssertionError
ckanext/canada/tests/test_webforms.py::TestRecombinantWebForms::test_editor_can_create_single_record
Stack Traces | 10.7s run time
self = <ckanext.canada.tests.test_webforms.TestRecombinantWebForms object at 0x7f96c4f887f0>
app = <ckan.tests.helpers.CKANTestApp object at 0x7f96c3f40280>

    @mock.patch.object(h, 'flash_notice', flashes.mock_flash)
    @mock.patch.object(h, 'get_flashed_messages', flashes.mock_get_flashed_messages)
    @mock.patch.object(h, 'is_registry_domain', mock_is_registry_domain)
    def test_editor_can_create_single_record(self, app):
        self._lc_init_pd()
    
        offset = h.url_for('canada.create_pd_record',
                           resource_name=self.pd_type,
                           owner_org=self.org['name'])
        response = app.get(offset, extra_environ=self.extra_environ_editor,
                           environ_overrides=self.environ_overrides_editor)
    
>       assert 'Create Record' in response.body
E       assert 'Create Record' in '<!DOCTYPE html><!--[if lt IE 9]>\n  <html class="no-js lt-ie9" lang="en" dir="ltr"><![endif]--> <!--[if gt IE 8]><!-->\n  <html class="no-js" lang="en" dir="ltr"> <!--<![endif]-->\n  <head>\n    \n  <meta charset="utf-8" />\n      <meta name="csrf_field_name" content="_csrf_token" />\n      <meta name="_csrf_token" content="IjExNWQzNmJmMmI2MzQ2MDNjOGY1NDU5ZTEwYWRmYTlkMjQyM2MzN2Mi.amt3og.2ZzjMYXZOfIJr23lULk1K8-BfOg" />\n\n      <meta name="generator" content="ckan 2.10.8" />\n      <meta name="viewport" content="width=device-width, initial-scale=1.0">\n  \n    <meta property="og:title" content="Error 500 - CKAN">\n  \n  \n    \n  \n\n    <title>Error 500 - CKAN</title>\n\n    \n    \n  <link rel="shortcut icon" href="https://www.canada..../wet-boew/assets/favicon.ico" />\n    \n  \n    \n    \n    \n  \n  \n  \n\n  \n  \n      \n      \n      \n    \n  \n\n  \n\n\n    \n      \n      <link rel="stylesheet" href="TEST_TEMPLATE_HEAD_END.css" type="text/css">\n    \n\n    \n    <link href="....../webassets/base/b44cc5bc_main.css" rel="stylesheet" nonce="nozOjZ9Xu3mAVAYeLMNqO2"/>\n<link href="....../webassets/ckanext-scheming/073d7cfa_scheming_css.css" rel="st...public_analytics.js" type="text/javascript" nonce="nozOjZ9Xu3mAVAYeLMNqO2"></script>\n<script src="....../webassets/canada_internal_analytics/201f40a5_canada_internal_analytics.js" type="text/javascript" nonce="nozOjZ9Xu3mAVAYeLMNqO2"></script>\n<script src="....../webassets/vendor/8c3c143a_jquery.js" type="text/javascript" nonce="nozOjZ9Xu3mAVAYeLMNqO2"></script>\n<script src="....../webassets/vendor/9cf42cfd_vendor.js" type="text/javascript" nonce="nozOjZ9Xu3mAVAYeLMNqO2"></script>\n<script src="....../webassets/vendor/6d1ad7e6_bootstrap.js" type="text/javascript" nonce="nozOjZ9Xu3mAVAYeLMNqO2"></script>\n<script src="....../webassets/base/6fddaa3f_main.js" type="text/javascript" nonce="nozOjZ9Xu3mAVAYeLMNqO2"></script>\n<script src="....../webassets/base/7435ee13_ckan.js" type="text/javascript" nonce="nozOjZ9Xu3mAVAYeLMNqO2"></script>\n<script src="....../webassets/base/cd2c0272_tracking.js" type="text/javascript" nonce="nozOjZ9Xu3mAVAYeLMNqO2"></script>\n<script src="....../webassets/activity/0fdb5ee0_activity.js" type="text/javascript" nonce="nozOjZ9Xu3mAVAYeLMNqO2"></script>\n<script src="....../webassets/canada_modules/d7367692_user_opt_in.js" type="text/javascript" nonce="nozOjZ9Xu3mAVAYeLMNqO2"></script>\n  </body>\n</html>'
E        +  where '<!DOCTYPE html><!--[if lt IE 9]>\n  <html class="no-js lt-ie9" lang="en" dir="ltr"><![endif]--> <!--[if gt IE 8]><!-->\n  <html class="no-js" lang="en" dir="ltr"> <!--<![endif]-->\n  <head>\n    \n  <meta charset="utf-8" />\n      <meta name="csrf_field_name" content="_csrf_token" />\n      <meta name="_csrf_token" content="IjExNWQzNmJmMmI2MzQ2MDNjOGY1NDU5ZTEwYWRmYTlkMjQyM2MzN2Mi.amt3og.2ZzjMYXZOfIJr23lULk1K8-BfOg" />\n\n      <meta name="generator" content="ckan 2.10.8" />\n      <meta name="viewport" content="width=device-width, initial-scale=1.0">\n  \n    <meta property="og:title" content="Error 500 - CKAN">\n  \n  \n    \n  \n\n    <title>Error 500 - CKAN</title>\n\n    \n    \n  <link rel="shortcut icon" href="https://www.canada..../wet-boew/assets/favicon.ico" />\n    \n  \n    \n    \n    \n  \n  \n  \n\n  \n  \n      \n      \n      \n    \n  \n\n  \n\n\n    \n      \n      <link rel="stylesheet" href="TEST_TEMPLATE_HEAD_END.css" type="text/css">\n    \n\n    \n    <link href="....../webassets/base/b44cc5bc_main.css" rel="stylesheet" nonce="nozOjZ9Xu3mAVAYeLMNqO2"/>\n<link href="....../webassets/ckanext-scheming/073d7cfa_scheming_css.css" rel="st...public_analytics.js" type="text/javascript" nonce="nozOjZ9Xu3mAVAYeLMNqO2"></script>\n<script src="....../webassets/canada_internal_analytics/201f40a5_canada_internal_analytics.js" type="text/javascript" nonce="nozOjZ9Xu3mAVAYeLMNqO2"></script>\n<script src="....../webassets/vendor/8c3c143a_jquery.js" type="text/javascript" nonce="nozOjZ9Xu3mAVAYeLMNqO2"></script>\n<script src="....../webassets/vendor/9cf42cfd_vendor.js" type="text/javascript" nonce="nozOjZ9Xu3mAVAYeLMNqO2"></script>\n<script src="....../webassets/vendor/6d1ad7e6_bootstrap.js" type="text/javascript" nonce="nozOjZ9Xu3mAVAYeLMNqO2"></script>\n<script src="....../webassets/base/6fddaa3f_main.js" type="text/javascript" nonce="nozOjZ9Xu3mAVAYeLMNqO2"></script>\n<script src="....../webassets/base/7435ee13_ckan.js" type="text/javascript" nonce="nozOjZ9Xu3mAVAYeLMNqO2"></script>\n<script src="....../webassets/base/cd2c0272_tracking.js" type="text/javascript" nonce="nozOjZ9Xu3mAVAYeLMNqO2"></script>\n<script src="....../webassets/activity/0fdb5ee0_activity.js" type="text/javascript" nonce="nozOjZ9Xu3mAVAYeLMNqO2"></script>\n<script src="....../webassets/canada_modules/d7367692_user_opt_in.js" type="text/javascript" nonce="nozOjZ9Xu3mAVAYeLMNqO2"></script>\n  </body>\n</html>' = <WrapperTestResponse 20975 bytes [500 INTERNAL SERVER ERROR]>.body

.../canada/tests/test_webforms.py:498: AssertionError

To view more test analytics, go to the Test Analytics Dashboard
📋 Got 3 mins? Take this short survey to help us improve Test Analytics.

- Created new workflow to semi-automate stuffs.
- Removed service name computed fields.
- Trigger type cast.
@JVickery-TBS

Copy link
Copy Markdown
Contributor Author

TODO: update any tests for test_service.py and its make tests if needed.

wardi and others added 28 commits May 30, 2026 16:51
- Add db table and logic for reference data file hashes during load.
# Conflicts:
#	ckanext/canada/helpers.py
#	test-core.ini
### RESOLVED.
- Added test coverage for service inventory fiscal year function.
- Added test coverage for service inventory org based fields.
- Flake8 fixes.
- Org validation for make service test.
- Org validation for make service test.
- Org validation for make service test.
- Trying to fix `Prep Test Coverage Split File`
- Trying to fix `Prep Test Coverage Split File`
# Conflicts:
#	ckanext/canada/tests/filters.py
#	ckanext/canada/tests/test_service.py
### RESOLVED.
- Rebuild service inventory ref data.
- Add fixme comment for overwritting dict assignment.
- Support multiple org names for one umd.
- Do not count new lines in max char func.
- Added test coverage.
- Added functionality for the new recombinant CLI markers.
- Added choice suffix to service inventory program_id.
- Added test coverage.
- Added support for old choice values in the recombinant webforms.
- If `service_migration` marker is set, invalid program_ids will be suffixed with -INV.
- Renamed the datastore_user temp table to be more accurate as an app_context table.
- Better handle legacy choices.
- Better handle choice suffixing.
- Handle insecure firefox preventing window.confirm
- Added change log files.
- Pyright fixes.
- Flake8 fixes.
- Pyright fixes.
- Flake8 fixes.
suffix_fr = ''
if id.endswith('-INV'):
suffix_en = ' (Invalid for Organization EN)'
suffix_fr = ' (Invalid for Organization FR)'

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need a FIXME to remember to get translated strings here?

Comment on lines +124 to +125
row['program_name_en'].append('"%s%s"' % (PROGRAM_IDS[id]['en'], suffix_en))
row['program_name_fr'].append('"%s%s"' % (PROGRAM_IDS[id]['fr'], suffix_fr))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe a little cursed, but you are allowed to use the same types of quotes inside f-strings that you use outside.

Suggested change
row['program_name_en'].append('"%s%s"' % (PROGRAM_IDS[id]['en'], suffix_en))
row['program_name_fr'].append('"%s%s"' % (PROGRAM_IDS[id]['fr'], suffix_fr))
row['program_name_en'].append(f'"{PROGRAM_IDS[id]['en']}{suffix_en}"')
row['program_name_fr'].append(f'"{PROGRAM_IDS[id]['fr']}{suffix_fr}"')


REQUEST_HEADERS = {'User-Agent': 'CKAN/open-gov/service/gen_ref_data'}

WHITE_SPACE_SUBS = re.compile(r'\xa0|\t|\n|\s+')

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you mean

Suggested change
WHITE_SPACE_SUBS = re.compile(r'\xa0|\t|\n|\s+')
WHITE_SPACE_SUBS = re.compile(r'[\xa0\s]+')

\t and \n are covered by \s and you want to allow any number of repeats of all of them, not only \s

org_id_abbr_map = {}
with requests.get(org_variants_uri, stream=True) as response:
response.encoding = 'utf-8-sig'
c = csv.DictReader(response.iter_lines(decode_unicode=True))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we've manually set the encoding do we need detect_unicode=True? If so that's a strange API

Comment thread ckanext/canada/logic.py
contextual_flags else 'FALSE',
org_name=literal_string(org_name) if org_name else None,
flag_definitions=(',' if contextual_flags else '') +
', '.join('%s boolean' % m for m in contextual_flags),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we'd want an identifier() here wrapping column names, but as we discussed it would be even better to use a text[] field for flags

BEGIN
IF value IS NOT NULL AND value <> '' AND LENGTH(value) > max_chars THEN
IF value IS NOT NULL AND value <> '' AND LENGTH(REGEXP_REPLACE(
value, E'[\r\n]', '', 'g')) > max_chars THEN

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as discussed we want to include all characters when computing length

Comment thread ckanext/canada/model.py
table_name = Column(Unicode, primary_key=True)
last_sync = Column(DateTime, nullable=False,
default=datetime.datetime.now(datetime.timezone.utc))
file_hash = Column(Unicode, nullable=False)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we're only ever storing sha265 hexdigest values here it would be better to call the column sha256 or similar

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants