Skip to content

feat: show requested skill strengths in search - #55

Merged
michaelmwu merged 1 commit into
mainfrom
michaelmwu/search-member-skills
Feb 25, 2026
Merged

feat: show requested skill strengths in search#55
michaelmwu merged 1 commit into
mainfrom
michaelmwu/search-member-skills

Conversation

@michaelmwu

@michaelmwumichaelmwu commented Feb 25, 2026

Copy link
Copy Markdown
Member

Description

Summary

Enhances the /search-members command to include requested-skill strength annotations (for example python (5)) directly in results when cSkillAttrs is available and valid.

  • Adds robust parsing/normalization for contact cSkillAttrs values with best-effort fallback for malformed data.
  • Extends CRM search select fields to include skills and cSkillAttrs so skill metadata is available in results.
  • Adds safeguards to keep search behavior stable when skill metadata is missing or invalid, and logs parse failures.
  • Adds unit tests covering strength rendering and malformed cSkillAttrs handling, plus select fields assertions.

Related Issue

None.

How Has This Been Tested?

Ran unit tests for tests/unit/test_crm.py and validated formatting/parsing scenarios through assertions.

Summary by CodeRabbit

Release Notes

  • New Features
    • Member search results now display skills paired with proficiency levels (e.g., Python: 5, SQL: 4), enabling more effective identification of team members with specific expertise and capabilities.
    • Strengthened skill data parsing to gracefully handle various formats and edge cases in member profiles, ensuring consistent display without errors.

@coderabbitai

coderabbitaiBot commented Feb 25, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR enhances the CRM Discord bot's search functionality by adding skill strength parsing and display. Three new helper methods parse contact skill attributes from JSON payloads, normalize skill names, and format requested skills with their strength values for display in search results. Unit tests verify correct handling of skill strengths and malformed payloads.

Changes

Cohort / File(s)Summary
CRM Skill Strength Enhancement
apps/discord_bot/src/five08/discord_bot/cogs/crm.py
Added _parse_contact_skill_attrs() to parse and validate skill strength data from contact attributes (JSON/dict/None), _normalize_skill() for standardized skill name formatting, and _format_requested_skills() to append strength values to requested skills. Modified search_members() to request skills and cSkillAttrs fields and display formatted skills in search results. Added json import.
CRM Skill Tests
tests/unit/test_crm.py
Added tests verifying select parameters include both skills and cSkillAttrs, skill strengths display correctly (e.g., python: 5), and malformed skill attribute payloads are gracefully ignored.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A skilled recruit joins our warren so fine,
With strengths five and sixes, in sync they align,
We parse what's requested, we normalize true,
And format the skills in a display anew—
No broken payloads shall daunt our command! 🥕✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly and specifically summarizes the main change: adding skill strength display to search results.
Docstring Coverage✅ PassedDocstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch michaelmwu/search-member-skills

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/discord_bot/src/five08/discord_bot/cogs/crm.py (1)

1336-1343: ⚠️ Potential issue | 🟠 Major

Add error handling for optional cSkillAttrs field in search.

The /search-members command currently fails if cSkillAttrs is unavailable in the workspace. EspoCRM's REST API will return a 500 error if a field in select exists in metadata but the corresponding database column is missing or misconfigured. Since cSkillAttrs is a custom field that may not exist in all workspaces, implement a fallback retry without it:

