From 93130484fed349bdcc66d32997cb49cc18c81351 Mon Sep 17 00:00:00 2001 From: Caden Buckhalt Date: Thu, 18 Apr 2024 10:30:10 -0700 Subject: [PATCH 1/7] fix: ordinal sort direction asc was sorted in desc and vice versa. this fixes those so they are sorted in the correct direction --- src/utils/createSorter.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/utils/createSorter.js b/src/utils/createSorter.js index 163e4e4ae..7afcfe959 100644 --- a/src/utils/createSorter.js +++ b/src/utils/createSorter.js @@ -95,19 +95,17 @@ const hierarchyFunction = ({ property, direction = 'desc', hierarchy = [] }) => if (direction === 'asc') { if (firstIndex > secondIndex) { - return -1; + return 1; } - if (firstIndex < secondIndex) { - return 1; + return -1; } } else { if (firstIndex < secondIndex) { - return -1; + return 1; } - if (firstIndex > secondIndex) { - return 1; + return -1; } } return 0; From be22e8d492df27c389a33da318d9e25cb8655c0a Mon Sep 17 00:00:00 2001 From: Caden Buckhalt Date: Thu, 18 Apr 2024 11:01:43 -0700 Subject: [PATCH 2/7] revert ordinal sort direction. correct as is. was not the cause of the bug --- src/utils/createSorter.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/utils/createSorter.js b/src/utils/createSorter.js index 7afcfe959..ca90ded8e 100644 --- a/src/utils/createSorter.js +++ b/src/utils/createSorter.js @@ -95,17 +95,17 @@ const hierarchyFunction = ({ property, direction = 'desc', hierarchy = [] }) => if (direction === 'asc') { if (firstIndex > secondIndex) { - return 1; + return -1; } if (firstIndex < secondIndex) { - return -1; + return 1; } } else { if (firstIndex < secondIndex) { - return 1; + return -1; } if (firstIndex > secondIndex) { - return -1; + return 1; } } return 0; From 8548a8c1886631d428c57bafd822cb9219fabe40 Mon Sep 17 00:00:00 2001 From: Caden Buckhalt Date: Thu, 18 Apr 2024 15:11:42 -0700 Subject: [PATCH 3/7] feat: sort categorical variables with hierarchy being order created --- src/utils/__tests__/createSorter.test.js | 113 ++++++++++++++++++++++- src/utils/createSorter.js | 39 +++++++- 2 files changed, 149 insertions(+), 3 deletions(-) diff --git a/src/utils/__tests__/createSorter.test.js b/src/utils/__tests__/createSorter.test.js index e1c30e02b..9865de34a 100644 --- a/src/utils/__tests__/createSorter.test.js +++ b/src/utils/__tests__/createSorter.test.js @@ -320,6 +320,117 @@ describe('Types', () => { }); }); +describe('Categorical sorting', () => { + it('sorts items based on categorical values', () => { + const mockItems = [ + { + category: ['cow'], + name: 'alice', + }, + { + category: ['duck'], + name: 'bob', + }, + { + category: ['lizard'], + name: 'charlie', + }, + { + category: ['cow'], + name: 'david', + }, + ]; + + const sorter = createSorter([ + { + property: 'category', + type: 'categorical', + hierarchy: ['duck', 'lizard', 'cow'], + }, + { + property: 'name', + type: 'string', + direction: 'asc', + }, + ]); + + const result = sorter(mockItems).map((item) => item.name); + expect(result).toEqual(['alice', 'david', 'charlie', 'bob']); + }); + + it('handles items with multiple categories', () => { + const mockItems = [ + { + category: ['duck', 'lizard'], + name: 'alice', + }, + { + category: ['cow', 'duck'], + name: 'bob', + }, + { + category: ['cow'], + name: 'charlie', + }, + { + category: ['lizard'], + name: 'david', + }, + ]; + + const sorter = createSorter([ + { + property: 'category', + type: 'categorical', + hierarchy: ['cow', 'duck', 'lizard'], + }, + { + property: 'name', + type: 'string', + direction: 'asc', + }, + ]); + + const result = sorter(mockItems).map((item) => item.name); + expect(result).toEqual(['david', 'alice', 'bob', 'charlie']); + }); + + it('handles missing categories', () => { + const mockItems = [ + { + name: 'alice', + }, + { + category: ['duck'], + name: 'bob', + }, + { + category: ['lizard'], + name: 'charlie', + }, + { + name: 'david', + }, + ]; + + const sorter = createSorter([ + { + property: 'category', + type: 'categorical', + hierarchy: ['lizard', 'duck', 'cow'], + }, + { + property: 'name', + type: 'string', + direction: 'asc', + }, + ]); + + const result = sorter(mockItems).map((item) => item.name); + expect(result).toEqual(['bob', 'charlie', 'alice', 'david']); + }); +}); + describe('Order direction', () => { it('orders ascending with "asc"', () => { const mockItems = [ @@ -994,7 +1105,7 @@ describe('processProtocolSortRule', () => { property: 'category', direction: 'asc', }; - expect(processProtocolSortRule(codebookVariables)(rule).type).toEqual('string'); + expect(processProtocolSortRule(codebookVariables)(rule).type).toEqual('categorical'); }); it('ordinal', () => { diff --git a/src/utils/createSorter.js b/src/utils/createSorter.js index ca90ded8e..87d8a94f4 100644 --- a/src/utils/createSorter.js +++ b/src/utils/createSorter.js @@ -74,6 +74,36 @@ const stringFunction = ({ property, direction }) => (a, b) => { return collator.compare(secondValue, firstValue); }; +const categoricalFunction = ({ property, direction, hierarchy = [] }) => (a, b) => { + // hierarchy is whatever order the variables were specified in the variable definition + const firstValues = get(a, property, []); + const secondValues = get(b, property, []); + + for (let i = 0; i < Math.max(firstValues.length, secondValues.length); i += 1) { + const firstValue = i < firstValues.length ? firstValues[i] : null; + const secondValue = i < secondValues.length ? secondValues[i] : null; + + if (firstValue !== secondValue) { + // If one of the values is not in the hierarchy, it is sorted to the end of the list + const firstIndex = hierarchy.indexOf(firstValue); + const secondIndex = hierarchy.indexOf(secondValue); + + if (firstIndex === -1) { + return 1; + } + if (secondIndex === -1) { + return -1; + } + + if (direction === 'asc') { + return firstIndex - secondIndex; + } return secondIndex - firstIndex; // desc + } + } + + return 0; +}; + /** * Creates a sort function that sorts items according to the index of their * property value in a hierarchy array. @@ -163,8 +193,10 @@ const getSortFunction = (rule) => { if (type === 'hierarchy') { return hierarchyFunction(rule); } + if (type === 'categorical') { return categoricalFunction(rule); } + // eslint-disable-next-line no-console - console.warn('🤔 Sort rule missing required property \'type\', or type was not recognized. Sorting as a string, which may cause incorrect results. Supported types are: number, boolean, string, date, hierarchy.'); + console.warn('🤔 Sort rule missing required property \'type\', or type was not recognized. Sorting as a string, which may cause incorrect results. Supported types are: number, boolean, string, date, hierarchy, categorical'); return stringFunction(rule); }; @@ -193,6 +225,7 @@ const createSorter = (sortRules = []) => { * - hierarchy * - number * - date + * - categorical * * Network Canvas Variables can be of type: * - "boolean", @@ -208,7 +241,6 @@ const createSorter = (sortRules = []) => { export const mapNCType = (type) => { switch (type) { case 'text': - case 'categorical': case 'layout': return 'string'; case 'number': @@ -219,6 +251,8 @@ export const mapNCType = (type) => { return 'date'; case 'ordinal': return 'hierarchy'; + case 'categorical': + return 'categorical'; case 'scalar': return 'number'; default: @@ -269,6 +303,7 @@ export const processProtocolSortRule = (codebookVariables) => (sortRule) => { type: mapNCType(type), // Generate a hierarchy if the variable is ordinal based on the ordinal options ...type === 'ordinal' && { hierarchy: variableDefinition.options.map((option) => option.value) }, + ...type === 'categorical' && { hierarchy: variableDefinition.options.map((option) => option.value) }, }; }; From 123793144324de342d992bc612c6f4bedfd36b26 Mon Sep 17 00:00:00 2001 From: Caden Buckhalt Date: Fri, 19 Apr 2024 07:29:15 -0700 Subject: [PATCH 4/7] add categorical type to comments about supported types --- src/utils/createSorter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/createSorter.js b/src/utils/createSorter.js index 87d8a94f4..f71ba2583 100644 --- a/src/utils/createSorter.js +++ b/src/utils/createSorter.js @@ -175,7 +175,7 @@ const getSortFunction = (rule) => { const { property, direction = 'asc', - type, // REQUIRED! number, boolean, string, date, hierarchy + type, // REQUIRED! number, boolean, string, date, hierarchy, categorical } = rule; // LIFO/FIFO rule sorted by _createdIndex From 2fbdd34c4837208a726589da16ac62ade32c779e Mon Sep 17 00:00:00 2001 From: Caden Buckhalt Date: Fri, 19 Apr 2024 07:58:18 -0700 Subject: [PATCH 5/7] bump version to 6.5.3 --- config.xml | 2 +- package-lock.json | 4 ++-- package.json | 2 +- public/package.json | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/config.xml b/config.xml index 499ee606f..7c293af40 100644 --- a/config.xml +++ b/config.xml @@ -1,5 +1,5 @@ - diff --git a/package-lock.json b/package-lock.json index 72bd56ae4..b5b70b84b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "network-canvas-interviewer", - "version": "6.5.2", + "version": "6.5.3", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "network-canvas-interviewer", - "version": "6.5.2", + "version": "6.5.3", "dependencies": { "@babel/runtime": "7.10.1", "@xmldom/xmldom": "~0.8.10", diff --git a/package.json b/package.json index 0e7613adf..4f20528fe 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "network-canvas-interviewer", - "version": "6.5.2", + "version": "6.5.3", "productName": "Network Canvas Interviewer", "description": "A tool for conducting Network Canvas Interviews.", "author": "Complex Data Collective", diff --git a/public/package.json b/public/package.json index bef634fc5..d9bac8f52 100644 --- a/public/package.json +++ b/public/package.json @@ -1,6 +1,6 @@ { "name": "network-canvas-interviewer", - "version": "6.5.2", + "version": "6.5.3", "productName": "Network Canvas Interviewer", "description": "A tool for conducting Network Canvas Interviews.", "author": "Complex Data Collective", From 0ecbdd7905fb841735fb23da7bcece2db1dd601a Mon Sep 17 00:00:00 2001 From: Joshua Melville Date: Mon, 22 Apr 2024 11:16:12 +0200 Subject: [PATCH 6/7] update references to codaco --- CODE_OF_CONDUCT.md | 22 +++++++++++----------- config.xml | 2 +- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 2e6425172..dc841d10a 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -8,19 +8,19 @@ In the interest of fostering an open and welcoming environment, we as contributo Examples of behavior that contributes to creating a positive environment include: -* Using welcoming and inclusive language -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy towards other community members +- Using welcoming and inclusive language +- Being respectful of differing viewpoints and experiences +- Gracefully accepting constructive criticism +- Focusing on what is best for the community +- Showing empathy towards other community members Examples of unacceptable behavior by participants include: -* The use of sexualized language or imagery and unwelcome sexual attention or advances -* Trolling, insulting/derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or electronic address, without explicit permission -* Other conduct which could reasonably be considered inappropriate in a professional setting +- The use of sexualized language or imagery and unwelcome sexual attention or advances +- Trolling, insulting/derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or electronic address, without explicit permission +- Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities @@ -34,7 +34,7 @@ This Code of Conduct applies both within project spaces and in public spaces whe ## Enforcement -Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at developers@coda.co. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at info@networkcanvas.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. diff --git a/config.xml b/config.xml index 7c293af40..1f25c52ce 100644 --- a/config.xml +++ b/config.xml @@ -7,7 +7,7 @@ A tool for conducting Network Canvas Interviews. - + Complex Data Collective From b78c652e9c509126a77a3824499ef1a98bb860b3 Mon Sep 17 00:00:00 2001 From: Joshua Melville Date: Tue, 23 Apr 2024 19:25:00 +0200 Subject: [PATCH 7/7] update electron notarize package --- build-resources/scripts/afterSignHook.js | 9 +++-- package-lock.json | 44 ++++++++++-------------- package.json | 2 +- 3 files changed, 24 insertions(+), 31 deletions(-) diff --git a/build-resources/scripts/afterSignHook.js b/build-resources/scripts/afterSignHook.js index 15fe3bab0..13eca274a 100644 --- a/build-resources/scripts/afterSignHook.js +++ b/build-resources/scripts/afterSignHook.js @@ -5,11 +5,10 @@ const path = require('path'); const electronNotarize = require('@electron/notarize'); async function note(params) { - // Only notarize the app on Mac OS only. + // Only notarize the app on macOS. if (process.platform !== 'darwin') { return; } - console.log('afterSign hook triggered', params); const appPath = path.join(params.appOutDir, `${params.packager.appInfo.productFilename}.app`); if (!fs.existsSync(appPath)) { @@ -23,9 +22,9 @@ async function note(params) { tool: 'notarytool', appBundleId: 'org.codaco.NetworkCanvasInterviewer6', appPath, - appleApiKey: '~/.private_keys/AuthKey_J58L47W6H9.p8', - appleApiKeyId: 'J58L47W6H9', // This is taken from the filename of the .p8 file in your icloud drive - appleApiIssuer: '69a6de92-60bf-47e3-e053-5b8c7c11a4d1', + appleApiKey: '~/.private_keys/AuthKey_A78M67RCH9.p8', + appleApiKeyId: 'A78M67RCH9', // Taken from https://appstoreconnect.apple.com/access/integrations/api + appleApiIssuer: '69a6de92-60bf-47e3-e053-5b8c7c11a4d1',// As above }); console.log('Done notarizing'); diff --git a/package-lock.json b/package-lock.json index b5b70b84b..d3e705135 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,7 +30,7 @@ "@codaco/eslint-plugin-spellcheck": "0.0.14", "@codaco/shared-consts": "~0.0.1-alpha.3", "@codaco/ui": "~5.8.5", - "@electron/notarize": "~1.2.3", + "@electron/notarize": "~2.3.0", "@faker-js/faker": "~6.0.0-alpha.5", "@zippytech/sorty": "^2.0.0", "ajv": "^6.5.4", @@ -2886,13 +2886,14 @@ } }, "node_modules/@electron/notarize": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@electron/notarize/-/notarize-1.2.3.tgz", - "integrity": "sha512-9oRzT56rKh5bspk3KpAVF8lPKHYQrBnRwcgiOeR0hdilVEQmszDaAu0IPCPrwwzJN0ugNs0rRboTreHMt/6mBQ==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@electron/notarize/-/notarize-2.3.0.tgz", + "integrity": "sha512-EiTBU0BwE7HZZjAG1fFWQaiQpCuPrVGn7jPss1kUjD6eTTdXXd29RiZqEqkgN7xqt/Pgn4g3I7Saqovanrfj3w==", "dev": true, "dependencies": { "debug": "^4.1.1", - "fs-extra": "^9.0.1" + "fs-extra": "^9.0.1", + "promise-retry": "^2.0.1" }, "engines": { "node": ">= 10.0.0" @@ -8252,9 +8253,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001524", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001524.tgz", - "integrity": "sha512-Jj917pJtYg9HSJBF95HVX3Cdr89JUyLT4IZ8SvM5aDRni95swKgYi3TgYLH5hnGfPE/U1dg6IfZ50UsIlLkwSA==", + "version": "1.0.30001612", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001612.tgz", + "integrity": "sha512-lFgnZ07UhaCcsSZgWW0K5j4e69dK1u/ltrL9lTUiFOwNHs12S3UMIEYgBV0Z6C6hRDev7iRnMzzYmKabYdXF9g==", "dev": true, "funding": [ { @@ -12518,9 +12519,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", - "dev": true, - "optional": true, - "peer": true + "dev": true }, "node_modules/errno": { "version": "0.1.8", @@ -26253,8 +26252,6 @@ "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", "dev": true, - "optional": true, - "peer": true, "dependencies": { "err-code": "^2.0.2", "retry": "^0.12.0" @@ -38183,13 +38180,14 @@ } }, "@electron/notarize": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@electron/notarize/-/notarize-1.2.3.tgz", - "integrity": "sha512-9oRzT56rKh5bspk3KpAVF8lPKHYQrBnRwcgiOeR0hdilVEQmszDaAu0IPCPrwwzJN0ugNs0rRboTreHMt/6mBQ==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@electron/notarize/-/notarize-2.3.0.tgz", + "integrity": "sha512-EiTBU0BwE7HZZjAG1fFWQaiQpCuPrVGn7jPss1kUjD6eTTdXXd29RiZqEqkgN7xqt/Pgn4g3I7Saqovanrfj3w==", "dev": true, "requires": { "debug": "^4.1.1", - "fs-extra": "^9.0.1" + "fs-extra": "^9.0.1", + "promise-retry": "^2.0.1" }, "dependencies": { "fs-extra": { @@ -42465,9 +42463,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001524", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001524.tgz", - "integrity": "sha512-Jj917pJtYg9HSJBF95HVX3Cdr89JUyLT4IZ8SvM5aDRni95swKgYi3TgYLH5hnGfPE/U1dg6IfZ50UsIlLkwSA==", + "version": "1.0.30001612", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001612.tgz", + "integrity": "sha512-lFgnZ07UhaCcsSZgWW0K5j4e69dK1u/ltrL9lTUiFOwNHs12S3UMIEYgBV0Z6C6hRDev7iRnMzzYmKabYdXF9g==", "dev": true }, "capture-exit": { @@ -45785,9 +45783,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", - "dev": true, - "optional": true, - "peer": true + "dev": true }, "errno": { "version": "0.1.8", @@ -56334,8 +56330,6 @@ "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", "dev": true, - "optional": true, - "peer": true, "requires": { "err-code": "^2.0.2", "retry": "^0.12.0" diff --git a/package.json b/package.json index 4f20528fe..2eca41f47 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,7 @@ "@codaco/eslint-plugin-spellcheck": "0.0.14", "@codaco/shared-consts": "~0.0.1-alpha.3", "@codaco/ui": "~5.8.5", - "@electron/notarize": "~1.2.3", + "@electron/notarize": "~2.3.0", "@faker-js/faker": "~6.0.0-alpha.5", "@zippytech/sorty": "^2.0.0", "ajv": "^6.5.4",