Move group from one board to another board

I’ve read a few posts about users checking if they can move a group to another board within the API in 2020-2021. Wondering if there has been any updates?

post1
post2

Hi @Binx96,

Unfortunately, that is not possible using the API. You can find all of the available groups mutations/actions in the documentation here.

We have an open feature request for that mutation, so I’ve added your vote to it :slight_smile:

Let us know if you have any other questions!

Best,
Rachel

1 Like

Hi @rachelatmonday,

I do have a follow up question then. The reason why I asked is because I am trying to accommodate for the lack of item creation speed I am seeing with the API. Currently I have over 500 groups and 7600 items to be created within those groups. I’ve noticed that each item mutation takes about a second to appear on the board. Unfortunately mutating 7600 items takes about 2 hours.

Instead of mutating one item at a time, I instead used multiprocessing. With a small dataset, this method worked. However with my larger dataset, groups started missing items and were empty. I cannot confirm the reason for this, but my hunch is that it’s due to sending data to the same board multiple times all at the same time.

My next thought was to use multiprocessing to build out one group in a new board and then merge them together into a master board. For example, I would have 500 boards with one board in each. All groups would be moved to another board (all having the same columns). I know that this can be done within monday.com, but apparently it cannot be done via the API, hence my original question.

Running out of ideas on how to speed up this process. What are your thoughts?

Multiprocessing seems to be one good step, but the other thing you’re likely to run into is API complexity limits. The API returns errors as 200 status codes, so if you’re not checking each query response for an error per the documentation, you will be missing the errors from complexity limits.

When the complexity limit hits you need to pause your queue, wait, and retry the outstanding failed requests.

If you’re using an app OAauth token you can mutate about 160 items per minute maximum (this also includes creating them, creating groups, etc.). If you’re using a user token you can mutate about 300 per minute (depending on how much board reading you do, since user tokens combine read and write capacity, app tokens do not).

Just wanted to confirm you are handling the complexity limits (returning complexity with the query and checking for complexity errors)

1 Like

Thanks for pointing those out, no I was not handling the complexity limits. I am getting a magnitude of 200 status codes a few 404’s and a few 429’s. If I am not mistaken, 200 is actually “OK” and 404/429 are the errors. Just printed out all the messages and yes some of them are complexity issues and others are “Group not found”. Though it’s likely that the “Group not found” errors are related to the complexity errors since the group writing was not properly executed. I’ll likely use a try except block with a time.sleep element to accommodate this. I’ll post back later with additional findings or other issues I encounter.

Since you seem to answer all of my questions, want to take a peak at this one?

See Error codes for the details on error codes.

Complexity errors are a 200 for the HTTP request - so they return without error, try/catch won’t pick up anything!

You need to check for presence of error keys on the response to see if there are any actual errors.

Then, on complexity exceptions, the message can be parsed to find out how many seconds to wait… yeah annoying it doesn’t return the complexity as structured data. Then you can wait that number of seconds before retrying.

2 Likes

Alright, I tossed some while loops together to check for those complexity issues and just do a simple 5 second wait (I will add the functionality of parsing out how many seconds from the error message later). I am facing a 404 status code error that seems to have happen to quite a few different users (from my research).

Within the below function “creating_new_board_from_csv()” there are 2 while loops. The first creates a group and ultimately returns the group id. Which is then passed into the second while loop to create one singular item. This is to initiate some status column groups for later processing (not important).

If I only run the first while loop (comment out the second while loop), the code runs without error. However, when I run it with both while loops, I receive this error on a multitude of groups. I understand the error in which the group cannot be found, but I am unsure why it is occurring since the group id is clearly being printed out above. Does this have something to do with the speed at which multiprocessing is occurring? I even tried adding a 0.5 second sleep function just after a group is created.

>>>
946323_r042576_00___valdez_lid
Item was not created correctly {'error_code': 'ResourceNotFoundException', 'status_code': 404, 'error_message': 'Group not found', 'error_data': {'resource_type': 'group', 'group_id': '946323_r042576_00___valdez_lid', 'board_id': 5504339963, 'error_reason': 'store.monday.automation.error.missing_group'}}

Multiprocessing:

if add_groups:

        # MULTI-CORE PROCESSING:
        # Always use n-1 processing cores.
        number_cores_used = int(multiprocessing.cpu_count() - 1)

        # Establish the multiprocessing pool.
        pool = multiprocessing.Pool(number_cores_used)

        args = []
        for group in add_groups:
            args.append((created_board_id, group, df, org_dict, cid_dict, gid_dict))

        results = [pool.apply_async(creating_new_board_from_csv, args=arg) for arg in args]

        # Check for all other errors
        for value in results:
            try:
                value.get()
            except Exception as e:
                pass
            
        # Close and join the pool process now.
        pool.close()
        pool.join()

