Skip to content

Conversation

@srtaalej
Copy link
Contributor

@srtaalej srtaalej commented Nov 18, 2025

This PR adds support for the following slackLists API methods and addresses issue #1503 :

Create, AccessDelete, AccessSet, DownloadGet, DownloadStart, ItemsCreate, ItemsDelete, ItemsDeleteMultiple, ItemsInfo, ItemsList, ItemsUpdate, Update

Testing

▶️ expand test example
            // 1. Create a list
            System.out.println("Creating list...");
            SlackListsCreateResponse createResponse = app.client()
                    .slackListsCreate(req -> req.name("Test List - SlackLists API")
                            .descriptionBlocks(List.of(RichTextBlock.builder()
                                    .elements(List.of(RichTextSectionElement.builder()
                                            .elements(List.of(RichTextSectionElement.Text.builder()
                                                    .text("List to keep track of tasks!")
                                                    .build()))
                                            .build()))
                                    .build()))
                            .schema(List.of(
                                    Map.of(
                                            "key", "task_name",
                                            "name", "Task Name",
                                            "type", "text",
                                            "is_primary_column", true),
                                    Map.of(
                                            "key", "due_date",
                                            "name", "Due Date",
                                            "type", "date"),
                                    Map.of(
                                            "key", "status",
                                            "name", "Status",
                                            "type", "select",
                                            "options",
                                                    Map.of(
                                                            "choices",
                                                            List.of(
                                                                    Map.of(
                                                                            "value",
                                                                            "not_started",
                                                                            "label",
                                                                            "Not Started",
                                                                            "color",
                                                                            "red"),
                                                                    Map.of(
                                                                            "value",
                                                                            "in_progress",
                                                                            "label",
                                                                            "In Progress",
                                                                            "color",
                                                                            "yellow"),
                                                                    Map.of(
                                                                            "value",
                                                                            "completed",
                                                                            "label",
                                                                            "Completed",
                                                                            "color",
                                                                            "green")))),
                                    Map.of(
                                            "key", "assignee",
                                            "name", "Assignee",
                                            "type", "user"))));
            System.out.println("List created: " + createResponse);
            String listId = createResponse.getListId();

            // Extract column IDs from the response (map key -> id)
            Map<String, String> keyToId = new HashMap<>();
            if (createResponse.getListMetadata() != null
                    && createResponse.getListMetadata().getSchema() != null) {
                createResponse.getListMetadata().getSchema().forEach(col -> {
                    keyToId.put(col.getKey(), col.getId());
                });
            }
            String taskNameColId = keyToId.get("task_name");
            System.out.println("Column IDs: " + keyToId);

            // 2. Set access for a user
            System.out.println("Setting access...");
            SlackListsAccessSetResponse accessSetResponse = app.client()
                    .slackListsAccessSet(
                            req -> req.listId(listId).accessLevel("write").userIds(List.of("U09G4FG3TRN")));
            System.out.println("Access set: " + accessSetResponse);

            // 3. Create an item
            System.out.println("Creating item...");
            SlackListsItemsCreateResponse createItemResponse = app.client().slackListsItemsCreate(req -> req.listId(
                            listId)
                    .initialFields(List.of(Map.of(
                            "column_id",
                            taskNameColId,
                            "rich_text",
                            List.of(Map.of(
                                    "type",
                                    "rich_text",
                                    "elements",
                                    List.of(Map.of(
                                            "type",
                                            "rich_text_section",
                                            "elements",
                                            List.of(Map.of(
                                                    "type", "text",
                                                    "text", "CLI app unlink command"))))))))));
            System.out.println("Item created: " + createItemResponse);
            String itemId = createItemResponse.getItem() != null
                    ? createItemResponse.getItem().getId()
                    : null;

            if (itemId != null) {
                // 4. Get item info
                System.out.println("Getting item info...");
                SlackListsItemsInfoResponse itemInfoResponse = app.client()
                        .slackListsItemsInfo(
                                req -> req.listId(listId).id(itemId).includeIsSubscribed(true));
                System.out.println("Item info retrieved: " + itemInfoResponse);

                 // 5. Update item
                System.out.println("Updating item...");
                SlackListsItemsUpdateResponse updateItemResponse = app.client()
                        .slackListsItemsUpdate(req -> req.listId(listId)
                                .cells(List.of(Map.of(
                                        "row_id",
                                        itemId,
                                        "column_id",
                                        taskNameColId,
                                        "rich_text",
                                        List.of(Map.of(
                                                "type",
                                                "rich_text",
                                                "elements",
                                                List.of(Map.of(
                                                        "type",
                                                        "rich_text_section",
                                                        "elements",
                                                        List.of(Map.of(
                                                                "type", "text",
                                                                "text", "Java"))))))))));
                System.out.println("Item updated: " + updateItemResponse);
            }


            // 6. List items
            System.out.println("Listing items...");
            SlackListsItemsListResponse listItemsResponse =
                    app.client().slackListsItemsList(req -> req.listId(listId).limit(50));
            System.out.println("Items listed: " + listItemsResponse);

            // 7. Start download
            System.out.println("Starting download...");
            SlackListsDownloadStartResponse downloadStartResponse = app.client()
                    .slackListsDownloadStart(req -> req.listId(listId).includeArchived(false));
            System.out.println("Download started: " + downloadStartResponse);
            String jobId = downloadStartResponse.getJobId();

            if (jobId != null) {
                // 8. Get download status
                System.out.println("Getting download status...");
                SlackListsDownloadGetResponse downloadGetResponse = app.client()
                        .slackListsDownloadGet(req -> req.listId(listId).jobId(jobId));
                System.out.println("Download status retrieved: " + downloadGetResponse);
            }

            if (itemId != null) {
                // 9. Delete single item
                System.out.println("Deleting item...");
                SlackListsItemsDeleteResponse deleteItemResponse = app.client()
                        .slackListsItemsDelete(req -> req.listId(listId).id(itemId));
                System.out.println("Item deleted: " + deleteItemResponse);
            }

            // 10. Delete multiple items (example with placeholder IDs)
            System.out.println("Deleting multiple items...");
            SlackListsItemsDeleteMultipleResponse deleteMultipleResponse = app.client()
                    .slackListsItemsDeleteMultiple(req -> req.listId(listId).ids(List.of("item1", "item2")));
            System.out.println("Multiple items deleted: " + deleteMultipleResponse);

            // 11. Delete access
            System.out.println("Deleting access...");
            SlackListsAccessDeleteResponse accessDeleteResponse = app.client()
                    .slackListsAccessDelete(req -> req.listId(listId).userIds(List.of("U09G4FG3TRN")));
            System.out.println("Access removed: " + accessDeleteResponse);

            System.out.println("\n⚡️ Slack Lists API demonstration completed!");

