static void ValidateSourceDatabaseMapping(MetabaseState state, IReadOnlyDictionary <DatabaseId, DatabaseId> databaseMapping)
        {
            var allDatabaseIds = state.Cards.SelectMany(c => new[] { c.DatabaseId, c.DatasetQuery.DatabaseId });
            var missingDatabaseIdsInMapping = allDatabaseIds.Where(x => databaseMapping.ContainsKey(x) == false).Distinct().ToList();

            if (missingDatabaseIdsInMapping.Count > 0)
            {
                throw new Exception("Missing databases in mapping: " + string.Join(",", missingDatabaseIdsInMapping));
            }
        }
        /// <summary>
        /// Export Metabase data
        /// </summary>
        public static async Task <MetabaseState> Export(this MetabaseApi api, bool excludePersonalCollections)
        {
            var mappedCollections = await api.GetMappedCollections(excludePersonalCollections);

            var mappedCards = await api.GetMappedCards(mappedCollections.CollectionMapping);

            var mappedDashboards = await api.GetMappedDashboards(mappedCards.CardMapping, mappedCollections.Collections);

            var state = new MetabaseState
            {
                Cards       = mappedCards.Cards.ToArray(),
                Dashboards  = mappedDashboards.Dashboards.ToArray(),
                Collections = mappedCollections.Collections.ToArray(),
            };

            return(state);
        }
        /// <summary>
        /// Imports Metabase data. DELETES all current dashboards/questions/etc.
        /// </summary>
        /// <param name="api"></param>
        /// <param name="state"></param>
        /// <param name="databaseMapping"></param>
        /// <returns></returns>
        public static async Task Import(this MetabaseApi api, MetabaseState state, IReadOnlyDictionary <DatabaseId, DatabaseId> databaseMapping)
        {
            // firstly check that the database mapping is complete and correct
            await api.ValidateDatabaseMapping(state, databaseMapping);

            // now map/create collections then cards then dashboards

            Console.WriteLine("Creating collections...");
            var collectionMapping = await api.MapAndCreateCollections(state.Collections);

            Console.WriteLine("Deleting all dashboards...");
            await api.DeleteAllDashboards();

            Console.WriteLine("Deleting all cards...");
            await api.DeleteAllCards();

            Console.WriteLine("Creating cards...");
            var partialCardMapping = await state.Cards
                                     .Traverse(async cardFromState => {
                var source  = cardFromState.Id;
                var target  = await api.MapAndCreateCard(cardFromState, collectionMapping, databaseMapping);
                var mapping = new Mapping <CardId?>(source: source, target: target?.Id);
                return(mapping);
            });

            var cardMapping = partialCardMapping
                              .Where(x => x.Source.HasValue && x.Target.HasValue)
                              .Select(x => new Mapping <CardId>(x.Source.Value, x.Target.Value))
                              .ToList();

            Console.WriteLine("Creating dashboards...");
            foreach (var dashboard in state.Dashboards)
            {
                await api.MapAndCreateDashboard(dashboard, cardMapping);
            }
            Console.WriteLine("Done importing");
        }
 static async Task ValidateDatabaseMapping(this MetabaseApi api, MetabaseState state, IReadOnlyDictionary <DatabaseId, DatabaseId> databaseMapping)
 {
     ValidateSourceDatabaseMapping(state, databaseMapping);
     await api.ValidateTargetDatabaseMapping(databaseMapping);
 }