```python:inference/core/workflows/core_steps/sinks/roboflow/dataset_upload/v2.py
from __future__ import annotations

import random
from concurrent.futures import ThreadPoolExecutor
from typing import List, Literal, Optional, Tuple, Union

import supervision as sv
from fastapi import BackgroundTasks
from inference.core.cache.base import BaseCache
from inference.core.workflows.core_steps.sinks.roboflow.dataset_upload.v1 import \
    register_datapoint_at_roboflow
from inference.core.workflows.execution_engine.entities.base import (
    Batch, WorkflowImageData)
from inference.core.workflows.prototypes.block import (BlockResult,
                                                       WorkflowBlock)

BatchCreationFrequency = Literal["never", "daily", "weekly", "monthly"]


class RoboflowDatasetUploadBlockV2(WorkflowBlock):

    def __init__(
        self,
        cache: BaseCache,
        api_key: Optional[str],
        background_tasks: Optional[BackgroundTasks],
        thread_pool_executor: Optional[ThreadPoolExecutor],
    ):
        self._cache = cache
        self._api_key = api_key
        self._background_tasks = background_tasks
        self._thread_pool_executor = thread_pool_executor

    def run(
        self,
        images: Batch[WorkflowImageData],
        predictions: Optional[Batch[Union[sv.Detections, dict]]],
        target_project: str,
        usage_quota_name: str,
        data_percentage: float,
        minutely_usage_limit: int,
        persist_predictions: bool,
        hourly_usage_limit: int,
        daily_usage_limit: int,
        max_image_size: Tuple[int, int],
        compression_level: int,
        registration_tags: List[str],
        disable_sink: bool,
        fire_and_forget: bool,
        labeling_batch_prefix: str,
        labeling_batches_recreation_frequency: BatchCreationFrequency,
        image_name: Optional[Batch[Optional[str]]] = None,
    ) -> BlockResult:
        if self._api_key is None:
            raise ValueError(
                "RoboflowDataCollector block cannot run without Roboflow API key. "
                "If you do not know how to get API key - visit "
                "https://docs.roboflow.com/api-reference/authentication#retrieve-an-api-key to learn how to "
                "retrieve one."
            )
        if disable_sink:
            return [
                {
                    "error_status": False,
                    "message": "Sink was disabled by parameter `disable_sink`",
                }
                for _ in range(len(images))
            ]
        result = []
        predictions = [None] * len(images) if predictions is None else predictions
        image_names = [None] * len(images) if image_name is None else image_name

        # Fast-paths and local aliases for hot loop
        normalized_probability = data_percentage / 100
        register_func = register_datapoint_at_roboflow
        append_result = result.append
        rand = random.random

        # If no sampling, skip all quickly
        if normalized_probability <= 0.0:
            for _ in images:
                append_result({"error_status": False, "message": "Registration skipped due to sampling settings"})
            return result

        # If full sampling, register all quickly
        if normalized_probability >= 1.0:
            for image, prediction, img_name in zip(images, predictions, image_names):
                error_status, message = register_func(
                    image=image,
                    prediction=prediction,
                    target_project=target_project,
                    usage_quota_name=usage_quota_name,
                    persist_predictions=persist_predictions,
                    minutely_usage_limit=minutely_usage_limit,
                    hourly_usage_limit=hourly_usage_limit,
                    daily_usage_limit=daily_usage_limit,
                    max_image_size=max_image_size,
                    compression_level=compression_level,
                    registration_tags=registration_tags,
                    fire_and_forget=fire_and_forget,
                    labeling_batch_prefix=labeling_batch_prefix,
                    new_labeling_batch_frequency=labeling_batches_recreation_frequency,
                    cache=self._cache,
                    background_tasks=self._background_tasks,
                    thread_pool_executor=self._thread_pool_executor,
                    api_key=self._api_key,
                    image_name=img_name,
                )
                append_result({"error_status": error_status, "message": message})
            return result

        # General case: sample once per image and only call register when selected
        prob = normalized_probability
        for image, prediction, img_name in zip(images, predictions, image_names):
            if rand() < prob:
                error_status, message = register_func(
                    image=image,
                    prediction=prediction,
                    target_project=target_project,
                    usage_quota_name=usage_quota_name,
                    persist_predictions=persist_predictions,
                    minutely_usage_limit=minutely_usage_limit,
                    hourly_usage_limit=hourly_usage_limit,
                    daily_usage_limit=daily_usage_limit,
                    max_image_size=max_image_size,
                    compression_level=compression_level,
                    registration_tags=registration_tags,
                    fire_and_forget=fire_and_forget,
                    labeling_batch_prefix=labeling_batch_prefix,
                    new_labeling_batch_frequency=labeling_batches_recreation_frequency,
                    cache=self._cache,
                    background_tasks=self._background_tasks,
                    thread_pool_executor=self._thread_pool_executor,
                    api_key=self._api_key,
                    image_name=img_name,
                )
            else:
                error_status, message = False, "Registration skipped due to sampling settings"
            append_result({"error_status": error_status, "message": message})
        return result


def maybe_register_datapoint_at_roboflow(
    image: WorkflowImageData,
    prediction: Optional[Union[sv.Detections, dict]],
    target_project: str,
    usage_quota_name: str,
    data_percentage: float,
    persist_predictions: bool,
    minutely_usage_limit: int,
    hourly_usage_limit: int,
    daily_usage_limit: int,
    max_image_size: Tuple[int, int],
    compression_level: int,
    registration_tags: List[str],
    fire_and_forget: bool,
    labeling_batch_prefix: str,
    new_labeling_batch_frequency: BatchCreationFrequency,
    cache: BaseCache,
    background_tasks: Optional[BackgroundTasks],
    thread_pool_executor: Optional[ThreadPoolExecutor],
    api_key: str,
    image_name: Optional[str] = None,
) -> Tuple[bool, str]:
    normalised_probability = data_percentage / 100
    if random.random() < normalised_probability:
        return register_datapoint_at_roboflow(
            image=image,
            prediction=prediction,
            target_project=target_project,
            usage_quota_name=usage_quota_name,
            persist_predictions=persist_predictions,
            minutely_usage_limit=minutely_usage_limit,
            hourly_usage_limit=hourly_usage_limit,
            daily_usage_limit=daily_usage_limit,
            max_image_size=max_image_size,
            compression_level=compression_level,
            registration_tags=registration_tags,
            fire_and_forget=fire_and_forget,
            labeling_batch_prefix=labeling_batch_prefix,
            new_labeling_batch_frequency=new_labeling_batch_frequency,
            cache=cache,
            background_tasks=background_tasks,
            thread_pool_executor=thread_pool_executor,
            api_key=api_key,
            image_name=image_name,
        )
    return False, "Registration skipped due to sampling settings"
```