Example #1
0
class PackageStatusRenderer(StatusRenderer):
    def __init__(self, context):
        super(PackageStatusRenderer, self).__init__(context)

        self.publish_steps_renderer = PublishStepStatusRenderer(context)

        # Publish Steps
        self.publish_steps_last_state = dict.fromkeys(
            constants.PUBLISH_STEPS,
            constants.STATE_NOT_STARTED)

        self.publish_http_last_state = constants.STATE_NOT_STARTED
        self.publish_https_last_state = constants.STATE_NOT_STARTED

        # UI Widgets
        self.packages_bar = self.prompt.create_progress_bar()
        self.publish_http_spinner = self.prompt.create_spinner()
        self.publish_https_spinner = self.prompt.create_spinner()

    def display_report(self, progress_report):
        """
        Displays the contents of the progress report to the user. This will
        aggregate the calls to render individual sections of the report.
        """

        # There's a small race condition where the task will indicate it's
        # begun running but the importer has yet to submit a progress report
        # (or it has yet to be saved into the task). This should be alleviated
        # by the if statements below.
        try:
            # Sync Steps
            if ids.TYPE_ID_IMPORTER_WIN in progress_report:
                pass

            # Publish Steps
            if ids.TYPE_ID_DISTRIBUTOR_WIN in progress_report:
                # Proxy to the standard renderer
                self.publish_steps_renderer.display_report(progress_report)

        except CancelException:
            self.prompt.render_failure_message(_('Operation canceled.'))
Example #2
0
class PackageStatusRenderer(StatusRenderer):
    def __init__(self, context):
        super(PackageStatusRenderer, self).__init__(context)

        self.publish_steps_renderer = PublishStepStatusRenderer(context)

        # Publish Steps
        self.publish_steps_last_state = dict.fromkeys(
            constants.PUBLISH_STEPS, constants.STATE_NOT_STARTED)

        self.publish_http_last_state = constants.STATE_NOT_STARTED
        self.publish_https_last_state = constants.STATE_NOT_STARTED

        # UI Widgets
        self.packages_bar = self.prompt.create_progress_bar()
        self.publish_http_spinner = self.prompt.create_spinner()
        self.publish_https_spinner = self.prompt.create_spinner()

    def display_report(self, progress_report):
        """
        Displays the contents of the progress report to the user. This will
        aggregate the calls to render individual sections of the report.
        """

        # There's a small race condition where the task will indicate it's
        # begun running but the importer has yet to submit a progress report
        # (or it has yet to be saved into the task). This should be alleviated
        # by the if statements below.
        try:
            # Sync Steps
            if ids.TYPE_ID_IMPORTER in progress_report:
                pass

            # Publish Steps
            if ids.TYPE_ID_DISTRIBUTOR in progress_report:
                # Proxy to the standard renderer
                self.publish_steps_renderer.display_report(progress_report)

        except CancelException:
            self.prompt.render_failure_message(_('Operation canceled.'))