Category (place an x in each of the [ ])

  • bolt (Bolt for Java)
  • bolt-{sub modules} (Bolt for Java - optional modules)
  • slack-api-client (Slack API Clients)
  • slack-api-model (Slack API Data Models)
  • slack-api-*-kotlin-extension (Kotlin Extensions for Slack API Clients)
  • slack-app-backend (The primitive layer of Bolt for Java)

Requirements

Please read the Contributing guidelines and Code of Conduct before creating this issue or pull request. By submitting, you agree to those rules.

@srtaalej srtaalej requested review from mwbrooks and zimeg November 18, 2025 16:51
@srtaalej srtaalej self-assigned this Nov 18, 2025
@srtaalej srtaalej added enhancement M-T: A feature request for new functionality project:slack-api-client project:slack-api-client project:slack-api-model project:slack-api-model labels Nov 18, 2025
@codecov
Copy link

codecov bot commented Nov 18, 2025

Codecov Report

❌ Patch coverage is 90.84507% with 13 lines in your changes missing coverage. Please review.
✅ Project coverage is 72.95%. Comparing base (d3c7c02) to head (9128b64).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
...java/com/slack/api/methods/RequestFormBuilder.java 84.14% 6 Missing and 7 partials ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##               main    #1537      +/-   ##
============================================
+ Coverage     72.77%   72.95%   +0.18%     
- Complexity     4412     4476      +64     
============================================
  Files           477      477              
  Lines         14081    14223     +142     
  Branches       1473     1483      +10     
