@Override
    public void upgrade() {
        if (clusterConfigService.get(MigrationCompleted.class) != null
                || clusterConfigService.get(LEGACY_MIGRATION_NAME, MigrationCompleted.class) != null) {
            LOG.debug("Migration already completed.");
            return;
        }

        final List<String> viewIds = new ArrayList<>();
        final FindIterable<Document> documents = viewsCollections.find();
        for (final Document view : documents) {
            try {
                final Document states = view.get("state", Document.class);
                states.forEach((String id, Object obj) -> {
                    final Document state = (Document) obj;
                    if (state.get("widgets") instanceof List) {
                        @SuppressWarnings("unchecked")
                        final List<Document> widgets = (List) state.get("widgets");
                        for (final Document widget : widgets) {
                            final String type = widget.getString("type");
                            if (type.equals("messages")) {
                                final Document config = widget.get("config", Document.class);
                                @SuppressWarnings("unchecked")
                                final List<String> fields = (List) config.get("fields");
                                fields.add(0, "timestamp");
                                if (fields.contains("message")) {
                                    config.put("show_message_row", true);
                                    config.remove("message");
                                }
                                config.put("fields", fields);
                            }
                        }
                    }
                    createAllMessagesWidget(view, id, state);
                });
                viewsCollections.updateOne(new BasicDBObject("_id", view.getObjectId("_id")), new Document("$set", view));
                final String viewId = view.getObjectId("_id").toString();
                viewIds.add(viewId);
            } catch (Exception e) {
                final String viewId = view.getObjectId("_id").toString();
                LOG.error("Could not migrate view with ID {}", viewId);
            }
        }

        clusterConfigService.write(MigrationCompleted.create(viewIds));
    }
@Test
    @MongoDBFixtures("V20190304102700_MigrateMessageListStructureTest.json")
    public void testMigratingViewStructure() {
        final BasicDBObject dbQuery1 = new BasicDBObject();
        dbQuery1.put("_id", new ObjectId("58458e442f857c314491344e"));
        final MongoCollection<Document> collection = mongodb.mongoConnection()
                .getMongoDatabase()
                .getCollection("views");

        migration.upgrade();

        final FindIterable<Document> views = collection.find(dbQuery1);
        final Document view1 = views.first();

        @SuppressWarnings("unchecked")
        final List<Document> widgets1 = (List) view1.get("state", Document.class).get("a2a804b7-27cf-4cac-8015-58d9a9640d33", Document.class).get("widgets");
        assertThat(widgets1.size()).isEqualTo(2);
        assertThat(widgets1.stream().filter(widget -> widget.getString("type").equals("messages")).count()).isEqualTo(1);
        assertThat(widgets1.stream().filter(widget -> widget.getString("type").equals("messages")).allMatch((widget) -> {
            final Document config = widget.get("config", Document.class);
            @SuppressWarnings("unchecked")
            final List<String> fields = (List) config.get("fields");
            final boolean startWithTimestamp = fields.get(0).contains("timestamp");
            final boolean showMessageRow = config.getBoolean("show_message_row");
            return startWithTimestamp && showMessageRow;
        })).isTrue();

        final BasicDBObject dbQuery2 = new BasicDBObject();
        dbQuery2.put("_id", new ObjectId("58458e442f857c314491344f"));

        final FindIterable<Document> views2 = collection.find(dbQuery2);
        final Document view2 = views2.first();

        final Document states = view2.get("state", Document.class);
        assertThat(states.values().size()).isEqualTo(13);
        assertThat(states.keySet()).containsExactly(
                "7c042319-530a-41b9-9dbb-9676fb1da1a4",
                "9e5144be-a445-4289-a4cc-0f55142524bc",
                "c13b2482-60e7-4b1e-98c9-0df8d6da8230",
                "5adc9297-dfc8-4fd9-b422-cbb097715a62",
                "ade8c853-503c-407f-b125-efbe2d368973",
                "cc2bf983-b398-4295-bf01-1c10ed1a97e1",
                "64feccae-9447-40ef-a401-79a7972078a2",
                "7c7e04c6-f9f0-495c-91cc-865f60687f8c",
                "eeaa8838-616f-40c0-88c0-1059ac64f37e",
                "91c6f8c9-024c-48ec-a869-90548fad218a",
                "955a71f2-673a-4e1c-a99f-ef97b1b4ae71",
                "343ff7b6-4554-49d4-bc0b-1339fdc5dac0",
                "7a84d053-e40a-48c1-a433-97521f7ce7ef");

        states.values().forEach(state -> {
            @SuppressWarnings("unchecked")
            final List<Document> widgets2 = (List) ((Document) state).get("widgets");
            assertThat(widgets2.stream().filter(widget -> widget.getString("type").equals("messages")).count()).isGreaterThan(0);
            widgets2.stream().filter(widget -> widget.getString("type").equals("messages")).forEach((widget) -> {
                final Document config = widget.get("config", Document.class);
                @SuppressWarnings("unchecked")
                final List<String> fields = (List) config.get("fields");
                final boolean startWithTimestamp = fields.get(0).contains("timestamp");
                final boolean showMessageRow = config.getBoolean("show_message_row");
                assertThat(startWithTimestamp).isTrue();
                assertThat(showMessageRow).isTrue();
            });
        });
    }