Example #3
0
class RpmStatusRenderer(StatusRenderer):
    def __init__(self, context):
        super(RpmStatusRenderer, self).__init__(context)

        self.publish_steps_renderer = PublishStepStatusRenderer(context)

        # Sync Steps
        self.metadata_last_state = constants.STATE_NOT_STARTED
        self.download_last_state = constants.STATE_NOT_STARTED
        self.distribution_sync_last_state = constants.STATE_NOT_STARTED
        self.errata_last_state = constants.STATE_NOT_STARTED
        self.comps_last_state = constants.STATE_NOT_STARTED
        self.purge_duplicates_last_state = constants.STATE_NOT_STARTED

        # Publish Steps
        self.publish_steps_last_state = dict.fromkeys(constants.PUBLISH_STEPS,
                                                      constants.STATE_NOT_STARTED)

        self.distribution_publish_last_state = constants.STATE_NOT_STARTED
        self.generate_metadata_last_state = constants.STATE_NOT_STARTED
        self.publish_http_last_state = constants.STATE_NOT_STARTED
        self.publish_https_last_state = constants.STATE_NOT_STARTED

        # UI Widgets
        self.metadata_spinner = self.prompt.create_spinner()
        self.download_bar = self.prompt.create_progress_bar()
        self.download_spinner = self.prompt.create_spinner()
        self.distribution_sync_bar = self.prompt.create_progress_bar()
        self.errata_spinner = self.prompt.create_spinner()
        self.comps_spinner = self.prompt.create_spinner()
        self.purge_duplicates_spinner = self.prompt.create_spinner()

        self.packages_bar = self.prompt.create_progress_bar()
        self.distribution_publish_bar = self.prompt.create_progress_bar()
        self.generate_metadata_spinner = self.prompt.create_spinner()
        self.publish_http_spinner = self.prompt.create_spinner()
        self.publish_https_spinner = self.prompt.create_spinner()

    def display_report(self, progress_report):
        """
        Displays the contents of the progress report to the user. This will
        aggregate the calls to render individual sections of the report.
        """

        # There's a small race condition where the task will indicate it's
        # begun running but the importer has yet to submit a progress report
        # (or it has yet to be saved into the task). This should be alleviated
        # by the if statements below.
        try:
            # Sync Steps
            if 'yum_importer' in progress_report:
                self.render_metadata_step(progress_report)
                self.render_download_step(progress_report)
                self.render_distribution_sync_step(progress_report)
                self.render_errata_step(progress_report)
                self.render_comps_step(progress_report)
                self.render_purge_duplicates_step(progress_report)

            # Publish Steps
            if ids.YUM_DISTRIBUTOR_ID in progress_report:
                # Proxy to the standard renderer
                self.publish_steps_renderer.display_report(progress_report)

        except CancelException:
            self.prompt.render_failure_message(_('Operation canceled.'))

    def check_for_cancelled_state(self, state):
        if state == constants.STATE_CANCELLED:
            raise CancelException

    # -- render sync steps -----------------------------------------------------

    def render_metadata_step(self, progress_report):

        # Example Data:
        # "metadata": {
        # "state": "FINISHED"
        # }

        current_state = progress_report['yum_importer']['metadata']['state']
        self.check_for_cancelled_state(current_state)

        def update_func(new_state):
            self.metadata_last_state = new_state

        render_general_spinner_step(self.prompt, self.metadata_spinner, current_state,
                                    self.metadata_last_state, _('Downloading metadata...'),
                                    update_func)

        if self.metadata_last_state == constants.STATE_FAILED:
            self.prompt.render_failure_message(progress_report['yum_importer']['metadata']['error'])

    def render_distribution_sync_step(self, progress_report):
        data = progress_report['yum_importer']['distribution']
        state = data['state']
        self.check_for_cancelled_state(state)
        # Render nothing if we haven't begun yet or if this step is skipped
        if state in (constants.STATE_NOT_STARTED, constants.STATE_SKIPPED):
            return

        # Only render this on the first non-not-started state
        if self.distribution_sync_last_state == constants.STATE_NOT_STARTED:
            self.prompt.write(_('Downloading distribution files...'))

        if (state in (constants.STATE_RUNNING, constants.STATE_COMPLETE) and
                self.distribution_sync_last_state not in constants.COMPLETE_STATES):
            render_itemized_in_progress_state(self.prompt, data, _('distributions'),
                                              self.distribution_sync_bar, state)

        elif state in constants.STATE_FAILED and \
                self.distribution_sync_last_state not in constants.COMPLETE_STATES:

            self.prompt.render_spacer()
            self.prompt.render_failure_message(_('Errors encountered during distribution sync:'))

            # TODO: read this from config
            # display_error_count = self.context.extension_config.getint('main',
            # 'num_display_errors')
            display_error_count = 5

            num_errors = min(len(data['error_details']), display_error_count)

            if num_errors > 0:

                # Each error is a list of filename and dict of details
                # Example:
                # "error_details": [
                #      [
                #        "file:///mnt/iso/f18/images/boot.iso",
                #        {
                #          "response_code": 0,
                #          "error_message": "Couldn't open file /mnt/iso/f18/images/boot.iso",
                #          "error_code": 37
                #        }
                #      ]
                #    ],

                for i in range(0, num_errors):
                    error = data['error_details'][i]

                    message_data = {
                        'filename': error[0],
                        'message': error[1].get('error_message'),
                        'code': error[1].get('error_code'),
                    }

                    template = 'File: %(filename)s\n'
                    template += 'Error Code:   %(code)s\n'
                    template += 'Error Message: %(message)s'
                    message = template % message_data

                    self.prompt.render_failure_message(message)
                self.prompt.render_spacer()

        self.distribution_sync_last_state = state

    def render_download_step(self, progress_report):
        """
        :param progress_report: A dictionary containing a key called 'yum_importer' that is a
                                dictionary that has a key that is 'content' that indexes a
                                pulp_rpm.plugins.importers.yum.report.ContentReport.
        :type  progress_report: dict
        """
        data = progress_report['yum_importer']['content']
        state = data['state']
        self.check_for_cancelled_state(state)

        # Render nothing if we haven't begun yet or if this step is skipped
        if state in (constants.STATE_NOT_STARTED, constants.STATE_SKIPPED):
            return

        details = data['details']

        # Only render this on the first non-not-started state
        if self.download_last_state == constants.STATE_NOT_STARTED:
            self.prompt.write(_('Downloading repository content...'))

        # If it's running or finished, the output is still the same. This way,
        # if the status is viewed after this step, the content download
        # summary is still available.

        if state in (constants.STATE_RUNNING, constants.STATE_COMPLETE) and \
           self.download_last_state not in constants.COMPLETE_STATES:

            self.download_last_state = state

            template = _('RPMs:       %(rpm_done)s/%(rpm_total)s items\n'
                         'Delta RPMs: %(drpm_done)s/%(drpm_total)s items\n')

            bar_message = template % details

            overall_done = data['size_total'] - data['size_left']
            overall_total = data['size_total']

            try:
                self.download_bar.render(overall_done, overall_total, message=bar_message)
            except ZeroDivisionError:
                # overall_total is 0 -- render a spinner until we know enough to make a bar
                self.download_spinner.next()

            if state == constants.STATE_COMPLETE:
                # If all of the packages are already downloaded and up to date, the overall total
                # will be 0. In that event, fill the download bar since the state is complete.
                if overall_total == 0:
                    overall_total = overall_done = 1
                    self.download_bar.render(overall_done, overall_total, message=bar_message)
                self.prompt.write(_('... completed'))
                self.prompt.render_spacer()

                # If there are any errors, write them out here
                # TODO: read this from config
                # display_error_count = self.context.extension_config.getint('main',
                # 'num_display_errors')
                display_error_count = 5

                num_errors = min(len(data['error_details']), display_error_count)

                if num_errors > 0:
                    self.prompt.render_failure_message(
                        _('Individual package errors encountered during sync:'))

                    for i in range(0, num_errors):
                        error = data['error_details'][i]
                        if error.get(constants.ERROR_CODE) == constants.ERROR_CHECKSUM_TYPE_UNKNOWN:
                            message_data = {
                                'name': error[constants.NAME],
                                'checksum_type': error[constants.CHECKSUM_TYPE],
                                'accepted': ','.join(
                                    error.get(constants.ACCEPTED_CHECKSUM_TYPES, []))
                            }
                            template = _('Package: %(name)s\nError: An invalid checksum type '
                                         '(%(checksum_type)s) was detected.\n'
                                         'Accepted checksum types: %(accepted)s')
                        elif error.get(constants.ERROR_CODE) == constants.ERROR_INVALID_PACKAGE_SIG:
                            message_data = {
                                'count': error['count'],
                            }
                            template = _('%(count)s packages failed signature filter and were not '
                                         'imported.')

                        elif error.get(
                                constants.ERROR_CODE) == constants.ERROR_CHECKSUM_VERIFICATION:
                            message_data = {
                                'name': error[constants.NAME],
                            }
                            template = _('Package: %(name)s\nError: An invalid checksum was '
                                         'detected.')

                        elif error.get(constants.ERROR_CODE) == constants.ERROR_SIZE_VERIFICATION:
                            message_data = {
                                'name': error[constants.NAME],
                            }
                            template = _('Package: %(name)s\nError: The size did not match the '
                                         'value specified in the repository metadata.')
                        else:
                            error_msg = error.get('error', '')
                            traceback = '\n'.join(error.get('traceback', []))

                            message_data = {
                                'name': error['url'],
                                'error': error_msg,
                                'traceback': traceback
                            }

                            template = 'Package: %(name)s\n'
                            template += 'Error:   %(error)s\n'
                            if message_data["traceback"]:
                                template += 'Traceback:\n'
                                template += '%(traceback)s'

                        message = template % message_data

                        self.prompt.render_failure_message(message)
                    self.prompt.render_spacer()

        elif state == constants.STATE_FAILED and self.download_last_state not in \
                constants.COMPLETE_STATES:

            # This state means something went horribly wrong. There won't be
            # individual package error details which is why they are only
            # displayed above and not in this case.

            self.prompt.write(_('... failed'))
            self.download_last_state = constants.STATE_FAILED

    def render_errata_step(self, progress_report):

        # Example Data:
        # "errata": {
        # "state": "FINISHED",
        #    "num_errata": 0
        # }
        current_state = progress_report['yum_importer']['errata']['state']
        self.check_for_cancelled_state(current_state)
        if current_state in (constants.STATE_NOT_STARTED, constants.STATE_SKIPPED):
            return

        def update_func(new_state):
            self.errata_last_state = new_state

        render_general_spinner_step(self.prompt, self.errata_spinner, current_state,
                                    self.errata_last_state, _('Importing errata...'), update_func)

    def render_comps_step(self, progress_report):
        # Example Data:
        # "comps": {
        # "state": "FINISHED",
        #    "num_available_groups": 0,
        #    "num_available_categories": 0,
        #    "num_orphaned_groups": 0,
        #    "num_orphaned_categories": 0,
        #    "num_new_groups": 0,
        #    "num_new_categories": 0,
        # }

        current_state = progress_report['yum_importer']['comps']['state']

        def update_func(new_state):
            self.comps_last_state = new_state

        render_general_spinner_step(self.prompt, self.comps_spinner, current_state,
                                    self.comps_last_state,
                                    _('Importing package groups/categories...'), update_func)

    def render_purge_duplicates_step(self, progress_report):
        # Example Data:
        # "purge_duplicates": {
        #   "state": "FINISHED"
        # }

        current_state = progress_report['yum_importer']['purge_duplicates']['state']

        def update_func(new_state):
            self.purge_duplicates_last_state = new_state

        render_general_spinner_step(self.prompt, self.purge_duplicates_spinner, current_state,
                                    self.purge_duplicates_last_state,
                                    _('Cleaning duplicate packages...'), update_func)