============================================
+ Hits          10247    10377     +130     
- Misses         2963     2967       +4     
- Partials        871      879       +8     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@srtaalej srtaalej marked this pull request as ready for review November 18, 2025 18:00
@srtaalej srtaalej requested a review from a team as a code owner November 18, 2025 18:00
@srtaalej srtaalej marked this pull request as draft November 18, 2025 20:00
@srtaalej srtaalej linked an issue Nov 19, 2025 that may be closed by this pull request
6 tasks
@srtaalej srtaalej marked this pull request as ready for review November 19, 2025 18:49
Copy link
Member

@zimeg zimeg left a comment

Choose a reason for hiding this comment

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

@srtaalej Sweeeeet! These are nice changes to have in progress! ☕ ✨

I'm starting a review now but wanted to leave a few notes on first findings. I plan to test this soon too and will share more then!

…lacklists/SlackListsAccessDeleteRequest.java

Co-authored-by: Eden Zimbelman <[email protected]>
@srtaalej srtaalej marked this pull request as draft November 21, 2025 18:44
@srtaalej srtaalej marked this pull request as ready for review December 1, 2025 17:11
@srtaalej srtaalej marked this pull request as draft December 1, 2025 18:53
@srtaalej srtaalej marked this pull request as ready for review December 1, 2025 20:36
@zimeg zimeg self-requested a review December 2, 2025 21:38
Copy link
Member

@zimeg zimeg left a comment

Choose a reason for hiding this comment

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

@srtaalej These are all solid changes! 🔏 ✨

Overall things are seeming good, but a few things stood out that we might want to address before merging:

  • Alphabetics: This is a quibble, but some implementations and imports of these "slackLists" methods are out of order around "stars" I noticed. Bringing this up here since this can cause added thought in surrounding changes at a later time...
  • Underscores: I'm unsure if slacklists or slack_lists is better for file names, but a decision before releasing this might be good to have!
  • Builders: The form builder has repetitive logic around "null" values that we should avoid. Sending encoded arrays has caused me issue before with JSON encodings that I hope to avoid with consistent setups around this.
  • Testing: Remote tests should match the testing example shared with this PR! This'll let us confirm the expected values are sent and received with the API.

Hoping these aren't significant changes needed, but I do realize this is a larger PR. Please let me know if I can share more about the remote testing or other notes!

Copy link
Member

@zimeg zimeg left a comment

Choose a reason for hiding this comment

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

👁️‍🗨️ Leaving a quick note on perhaps unusual JSON logs appearing!

Comment on lines 18 to 20
"rich_text": []
"rich_text": [
""
],
Copy link
Member

Choose a reason for hiding this comment

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

🚧 issue: I notice this string value might be causing CI to error with unexpected properties:

Error:    SlackListsTest.slackListsItemsCreate:107 » JsonSyntax java.lang.IllegalStateException: Expected BEGIN_OBJECT but was STRING at line 1 column 304 path $.item.fields[0].rich_text[0]

🔗 https://github.com/slackapi/java-slack-sdk/actions/runs/19905747957/job/57061381182?pr=1537#step:4:908

Copy link
Member

Choose a reason for hiding this comment

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

✏️ suggestion: For now we can revert this to be an empty array, unless a more descriptive typing is found with later tests?

🗣️ ramble: A similar issue might exist in adjacent logs too:

  • $.subtasks[0] of the slackLists.items.info method

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I fixed it by adding "rich_text": [ { "type": "rich_text", "elements": [ { "type": "rich_text_section", "elements": [ { "type": "text", "text": "" } ] } ] } ]

