Refactor getting all namespaces

This commit is contained in:
kolaente 2021-02-16 22:35:55 +01:00
parent 2f1a3fea88
commit aaeb89db4f
No known key found for this signature in database
GPG key ID: F40E70337AB24C9B
2 changed files with 311 additions and 194 deletions

View file

@ -181,7 +181,7 @@ type NamespaceWithLists struct {
Lists []*List `xorm:"-" json:"lists"` Lists []*List `xorm:"-" json:"lists"`
} }
func makeNamespaceSliceFromMap(namespaces map[int64]*NamespaceWithLists, userMap map[int64]*user.User, subscriptions map[int64]*Subscription) []*NamespaceWithLists { func makeNamespaceSlice(namespaces map[int64]*NamespaceWithLists, userMap map[int64]*user.User, subscriptions map[int64]*Subscription) []*NamespaceWithLists {
all := make([]*NamespaceWithLists, 0, len(namespaces)) all := make([]*NamespaceWithLists, 0, len(namespaces))
for _, n := range namespaces { for _, n := range namespaces {
n.Owner = userMap[n.OwnerID] n.Owner = userMap[n.OwnerID]
@ -195,6 +195,254 @@ func makeNamespaceSliceFromMap(namespaces map[int64]*NamespaceWithLists, userMap
return all return all
} }
func getNamespaceFilterCond(search string) (filterCond builder.Cond) {
filterCond = &builder.Like{"namespaces.title", "%" + search + "%"}
if search == "" {
return
}
vals := strings.Split(search, ",")
if len(vals) == 0 {
return
}
ids := []int64{}
for _, val := range vals {
v, err := strconv.ParseInt(val, 10, 64)
if err != nil {
log.Debugf("Namespace search string part '%s' is not a number: %s", val, err)
continue
}
ids = append(ids, v)
}
if len(ids) > 0 {
filterCond = builder.In("namespaces.id", ids)
}
return
}
func getNamespaceArchivedCond(archived bool) builder.Cond {
// Adding a 1=1 condition by default here because xorm always needs a condition and cannot handle nil conditions
var isArchivedCond builder.Cond = builder.Eq{"1": 1}
if !archived {
isArchivedCond = builder.And(
builder.Eq{"namespaces.is_archived": false},
)
}
return isArchivedCond
}
func getNamespacesWithLists(s *xorm.Session, namespaces *map[int64]*NamespaceWithLists, search string, isArchived bool, page, perPage int, userID int64) (numberOfTotalItems int64, err error) {
isArchivedCond := getNamespaceArchivedCond(isArchived)
filterCond := getNamespaceFilterCond(search)
limit, start := getLimitFromPageIndex(page, perPage)
query := s.Select("namespaces.*").
Table("namespaces").
Join("LEFT", "team_namespaces", "namespaces.id = team_namespaces.namespace_id").
Join("LEFT", "team_members", "team_members.team_id = team_namespaces.team_id").
Join("LEFT", "users_namespace", "users_namespace.namespace_id = namespaces.id").
Where("team_members.user_id = ?", userID).
Or("namespaces.owner_id = ?", userID).
Or("users_namespace.user_id = ?", userID).
GroupBy("namespaces.id").
Where(filterCond).
Where(isArchivedCond)
if limit > 0 {
query = query.Limit(limit, start)
}
err = query.Find(namespaces)
if err != nil {
return 0, err
}
numberOfTotalItems, err = s.
Table("namespaces").
Join("LEFT", "team_namespaces", "namespaces.id = team_namespaces.namespace_id").
Join("LEFT", "team_members", "team_members.team_id = team_namespaces.team_id").
Join("LEFT", "users_namespace", "users_namespace.namespace_id = namespaces.id").
Where("team_members.user_id = ?", userID).
Or("namespaces.owner_id = ?", userID).
Or("users_namespace.user_id = ?", userID).
And("namespaces.is_archived = false").
GroupBy("namespaces.id").
Where(filterCond).
Where(isArchivedCond).
Count(&NamespaceWithLists{})
return numberOfTotalItems, err
}
func getNamespaceOwnerIDs(namespaces map[int64]*NamespaceWithLists) (namespaceIDs, ownerIDs []int64) {
for _, nsp := range namespaces {
namespaceIDs = append(namespaceIDs, nsp.ID)
ownerIDs = append(ownerIDs, nsp.OwnerID)
}
return
}
func getNamespaceSubscriptions(s *xorm.Session, namespaceIDs []int64, userID int64) (map[int64]*Subscription, error) {
subscriptions := []*Subscription{}
err := s.
Where("entity_type = ? AND user_id = ?", SubscriptionEntityNamespace, userID).
In("entity_id", namespaceIDs).
Find(&subscriptions)
if err != nil {
return nil, err
}
subscriptionsMap := make(map[int64]*Subscription)
for _, sub := range subscriptions {
sub.Entity = sub.EntityType.String()
subscriptionsMap[sub.EntityID] = sub
}
return subscriptionsMap, err
}
func getListsForNamespaces(s *xorm.Session, namespaceIDs []int64, archived bool) ([]*List, error) {
lists := []*List{}
listQuery := s.
In("namespace_id", namespaceIDs)
if !archived {
listQuery.And("is_archived = false")
}
err := listQuery.Find(&lists)
return lists, err
}
func getSharedListsInNamespace(s *xorm.Session, archived bool, doer *user.User) (sharedListsNamespace *NamespaceWithLists, err error) {
// Create our pseudo namespace to hold the shared lists
sharedListsPseudonamespace := SharedListsPseudoNamespace
sharedListsPseudonamespace.Owner = doer
sharedListsNamespace = &NamespaceWithLists{
sharedListsPseudonamespace,
[]*List{},
}
// Get all lists individually shared with our user (not via a namespace)
individualLists := []*List{}
iListQuery := s.Select("l.*").
Table("list").
Alias("l").
Join("LEFT", []string{"team_list", "tl"}, "l.id = tl.list_id").
Join("LEFT", []string{"team_members", "tm"}, "tm.team_id = tl.team_id").
Join("LEFT", []string{"users_list", "ul"}, "ul.list_id = l.id").
Where(builder.And(
builder.Eq{"tm.user_id": doer.ID},
builder.Neq{"l.owner_id": doer.ID},
)).
Or(builder.And(
builder.Eq{"ul.user_id": doer.ID},
builder.Neq{"l.owner_id": doer.ID},
)).
GroupBy("l.id")
if !archived {
iListQuery.And("l.is_archived = false")
}
err = iListQuery.Find(&individualLists)
if err != nil {
return
}
// Make the namespace -1 so we now later which one it was
// + Append it to all lists we already have
for _, l := range individualLists {
l.NamespaceID = sharedListsNamespace.ID
}
sharedListsNamespace.Lists = individualLists
// Remove the sharedListsPseudonamespace if we don't have any shared lists
if len(individualLists) == 0 {
sharedListsNamespace = nil
}
return
}
func getFavoriteLists(s *xorm.Session, lists []*List, namespaceIDs []int64, doer *user.User) (favoriteNamespace *NamespaceWithLists, err error) {
// Create our pseudo namespace with favorite lists
pseudoFavoriteNamespace := FavoritesPseudoNamespace
pseudoFavoriteNamespace.Owner = doer
favoriteNamespace = &NamespaceWithLists{
Namespace: pseudoFavoriteNamespace,
Lists: []*List{{}},
}
*favoriteNamespace.Lists[0] = FavoritesPseudoList // Copying the list to be able to modify it later
for _, list := range lists {
if !list.IsFavorite {
continue
}
favoriteNamespace.Lists = append(favoriteNamespace.Lists, list)
}
// Check if we have any favorites or favorited lists and remove the favorites namespace from the list if not
var favoriteCount int64
favoriteCount, err = s.
Join("INNER", "list", "tasks.list_id = list.id").
Join("INNER", "namespaces", "list.namespace_id = namespaces.id").
Where(builder.And(builder.Eq{"tasks.is_favorite": true}, builder.In("namespaces.id", namespaceIDs))).
Count(&Task{})
if err != nil {
return
}
// If we don't have any favorites in the favorites pseudo list, remove that pseudo list from the namespace
if favoriteCount == 0 {
for in, l := range favoriteNamespace.Lists {
if l.ID == FavoritesPseudoList.ID {
favoriteNamespace.Lists = append(favoriteNamespace.Lists[:in], favoriteNamespace.Lists[in+1:]...)
break
}
}
}
// If we don't have any favorites in the namespace, remove it
if len(favoriteNamespace.Lists) == 0 {
return nil, nil
}
return
}
func getSavedFilters(s *xorm.Session, doer *user.User) (savedFiltersNamespace *NamespaceWithLists, err error) {
savedFilters, err := getSavedFiltersForUser(s, doer)
if err != nil {
return
}
if len(savedFilters) == 0 {
return nil, nil
}
savedFiltersPseudoNamespace := SavedFiltersPseudoNamespace
savedFiltersPseudoNamespace.Owner = doer
savedFiltersNamespace = &NamespaceWithLists{
Namespace: savedFiltersPseudoNamespace,
Lists: make([]*List, 0, len(savedFilters)),
}
for _, filter := range savedFilters {
savedFiltersNamespace.Lists = append(savedFiltersNamespace.Lists, &List{
ID: getListIDFromSavedFilterID(filter.ID),
Title: filter.Title,
Description: filter.Description,
Created: filter.Created,
Updated: filter.Updated,
Owner: doer,
})
}
return
}
// ReadAll gets all namespaces a user has access to // ReadAll gets all namespaces a user has access to
// @Summary Get all namespaces a user has access to // @Summary Get all namespaces a user has access to
// @Description Returns all namespaces a user has access to. // @Description Returns all namespaces a user has access to.
@ -228,110 +476,30 @@ func (n *Namespace) ReadAll(s *xorm.Session, a web.Auth, search string, page int
return nil, 0, 0, err return nil, 0, 0, err
} }
// Adding a 1=1 condition by default here because xorm always needs a condition and cannot handle nil conditions numberOfTotalItems, err = getNamespacesWithLists(s, &namespaces, search, n.IsArchived, page, perPage, doer.ID)
var isArchivedCond builder.Cond = builder.Eq{"1": 1}
if !n.IsArchived {
isArchivedCond = builder.And(
builder.Eq{"namespaces.is_archived": false},
)
}
var filterCond builder.Cond = &builder.Like{"namespaces.title", "%" + search + "%"}
vals := strings.Split(search, ",")
ids := []int64{}
for _, val := range vals {
v, err := strconv.ParseInt(val, 10, 64)
if err != nil {
log.Debugf("Namespace search string part '%s' is not a number: %s", val, err)
continue
}
ids = append(ids, v)
}
if len(ids) > 0 {
filterCond = builder.In("namespaces.id", ids)
}
limit, start := getLimitFromPageIndex(page, perPage)
query := s.Select("namespaces.*").
Table("namespaces").
Join("LEFT", "team_namespaces", "namespaces.id = team_namespaces.namespace_id").
Join("LEFT", "team_members", "team_members.team_id = team_namespaces.team_id").
Join("LEFT", "users_namespace", "users_namespace.namespace_id = namespaces.id").
Where("team_members.user_id = ?", doer.ID).
Or("namespaces.owner_id = ?", doer.ID).
Or("users_namespace.user_id = ?", doer.ID).
GroupBy("namespaces.id").
Where(filterCond).
Where(isArchivedCond)
if limit > 0 {
query = query.Limit(limit, start)
}
err = query.Find(&namespaces)
if err != nil { if err != nil {
return nil, 0, 0, err return nil, 0, 0, err
} }
numberOfTotalItems, err = s. namespaceIDs, ownerIDs := getNamespaceOwnerIDs(namespaces)
Table("namespaces").
Join("LEFT", "team_namespaces", "namespaces.id = team_namespaces.namespace_id"). subscriptionsMap, err := getNamespaceSubscriptions(s, namespaceIDs, doer.ID)
Join("LEFT", "team_members", "team_members.team_id = team_namespaces.team_id").
Join("LEFT", "users_namespace", "users_namespace.namespace_id = namespaces.id").
Where("team_members.user_id = ?", doer.ID).
Or("namespaces.owner_id = ?", doer.ID).
Or("users_namespace.user_id = ?", doer.ID).
And("namespaces.is_archived = false").
GroupBy("namespaces.id").
Where(filterCond).
Count(&NamespaceWithLists{})
if err != nil { if err != nil {
return nil, 0, 0, err return nil, 0, 0, err
} }
// Make a list of namespace ids ownerMap, err := user.GetUsersByIDs(s, ownerIDs)
var namespaceids []int64
var userIDs []int64
for _, nsp := range namespaces {
namespaceids = append(namespaceids, nsp.ID)
userIDs = append(userIDs, nsp.OwnerID)
}
// Get all subscriptions
subscriptions := []*Subscription{}
err = s.
Where("entity_type = ? AND user_id = ?", SubscriptionEntityNamespace, a.GetID()).
In("entity_id", namespaceids).
Find(&subscriptions)
if err != nil {
return nil, 0, 0, err
}
subscriptionsMap := make(map[int64]*Subscription)
for _, sub := range subscriptions {
sub.Entity = sub.EntityType.String()
subscriptionsMap[sub.EntityID] = sub
}
// Get all owners
userMap := make(map[int64]*user.User)
err = s.In("id", userIDs).Find(&userMap)
if err != nil { if err != nil {
return nil, 0, 0, err return nil, 0, 0, err
} }
if n.NamespacesOnly { if n.NamespacesOnly {
all := makeNamespaceSliceFromMap(namespaces, userMap, subscriptionsMap) all := makeNamespaceSlice(namespaces, ownerMap, subscriptionsMap)
return all, len(all), numberOfTotalItems, nil return all, len(all), numberOfTotalItems, nil
} }
// Get all lists // Get all lists
lists := []*List{} lists, err := getListsForNamespaces(s, namespaceIDs, n.IsArchived)
listQuery := s.
In("namespace_id", namespaceids)
if !n.IsArchived {
listQuery.And("is_archived = false")
}
err = listQuery.Find(&lists)
if err != nil { if err != nil {
return nil, 0, 0, err return nil, 0, 0, err
} }
@ -339,134 +507,54 @@ func (n *Namespace) ReadAll(s *xorm.Session, a web.Auth, search string, page int
/////////////// ///////////////
// Shared Lists // Shared Lists
// Create our pseudo namespace to hold the shared lists sharedListsNamespace, err := getSharedListsInNamespace(s, n.IsArchived, doer)
sharedListsPseudonamespace := SharedListsPseudoNamespace
sharedListsPseudonamespace.Owner = doer
namespaces[sharedListsPseudonamespace.ID] = &NamespaceWithLists{
sharedListsPseudonamespace,
[]*List{},
}
// Get all lists individually shared with our user (not via a namespace)
individualLists := []*List{}
iListQuery := s.Select("l.*").
Table("list").
Alias("l").
Join("LEFT", []string{"team_list", "tl"}, "l.id = tl.list_id").
Join("LEFT", []string{"team_members", "tm"}, "tm.team_id = tl.team_id").
Join("LEFT", []string{"users_list", "ul"}, "ul.list_id = l.id").
Where(builder.And(
builder.Eq{"tm.user_id": doer.ID},
builder.Neq{"l.owner_id": doer.ID},
)).
Or(builder.And(
builder.Eq{"ul.user_id": doer.ID},
builder.Neq{"l.owner_id": doer.ID},
)).
GroupBy("l.id")
if !n.IsArchived {
iListQuery.And("l.is_archived = false")
}
err = iListQuery.Find(&individualLists)
if err != nil { if err != nil {
return nil, 0, 0, err return nil, 0, 0, err
} }
// Make the namespace -1 so we now later which one it was if sharedListsNamespace != nil {
// + Append it to all lists we already have namespaces[sharedListsNamespace.ID] = sharedListsNamespace
for _, l := range individualLists { lists = append(lists, sharedListsNamespace.Lists...)
l.NamespaceID = -1
lists = append(lists, l)
}
// Remove the sharedListsPseudonamespace if we don't have any shared lists
if len(individualLists) == 0 {
delete(namespaces, sharedListsPseudonamespace.ID)
}
// More details for the lists
err = addListDetails(s, lists)
if err != nil {
return nil, 0, 0, err
} }
///////////////// /////////////////
// Favorite lists // Favorite lists
// Create our pseudo namespace with favorite lists favoritesNamespace, err := getFavoriteLists(s, lists, namespaceIDs, doer)
pseudoFavoriteNamespace := FavoritesPseudoNamespace
pseudoFavoriteNamespace.Owner = doer
namespaces[pseudoFavoriteNamespace.ID] = &NamespaceWithLists{
Namespace: pseudoFavoriteNamespace,
Lists: []*List{{}},
}
*namespaces[pseudoFavoriteNamespace.ID].Lists[0] = FavoritesPseudoList // Copying the list to be able to modify it later
for _, list := range lists {
if list.IsFavorite {
namespaces[pseudoFavoriteNamespace.ID].Lists = append(namespaces[pseudoFavoriteNamespace.ID].Lists, list)
}
namespaces[list.NamespaceID].Lists = append(namespaces[list.NamespaceID].Lists, list)
}
// Check if we have any favorites or favorited lists and remove the favorites namespace from the list if not
var favoriteCount int64
favoriteCount, err = s.
Join("INNER", "list", "tasks.list_id = list.id").
Join("INNER", "namespaces", "list.namespace_id = namespaces.id").
Where(builder.And(builder.Eq{"tasks.is_favorite": true}, builder.In("namespaces.id", namespaceids))).
Count(&Task{})
if err != nil { if err != nil {
return nil, 0, 0, err return nil, 0, 0, err
} }
// If we don't have any favorites in the favorites pseudo list, remove that pseudo list from the namespace if favoritesNamespace != nil {
if favoriteCount == 0 { namespaces[favoritesNamespace.ID] = favoritesNamespace
for in, l := range namespaces[pseudoFavoriteNamespace.ID].Lists {
if l.ID == FavoritesPseudoList.ID {
namespaces[pseudoFavoriteNamespace.ID].Lists = append(namespaces[pseudoFavoriteNamespace.ID].Lists[:in], namespaces[pseudoFavoriteNamespace.ID].Lists[in+1:]...)
break
}
}
}
// If we don't have any favorites in the namespace, remove it
if len(namespaces[pseudoFavoriteNamespace.ID].Lists) == 0 {
delete(namespaces, pseudoFavoriteNamespace.ID)
} }
///////////////// /////////////////
// Saved Filters // Saved Filters
savedFilters, err := getSavedFiltersForUser(s, a) savedFiltersNamespace, err := getSavedFilters(s, doer)
if err != nil { if err != nil {
return nil, 0, 0, err return nil, 0, 0, err
} }
if len(savedFilters) > 0 { if savedFiltersNamespace != nil {
savedFiltersPseudoNamespace := SavedFiltersPseudoNamespace namespaces[savedFiltersNamespace.ID] = savedFiltersNamespace
savedFiltersPseudoNamespace.Owner = doer
namespaces[savedFiltersPseudoNamespace.ID] = &NamespaceWithLists{
Namespace: savedFiltersPseudoNamespace,
Lists: make([]*List, 0, len(savedFilters)),
}
for _, filter := range savedFilters {
namespaces[savedFiltersPseudoNamespace.ID].Lists = append(namespaces[savedFiltersPseudoNamespace.ID].Lists, &List{
ID: getListIDFromSavedFilterID(filter.ID),
Title: filter.Title,
Description: filter.Description,
Created: filter.Created,
Updated: filter.Updated,
Owner: doer,
})
}
} }
////////////////////// //////////////////////
// Put it all together (and sort it) // Put it all together
all := makeNamespaceSliceFromMap(namespaces, userMap, subscriptionsMap)
return all, len(all), numberOfTotalItems, nil err = addListDetails(s, lists)
if err != nil {
return
}
for _, list := range lists {
namespaces[list.NamespaceID].Lists = append(namespaces[list.NamespaceID].Lists, list)
}
all := makeNamespaceSlice(namespaces, ownerMap, subscriptionsMap)
return all, len(all), numberOfTotalItems, err
} }
// Create implements the creation method via the interface // Create implements the creation method via the interface