Suggested fix
- search_params = {+ base_select = (+ "id,name,emailAddress,c508Email,cDiscordUsername,cDiscordUserID,"+ "phoneNumber,type,resumeIds,resumeNames,resumeTypes,skills"+ )+ search_params = {
"where": where_filters,
"maxSize": 10,
- "select": "id,name,emailAddress,c508Email,cDiscordUsername,cDiscordUserID,phoneNumber,type,resumeIds,resumeNames,resumeTypes,skills,cSkillAttrs",+ "select": f"{base_select},cSkillAttrs",
}
-- response = self.espo_api.request("GET", "Contact", search_params)+ try:+ response = self.espo_api.request("GET", "Contact", search_params)+ except EspoAPIError as exc:+ if "cSkillAttrs" in str(exc):+ logger.warning(+ "cSkillAttrs unavailable in this CRM; retrying search without it: %s",+ exc,+ )+ search_params["select"] = base_select+ response = self.espo_api.request("GET", "Contact", search_params)+ else:+ raise
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/discord_bot/src/five08/discord_bot/cogs/crm.py` around lines 1336 -
1343, The search currently includes "cSkillAttrs" in the select and will raise
if the field/column is missing; wrap the call to self.espo_api.request("GET",
"Contact", search_params) in error handling that detects a 500/field-missing
response and retries the request with a fallback search_params that removes
"cSkillAttrs" from the select string (keep other keys like "where" and "maxSize"
unchanged), then set contacts = response.get("list", []) from the successful
call; ensure this logic lives around where search_params, response and contacts
are defined so the retry path populates contacts the same way.
🧹 Nitpick comments (1)
tests/unit/test_crm.py (1)

229-289: Add one happy-path test for JSON-string cSkillAttrs.

These tests cover dict payloads and malformed payloads well, but not the valid JSON-string path the parser supports. Adding that case would fully cover the new parsing behavior.

✅ Suggested test addition
+ `@pytest.mark.asyncio`+ async def test_search_contacts_parses_skill_attrs_json_string(+ self, crm_cog, mock_interaction, mock_member_role+ ):+ mock_interaction.user.roles = [mock_member_role]+ crm_cog.espo_api.request.return_value = {+ "list": [+ {+ "id": "contact123",+ "name": "John Doe",+ "emailAddress": "john@example.com",+ "type": "Member",+ "cSkillAttrs": '{"python":{"strength":"5"},"sql":{"strength":4}}',+ }+ ]+ }++ await crm_cog.search_members.callback(+ crm_cog, mock_interaction, None, "python,sql"+ )++ embed = mock_interaction.followup.send.call_args.kwargs["embed"]+ field_value = embed.fields[0].value+ assert "🧠 Skills: python (5), sql (4)" in field_value
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@tests/unit/test_crm.py` around lines 229 - 289, Add a new async pytest in
tests/unit/test_crm.py that exercises the JSON-string path for cSkillAttrs:
create a test (e.g., test_search_contacts_parses_json_string_cskillattrs) which
sets mock_interaction.user.roles = [mock_member_role], sets
crm_cog.espo_api.request.return_value to return a contact whose "cSkillAttrs"
value is a JSON string (json.dumps of the same dict used in
test_search_contacts_shows_skill_strengths), call
crm_cog.search_members.callback(crm_cog, mock_interaction, None,
"python,sql,aws"), then assert mock_interaction.followup.send was called and the
embed (from call_args.kwargs["embed"]) contains the expected skills/strengths
text similar to the existing assertions; follow the same structure and naming
used by test_search_contacts_shows_skill_strengths and
test_search_contacts_ignores_broken_skill_attrs so the test integrates
consistently.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Outside diff comments:
In `@apps/discord_bot/src/five08/discord_bot/cogs/crm.py`:
- Around line 1336-1343: The search currently includes "cSkillAttrs" in the
select and will raise if the field/column is missing; wrap the call to
self.espo_api.request("GET", "Contact", search_params) in error handling that
detects a 500/field-missing response and retries the request with a fallback
search_params that removes "cSkillAttrs" from the select string (keep other keys
like "where" and "maxSize" unchanged), then set contacts = response.get("list",
[]) from the successful call; ensure this logic lives around where
search_params, response and contacts are defined so the retry path populates
contacts the same way.
---
Nitpick comments:
In `@tests/unit/test_crm.py`:
- Around line 229-289: Add a new async pytest in tests/unit/test_crm.py that
exercises the JSON-string path for cSkillAttrs: create a test (e.g.,
test_search_contacts_parses_json_string_cskillattrs) which sets
mock_interaction.user.roles = [mock_member_role], sets
crm_cog.espo_api.request.return_value to return a contact whose "cSkillAttrs"
value is a JSON string (json.dumps of the same dict used in
test_search_contacts_shows_skill_strengths), call
crm_cog.search_members.callback(crm_cog, mock_interaction, None,
"python,sql,aws"), then assert mock_interaction.followup.send was called and the
embed (from call_args.kwargs["embed"]) contains the expected skills/strengths
text similar to the existing assertions; follow the same structure and naming
used by test_search_contacts_shows_skill_strengths and
test_search_contacts_ignores_broken_skill_attrs so the test integrates
consistently.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4da8572 and df9243d.

📒 Files selected for processing (2)
  • apps/discord_bot/src/five08/discord_bot/cogs/crm.py
  • tests/unit/test_crm.py

@michaelmwu
michaelmwu merged commit 0545357 into mainFeb 25, 2026
5 checks passed
@michaelmwu
michaelmwu deleted the michaelmwu/search-member-skills branch February 25, 2026 15:55
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@michaelmwu
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
feat: show requested skill strengths in search by michaelmwu · Pull Request #55 · 508-dev/508-workflows · GitHub
Skip to content

feat: show requested skill strengths in search - #55

Merged
michaelmwu merged 1 commit into
mainfrom
michaelmwu/search-member-skills
Feb 25, 2026
Merged

feat: show requested skill strengths in search#55
michaelmwu merged 1 commit into
mainfrom
michaelmwu/search-member-skills

Conversation

@michaelmwu

@michaelmwumichaelmwu commented Feb 25, 2026

Copy link
Copy Markdown
Member

Description

Summary

Enhances the /search-members command to include requested-skill strength annotations (for example python (5)) directly in results when cSkillAttrs is available and valid.

  • Adds robust parsing/normalization for contact cSkillAttrs values with best-effort fallback for malformed data.
  • Extends CRM search select fields to include skills and cSkillAttrs so skill metadata is available in results.
  • Adds safeguards to keep search behavior stable when skill metadata is missing or invalid, and logs parse failures.
  • Adds unit tests covering strength rendering and malformed cSkillAttrs handling, plus select fields assertions.

Related Issue

None.

How Has This Been Tested?

Ran unit tests for tests/unit/test_crm.py and validated formatting/parsing scenarios through assertions.

Summary by CodeRabbit

Release Notes

  • New Features
    • Member search results now display skills paired with proficiency levels (e.g., Python: 5, SQL: 4), enabling more effective identification of team members with specific expertise and capabilities.
    • Strengthened skill data parsing to gracefully handle various formats and edge cases in member profiles, ensuring consistent display without errors.

@coderabbitai

coderabbitaiBot commented Feb 25, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR enhances the CRM Discord bot's search functionality by adding skill strength parsing and display. Three new helper methods parse contact skill attributes from JSON payloads, normalize skill names, and format requested skills with their strength values for display in search results. Unit tests verify correct handling of skill strengths and malformed payloads.

Changes

Cohort / File(s)Summary
CRM Skill Strength Enhancement
apps/discord_bot/src/five08/discord_bot/cogs/crm.py
Added _parse_contact_skill_attrs() to parse and validate skill strength data from contact attributes (JSON/dict/None), _normalize_skill() for standardized skill name formatting, and _format_requested_skills() to append strength values to requested skills. Modified search_members() to request skills and cSkillAttrs fields and display formatted skills in search results. Added json import.
CRM Skill Tests
tests/unit/test_crm.py
Added tests verifying select parameters include both skills and cSkillAttrs, skill strengths display correctly (e.g., python: 5), and malformed skill attribute payloads are gracefully ignored.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A skilled recruit joins our warren so fine,
With strengths five and sixes, in sync they align,
We parse what's requested, we normalize true,
And format the skills in a display anew—
No broken payloads shall daunt our command! 🥕✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly and specifically summarizes the main change: adding skill strength display to search results.
Docstring Coverage✅ PassedDocstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch michaelmwu/search-member-skills

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/discord_bot/src/five08/discord_bot/cogs/crm.py (1)

1336-1343: ⚠️ Potential issue | 🟠 Major

Add error handling for optional cSkillAttrs field in search.

The /search-members command currently fails if cSkillAttrs is unavailable in the workspace. EspoCRM's REST API will return a 500 error if a field in select exists in metadata but the corresponding database column is missing or misconfigured. Since cSkillAttrs is a custom field that may not exist in all workspaces, implement a fallback retry without it:

Suggested fix
- search_params = {+ base_select = (+ "id,name,emailAddress,c508Email,cDiscordUsername,cDiscordUserID,"+ "phoneNumber,type,resumeIds,resumeNames,resumeTypes,skills"+ )+ search_params = {
"where": where_filters,
"maxSize": 10,
- "select": "id,name,emailAddress,c508Email,cDiscordUsername,cDiscordUserID,phoneNumber,type,resumeIds,resumeNames,resumeTypes,skills,cSkillAttrs",+ "select": f"{base_select},cSkillAttrs",
}
-- response = self.espo_api.request("GET", "Contact", search_params)+ try:+ response = self.espo_api.request("GET", "Contact", search_params)+ except EspoAPIError as exc:+ if "cSkillAttrs" in str(exc):+ logger.warning(+ "cSkillAttrs unavailable in this CRM; retrying search without it: %s",+ exc,+ )+ search_params["select"] = base_select+ response = self.espo_api.request("GET", "Contact", search_params)+ else:+ raise
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/discord_bot/src/five08/discord_bot/cogs/crm.py` around lines 1336 -
1343, The search currently includes "cSkillAttrs" in the select and will raise
if the field/column is missing; wrap the call to self.espo_api.request("GET",
"Contact", search_params) in error handling that detects a 500/field-missing
response and retries the request with a fallback search_params that removes
"cSkillAttrs" from the select string (keep other keys like "where" and "maxSize"
unchanged), then set contacts = response.get("list", []) from the successful
call; ensure this logic lives around where search_params, response and contacts
are defined so the retry path populates contacts the same way.
🧹 Nitpick comments (1)
tests/unit/test_crm.py (1)

229-289: Add one happy-path test for JSON-string cSkillAttrs.

These tests cover dict payloads and malformed payloads well, but not the valid JSON-string path the parser supports. Adding that case would fully cover the new parsing behavior.

✅ Suggested test addition
+ `@pytest.mark.asyncio`+ async def test_search_contacts_parses_skill_attrs_json_string(+ self, crm_cog, mock_interaction, mock_member_role+ ):+ mock_interaction.user.roles = [mock_member_role]+ crm_cog.espo_api.request.return_value = {+ "list": [+ {+ "id": "contact123",+ "name": "John Doe",+ "emailAddress": "john@example.com",+ "type": "Member",+ "cSkillAttrs": '{"python":{"strength":"5"},"sql":{"strength":4}}',+ }+ ]+ }++ await crm_cog.search_members.callback(+ crm_cog, mock_interaction, None, "python,sql"+ )++ embed = mock_interaction.followup.send.call_args.kwargs["embed"]+ field_value = embed.fields[0].value+ assert "🧠 Skills: python (5), sql (4)" in field_value
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@tests/unit/test_crm.py` around lines 229 - 289, Add a new async pytest in
tests/unit/test_crm.py that exercises the JSON-string path for cSkillAttrs:
create a test (e.g., test_search_contacts_parses_json_string_cskillattrs) which
sets mock_interaction.user.roles = [mock_member_role], sets
crm_cog.espo_api.request.return_value to return a contact whose "cSkillAttrs"
value is a JSON string (json.dumps of the same dict used in
test_search_contacts_shows_skill_strengths), call
crm_cog.search_members.callback(crm_cog, mock_interaction, None,
"python,sql,aws"), then assert mock_interaction.followup.send was called and the
embed (from call_args.kwargs["embed"]) contains the expected skills/strengths
text similar to the existing assertions; follow the same structure and naming
used by test_search_contacts_shows_skill_strengths and
test_search_contacts_ignores_broken_skill_attrs so the test integrates
consistently.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Outside diff comments:
In `@apps/discord_bot/src/five08/discord_bot/cogs/crm.py`:
- Around line 1336-1343: The search currently includes "cSkillAttrs" in the
select and will raise if the field/column is missing; wrap the call to
self.espo_api.request("GET", "Contact", search_params) in error handling that
detects a 500/field-missing response and retries the request with a fallback
search_params that removes "cSkillAttrs" from the select string (keep other keys
like "where" and "maxSize" unchanged), then set contacts = response.get("list",
[]) from the successful call; ensure this logic lives around where
search_params, response and contacts are defined so the retry path populates
contacts the same way.
---
Nitpick comments:
In `@tests/unit/test_crm.py`:
- Around line 229-289: Add a new async pytest in tests/unit/test_crm.py that
exercises the JSON-string path for cSkillAttrs: create a test (e.g.,
test_search_contacts_parses_json_string_cskillattrs) which sets
mock_interaction.user.roles = [mock_member_role], sets
crm_cog.espo_api.request.return_value to return a contact whose "cSkillAttrs"
value is a JSON string (json.dumps of the same dict used in
test_search_contacts_shows_skill_strengths), call
crm_cog.search_members.callback(crm_cog, mock_interaction, None,
"python,sql,aws"), then assert mock_interaction.followup.send was called and the
embed (from call_args.kwargs["embed"]) contains the expected skills/strengths
text similar to the existing assertions; follow the same structure and naming
used by test_search_contacts_shows_skill_strengths and
test_search_contacts_ignores_broken_skill_attrs so the test integrates
consistently.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4da8572 and df9243d.

📒 Files selected for processing (2)
  • apps/discord_bot/src/five08/discord_bot/cogs/crm.py
  • tests/unit/test_crm.py

@michaelmwu
michaelmwu merged commit 0545357 into mainFeb 25, 2026
5 checks passed
@michaelmwu
michaelmwu deleted the michaelmwu/search-member-skills branch February 25, 2026 15:55
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@michaelmwu
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' feat: show requested skill strengths in search by michaelmwu · Pull Request #55 · 508-dev/508-workflows · GitHub
Skip to content

feat: show requested skill strengths in search - #55

Merged
michaelmwu merged 1 commit into
mainfrom
michaelmwu/search-member-skills
Feb 25, 2026
Merged

feat: show requested skill strengths in search#55
michaelmwu merged 1 commit into
mainfrom
michaelmwu/search-member-skills

Conversation

@michaelmwu

@michaelmwumichaelmwu commented Feb 25, 2026

Copy link
Copy Markdown
Member

Description

Summary

Enhances the /search-members command to include requested-skill strength annotations (for example python (5)) directly in results when cSkillAttrs is available and valid.

  • Adds robust parsing/normalization for contact cSkillAttrs values with best-effort fallback for malformed data.
  • Extends CRM search select fields to include skills and cSkillAttrs so skill metadata is available in results.
  • Adds safeguards to keep search behavior stable when skill metadata is missing or invalid, and logs parse failures.
  • Adds unit tests covering strength rendering and malformed cSkillAttrs handling, plus select fields assertions.

Related Issue

None.

How Has This Been Tested?

Ran unit tests for tests/unit/test_crm.py and validated formatting/parsing scenarios through assertions.

Summary by CodeRabbit

Release Notes

  • New Features
    • Member search results now display skills paired with proficiency levels (e.g., Python: 5, SQL: 4), enabling more effective identification of team members with specific expertise and capabilities.
    • Strengthened skill data parsing to gracefully handle various formats and edge cases in member profiles, ensuring consistent display without errors.

@coderabbitai

coderabbitaiBot commented Feb 25, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR enhances the CRM Discord bot's search functionality by adding skill strength parsing and display. Three new helper methods parse contact skill attributes from JSON payloads, normalize skill names, and format requested skills with their strength values for display in search results. Unit tests verify correct handling of skill strengths and malformed payloads.

Changes

Cohort / File(s)Summary
CRM Skill Strength Enhancement
apps/discord_bot/src/five08/discord_bot/cogs/crm.py
Added _parse_contact_skill_attrs() to parse and validate skill strength data from contact attributes (JSON/dict/None), _normalize_skill() for standardized skill name formatting, and _format_requested_skills() to append strength values to requested skills. Modified search_members() to request skills and cSkillAttrs fields and display formatted skills in search results. Added json import.
CRM Skill Tests
tests/unit/test_crm.py
Added tests verifying select parameters include both skills and cSkillAttrs, skill strengths display correctly (e.g., python: 5), and malformed skill attribute payloads are gracefully ignored.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A skilled recruit joins our warren so fine,
With strengths five and sixes, in sync they align,
We parse what's requested, we normalize true,
And format the skills in a display anew—
No broken payloads shall daunt our command! 🥕✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly and specifically summarizes the main change: adding skill strength display to search results.
Docstring Coverage✅ PassedDocstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch michaelmwu/search-member-skills

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/discord_bot/src/five08/discord_bot/cogs/crm.py (1)

1336-1343: ⚠️ Potential issue | 🟠 Major

Add error handling for optional cSkillAttrs field in search.

The /search-members command currently fails if cSkillAttrs is unavailable in the workspace. EspoCRM's REST API will return a 500 error if a field in select exists in metadata but the corresponding database column is missing or misconfigured. Since cSkillAttrs is a custom field that may not exist in all workspaces, implement a fallback retry without it:

Suggested fix
- search_params = {+ base_select = (+ "id,name,emailAddress,c508Email,cDiscordUsername,cDiscordUserID,"+ "phoneNumber,type,resumeIds,resumeNames,resumeTypes,skills"+ )+ search_params = {
"where": where_filters,
"maxSize": 10,
- "select": "id,name,emailAddress,c508Email,cDiscordUsername,cDiscordUserID,phoneNumber,type,resumeIds,resumeNames,resumeTypes,skills,cSkillAttrs",+ "select": f"{base_select},cSkillAttrs",
}
-- response = self.espo_api.request("GET", "Contact", search_params)+ try:+ response = self.espo_api.request("GET", "Contact", search_params)+ except EspoAPIError as exc:+ if "cSkillAttrs" in str(exc):+ logger.warning(+ "cSkillAttrs unavailable in this CRM; retrying search without it: %s",+ exc,+ )+ search_params["select"] = base_select+ response = self.espo_api.request("GET", "Contact", search_params)+ else:+ raise
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/discord_bot/src/five08/discord_bot/cogs/crm.py` around lines 1336 -
1343, The search currently includes "cSkillAttrs" in the select and will raise
if the field/column is missing; wrap the call to self.espo_api.request("GET",
"Contact", search_params) in error handling that detects a 500/field-missing
response and retries the request with a fallback search_params that removes
"cSkillAttrs" from the select string (keep other keys like "where" and "maxSize"
unchanged), then set contacts = response.get("list", []) from the successful
call; ensure this logic lives around where search_params, response and contacts
are defined so the retry path populates contacts the same way.
🧹 Nitpick comments (1)
tests/unit/test_crm.py (1)

229-289: Add one happy-path test for JSON-string cSkillAttrs.

These tests cover dict payloads and malformed payloads well, but not the valid JSON-string path the parser supports. Adding that case would fully cover the new parsing behavior.

✅ Suggested test addition
+ `@pytest.mark.asyncio`+ async def test_search_contacts_parses_skill_attrs_json_string(+ self, crm_cog, mock_interaction, mock_member_role+ ):+ mock_interaction.user.roles = [mock_member_role]+ crm_cog.espo_api.request.return_value = {+ "list": [+ {+ "id": "contact123",+ "name": "John Doe",+ "emailAddress": "john@example.com",+ "type": "Member",+ "cSkillAttrs": '{"python":{"strength":"5"},"sql":{"strength":4}}',+ }+ ]+ }++ await crm_cog.search_members.callback(+ crm_cog, mock_interaction, None, "python,sql"+ )++ embed = mock_interaction.followup.send.call_args.kwargs["embed"]+ field_value = embed.fields[0].value+ assert "🧠 Skills: python (5), sql (4)" in field_value
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@tests/unit/test_crm.py` around lines 229 - 289, Add a new async pytest in
tests/unit/test_crm.py that exercises the JSON-string path for cSkillAttrs:
create a test (e.g., test_search_contacts_parses_json_string_cskillattrs) which
sets mock_interaction.user.roles = [mock_member_role], sets
crm_cog.espo_api.request.return_value to return a contact whose "cSkillAttrs"
value is a JSON string (json.dumps of the same dict used in
test_search_contacts_shows_skill_strengths), call
crm_cog.search_members.callback(crm_cog, mock_interaction, None,
"python,sql,aws"), then assert mock_interaction.followup.send was called and the
embed (from call_args.kwargs["embed"]) contains the expected skills/strengths
text similar to the existing assertions; follow the same structure and naming
used by test_search_contacts_shows_skill_strengths and
test_search_contacts_ignores_broken_skill_attrs so the test integrates
consistently.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Outside diff comments:
In `@apps/discord_bot/src/five08/discord_bot/cogs/crm.py`:
- Around line 1336-1343: The search currently includes "cSkillAttrs" in the
select and will raise if the field/column is missing; wrap the call to
self.espo_api.request("GET", "Contact", search_params) in error handling that
detects a 500/field-missing response and retries the request with a fallback
search_params that removes "cSkillAttrs" from the select string (keep other keys
like "where" and "maxSize" unchanged), then set contacts = response.get("list",
[]) from the successful call; ensure this logic lives around where
search_params, response and contacts are defined so the retry path populates
contacts the same way.
---
Nitpick comments:
In `@tests/unit/test_crm.py`:
- Around line 229-289: Add a new async pytest in tests/unit/test_crm.py that
exercises the JSON-string path for cSkillAttrs: create a test (e.g.,
test_search_contacts_parses_json_string_cskillattrs) which sets
mock_interaction.user.roles = [mock_member_role], sets
crm_cog.espo_api.request.return_value to return a contact whose "cSkillAttrs"
value is a JSON string (json.dumps of the same dict used in
test_search_contacts_shows_skill_strengths), call
crm_cog.search_members.callback(crm_cog, mock_interaction, None,
"python,sql,aws"), then assert mock_interaction.followup.send was called and the
embed (from call_args.kwargs["embed"]) contains the expected skills/strengths
text similar to the existing assertions; follow the same structure and naming
used by test_search_contacts_shows_skill_strengths and
test_search_contacts_ignores_broken_skill_attrs so the test integrates
consistently.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4da8572 and df9243d.

📒 Files selected for processing (2)
  • apps/discord_bot/src/five08/discord_bot/cogs/crm.py
  • tests/unit/test_crm.py

@michaelmwu
michaelmwu merged commit 0545357 into mainFeb 25, 2026
5 checks passed
@michaelmwu
michaelmwu deleted the michaelmwu/search-member-skills branch February 25, 2026 15:55
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@michaelmwu
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' feat: show requested skill strengths in search by michaelmwu · Pull Request #55 · 508-dev/508-workflows · GitHub
Skip to content

feat: show requested skill strengths in search - #55

Merged
michaelmwu merged 1 commit into
mainfrom
michaelmwu/search-member-skills
Feb 25, 2026
Merged

feat: show requested skill strengths in search#55
michaelmwu merged 1 commit into
mainfrom
michaelmwu/search-member-skills

Conversation

@michaelmwu

@michaelmwumichaelmwu commented Feb 25, 2026

Copy link
Copy Markdown
Member

Description

Summary

Enhances the /search-members command to include requested-skill strength annotations (for example python (5)) directly in results when cSkillAttrs is available and valid.

  • Adds robust parsing/normalization for contact cSkillAttrs values with best-effort fallback for malformed data.
  • Extends CRM search select fields to include skills and cSkillAttrs so skill metadata is available in results.
  • Adds safeguards to keep search behavior stable when skill metadata is missing or invalid, and logs parse failures.
  • Adds unit tests covering strength rendering and malformed cSkillAttrs handling, plus select fields assertions.

Related Issue

None.

How Has This Been Tested?

Ran unit tests for tests/unit/test_crm.py and validated formatting/parsing scenarios through assertions.

Summary by CodeRabbit

Release Notes

  • New Features
    • Member search results now display skills paired with proficiency levels (e.g., Python: 5, SQL: 4), enabling more effective identification of team members with specific expertise and capabilities.
    • Strengthened skill data parsing to gracefully handle various formats and edge cases in member profiles, ensuring consistent display without errors.

@coderabbitai

coderabbitaiBot commented Feb 25, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR enhances the CRM Discord bot's search functionality by adding skill strength parsing and display. Three new helper methods parse contact skill attributes from JSON payloads, normalize skill names, and format requested skills with their strength values for display in search results. Unit tests verify correct handling of skill strengths and malformed payloads.

Changes

Cohort / File(s)Summary
CRM Skill Strength Enhancement
apps/discord_bot/src/five08/discord_bot/cogs/crm.py
Added _parse_contact_skill_attrs() to parse and validate skill strength data from contact attributes (JSON/dict/None), _normalize_skill() for standardized skill name formatting, and _format_requested_skills() to append strength values to requested skills. Modified search_members() to request skills and cSkillAttrs fields and display formatted skills in search results. Added json import.
CRM Skill Tests
tests/unit/test_crm.py
Added tests verifying select parameters include both skills and cSkillAttrs, skill strengths display correctly (e.g., python: 5), and malformed skill attribute payloads are gracefully ignored.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A skilled recruit joins our warren so fine,
With strengths five and sixes, in sync they align,
We parse what's requested, we normalize true,
And format the skills in a display anew—
No broken payloads shall daunt our command! 🥕✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly and specifically summarizes the main change: adding skill strength display to search results.
Docstring Coverage✅ PassedDocstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch michaelmwu/search-member-skills

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/discord_bot/src/five08/discord_bot/cogs/crm.py (1)

1336-1343: ⚠️ Potential issue | 🟠 Major

Add error handling for optional cSkillAttrs field in search.

The /search-members command currently fails if cSkillAttrs is unavailable in the workspace. EspoCRM's REST API will return a 500 error if a field in select exists in metadata but the corresponding database column is missing or misconfigured. Since cSkillAttrs is a custom field that may not exist in all workspaces, implement a fallback retry without it:

Suggested fix
- search_params = {+ base_select = (+ "id,name,emailAddress,c508Email,cDiscordUsername,cDiscordUserID,"+ "phoneNumber,type,resumeIds,resumeNames,resumeTypes,skills"+ )+ search_params = {
"where": where_filters,
"maxSize": 10,
- "select": "id,name,emailAddress,c508Email,cDiscordUsername,cDiscordUserID,phoneNumber,type,resumeIds,resumeNames,resumeTypes,skills,cSkillAttrs",+ "select": f"{base_select},cSkillAttrs",
}
-- response = self.espo_api.request("GET", "Contact", search_params)+ try:+ response = self.espo_api.request("GET", "Contact", search_params)+ except EspoAPIError as exc:+ if "cSkillAttrs" in str(exc):+ logger.warning(+ "cSkillAttrs unavailable in this CRM; retrying search without it: %s",+ exc,+ )+ search_params["select"] = base_select+ response = self.espo_api.request("GET", "Contact", search_params)+ else:+ raise
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/discord_bot/src/five08/discord_bot/cogs/crm.py` around lines 1336 -
1343, The search currently includes "cSkillAttrs" in the select and will raise
if the field/column is missing; wrap the call to self.espo_api.request("GET",
"Contact", search_params) in error handling that detects a 500/field-missing
response and retries the request with a fallback search_params that removes
"cSkillAttrs" from the select string (keep other keys like "where" and "maxSize"
unchanged), then set contacts = response.get("list", []) from the successful
call; ensure this logic lives around where search_params, response and contacts
are defined so the retry path populates contacts the same way.
🧹 Nitpick comments (1)
tests/unit/test_crm.py (1)

229-289: Add one happy-path test for JSON-string cSkillAttrs.

These tests cover dict payloads and malformed payloads well, but not the valid JSON-string path the parser supports. Adding that case would fully cover the new parsing behavior.

✅ Suggested test addition
+ `@pytest.mark.asyncio`+ async def test_search_contacts_parses_skill_attrs_json_string(+ self, crm_cog, mock_interaction, mock_member_role+ ):+ mock_interaction.user.roles = [mock_member_role]+ crm_cog.espo_api.request.return_value = {+ "list": [+ {+ "id": "contact123",+ "name": "John Doe",+ "emailAddress": "john@example.com",+ "type": "Member",+ "cSkillAttrs": '{"python":{"strength":"5"},"sql":{"strength":4}}',+ }+ ]+ }++ await crm_cog.search_members.callback(+ crm_cog, mock_interaction, None, "python,sql"+ )++ embed = mock_interaction.followup.send.call_args.kwargs["embed"]+ field_value = embed.fields[0].value+ assert "🧠 Skills: python (5), sql (4)" in field_value
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@tests/unit/test_crm.py` around lines 229 - 289, Add a new async pytest in
tests/unit/test_crm.py that exercises the JSON-string path for cSkillAttrs:
create a test (e.g., test_search_contacts_parses_json_string_cskillattrs) which
sets mock_interaction.user.roles = [mock_member_role], sets
crm_cog.espo_api.request.return_value to return a contact whose "cSkillAttrs"
value is a JSON string (json.dumps of the same dict used in
test_search_contacts_shows_skill_strengths), call
crm_cog.search_members.callback(crm_cog, mock_interaction, None,
"python,sql,aws"), then assert mock_interaction.followup.send was called and the
embed (from call_args.kwargs["embed"]) contains the expected skills/strengths
text similar to the existing assertions; follow the same structure and naming
used by test_search_contacts_shows_skill_strengths and
test_search_contacts_ignores_broken_skill_attrs so the test integrates
consistently.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Outside diff comments:
In `@apps/discord_bot/src/five08/discord_bot/cogs/crm.py`:
- Around line 1336-1343: The search currently includes "cSkillAttrs" in the
select and will raise if the field/column is missing; wrap the call to
self.espo_api.request("GET", "Contact", search_params) in error handling that
detects a 500/field-missing response and retries the request with a fallback
search_params that removes "cSkillAttrs" from the select string (keep other keys
like "where" and "maxSize" unchanged), then set contacts = response.get("list",
[]) from the successful call; ensure this logic lives around where
search_params, response and contacts are defined so the retry path populates
contacts the same way.
---
Nitpick comments:
In `@tests/unit/test_crm.py`:
- Around line 229-289: Add a new async pytest in tests/unit/test_crm.py that
exercises the JSON-string path for cSkillAttrs: create a test (e.g.,
test_search_contacts_parses_json_string_cskillattrs) which sets
mock_interaction.user.roles = [mock_member_role], sets
crm_cog.espo_api.request.return_value to return a contact whose "cSkillAttrs"
value is a JSON string (json.dumps of the same dict used in
test_search_contacts_shows_skill_strengths), call
crm_cog.search_members.callback(crm_cog, mock_interaction, None,
"python,sql,aws"), then assert mock_interaction.followup.send was called and the
embed (from call_args.kwargs["embed"]) contains the expected skills/strengths
text similar to the existing assertions; follow the same structure and naming
used by test_search_contacts_shows_skill_strengths and
test_search_contacts_ignores_broken_skill_attrs so the test integrates
consistently.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4da8572 and df9243d.

📒 Files selected for processing (2)
  • apps/discord_bot/src/five08/discord_bot/cogs/crm.py
  • tests/unit/test_crm.py

@michaelmwu
michaelmwu merged commit 0545357 into mainFeb 25, 2026
5 checks passed
@michaelmwu
michaelmwu deleted the michaelmwu/search-member-skills branch February 25, 2026 15:55
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@michaelmwu
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' feat: show requested skill strengths in search by michaelmwu · Pull Request #55 · 508-dev/508-workflows · GitHub
Skip to content

feat: show requested skill strengths in search - #55

Merged
michaelmwu merged 1 commit into
mainfrom
michaelmwu/search-member-skills
Feb 25, 2026
Merged

feat: show requested skill strengths in search#55
michaelmwu merged 1 commit into
mainfrom
michaelmwu/search-member-skills

Conversation

@michaelmwu

@michaelmwumichaelmwu commented Feb 25, 2026

Copy link
Copy Markdown
Member

Description

Summary

Enhances the /search-members command to include requested-skill strength annotations (for example python (5)) directly in results when cSkillAttrs is available and valid.

  • Adds robust parsing/normalization for contact cSkillAttrs values with best-effort fallback for malformed data.
  • Extends CRM search select fields to include skills and cSkillAttrs so skill metadata is available in results.
  • Adds safeguards to keep search behavior stable when skill metadata is missing or invalid, and logs parse failures.
  • Adds unit tests covering strength rendering and malformed cSkillAttrs handling, plus select fields assertions.

Related Issue

None.

How Has This Been Tested?

Ran unit tests for tests/unit/test_crm.py and validated formatting/parsing scenarios through assertions.

Summary by CodeRabbit

Release Notes

  • New Features
    • Member search results now display skills paired with proficiency levels (e.g., Python: 5, SQL: 4), enabling more effective identification of team members with specific expertise and capabilities.
    • Strengthened skill data parsing to gracefully handle various formats and edge cases in member profiles, ensuring consistent display without errors.

@coderabbitai

coderabbitaiBot commented Feb 25, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR enhances the CRM Discord bot's search functionality by adding skill strength parsing and display. Three new helper methods parse contact skill attributes from JSON payloads, normalize skill names, and format requested skills with their strength values for display in search results. Unit tests verify correct handling of skill strengths and malformed payloads.

Changes

Cohort / File(s)Summary
CRM Skill Strength Enhancement
apps/discord_bot/src/five08/discord_bot/cogs/crm.py
Added _parse_contact_skill_attrs() to parse and validate skill strength data from contact attributes (JSON/dict/None), _normalize_skill() for standardized skill name formatting, and _format_requested_skills() to append strength values to requested skills. Modified search_members() to request skills and cSkillAttrs fields and display formatted skills in search results. Added json import.
CRM Skill Tests
tests/unit/test_crm.py
Added tests verifying select parameters include both skills and cSkillAttrs, skill strengths display correctly (e.g., python: 5), and malformed skill attribute payloads are gracefully ignored.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A skilled recruit joins our warren so fine,
With strengths five and sixes, in sync they align,
We parse what's requested, we normalize true,
And format the skills in a display anew—
No broken payloads shall daunt our command! 🥕✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly and specifically summarizes the main change: adding skill strength display to search results.
Docstring Coverage✅ PassedDocstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch michaelmwu/search-member-skills

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/discord_bot/src/five08/discord_bot/cogs/crm.py (1)

1336-1343: ⚠️ Potential issue | 🟠 Major

Add error handling for optional cSkillAttrs field in search.

The /search-members command currently fails if cSkillAttrs is unavailable in the workspace. EspoCRM's REST API will return a 500 error if a field in select exists in metadata but the corresponding database column is missing or misconfigured. Since cSkillAttrs is a custom field that may not exist in all workspaces, implement a fallback retry without it:

Suggested fix
- search_params = {+ base_select = (+ "id,name,emailAddress,c508Email,cDiscordUsername,cDiscordUserID,"+ "phoneNumber,type,resumeIds,resumeNames,resumeTypes,skills"+ )+ search_params = {
"where": where_filters,
"maxSize": 10,
- "select": "id,name,emailAddress,c508Email,cDiscordUsername,cDiscordUserID,phoneNumber,type,resumeIds,resumeNames,resumeTypes,skills,cSkillAttrs",+ "select": f"{base_select},cSkillAttrs",
}
-- response = self.espo_api.request("GET", "Contact", search_params)+ try:+ response = self.espo_api.request("GET", "Contact", search_params)+ except EspoAPIError as exc:+ if "cSkillAttrs" in str(exc):+ logger.warning(+ "cSkillAttrs unavailable in this CRM; retrying search without it: %s",+ exc,+ )+ search_params["select"] = base_select+ response = self.espo_api.request("GET", "Contact", search_params)+ else:+ raise
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/discord_bot/src/five08/discord_bot/cogs/crm.py` around lines 1336 -
1343, The search currently includes "cSkillAttrs" in the select and will raise
if the field/column is missing; wrap the call to self.espo_api.request("GET",
"Contact", search_params) in error handling that detects a 500/field-missing
response and retries the request with a fallback search_params that removes
"cSkillAttrs" from the select string (keep other keys like "where" and "maxSize"
unchanged), then set contacts = response.get("list", []) from the successful
call; ensure this logic lives around where search_params, response and contacts
are defined so the retry path populates contacts the same way.
🧹 Nitpick comments (1)
tests/unit/test_crm.py (1)

229-289: Add one happy-path test for JSON-string cSkillAttrs.

These tests cover dict payloads and malformed payloads well, but not the valid JSON-string path the parser supports. Adding that case would fully cover the new parsing behavior.

✅ Suggested test addition
+ `@pytest.mark.asyncio`+ async def test_search_contacts_parses_skill_attrs_json_string(+ self, crm_cog, mock_interaction, mock_member_role+ ):+ mock_interaction.user.roles = [mock_member_role]+ crm_cog.espo_api.request.return_value = {+ "list": [+ {+ "id": "contact123",+ "name": "John Doe",+ "emailAddress": "john@example.com",+ "type": "Member",+ "cSkillAttrs": '{"python":{"strength":"5"},"sql":{"strength":4}}',+ }+ ]+ }++ await crm_cog.search_members.callback(+ crm_cog, mock_interaction, None, "python,sql"+ )++ embed = mock_interaction.followup.send.call_args.kwargs["embed"]+ field_value = embed.fields[0].value+ assert "🧠 Skills: python (5), sql (4)" in field_value
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@tests/unit/test_crm.py` around lines 229 - 289, Add a new async pytest in
tests/unit/test_crm.py that exercises the JSON-string path for cSkillAttrs:
create a test (e.g., test_search_contacts_parses_json_string_cskillattrs) which
sets mock_interaction.user.roles = [mock_member_role], sets
crm_cog.espo_api.request.return_value to return a contact whose "cSkillAttrs"
value is a JSON string (json.dumps of the same dict used in
test_search_contacts_shows_skill_strengths), call
crm_cog.search_members.callback(crm_cog, mock_interaction, None,
"python,sql,aws"), then assert mock_interaction.followup.send was called and the
embed (from call_args.kwargs["embed"]) contains the expected skills/strengths
text similar to the existing assertions; follow the same structure and naming
used by test_search_contacts_shows_skill_strengths and
test_search_contacts_ignores_broken_skill_attrs so the test integrates
consistently.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Outside diff comments:
In `@apps/discord_bot/src/five08/discord_bot/cogs/crm.py`:
- Around line 1336-1343: The search currently includes "cSkillAttrs" in the
select and will raise if the field/column is missing; wrap the call to
self.espo_api.request("GET", "Contact", search_params) in error handling that
detects a 500/field-missing response and retries the request with a fallback
search_params that removes "cSkillAttrs" from the select string (keep other keys
like "where" and "maxSize" unchanged), then set contacts = response.get("list",
[]) from the successful call; ensure this logic lives around where
search_params, response and contacts are defined so the retry path populates
contacts the same way.
---
Nitpick comments:
In `@tests/unit/test_crm.py`:
- Around line 229-289: Add a new async pytest in tests/unit/test_crm.py that
exercises the JSON-string path for cSkillAttrs: create a test (e.g.,
test_search_contacts_parses_json_string_cskillattrs) which sets
mock_interaction.user.roles = [mock_member_role], sets
crm_cog.espo_api.request.return_value to return a contact whose "cSkillAttrs"
value is a JSON string (json.dumps of the same dict used in
test_search_contacts_shows_skill_strengths), call
crm_cog.search_members.callback(crm_cog, mock_interaction, None,
"python,sql,aws"), then assert mock_interaction.followup.send was called and the
embed (from call_args.kwargs["embed"]) contains the expected skills/strengths
text similar to the existing assertions; follow the same structure and naming
used by test_search_contacts_shows_skill_strengths and
test_search_contacts_ignores_broken_skill_attrs so the test integrates
consistently.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4da8572 and df9243d.

📒 Files selected for processing (2)
  • apps/discord_bot/src/five08/discord_bot/cogs/crm.py
  • tests/unit/test_crm.py

@michaelmwu
michaelmwu merged commit 0545357 into mainFeb 25, 2026
5 checks passed
@michaelmwu
michaelmwu deleted the michaelmwu/search-member-skills branch February 25, 2026 15:55
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@michaelmwu
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' feat: show requested skill strengths in search by michaelmwu · Pull Request #55 · 508-dev/508-workflows · GitHub
Skip to content

feat: show requested skill strengths in search - #55

Merged
michaelmwu merged 1 commit into
mainfrom
michaelmwu/search-member-skills
Feb 25, 2026
Merged

feat: show requested skill strengths in search#55
michaelmwu merged 1 commit into
mainfrom
michaelmwu/search-member-skills

Conversation

@michaelmwu

@michaelmwumichaelmwu commented Feb 25, 2026

Copy link
Copy Markdown
Member

Description

Summary

Enhances the /search-members command to include requested-skill strength annotations (for example python (5)) directly in results when cSkillAttrs is available and valid.

  • Adds robust parsing/normalization for contact cSkillAttrs values with best-effort fallback for malformed data.
  • Extends CRM search select fields to include skills and cSkillAttrs so skill metadata is available in results.
  • Adds safeguards to keep search behavior stable when skill metadata is missing or invalid, and logs parse failures.
  • Adds unit tests covering strength rendering and malformed cSkillAttrs handling, plus select fields assertions.

Related Issue

None.

How Has This Been Tested?

Ran unit tests for tests/unit/test_crm.py and validated formatting/parsing scenarios through assertions.

Summary by CodeRabbit

Release Notes

  • New Features
    • Member search results now display skills paired with proficiency levels (e.g., Python: 5, SQL: 4), enabling more effective identification of team members with specific expertise and capabilities.
    • Strengthened skill data parsing to gracefully handle various formats and edge cases in member profiles, ensuring consistent display without errors.

@coderabbitai

coderabbitaiBot commented Feb 25, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR enhances the CRM Discord bot's search functionality by adding skill strength parsing and display. Three new helper methods parse contact skill attributes from JSON payloads, normalize skill names, and format requested skills with their strength values for display in search results. Unit tests verify correct handling of skill strengths and malformed payloads.

Changes

Cohort / File(s)Summary
CRM Skill Strength Enhancement
apps/discord_bot/src/five08/discord_bot/cogs/crm.py
Added _parse_contact_skill_attrs() to parse and validate skill strength data from contact attributes (JSON/dict/None), _normalize_skill() for standardized skill name formatting, and _format_requested_skills() to append strength values to requested skills. Modified search_members() to request skills and cSkillAttrs fields and display formatted skills in search results. Added json import.
CRM Skill Tests
tests/unit/test_crm.py
Added tests verifying select parameters include both skills and cSkillAttrs, skill strengths display correctly (e.g., python: 5), and malformed skill attribute payloads are gracefully ignored.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A skilled recruit joins our warren so fine,
With strengths five and sixes, in sync they align,
We parse what's requested, we normalize true,
And format the skills in a display anew—
No broken payloads shall daunt our command! 🥕✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly and specifically summarizes the main change: adding skill strength display to search results.
Docstring Coverage✅ PassedDocstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch michaelmwu/search-member-skills

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/discord_bot/src/five08/discord_bot/cogs/crm.py (1)

1336-1343: ⚠️ Potential issue | 🟠 Major

Add error handling for optional cSkillAttrs field in search.

The /search-members command currently fails if cSkillAttrs is unavailable in the workspace. EspoCRM's REST API will return a 500 error if a field in select exists in metadata but the corresponding database column is missing or misconfigured. Since cSkillAttrs is a custom field that may not exist in all workspaces, implement a fallback retry without it:

Suggested fix
- search_params = {+ base_select = (+ "id,name,emailAddress,c508Email,cDiscordUsername,cDiscordUserID,"+ "phoneNumber,type,resumeIds,resumeNames,resumeTypes,skills"+ )+ search_params = {
"where": where_filters,
"maxSize": 10,
- "select": "id,name,emailAddress,c508Email,cDiscordUsername,cDiscordUserID,phoneNumber,type,resumeIds,resumeNames,resumeTypes,skills,cSkillAttrs",+ "select": f"{base_select},cSkillAttrs",
}
-- response = self.espo_api.request("GET", "Contact", search_params)+ try:+ response = self.espo_api.request("GET", "Contact", search_params)+ except EspoAPIError as exc:+ if "cSkillAttrs" in str(exc):+ logger.warning(+ "cSkillAttrs unavailable in this CRM; retrying search without it: %s",+ exc,+ )+ search_params["select"] = base_select+ response = self.espo_api.request("GET", "Contact", search_params)+ else:+ raise
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/discord_bot/src/five08/discord_bot/cogs/crm.py` around lines 1336 -
1343, The search currently includes "cSkillAttrs" in the select and will raise
if the field/column is missing; wrap the call to self.espo_api.request("GET",
"Contact", search_params) in error handling that detects a 500/field-missing
response and retries the request with a fallback search_params that removes
"cSkillAttrs" from the select string (keep other keys like "where" and "maxSize"
unchanged), then set contacts = response.get("list", []) from the successful
call; ensure this logic lives around where search_params, response and contacts
are defined so the retry path populates contacts the same way.
🧹 Nitpick comments (1)
tests/unit/test_crm.py (1)

229-289: Add one happy-path test for JSON-string cSkillAttrs.

These tests cover dict payloads and malformed payloads well, but not the valid JSON-string path the parser supports. Adding that case would fully cover the new parsing behavior.

✅ Suggested test addition
+ `@pytest.mark.asyncio`+ async def test_search_contacts_parses_skill_attrs_json_string(+ self, crm_cog, mock_interaction, mock_member_role+ ):+ mock_interaction.user.roles = [mock_member_role]+ crm_cog.espo_api.request.return_value = {+ "list": [+ {+ "id": "contact123",+ "name": "John Doe",+ "emailAddress": "john@example.com",+ "type": "Member",+ "cSkillAttrs": '{"python":{"strength":"5"},"sql":{"strength":4}}',+ }+ ]+ }++ await crm_cog.search_members.callback(+ crm_cog, mock_interaction, None, "python,sql"+ )++ embed = mock_interaction.followup.send.call_args.kwargs["embed"]+ field_value = embed.fields[0].value+ assert "🧠 Skills: python (5), sql (4)" in field_value
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@tests/unit/test_crm.py` around lines 229 - 289, Add a new async pytest in
tests/unit/test_crm.py that exercises the JSON-string path for cSkillAttrs:
create a test (e.g., test_search_contacts_parses_json_string_cskillattrs) which
sets mock_interaction.user.roles = [mock_member_role], sets
crm_cog.espo_api.request.return_value to return a contact whose "cSkillAttrs"
value is a JSON string (json.dumps of the same dict used in
test_search_contacts_shows_skill_strengths), call
crm_cog.search_members.callback(crm_cog, mock_interaction, None,
"python,sql,aws"), then assert mock_interaction.followup.send was called and the
embed (from call_args.kwargs["embed"]) contains the expected skills/strengths
text similar to the existing assertions; follow the same structure and naming
used by test_search_contacts_shows_skill_strengths and
test_search_contacts_ignores_broken_skill_attrs so the test integrates
consistently.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Outside diff comments:
In `@apps/discord_bot/src/five08/discord_bot/cogs/crm.py`:
- Around line 1336-1343: The search currently includes "cSkillAttrs" in the
select and will raise if the field/column is missing; wrap the call to
self.espo_api.request("GET", "Contact", search_params) in error handling that
detects a 500/field-missing response and retries the request with a fallback
search_params that removes "cSkillAttrs" from the select string (keep other keys
like "where" and "maxSize" unchanged), then set contacts = response.get("list",
[]) from the successful call; ensure this logic lives around where
search_params, response and contacts are defined so the retry path populates
contacts the same way.
---
Nitpick comments:
In `@tests/unit/test_crm.py`:
- Around line 229-289: Add a new async pytest in tests/unit/test_crm.py that
exercises the JSON-string path for cSkillAttrs: create a test (e.g.,
test_search_contacts_parses_json_string_cskillattrs) which sets
mock_interaction.user.roles = [mock_member_role], sets
crm_cog.espo_api.request.return_value to return a contact whose "cSkillAttrs"
value is a JSON string (json.dumps of the same dict used in
test_search_contacts_shows_skill_strengths), call
crm_cog.search_members.callback(crm_cog, mock_interaction, None,
"python,sql,aws"), then assert mock_interaction.followup.send was called and the
embed (from call_args.kwargs["embed"]) contains the expected skills/strengths
text similar to the existing assertions; follow the same structure and naming
used by test_search_contacts_shows_skill_strengths and
test_search_contacts_ignores_broken_skill_attrs so the test integrates
consistently.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4da8572 and df9243d.

📒 Files selected for processing (2)
  • apps/discord_bot/src/five08/discord_bot/cogs/crm.py
  • tests/unit/test_crm.py

@michaelmwu
michaelmwu merged commit 0545357 into mainFeb 25, 2026
5 checks passed
@michaelmwu
michaelmwu deleted the michaelmwu/search-member-skills branch February 25, 2026 15:55
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@michaelmwu
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' feat: show requested skill strengths in search by michaelmwu · Pull Request #55 · 508-dev/508-workflows · GitHub
Skip to content

feat: show requested skill strengths in search - #55

Merged
michaelmwu merged 1 commit into
mainfrom
michaelmwu/search-member-skills
Feb 25, 2026
Merged

feat: show requested skill strengths in search#55
michaelmwu merged 1 commit into
mainfrom
michaelmwu/search-member-skills

Conversation

@michaelmwu

@michaelmwumichaelmwu commented Feb 25, 2026

Copy link
Copy Markdown
Member

Description

Summary

Enhances the /search-members command to include requested-skill strength annotations (for example python (5)) directly in results when cSkillAttrs is available and valid.

  • Adds robust parsing/normalization for contact cSkillAttrs values with best-effort fallback for malformed data.
  • Extends CRM search select fields to include skills and cSkillAttrs so skill metadata is available in results.
  • Adds safeguards to keep search behavior stable when skill metadata is missing or invalid, and logs parse failures.
  • Adds unit tests covering strength rendering and malformed cSkillAttrs handling, plus select fields assertions.

Related Issue

None.

How Has This Been Tested?

Ran unit tests for tests/unit/test_crm.py and validated formatting/parsing scenarios through assertions.

Summary by CodeRabbit

Release Notes

  • New Features
    • Member search results now display skills paired with proficiency levels (e.g., Python: 5, SQL: 4), enabling more effective identification of team members with specific expertise and capabilities.
    • Strengthened skill data parsing to gracefully handle various formats and edge cases in member profiles, ensuring consistent display without errors.

@coderabbitai

coderabbitaiBot commented Feb 25, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR enhances the CRM Discord bot's search functionality by adding skill strength parsing and display. Three new helper methods parse contact skill attributes from JSON payloads, normalize skill names, and format requested skills with their strength values for display in search results. Unit tests verify correct handling of skill strengths and malformed payloads.

Changes

Cohort / File(s)Summary
CRM Skill Strength Enhancement
apps/discord_bot/src/five08/discord_bot/cogs/crm.py
Added _parse_contact_skill_attrs() to parse and validate skill strength data from contact attributes (JSON/dict/None), _normalize_skill() for standardized skill name formatting, and _format_requested_skills() to append strength values to requested skills. Modified search_members() to request skills and cSkillAttrs fields and display formatted skills in search results. Added json import.
CRM Skill Tests
tests/unit/test_crm.py
Added tests verifying select parameters include both skills and cSkillAttrs, skill strengths display correctly (e.g., python: 5), and malformed skill attribute payloads are gracefully ignored.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A skilled recruit joins our warren so fine,
With strengths five and sixes, in sync they align,
We parse what's requested, we normalize true,
And format the skills in a display anew—
No broken payloads shall daunt our command! 🥕✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly and specifically summarizes the main change: adding skill strength display to search results.
Docstring Coverage✅ PassedDocstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch michaelmwu/search-member-skills

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/discord_bot/src/five08/discord_bot/cogs/crm.py (1)

1336-1343: ⚠️ Potential issue | 🟠 Major

Add error handling for optional cSkillAttrs field in search.

The /search-members command currently fails if cSkillAttrs is unavailable in the workspace. EspoCRM's REST API will return a 500 error if a field in select exists in metadata but the corresponding database column is missing or misconfigured. Since cSkillAttrs is a custom field that may not exist in all workspaces, implement a fallback retry without it:

Suggested fix
- search_params = {+ base_select = (+ "id,name,emailAddress,c508Email,cDiscordUsername,cDiscordUserID,"+ "phoneNumber,type,resumeIds,resumeNames,resumeTypes,skills"+ )+ search_params = {
"where": where_filters,
"maxSize": 10,
- "select": "id,name,emailAddress,c508Email,cDiscordUsername,cDiscordUserID,phoneNumber,type,resumeIds,resumeNames,resumeTypes,skills,cSkillAttrs",+ "select": f"{base_select},cSkillAttrs",
}
-- response = self.espo_api.request("GET", "Contact", search_params)+ try:+ response = self.espo_api.request("GET", "Contact", search_params)+ except EspoAPIError as exc:+ if "cSkillAttrs" in str(exc):+ logger.warning(+ "cSkillAttrs unavailable in this CRM; retrying search without it: %s",+ exc,+ )+ search_params["select"] = base_select+ response = self.espo_api.request("GET", "Contact", search_params)+ else:+ raise
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/discord_bot/src/five08/discord_bot/cogs/crm.py` around lines 1336 -
1343, The search currently includes "cSkillAttrs" in the select and will raise
if the field/column is missing; wrap the call to self.espo_api.request("GET",
"Contact", search_params) in error handling that detects a 500/field-missing
response and retries the request with a fallback search_params that removes
"cSkillAttrs" from the select string (keep other keys like "where" and "maxSize"
unchanged), then set contacts = response.get("list", []) from the successful
call; ensure this logic lives around where search_params, response and contacts
are defined so the retry path populates contacts the same way.
🧹 Nitpick comments (1)
tests/unit/test_crm.py (1)

229-289: Add one happy-path test for JSON-string cSkillAttrs.

These tests cover dict payloads and malformed payloads well, but not the valid JSON-string path the parser supports. Adding that case would fully cover the new parsing behavior.

✅ Suggested test addition
+ `@pytest.mark.asyncio`+ async def test_search_contacts_parses_skill_attrs_json_string(+ self, crm_cog, mock_interaction, mock_member_role+ ):+ mock_interaction.user.roles = [mock_member_role]+ crm_cog.espo_api.request.return_value = {+ "list": [+ {+ "id": "contact123",+ "name": "John Doe",+ "emailAddress": "john@example.com",+ "type": "Member",+ "cSkillAttrs": '{"python":{"strength":"5"},"sql":{"strength":4}}',+ }+ ]+ }++ await crm_cog.search_members.callback(+ crm_cog, mock_interaction, None, "python,sql"+ )++ embed = mock_interaction.followup.send.call_args.kwargs["embed"]+ field_value = embed.fields[0].value+ assert "🧠 Skills: python (5), sql (4)" in field_value
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@tests/unit/test_crm.py` around lines 229 - 289, Add a new async pytest in
tests/unit/test_crm.py that exercises the JSON-string path for cSkillAttrs:
create a test (e.g., test_search_contacts_parses_json_string_cskillattrs) which
sets mock_interaction.user.roles = [mock_member_role], sets
crm_cog.espo_api.request.return_value to return a contact whose "cSkillAttrs"
value is a JSON string (json.dumps of the same dict used in
test_search_contacts_shows_skill_strengths), call
crm_cog.search_members.callback(crm_cog, mock_interaction, None,
"python,sql,aws"), then assert mock_interaction.followup.send was called and the
embed (from call_args.kwargs["embed"]) contains the expected skills/strengths
text similar to the existing assertions; follow the same structure and naming
used by test_search_contacts_shows_skill_strengths and
test_search_contacts_ignores_broken_skill_attrs so the test integrates
consistently.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Outside diff comments:
In `@apps/discord_bot/src/five08/discord_bot/cogs/crm.py`:
- Around line 1336-1343: The search currently includes "cSkillAttrs" in the
select and will raise if the field/column is missing; wrap the call to
self.espo_api.request("GET", "Contact", search_params) in error handling that
detects a 500/field-missing response and retries the request with a fallback
search_params that removes "cSkillAttrs" from the select string (keep other keys
like "where" and "maxSize" unchanged), then set contacts = response.get("list",
[]) from the successful call; ensure this logic lives around where
search_params, response and contacts are defined so the retry path populates
contacts the same way.
---
Nitpick comments:
In `@tests/unit/test_crm.py`:
- Around line 229-289: Add a new async pytest in tests/unit/test_crm.py that
exercises the JSON-string path for cSkillAttrs: create a test (e.g.,
test_search_contacts_parses_json_string_cskillattrs) which sets
mock_interaction.user.roles = [mock_member_role], sets
crm_cog.espo_api.request.return_value to return a contact whose "cSkillAttrs"
value is a JSON string (json.dumps of the same dict used in
test_search_contacts_shows_skill_strengths), call
crm_cog.search_members.callback(crm_cog, mock_interaction, None,
"python,sql,aws"), then assert mock_interaction.followup.send was called and the
embed (from call_args.kwargs["embed"]) contains the expected skills/strengths
text similar to the existing assertions; follow the same structure and naming
used by test_search_contacts_shows_skill_strengths and
test_search_contacts_ignores_broken_skill_attrs so the test integrates
consistently.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4da8572 and df9243d.

📒 Files selected for processing (2)
  • apps/discord_bot/src/five08/discord_bot/cogs/crm.py
  • tests/unit/test_crm.py

@michaelmwu
michaelmwu merged commit 0545357 into mainFeb 25, 2026
5 checks passed
@michaelmwu
michaelmwu deleted the michaelmwu/search-member-skills branch February 25, 2026 15:55
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@michaelmwu
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); feat: show requested skill strengths in search by michaelmwu · Pull Request #55 · 508-dev/508-workflows · GitHub
Skip to content

feat: show requested skill strengths in search - #55

Merged
michaelmwu merged 1 commit into
mainfrom
michaelmwu/search-member-skills
Feb 25, 2026
Merged

feat: show requested skill strengths in search#55
michaelmwu merged 1 commit into
mainfrom
michaelmwu/search-member-skills

Conversation

@michaelmwu

@michaelmwumichaelmwu commented Feb 25, 2026

Copy link
Copy Markdown
Member

Description

Summary

Enhances the /search-members command to include requested-skill strength annotations (for example python (5)) directly in results when cSkillAttrs is available and valid.

  • Adds robust parsing/normalization for contact cSkillAttrs values with best-effort fallback for malformed data.
  • Extends CRM search select fields to include skills and cSkillAttrs so skill metadata is available in results.
  • Adds safeguards to keep search behavior stable when skill metadata is missing or invalid, and logs parse failures.
  • Adds unit tests covering strength rendering and malformed cSkillAttrs handling, plus select fields assertions.

Related Issue

None.

How Has This Been Tested?

Ran unit tests for tests/unit/test_crm.py and validated formatting/parsing scenarios through assertions.

Summary by CodeRabbit

Release Notes

  • New Features
    • Member search results now display skills paired with proficiency levels (e.g., Python: 5, SQL: 4), enabling more effective identification of team members with specific expertise and capabilities.
    • Strengthened skill data parsing to gracefully handle various formats and edge cases in member profiles, ensuring consistent display without errors.

@coderabbitai

coderabbitaiBot commented Feb 25, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR enhances the CRM Discord bot's search functionality by adding skill strength parsing and display. Three new helper methods parse contact skill attributes from JSON payloads, normalize skill names, and format requested skills with their strength values for display in search results. Unit tests verify correct handling of skill strengths and malformed payloads.

Changes

Cohort / File(s)Summary
CRM Skill Strength Enhancement
apps/discord_bot/src/five08/discord_bot/cogs/crm.py
Added _parse_contact_skill_attrs() to parse and validate skill strength data from contact attributes (JSON/dict/None), _normalize_skill() for standardized skill name formatting, and _format_requested_skills() to append strength values to requested skills. Modified search_members() to request skills and cSkillAttrs fields and display formatted skills in search results. Added json import.
CRM Skill Tests
tests/unit/test_crm.py
Added tests verifying select parameters include both skills and cSkillAttrs, skill strengths display correctly (e.g., python: 5), and malformed skill attribute payloads are gracefully ignored.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A skilled recruit joins our warren so fine,
With strengths five and sixes, in sync they align,
We parse what's requested, we normalize true,
And format the skills in a display anew—
No broken payloads shall daunt our command! 🥕✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly and specifically summarizes the main change: adding skill strength display to search results.
Docstring Coverage✅ PassedDocstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch michaelmwu/search-member-skills

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/discord_bot/src/five08/discord_bot/cogs/crm.py (1)

1336-1343: ⚠️ Potential issue | 🟠 Major

Add error handling for optional cSkillAttrs field in search.

The /search-members command currently fails if cSkillAttrs is unavailable in the workspace. EspoCRM's REST API will return a 500 error if a field in select exists in metadata but the corresponding database column is missing or misconfigured. Since cSkillAttrs is a custom field that may not exist in all workspaces, implement a fallback retry without it:

Suggested fix
- search_params = {+ base_select = (+ "id,name,emailAddress,c508Email,cDiscordUsername,cDiscordUserID,"+ "phoneNumber,type,resumeIds,resumeNames,resumeTypes,skills"+ )+ search_params = {
"where": where_filters,
"maxSize": 10,
- "select": "id,name,emailAddress,c508Email,cDiscordUsername,cDiscordUserID,phoneNumber,type,resumeIds,resumeNames,resumeTypes,skills,cSkillAttrs",+ "select": f"{base_select},cSkillAttrs",
}
-- response = self.espo_api.request("GET", "Contact", search_params)+ try:+ response = self.espo_api.request("GET", "Contact", search_params)+ except EspoAPIError as exc:+ if "cSkillAttrs" in str(exc):+ logger.warning(+ "cSkillAttrs unavailable in this CRM; retrying search without it: %s",+ exc,+ )+ search_params["select"] = base_select+ response = self.espo_api.request("GET", "Contact", search_params)+ else:+ raise
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/discord_bot/src/five08/discord_bot/cogs/crm.py` around lines 1336 -
1343, The search currently includes "cSkillAttrs" in the select and will raise
if the field/column is missing; wrap the call to self.espo_api.request("GET",
"Contact", search_params) in error handling that detects a 500/field-missing
response and retries the request with a fallback search_params that removes
"cSkillAttrs" from the select string (keep other keys like "where" and "maxSize"
unchanged), then set contacts = response.get("list", []) from the successful
call; ensure this logic lives around where search_params, response and contacts
are defined so the retry path populates contacts the same way.
🧹 Nitpick comments (1)
tests/unit/test_crm.py (1)

229-289: Add one happy-path test for JSON-string cSkillAttrs.

These tests cover dict payloads and malformed payloads well, but not the valid JSON-string path the parser supports. Adding that case would fully cover the new parsing behavior.

✅ Suggested test addition
+ `@pytest.mark.asyncio`+ async def test_search_contacts_parses_skill_attrs_json_string(+ self, crm_cog, mock_interaction, mock_member_role+ ):+ mock_interaction.user.roles = [mock_member_role]+ crm_cog.espo_api.request.return_value = {+ "list": [+ {+ "id": "contact123",+ "name": "John Doe",+ "emailAddress": "john@example.com",+ "type": "Member",+ "cSkillAttrs": '{"python":{"strength":"5"},"sql":{"strength":4}}',+ }+ ]+ }++ await crm_cog.search_members.callback(+ crm_cog, mock_interaction, None, "python,sql"+ )++ embed = mock_interaction.followup.send.call_args.kwargs["embed"]+ field_value = embed.fields[0].value+ assert "🧠 Skills: python (5), sql (4)" in field_value
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@tests/unit/test_crm.py` around lines 229 - 289, Add a new async pytest in
tests/unit/test_crm.py that exercises the JSON-string path for cSkillAttrs:
create a test (e.g., test_search_contacts_parses_json_string_cskillattrs) which
sets mock_interaction.user.roles = [mock_member_role], sets
crm_cog.espo_api.request.return_value to return a contact whose "cSkillAttrs"
value is a JSON string (json.dumps of the same dict used in
test_search_contacts_shows_skill_strengths), call
crm_cog.search_members.callback(crm_cog, mock_interaction, None,
"python,sql,aws"), then assert mock_interaction.followup.send was called and the
embed (from call_args.kwargs["embed"]) contains the expected skills/strengths
text similar to the existing assertions; follow the same structure and naming
used by test_search_contacts_shows_skill_strengths and
test_search_contacts_ignores_broken_skill_attrs so the test integrates
consistently.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Outside diff comments:
In `@apps/discord_bot/src/five08/discord_bot/cogs/crm.py`:
- Around line 1336-1343: The search currently includes "cSkillAttrs" in the
select and will raise if the field/column is missing; wrap the call to
self.espo_api.request("GET", "Contact", search_params) in error handling that
detects a 500/field-missing response and retries the request with a fallback
search_params that removes "cSkillAttrs" from the select string (keep other keys
like "where" and "maxSize" unchanged), then set contacts = response.get("list",
[]) from the successful call; ensure this logic lives around where
search_params, response and contacts are defined so the retry path populates
contacts the same way.
---
Nitpick comments:
In `@tests/unit/test_crm.py`:
- Around line 229-289: Add a new async pytest in tests/unit/test_crm.py that
exercises the JSON-string path for cSkillAttrs: create a test (e.g.,
test_search_contacts_parses_json_string_cskillattrs) which sets
mock_interaction.user.roles = [mock_member_role], sets
crm_cog.espo_api.request.return_value to return a contact whose "cSkillAttrs"
value is a JSON string (json.dumps of the same dict used in
test_search_contacts_shows_skill_strengths), call
crm_cog.search_members.callback(crm_cog, mock_interaction, None,
"python,sql,aws"), then assert mock_interaction.followup.send was called and the
embed (from call_args.kwargs["embed"]) contains the expected skills/strengths
text similar to the existing assertions; follow the same structure and naming
used by test_search_contacts_shows_skill_strengths and
test_search_contacts_ignores_broken_skill_attrs so the test integrates
consistently.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4da8572 and df9243d.

📒 Files selected for processing (2)
  • apps/discord_bot/src/five08/discord_bot/cogs/crm.py
  • tests/unit/test_crm.py

@michaelmwu
michaelmwu merged commit 0545357 into mainFeb 25, 2026
5 checks passed
@michaelmwu
michaelmwu deleted the michaelmwu/search-member-skills branch February 25, 2026 15:55
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@michaelmwu