Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 65 additions & 27 deletions .github/scripts/bot-next-issue-recommendation.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ module.exports = async ({ github, context, core }) => {
}

// Get the first linked issue number
const issueNumber = parseInt(matches[0][2]);
const issueNumber = parseInt(matches[0][2], 10);
core.info(`Found linked issue #${issueNumber}`);

try {
Expand Down Expand Up @@ -78,6 +78,7 @@ module.exports = async ({ github, context, core }) => {
let recommendedIssues = [];
let recommendedLabel = null;
let isFallback = false;
let recommendationScope = 'repo';

recommendedIssues = await searchIssues(github, core, repoOwner, repoName, 'beginner');
recommendedLabel = 'Beginner';
Expand All @@ -88,6 +89,11 @@ module.exports = async ({ github, context, core }) => {
recommendedLabel = 'Good First Issue';
}

if (recommendedIssues.length === 0) {
recommendationScope = 'org';
recommendedLabel = 'Good First Issue';
recommendedIssues = await searchOrgIssues(github, core, repoOwner, 'good first issue');
}

// Remove the issue they just solved
recommendedIssues = recommendedIssues.filter(i => i.number !== issueNumber);
Expand All @@ -99,9 +105,9 @@ module.exports = async ({ github, context, core }) => {
completedLabelText,
recommendedLabel,
isFallback,
recommendationScope,
};
await generateAndPostComment(github, context, core, prNumber, recommendedIssues, recommendationMeta);

} catch (error) {
core.setFailed(`Error processing issue #${issueNumber}: ${error.message}`);
}
Expand All @@ -125,16 +131,42 @@ async function searchIssues(github, core, owner, repo, label) {
}
}

