def test_sort_widgets(self) -> None:
     widgets = [{
         'text': 'a',
         'options': {}
     }, {
         'text': 'b',
         'options': {
             'position': {
                 'row': 1,
                 'col': 1
             }
         }
     }, {
         'text': 'c',
         'options': {
             'position': {
                 'row': 1,
                 'col': 2
             }
         }
     }, {
         'text': 'd',
         'options': {
             'position': {
                 'row': 2,
                 'col': 1
             }
         }
     }]
     random.shuffle(widgets)
     sorted_widgets = sort_widgets(widgets)
     self.assertListEqual([widget['text'] for widget in sorted_widgets],
                          ['a', 'b', 'c', 'd'])
    def _get_extract_iter(self) -> Iterator[Any]:

        while True:
            record = self._extractor.extract()
            if not record:
                break  # the end.

            record = next(self._transformer.transform(record=record), None)

            if not self._is_published_dashboard(record):
                continue  # filter this one out

            identity_data = {
                'cluster':
                self._cluster,
                'product':
                RedashDashboardExtractor.PRODUCT,
                'dashboard_group_id':
                str(RedashDashboardExtractor.DASHBOARD_GROUP_ID),
                'dashboard_id':
                str(record['dashboard_id'])
            }

            dash_data = {
                'dashboard_group':
                RedashDashboardExtractor.DASHBOARD_GROUP_NAME,
                'dashboard_group_url': self._redash_base_url,
                'dashboard_name': record['dashboard_name'],
                'dashboard_url':
                f'{self._redash_base_url}/dashboards/{record["dashboard_id"]}',
                'created_timestamp': record['created_timestamp']
            }
            dash_data.update(identity_data)

            widgets = sort_widgets(record['widgets'])
            text_widgets = get_text_widgets(widgets)
            viz_widgets = get_visualization_widgets(widgets)

            # generate a description for this dashboard, since Redash does not have descriptions
            dash_data['description'] = generate_dashboard_description(
                text_widgets, viz_widgets)

            yield DashboardMetadata(**dash_data)

            last_mod_data = {
                'last_modified_timestamp': record['last_modified_timestamp']
            }
            last_mod_data.update(identity_data)

            yield DashboardLastModifiedTimestamp(**last_mod_data)

            owner_data = {'email': record['user']['email']}
            owner_data.update(identity_data)

            yield DashboardOwner(**owner_data)

            table_keys = set()

            for viz in viz_widgets:
                query_data = {
                    'query_id': str(viz.query_id),
                    'query_name': viz.query_name,
                    'url': self._redash_base_url + viz.query_relative_url,
                    'query_text': viz.raw_query
                }

                query_data.update(identity_data)
                yield DashboardQuery(**query_data)

                chart_data = {
                    'query_id': str(viz.query_id),
                    'chart_id': str(viz.visualization_id),
                    'chart_name': viz.visualization_name,
                    'chart_type': viz.visualization_type,
                }
                chart_data.update(identity_data)
                yield DashboardChart(**chart_data)

                # if a table parser is provided, retrieve tables from this viz
                if self._parse_tables:
                    for tbl in self._parse_tables(viz):
                        table_keys.add(tbl.key)

            if len(table_keys) > 0:
                yield DashboardTable(table_ids=list(table_keys),
                                     **identity_data)