View file

@ -198,10 +198,11 @@ func TestNamespace_ReadAll(t *testing.T) {
user11 := &user.User{ID: 11} user11 := &user.User{ID: 11}
user12 := &user.User{ID: 12} user12 := &user.User{ID: 12}
s := db.NewSession()
defer s.Close()
t.Run("normal", func(t *testing.T) { t.Run("normal", func(t *testing.T) {
db.LoadAndAssertFixtures(t)
s := db.NewSession()
defer s.Close()
n := &Namespace{} n := &Namespace{}
nn, _, _, err := n.ReadAll(s, user1, "", 1, -1) nn, _, _, err := n.ReadAll(s, user1, "", 1, -1)
assert.NoError(t, err) assert.NoError(t, err)
@ -220,6 +221,10 @@ func TestNamespace_ReadAll(t *testing.T) {
} }
}) })
t.Run("namespaces only", func(t *testing.T) { t.Run("namespaces only", func(t *testing.T) {
db.LoadAndAssertFixtures(t)
s := db.NewSession()
defer s.Close()
n := &Namespace{ n := &Namespace{
NamespacesOnly: true, NamespacesOnly: true,
} }
@ -234,6 +239,10 @@ func TestNamespace_ReadAll(t *testing.T) {
} }
}) })
t.Run("ids only", func(t *testing.T) { t.Run("ids only", func(t *testing.T) {
db.LoadAndAssertFixtures(t)
s := db.NewSession()
defer s.Close()
n := &Namespace{ n := &Namespace{
NamespacesOnly: true, NamespacesOnly: true,
} }
@ -246,6 +255,10 @@ func TestNamespace_ReadAll(t *testing.T) {
assert.Equal(t, int64(14), namespaces[1].ID) assert.Equal(t, int64(14), namespaces[1].ID)
}) })
t.Run("ids only but ids with other people's namespace", func(t *testing.T) { t.Run("ids only but ids with other people's namespace", func(t *testing.T) {
db.LoadAndAssertFixtures(t)
s := db.NewSession()
defer s.Close()
n := &Namespace{ n := &Namespace{
NamespacesOnly: true, NamespacesOnly: true,
} }
@ -257,6 +270,10 @@ func TestNamespace_ReadAll(t *testing.T) {
assert.Equal(t, int64(1), namespaces[0].ID) assert.Equal(t, int64(1), namespaces[0].ID)
}) })
t.Run("archived", func(t *testing.T) { t.Run("archived", func(t *testing.T) {
db.LoadAndAssertFixtures(t)
s := db.NewSession()
defer s.Close()
n := &Namespace{ n := &Namespace{
IsArchived: true, IsArchived: true,
} }
@ -270,6 +287,10 @@ func TestNamespace_ReadAll(t *testing.T) {
assert.Equal(t, int64(-1), namespaces[2].ID) // The third one should be the one with the shared namespaces assert.Equal(t, int64(-1), namespaces[2].ID) // The third one should be the one with the shared namespaces
}) })
t.Run("no favorites", func(t *testing.T) { t.Run("no favorites", func(t *testing.T) {
db.LoadAndAssertFixtures(t)
s := db.NewSession()
defer s.Close()
n := &Namespace{} n := &Namespace{}
nn, _, _, err := n.ReadAll(s, user11, "", 1, -1) nn, _, _, err := n.ReadAll(s, user11, "", 1, -1)
namespaces := nn.([]*NamespaceWithLists) namespaces := nn.([]*NamespaceWithLists)
@ -278,6 +299,10 @@ func TestNamespace_ReadAll(t *testing.T) {
assert.NotEqual(t, FavoritesPseudoNamespace.ID, namespaces[0].ID) assert.NotEqual(t, FavoritesPseudoNamespace.ID, namespaces[0].ID)
}) })
t.Run("no favorite tasks but namespace", func(t *testing.T) { t.Run("no favorite tasks but namespace", func(t *testing.T) {
db.LoadAndAssertFixtures(t)
s := db.NewSession()
defer s.Close()
n := &Namespace{} n := &Namespace{}
nn, _, _, err := n.ReadAll(s, user12, "", 1, -1) nn, _, _, err := n.ReadAll(s, user12, "", 1, -1)
namespaces := nn.([]*NamespaceWithLists) namespaces := nn.([]*NamespaceWithLists)
@ -287,6 +312,10 @@ func TestNamespace_ReadAll(t *testing.T) {
assert.NotEqual(t, 0, namespaces[0].Lists) assert.NotEqual(t, 0, namespaces[0].Lists)
}) })
t.Run("no saved filters", func(t *testing.T) { t.Run("no saved filters", func(t *testing.T) {
db.LoadAndAssertFixtures(t)
s := db.NewSession()
defer s.Close()
n := &Namespace{} n := &Namespace{}
nn, _, _, err := n.ReadAll(s, user11, "", 1, -1) nn, _, _, err := n.ReadAll(s, user11, "", 1, -1)
namespaces := nn.([]*NamespaceWithLists) namespaces := nn.([]*NamespaceWithLists)