Example #4
0
class RpmStatusRenderer(StatusRenderer):
    def __init__(self, context):
        super(RpmStatusRenderer, self).__init__(context)

        self.publish_steps_renderer = PublishStepStatusRenderer(context)

        # Sync Steps
        self.metadata_last_state = constants.STATE_NOT_STARTED
        self.download_last_state = constants.STATE_NOT_STARTED
        self.distribution_sync_last_state = constants.STATE_NOT_STARTED
        self.errata_last_state = constants.STATE_NOT_STARTED
        self.comps_last_state = constants.STATE_NOT_STARTED

        # Publish Steps
        self.publish_steps_last_state = dict.fromkeys(
            constants.PUBLISH_STEPS, constants.STATE_NOT_STARTED)

        self.distribution_publish_last_state = constants.STATE_NOT_STARTED
        self.generate_metadata_last_state = constants.STATE_NOT_STARTED
        self.publish_http_last_state = constants.STATE_NOT_STARTED
        self.publish_https_last_state = constants.STATE_NOT_STARTED

        # UI Widgets
        self.metadata_spinner = self.prompt.create_spinner()
        self.download_bar = self.prompt.create_progress_bar()
        self.distribution_sync_bar = self.prompt.create_progress_bar()
        self.errata_spinner = self.prompt.create_spinner()
        self.comps_spinner = self.prompt.create_spinner()

        self.packages_bar = self.prompt.create_progress_bar()
        self.distribution_publish_bar = self.prompt.create_progress_bar()
        self.generate_metadata_spinner = self.prompt.create_spinner()
        self.publish_http_spinner = self.prompt.create_spinner()
        self.publish_https_spinner = self.prompt.create_spinner()

    def display_report(self, progress_report):
        """
        Displays the contents of the progress report to the user. This will
        aggregate the calls to render individual sections of the report.
        """

        # There's a small race condition where the task will indicate it's
        # begun running but the importer has yet to submit a progress report
        # (or it has yet to be saved into the task). This should be alleviated
        # by the if statements below.
        try:
            # Sync Steps
            if 'yum_importer' in progress_report:
                self.render_metadata_step(progress_report)
                self.render_download_step(progress_report)
                self.render_distribution_sync_step(progress_report)
                self.render_errata_step(progress_report)
                self.render_comps_step(progress_report)

            # Publish Steps
            if ids.YUM_DISTRIBUTOR_ID in progress_report:
                # Proxy to the standard renderer
                self.publish_steps_renderer.display_report(progress_report)

        except CancelException:
            self.prompt.render_failure_message(_('Operation canceled.'))

    def check_for_cancelled_state(self, state):
        if state == constants.STATE_CANCELLED:
            raise CancelException

    # -- render sync steps -----------------------------------------------------

    def render_metadata_step(self, progress_report):

        # Example Data:
        # "metadata": {
        # "state": "FINISHED"
        # }

        current_state = progress_report['yum_importer']['metadata']['state']
        self.check_for_cancelled_state(current_state)

        def update_func(new_state):
            self.metadata_last_state = new_state

        render_general_spinner_step(self.prompt, self.metadata_spinner,
                                    current_state, self.metadata_last_state,
                                    _('Downloading metadata...'), update_func)

        if self.metadata_last_state == constants.STATE_FAILED:
            self.prompt.render_failure_message(
                progress_report['yum_importer']['metadata']['error'])

    def render_distribution_sync_step(self, progress_report):
        data = progress_report['yum_importer']['distribution']
        state = data['state']
        self.check_for_cancelled_state(state)
        # Render nothing if we haven't begun yet or if this step is skipped
        if state in (constants.STATE_NOT_STARTED, constants.STATE_SKIPPED):
            return

        # Only render this on the first non-not-started state
        if self.distribution_sync_last_state == constants.STATE_NOT_STARTED:
            self.prompt.write(_('Downloading distribution files...'))

        if (state in (constants.STATE_RUNNING, constants.STATE_COMPLETE)
                and self.distribution_sync_last_state
                not in constants.COMPLETE_STATES):
            render_itemized_in_progress_state(self.prompt, data,
                                              _('distributions'),
                                              self.distribution_sync_bar,
                                              state)

        elif state in constants.STATE_FAILED and \
                self.distribution_sync_last_state not in constants.COMPLETE_STATES:

            self.prompt.render_spacer()
            self.prompt.render_failure_message(
                _('Errors encountered during distribution sync:'))

            # TODO: read this from config
            # display_error_count = self.context.extension_config.getint('main',
            # 'num_display_errors')
            display_error_count = 5

            num_errors = min(len(data['error_details']), display_error_count)

            if num_errors > 0:

                # Each error is a list of filename and dict of details
                # Example:
                # "error_details": [
                #      [
                #        "file:///mnt/iso/f18/images/boot.iso",
                #        {
                #          "response_code": 0,
                #          "error_message": "Couldn't open file /mnt/iso/f18/images/boot.iso",
                #          "error_code": 37
                #        }
                #      ]
                #    ],

                for i in range(0, num_errors):
                    error = data['error_details'][i]

                    message_data = {
                        'filename': error[0],
                        'message': error[1].get('error_message'),
                        'code': error[1].get('error_code'),
                    }

                    template = 'File: %(filename)s\n'
                    template += 'Error Code:   %(code)s\n'
                    template += 'Error Message: %(message)s'
                    message = template % message_data

                    self.prompt.render_failure_message(message)
                self.prompt.render_spacer()

        self.distribution_sync_last_state = state

    def render_download_step(self, progress_report):
        """
        :param progress_report: A dictionary containing a key called 'yum_importer' that is a
                                dictionary that has a key that is 'content' that indexes a
                                pulp_rpm.plugins.importers.yum.report.ContentReport.
        :type  progress_report: dict
        """
        data = progress_report['yum_importer']['content']
        state = data['state']
        self.check_for_cancelled_state(state)

        # Render nothing if we haven't begun yet or if this step is skipped
        if state in (constants.STATE_NOT_STARTED, constants.STATE_SKIPPED):
            return

        details = data['details']

        # Only render this on the first non-not-started state
        if self.download_last_state == constants.STATE_NOT_STARTED:
            self.prompt.write(_('Downloading repository content...'))

        # If it's running or finished, the output is still the same. This way,
        # if the status is viewed after this step, the content download
        # summary is still available.

        if state in (constants.STATE_RUNNING, constants.STATE_COMPLETE) and \
           self.download_last_state not in constants.COMPLETE_STATES:

            self.download_last_state = state

            template = _('RPMs:       %(rpm_done)s/%(rpm_total)s items\n'
                         'Delta RPMs: %(drpm_done)s/%(drpm_total)s items\n')

            bar_message = template % details

            overall_done = data['size_total'] - data['size_left']
            overall_total = data['size_total']

            # If all of the packages are already downloaded and up to date,
            # the total bytes to process will be 0. This means the download
            # step is basically finished, so fill the progress bar.
            if overall_total == 0:
                overall_total = overall_done = 1

            self.download_bar.render(overall_done,
                                     overall_total,
                                     message=bar_message)

            if state == constants.STATE_COMPLETE:
                self.prompt.write(_('... completed'))
                self.prompt.render_spacer()

                # If there are any errors, write them out here
                # TODO: read this from config
                # display_error_count = self.context.extension_config.getint('main',
                # 'num_display_errors')
                display_error_count = 5

                num_errors = min(len(data['error_details']),
                                 display_error_count)

                if num_errors > 0:
                    self.prompt.render_failure_message(
                        _('Individual package errors encountered during sync:')
                    )

                    for i in range(0, num_errors):
                        error = data['error_details'][i]
                        if error.get(
                                constants.ERROR_CODE
                        ) == constants.ERROR_CHECKSUM_TYPE_UNKNOWN:
                            message_data = {
                                'name':
                                error[constants.NAME],
                                'checksum_type':
                                error[constants.CHECKSUM_TYPE],
                                'accepted':
                                ','.join(
                                    error.get(
                                        constants.ACCEPTED_CHECKSUM_TYPES, []))
                            }
                            template = _(
                                'Package: %(name)s\nError: An invalid checksum type '
                                '(%(checksum_type)s) was detected.\n'
                                'Accepted checksum types: %(accepted)s')
                        elif error.get(
                                constants.ERROR_CODE
                        ) == constants.ERROR_CHECKSUM_VERIFICATION:
                            message_data = {
                                'name': error[constants.NAME],
                            }
                            template = _(
                                'Package: %(name)s\nError: An invalid checksum was '
                                'detected.')

                        elif error.get(constants.ERROR_CODE
                                       ) == constants.ERROR_SIZE_VERIFICATION:
                            message_data = {
                                'name': error[constants.NAME],
                            }
                            template = _(
                                'Package: %(name)s\nError: The size did not match the '
                                'value specified in the repository metadata.')
                        else:
                            error_msg = error.get('error', '')
                            traceback = '\n'.join(error.get('traceback', []))

                            message_data = {
                                'name': error['url'],
                                'error': error_msg,
                                'traceback': traceback
                            }

                            template = 'Package: %(name)s\n'
                            template += 'Error:   %(error)s\n'
                            if message_data["traceback"]:
                                template += 'Traceback:\n'
                                template += '%(traceback)s'

                        message = template % message_data

                        self.prompt.render_failure_message(message)
                    self.prompt.render_spacer()

        elif state == constants.STATE_FAILED and self.download_last_state not in \
                constants.COMPLETE_STATES:

            # This state means something went horribly wrong. There won't be
            # individual package error details which is why they are only
            # displayed above and not in this case.

            self.prompt.write(_('... failed'))
            self.download_last_state = constants.STATE_FAILED

    def render_errata_step(self, progress_report):

        # Example Data:
        # "errata": {
        # "state": "FINISHED",
        #    "num_errata": 0
        # }
        current_state = progress_report['yum_importer']['errata']['state']
        self.check_for_cancelled_state(current_state)
        if current_state in (constants.STATE_NOT_STARTED,
                             constants.STATE_SKIPPED):
            return

        def update_func(new_state):
            self.errata_last_state = new_state

        render_general_spinner_step(self.prompt, self.errata_spinner,
                                    current_state, self.errata_last_state,
                                    _('Importing errata...'), update_func)

    def render_comps_step(self, progress_report):
        # Example Data:
        # "comps": {
        # "state": "FINISHED",
        #    "num_available_groups": 0,
        #    "num_available_categories": 0,
        #    "num_orphaned_groups": 0,
        #    "num_orphaned_categories": 0,
        #    "num_new_groups": 0,
        #    "num_new_categories": 0,
        # }

        current_state = progress_report['yum_importer']['comps']['state']

        def update_func(new_state):
            self.comps_last_state = new_state

        render_general_spinner_step(
            self.prompt, self.comps_spinner,
            current_state, self.comps_last_state,
            _('Importing package groups/categories...'), update_func)