Copy link
Member

@mwbrooks mwbrooks left a comment

Choose a reason for hiding this comment

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

✅ Looks good to me and thank you for handling all of @zimeg's feedback! 🙇🏻

👀 Let's hold off on merging until later Monday to give @WilliamBergamin a chance to look at the pull request.

🚀 After Monday, I'm happy to merge! We can always iterate in follow-up PRs with any additional feedback 👌🏻

🙏🏻 Great work on adding all of the lists methods to the Java SDK! It's a huge feat!

@srtaalej srtaalej requested a review from zimeg December 9, 2025 16:05
Copy link
Member

@zimeg zimeg left a comment

Choose a reason for hiding this comment

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

@srtaalej LGTM! This is a super impressive PR 🌟

Thank you for addressing the rambling comments as well as adding a test that we can use for continued maintenance. And the testing steps. I left a comment on possible errors in related testing, but nothing that stops this from landing! 🚢 💨

@Override
@Deprecated // https://docs.slack.dev/changelog/2023-07-its-later-already-for-stars-and-reminders
public StarsRemoveResponse starsRemove(RequestConfigurator<StarsRemoveRequest.StarsRemoveRequestBuilder> req) throws IOException, SlackApiException {
return starsRemove(req.configure(StarsRemoveRequest.builder()).build());
Copy link
Member

Choose a reason for hiding this comment

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

🧮 nit(non-blocking): We can alphabetical order the list methods before these "stars" methods!

assertThat(accessSetResponse.getError(), is(nullValue()));
assertThat(accessSetResponse.isOk(), is(true));

try {
Copy link
Member

Choose a reason for hiding this comment

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

🪓 question: Are we alright to remove the try from this test? Since we're not running this in CI at the moment, these failures might be nice to raise for more investigation!

Copy link
Contributor

Choose a reason for hiding this comment

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

this try clause does not have catch clauses, so an exception thrown here would be re-thrown to the test runner. this means if you want to use try clause without catch clauses for auto-closing resources, that's totally fine.

in this case, the finally clause just prints info-level logging, so the try/finally may not be necessary though

private String provided;
private transient Map<String, List<String>> httpResponseHeaders;

private File list;
Copy link
Member

Choose a reason for hiding this comment

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

🧠 praise: TIL lists are a special file!

Copy link
Contributor

@seratch seratch left a comment

Choose a reason for hiding this comment

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

glad to see the team continues maintaining this!

/**
* Encoded ID of the List.
*/
@SerializedName("list_id")
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: when the snake_case to camelCase (vice versa) is straight-forward like this one, the SerializedName annotation is not required.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thank you so much for taking the time to drop a review! 😸

/**
* Column definition for the List. (Optional)
*/
private List<Map<String, Object>> schema;
Copy link
Contributor

Choose a reason for hiding this comment

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

If you have the concrete object types of possible values here, avoiding Object is more developer friendly.

* Initial item data. (Optional)
*/
@SerializedName("initial_fields")
private List<Map<String, Object>> initialFields;
Copy link
Contributor

Choose a reason for hiding this comment

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

same as above

assertThat(accessSetResponse.getError(), is(nullValue()));
assertThat(accessSetResponse.isOk(), is(true));

try {
Copy link
Contributor

Choose a reason for hiding this comment

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

this try clause does not have catch clauses, so an exception thrown here would be re-thrown to the test runner. this means if you want to use try clause without catch clauses for auto-closing resources, that's totally fine.

in this case, the finally clause just prints info-level logging, so the try/finally may not be necessary though

@srtaalej srtaalej merged commit 7f39adf into main Dec 11, 2025
6 checks passed
@srtaalej srtaalej deleted the ale-feat-lists-mthds branch December 11, 2025 16:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement M-T: A feature request for new functionality project:slack-api-client project:slack-api-client project:slack-api-model project:slack-api-model

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature Request: Add support for SlackLists API methods

5 participants