async function generateAndPostComment(github, context, core, prNumber, recommendedIssues, { completedLabelText, recommendedLabel, isFallback}) {
async function searchOrgIssues(github, core, owner, label) {
try {
const query = `org:${owner} type:issue state:open label:"${label}" no:assignee`;
core.info(`Searching org issues with query: ${query}`);

const { data: searchResult } = await github.rest.search.issuesAndPullRequests({
q: query,
per_page: 6,
});

core.info(`Found ${searchResult.items.length} org issues with label "${label}"`);
return searchResult.items;
} catch (error) {
core.warning(`Error searching org issues with label "${label}": ${error.message}`);
return [];
}
}

async function generateAndPostComment(
github,
context,
core,
prNumber,
recommendedIssues,
{ completedLabelText, recommendedLabel, isFallback, recommendationScope }
) {
const marker = '<!-- next-issue-bot-marker -->';

// Build comment content
let comment = `${marker}\n\n🎉 **Nice work completing a ${completedLabelText}!**\n\n`;
comment += `Thank you for your contribution to the Hiero Python SDK! We're excited to have you as part of our community.\n\n`;

if (recommendedIssues.length > 0) {

if (isFallback) {
if (recommendationScope === 'org') {
comment += 'Here are some **Good First Issues across the Hiero organization** you might be interested in working on next:\n\n';
} else if (isFallback) {
comment += `Here are some **${recommendedLabel}** issues at a similar level you might be interested in working on next:\n\n`;
} else {
comment += `Here are some issues labeled **${recommendedLabel}** you might be interested in working on next:\n\n`;
Expand All @@ -159,37 +191,43 @@ async function generateAndPostComment(github, context, core, prNumber, recommend
const description = sanitized.substring(0, 150);
comment += ` ${description}${sanitized.length > 150 ? '...' : ''}\n\n`;
} else {
comment += ` *No description available*\n\n`;
comment += ' *No description available*\n\n';
}
});
} else {
comment += `There are currently no open issues available at or near the ${completedLabelText} level in this repository.\n\n`;
comment += `You can check out **Good First Issues** in other Hiero repositories:\n\n`;
const repoQuery = SUPPORTED_GFI_REPOS
.map(repo => `repo:${context.repo.owner}/${repo}`)
.join(' OR ');

const gfiSearchQuery = [
'is:open',
'is:issue',
`org:${context.repo.owner}`,
'archived:false',
'no:assignee',
'(label:"good first issue" OR label:"skill: good first issue")',
`(${repoQuery})`,
].join(' ');

const gfiQuery = `https://github.com/issues?q=${encodeURIComponent(gfiSearchQuery)}`;

comment += `[View Good First Issues across supported Hiero repositories](${gfiQuery})\n\n`;
if (recommendationScope === 'org') {
const orgLabel = recommendedLabel === 'Beginner' ? 'beginner' : 'good first issue';
const orgQuery = `org:${context.repo.owner} type:issue state:open label:"${orgLabel}"`;
comment += `You can check out ${recommendedLabel.toLowerCase()} issues across the entire Hiero organization: ` +
`[Hiero ${recommendedLabel} Issues](https://github.com/issues?q=${encodeURIComponent(orgQuery)})\n\n`;
} else {
comment += 'You can check out **Good First Issues** in other Hiero repositories:\n\n';
const repoQuery = SUPPORTED_GFI_REPOS
.map(repo => `repo:${context.repo.owner}/${repo}`)
.join(' OR ');

const gfiSearchQuery = [
'is:open',
'is:issue',
`org:${context.repo.owner}`,
'archived:false',
'no:assignee',
'(label:"good first issue" OR label:"skill: good first issue")',
`(${repoQuery})`,
].join(' ');

const gfiQuery = `https://github.com/issues?q=${encodeURIComponent(gfiSearchQuery)}`;
comment += `[View Good First Issues across supported Hiero repositories](${gfiQuery})\n\n`;
}
}

comment += `🌟 **Stay connected with the project:**\n`;
comment += '🌟 **Stay connected with the project:**\n';
comment += `- ⭐ [Star this repository](https://github.com/${context.repo.owner}/${context.repo.repo}) to show your support\n`;
comment += `- 👀 [Watch this repository](https://github.com/${context.repo.owner}/${context.repo.repo}/watchers) to get notified of new issues and releases\n\n`;

comment += `We look forward to seeing more contributions from you! If you have any questions, feel free to ask in our [Discord community](https://github.com/hiero-ledger/hiero-sdk-python/blob/main/docs/discord.md).\n\n`;
comment += `From the Hiero Python SDK Team 🚀`;
comment += 'We look forward to seeing more contributions from you! If you have any questions, feel free to ask in our [Discord community](https://github.com/hiero-ledger/hiero-sdk-python/blob/main/docs/discord.md).\n\n';
comment += 'From the Hiero Python SDK Team 🚀';

// Check for existing comment
try {
Expand Down
14 changes: 9 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ This changelog is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.
## [Unreleased]

### Tests

- Standardize formatting of `tests/unit/entity_id_helper_test.py` using Black for consistent code style across the test suite (#1527)

- Added tests for ProtoBuf Training Example Implementation
Expand All @@ -28,13 +29,13 @@ This changelog is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.
- Format tests/unit/network_tls_test.py with black for code style consistency (#1543)
- Formatted `ethereum_transaction_test.py` using Black.
- Formatted client_test.py using Black.
- Format tests/unit/query*.py using black (#1547)
- Format `tests/unit/custom_fee_test.py` with black for code style consistency. (#1525)

### Added

- Add constructor-style `__repr__` for `FileId` to improve debugging output (#1628)
- Added logging in bot-gfi-assign-on-comment.js to prevent silent skips. (`#1668`)
- Added `AssessedCustomFee` domain model to represent assessed custom fees. (`#1637`)
- Add __repr__ method for ContractId class to improve debugging (#1714)
- Add `__repr__` method for ContractId class to improve debugging (#1714)
- Added Protobuf Training guide to enhance developer understanding of proto serialization
and deserialization (#1645)
- Add `__repr__()` method to `TopicId` class for improved debugging with constructor-style representation (#1629)
Expand Down Expand Up @@ -146,13 +147,15 @@ This changelog is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.
- Added MirrorNode based population for `ContractId`, including parsing and serialization support.
- Added `/working` command to reset the inactivity timer on issues and PRs. ([#1552](https://github.com/hiero-ledger/hiero-sdk-python/issues/1552))
- Added `grpc_deadline` support for transaction and query execution.
- Type hints to exception classes (`PrecheckError`, `MaxAttemptsError`, `ReceiptStatusError`) constructors and string methods.

### Documentation

- Type hints to exception classes (`PrecheckError`, `MaxAttemptsError`, `ReceiptStatusError`) constructors and string methods.
- Added comprehensive docstring to `compress_with_cryptography` function (#1626)
- Replaced the docstring in `entity_id_helper.py` with one that is correct. (#1623)

### Changed

- Refactored `setup_client()` in all `examples/query/` files to use `Client.from_env()` for simplified client initialization (#1449)
- Updated return of to_bytes function in `src/hiero_sdk_python/transaction/transaction.py`. (#1631)
- Added missing return type `src/hiero_sdk_python/utils/entity_id_helper.py`. (#1622)
Expand Down Expand Up @@ -253,6 +256,7 @@ This changelog is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.
- chore: update MAINTAINERS.md to include new maintainer Manish Dait and sort maintainers by GitHub ID. (#1691)

### Fixed

- Updated Good First Issue recommendations to supported Hiero repositories. (#1689)
- Fix the next-issue recommendation bot to post the correct issue recommendation comment. (#1593)
- Ensured that the GFI assignment bot skips posting `/assign` reminders for repository collaborators to avoid unnecessary notifications.(#1568).
Expand Down Expand Up @@ -417,6 +421,7 @@ This changelog is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.
- docs: Add `docs/sdk_developers/project_structure.md` to explain repository layout and import paths.

### Changed

- chore: renamed examples to match src where possible
- Moved examples/ to be inside subfiles to match src structure
- changed example script workflow to run on new subdirectory structure
Expand Down Expand Up @@ -860,5 +865,4 @@ contract_call_local_pb2.ContractLoginfo -> contract_types_pb2.ContractLoginfo

- N/A


# [0.1.0] - 2025-02-19
9 changes: 9 additions & 0 deletions src/hiero_sdk_python/file/file_id.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,15 @@ def __str__(self) -> str:
"""
return f"{self.shard}.{self.realm}.{self.file}"

def __repr__(self) -> str:
"""
Returns a detailed representation of the FileId suitable for debugging.

Returns:
str: A string in constructor format 'FileId(shard=X, realm=Y, file=Z)'.
"""
return f"FileId(shard={self.shard}, realm={self.realm}, file={self.file})"

def validate_checksum(self, client: Client) -> None:
"""
Validates the stored checksum against the calculated checksum using the provided client.
Expand Down
14 changes: 14 additions & 0 deletions tests/unit/file_id_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,20 @@ def test_str_representation_default():
assert str(file_id) == "0.0.0"


def test_repr_representation():
"""Test repr representation of FileId."""
file_id = FileId(0, 0, 150)

assert repr(file_id) == "FileId(shard=0, realm=0, file=150)"


def test_repr_representation_with_checksum():
"""Test repr representation of FileId with checksum."""
file_id = FileId.from_string("0.0.1-dfkxr")

assert repr(file_id) == "FileId(shard=0, realm=0, file=1)"
Comment on lines +55 to +59
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Test asserts the wrong expected output — checksum should be included in repr.

Issue #1628 requires __repr__ to include checksum when it is not None. FileId.from_string("0.0.1-dfkxr") sets checksum="dfkxr", so the expected repr should include it. This test currently validates the buggy behavior and will need to be updated alongside the __repr__ fix.

Proposed fix (aligns with the __repr__ fix)
 def test_repr_representation_with_checksum():
     """Test repr representation of FileId with checksum."""
     file_id = FileId.from_string("0.0.1-dfkxr")
 
-    assert repr(file_id) == "FileId(shard=0, realm=0, file=1)"
+    assert repr(file_id) == "FileId(shard=0, realm=0, file=1, checksum='dfkxr')", \
+        "repr should include checksum when checksum is not None"



def test_from_string_valid():
"""Test creating FileId from valid string format."""
file_id = FileId.from_string("1.2.3")
Expand Down