Createing_new_board_from_csv()

    run_group = True
    run_group_item = True

    while run_group:
        group_data = create_group(created_board_id_x, group_x)
        created_group_id = group_data.json()['data']['create_group']['id']
        group_status = group_data.status_code
        if not group_status == 200:
            print(f'Group was not created correctly: {group_data.json()}')
            print(group_status)
            time.sleep(5)
        else:
            run_group = False
            time.sleep(0.5)
    
    while run_group_item:
        gid_dict_x[group_x] = created_group_id
        # Create an empty item for each group to activate the status column types
        item_data = create_item(created_board_id_x, created_group_id, 'Zarklock', '')
        item_status = item_data.status_code

        if not item_status == 200:
            print(created_group_id)
            print(f'Item was not created correctly {item_data.json()}')
            time.sleep(5)
        else:
            run_group_item = False

create_group()

def create_group(board_id, group_name):
    payload = f"""
    mutation {{
        create_group (board_id: {board_id} group_name: {json.dumps(group_name)}) {{
            id
        }}
    }}
    """
    
    data = {'query' : payload}
    r_boards = requests.post(url=apiUrl, headers=headers, data=json.dumps(data)) # make request

    return r_boards

create_item()

def create_item(board_id, group_id, item_name, column_values):
    query = """
    mutation ($boardId: ID!, $itemName: String!, $groupId: String, $columnValues: JSON) {
        create_item(
            board_id: $boardId
            group_id: $groupId
            item_name: $itemName
            column_values: $columnValues
        ) {
            id
        }
        }
    """

    variables = {
        'boardId': board_id,
        'groupId': group_id,
        'itemName': item_name,
        'columnValues': json.dumps(column_values, separators=(',', ':'))
    }

    datas = {
        'query': query,
        'variables': variables
    }

    r_boards = requests.post(url=apiUrl, headers=headers, data=json.dumps(datas)) # make request

    return r_boards

Can I confirm you’re setting API-Version header to 2023-10 for your requests?

2023-10 is backed by the new mondayDB database, and 2023-07 is the old database.

2023-10 would likely have less replication latency, so the new groups will be available sooner across all the nodes. (I believe should be instant)

That said, there is always the potential that a new group won’t yet be found for the create_item operation. So you’ll need to handle this exception and retry. I’d sleep for say 15 seconds in this case - that usually seems to be a good enough to start.

If you haven’t been setting your version header, its a good idea to do it since on January 15th the 2023-07 is being removed, 2024-01 is being made stable and the default, 2023-10 is being made the deprecated version. There are not many (no significant) breaking changes from 2023-10 to 2024-01 but a lot from 2023-07 to 2023-10 - so you’ll want to get on those now.

1 Like

Yes, I the API-Version in my headers set to 2023-10. I’ve updated my creating_new_board_from_csv() function to this and it appears that it is working. I’ve found that it is important where the time.sleep() are placed. One after a group is created and one if the group is not found after creation. It seems that it takes just a moment for monday to update it’s database to account for the changes. 15 seconds seemed to do the trick as well.

Because the number of groups needed to be created is relatively small, checking if the group was created by querying the groups is okay, but a different approach will need to be made when adding items to them.

I’ll need to play with the status codes a bit more. While printing out the status codes, I regularly would get 200, but when I printed out the request.json() object, sometimes I would get 500. May be best to parse out the data instead of asking for the .status_code.

    run_group = True
    run_group_item = True

    while run_group:
        group_data = create_group(created_board_id_x, group_x)
        time.sleep(15)
        group_info = check_group_name(created_board_id_x).json()['data']['boards'][0]['groups']

        title_list = []
        for group in group_info:
            title_list.append(group['title'])
        
        if group_x in title_list:
            run_group = False
        else:
            print(f'{group_x} not found, waiting 15 seconds to try again')
            time.sleep(2)

If you dig into the error documentation, you’re going to find out that most of the errors say status_code: 500 in the request.json() but the HTTP status code is 200. GraphQL returns errors at the GraphQL level (not network, authentication, etc.) as HTTP 200. There is then in the response body, details of any errors.

Thats what you’re seeing, is many GraphQL errors that are going to show up as HTTP 200, but in the body are errors with other status codes.

so looking at .status_code is not a reliable way to check for GraphQL errors, you have to look at the body thats returned. That is the only place you’ll see the actual API errors most of the time.

3 Likes