import org.sonar.server.ui.JRubyI18n;
import org.sonar.server.ui.PageDecorations;
import org.sonar.server.ui.Views;
+import org.sonar.server.ui.ws.GlobalNavigationAction;
+import org.sonar.server.ui.ws.NavigationWs;
import org.sonar.server.updatecenter.ws.UpdateCenterWs;
import org.sonar.server.user.DefaultUserService;
import org.sonar.server.user.DoPrivileged;
pico.addSingleton(ProjectSettingsFactory.class);
pico.addSingleton(IndexPurgeListener.class);
+ // UI
+ pico.addSingleton(GlobalNavigationAction.class);
+ pico.addSingleton(NavigationWs.class);
+
for (Object components : level4AddedComponents) {
pico.addSingleton(components);
}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.ui.ws;
+
+import org.sonar.api.config.Settings;
+import org.sonar.api.server.ws.Request;
+import org.sonar.api.server.ws.Response;
+import org.sonar.api.server.ws.WebService.NewController;
+import org.sonar.api.utils.text.JsonWriter;
+import org.sonar.api.web.NavigationSection;
+import org.sonar.api.web.Page;
+import org.sonar.core.dashboard.ActiveDashboardDao;
+import org.sonar.core.dashboard.DashboardDto;
+import org.sonar.server.ui.ViewProxy;
+import org.sonar.server.ui.Views;
+import org.sonar.server.user.UserSession;
+
+import java.util.List;
+
+public class GlobalNavigationAction implements NavigationAction {
+
+ private final ActiveDashboardDao activeDashboardDao;
+ private final Views views;
+ private final Settings settings;
+
+ public GlobalNavigationAction(ActiveDashboardDao activeDashboardDao, Views views, Settings settings) {
+ this.activeDashboardDao = activeDashboardDao;
+ this.views = views;
+ this.settings = settings;
+ }
+
+ @Override
+ public void define(NewController context) {
+ context.createAction("global")
+ .setDescription("Get information concerning global navigation for the current user.")
+ .setHandler(this)
+ .setInternal(true)
+ .setResponseExample(getClass().getResource("example-global.json"))
+ .setSince("5.2");
+ }
+
+ @Override
+ public void handle(Request request, Response response) throws Exception {
+ UserSession userSession = UserSession.get();
+
+ List<DashboardDto> dashboards = activeDashboardDao.selectGlobalDashboardsForUserLogin(userSession.login());
+
+ JsonWriter json = response.newJsonWriter().beginObject();
+ writeDashboards(json, dashboards);
+ writePages(json);
+ writeLogoProperties(json);
+ json.endObject().close();
+ }
+
+ private void writeDashboards(JsonWriter json, List<DashboardDto> dashboards) {
+ json.name("globalDashboards").beginArray();
+ for (DashboardDto dashboard : dashboards) {
+ json.beginObject()
+ .prop("key", dashboard.getKey())
+ .prop("name", dashboard.getName())
+ .endObject();
+ }
+ json.endArray();
+ }
+
+ private void writePages(JsonWriter json) {
+ json.name("globalPages").beginArray();
+ for (ViewProxy<Page> page : views.getPages(NavigationSection.HOME)) {
+ json.beginObject()
+ .prop("name", page.getTitle())
+ .prop("url", page.isController() ? page.getId() : String.format("/plugins/home/%s", page.getId()))
+ .endObject();
+ }
+ json.endArray();
+ }
+
+ private void writeLogoProperties(JsonWriter json) {
+ json.prop("logoUrl", settings.getString("sonar.lf.logoUrl"));
+ json.prop("logoWidth", settings.getString("sonar.lf.logoWidthPx"));
+ }
+
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.ui.ws;
+
+import org.sonar.server.ws.WsAction;
+
+public interface NavigationAction extends WsAction {
+
+ // Marker interface for navigation related actions
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.ui.ws;
+
+import org.sonar.api.server.ws.WebService;
+
+public class NavigationWs implements WebService {
+
+ private final NavigationAction[] actions;
+
+ public NavigationWs(NavigationAction... actions) {
+ this.actions = actions;
+ }
+
+ @Override
+ public void define(Context context) {
+ NewController navigation = context.createController("api/navigation")
+ .setDescription("Get information required to build navigation UI components")
+ .setSince("5.2");
+
+ for (NavigationAction action : actions) {
+ action.define(navigation);
+ }
+
+ navigation.done();
+ }
+
+}
--- /dev/null
+{
+ "globalDashboards": [
+ {
+ "id": 51,
+ "name": "Helicopter View"
+ }
+ ],
+ "globalPages": [
+ {
+ "name": "My Plugin",
+ "url": "/plugins/home/my-plugin"
+ }
+ ],
+ "logoUrl": "http://example.com/my-custom-logo.png",
+ "logoWidth": 135
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.ui.ws;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.sonar.api.config.Settings;
+import org.sonar.api.utils.System2;
+import org.sonar.api.web.Page;
+import org.sonar.api.web.View;
+import org.sonar.core.dashboard.ActiveDashboardDao;
+import org.sonar.core.dashboard.ActiveDashboardDto;
+import org.sonar.core.dashboard.DashboardDao;
+import org.sonar.core.dashboard.DashboardDto;
+import org.sonar.core.persistence.DbSession;
+import org.sonar.core.persistence.DbTester;
+import org.sonar.core.user.UserDto;
+import org.sonar.server.db.DbClient;
+import org.sonar.server.ui.Views;
+import org.sonar.server.user.MockUserSession;
+import org.sonar.server.user.db.UserDao;
+import org.sonar.server.ws.WsTester;
+
+import static org.mockito.Mockito.mock;
+
+public class GlobalNavigationActionTest {
+
+ @ClassRule
+ public static final DbTester dbTester = new DbTester();
+
+ private DbSession session;
+
+ private WsTester wsTester;
+
+ private UserDao userDao;
+
+ private DashboardDao dashboardDao;
+
+ private ActiveDashboardDao activeDashboardDao;
+
+ @Before
+ public void before() throws Exception {
+ dbTester.truncateTables();
+
+ userDao = new UserDao(dbTester.myBatis(), mock(System2.class));
+ dashboardDao = new DashboardDao(dbTester.myBatis());
+ activeDashboardDao = new ActiveDashboardDao(dbTester.myBatis());
+ DbClient dbClient = new DbClient(
+ dbTester.database(), dbTester.myBatis(), userDao, dashboardDao, activeDashboardDao);
+
+ session = dbClient.openSession(false);
+ }
+
+ @After
+ public void after() throws Exception {
+ session.close();
+ }
+
+ @Test
+ public void empty_call() throws Exception {
+ wsTester = new WsTester(new NavigationWs(new GlobalNavigationAction(activeDashboardDao, new Views(), new Settings())));
+
+ MockUserSession.set();
+
+ wsTester.newGetRequest("api/navigation", "global").execute().assertJson(getClass(), "empty.json");
+ }
+
+ @Test
+ public void only_logo() throws Exception {
+ wsTester = new WsTester(new NavigationWs(new GlobalNavigationAction(activeDashboardDao, new Views(),
+ new Settings()
+ .setProperty("sonar.lf.logoUrl", "http://some-server.tld/logo.png")
+ .setProperty("sonar.lf.logoWidthPx", "123"))));
+
+ MockUserSession.set();
+
+ wsTester.newGetRequest("api/navigation", "global").execute().assertJson(getClass(), "only_logo.json");
+ }
+
+ @Test
+ public void nominal_call_for_anonymous() throws Exception {
+ nominalSetup();
+
+ MockUserSession.set();
+
+ wsTester.newGetRequest("api/navigation", "global").execute().assertJson(getClass(), "anonymous.json");
+ }
+
+ @Test
+ public void nominal_call_for_user() throws Exception {
+ nominalSetup();
+
+ MockUserSession.set().setLogin("obiwan");
+
+ wsTester.newGetRequest("api/navigation", "global").execute().assertJson(getClass(), "user.json");
+ }
+
+ private void nominalSetup() {
+ createAndConfigureDashboardForUser();
+ createAndConfigureDashboardForAnonymous();
+
+ session.commit();
+
+ Settings settings = new Settings()
+ .setProperty("sonar.lf.logoUrl", "http://some-server.tld/logo.png")
+ .setProperty("sonar.lf.logoWidthPx", "123");
+ wsTester = new WsTester(new NavigationWs(new GlobalNavigationAction(activeDashboardDao, createViews(), settings)));
+ }
+
+ private void createAndConfigureDashboardForUser() {
+ UserDto user = createUser("obiwan", 42L);
+ userDao.insert(session, user);
+ DashboardDto defaultDashboardForUser = createDashboard(1L, "Default Dashboard for User", true);
+ dashboardDao.insert(defaultDashboardForUser);
+ activeDashboardDao.insert(createActiveDashboard(user.getId(), defaultDashboardForUser.getId(), 1));
+ }
+
+ private void createAndConfigureDashboardForAnonymous() {
+ DashboardDto defaultDashboardForAnonymous = createDashboard(2L, "Default Dashboard for Anonymous", true);
+ dashboardDao.insert(defaultDashboardForAnonymous);
+ activeDashboardDao.insert(createActiveDashboard(null, defaultDashboardForAnonymous.getId(), 1));
+ }
+
+ private UserDto createUser(String login, Long userId) {
+ return new UserDto()
+ .setActive(true)
+ .setId(userId)
+ .setLogin(login);
+ }
+
+ private DashboardDto createDashboard(long id, String name, boolean global) {
+ return new DashboardDto()
+ .setGlobal(global)
+ .setId(id)
+ .setName(name)
+ .setShared(true);
+ }
+
+ private ActiveDashboardDto createActiveDashboard(Long userId, Long dashboardId, int orderIndex) {
+ return new ActiveDashboardDto()
+ .setOrderIndex(orderIndex)
+ .setDashboardId(dashboardId)
+ .setUserId(userId);
+ }
+
+ private Views createViews() {
+ Page page = new Page() {
+ @Override
+ public String getTitle() {
+ return "My Plugin Page";
+ }
+ @Override
+ public String getId() {
+ return "my_plugin_page";
+ }
+ };
+ Page controller = new Page() {
+ @Override
+ public String getTitle() {
+ return "My Rails App";
+ }
+ @Override
+ public String getId() {
+ return "/my_rails_app";
+ }
+ };
+ return new Views(new View[] {page, controller});
+ }
+}
--- /dev/null
+{
+ "globalDashboards": [
+ {
+ "name": "Default Dashboard for Anonymous"
+ }
+ ],
+ "globalPages": [
+ {
+ "name": "My Plugin Page",
+ "url": "/plugins/home/my_plugin_page"
+ },
+ {
+ "name": "My Rails App",
+ "url": "/my_rails_app"
+ }
+ ],
+ "logoUrl": "http://some-server.tld/logo.png",
+ "logoWidth": "123"
+}
--- /dev/null
+{
+ "globalDashboards": [],
+ "globalPages": []
+}
--- /dev/null
+{
+ "globalDashboards": [],
+ "globalPages": [],
+ "logoUrl": "http://some-server.tld/logo.png",
+ "logoWidth": "123"
+}
--- /dev/null
+{
+ "globalDashboards": [
+ {
+ "name": "Default Dashboard for User"
+ }
+ ],
+ "globalPages": [
+ {
+ "name": "My Plugin Page",
+ "url": "/plugins/home/my_plugin_page"
+ },
+ {
+ "name": "My Rails App",
+ "url": "/my_rails_app"
+ }
+ ],
+ "logoUrl": "http://some-server.tld/logo.png",
+ "logoWidth": "123"
+}
import org.apache.ibatis.session.SqlSession;
import org.sonar.api.BatchComponent;
import org.sonar.api.ServerComponent;
+import org.sonar.core.persistence.DaoComponent;
import org.sonar.core.persistence.MyBatis;
-public class ActiveDashboardDao implements BatchComponent, ServerComponent {
+import javax.annotation.Nullable;
+
+import java.util.List;
+
+public class ActiveDashboardDao implements BatchComponent, ServerComponent, DaoComponent {
private MyBatis mybatis;
public void insert(ActiveDashboardDto activeDashboardDto) {
SqlSession session = mybatis.openSession(false);
- ActiveDashboardMapper mapper = session.getMapper(ActiveDashboardMapper.class);
try {
- mapper.insert(activeDashboardDto);
+ getMapper(session).insert(activeDashboardDto);
session.commit();
} finally {
MyBatis.closeQuietly(session);
public int selectMaxOrderIndexForNullUser() {
SqlSession session = mybatis.openSession(false);
- ActiveDashboardMapper mapper = session.getMapper(ActiveDashboardMapper.class);
try {
- Integer max = mapper.selectMaxOrderIndexForNullUser();
+ Integer max = getMapper(session).selectMaxOrderIndexForNullUser();
return max != null ? max.intValue() : 0;
} finally {
session.close();
}
+ public List<DashboardDto> selectGlobalDashboardsForUserLogin(@Nullable String login) {
+ SqlSession session = mybatis.openSession(false);
+ try {
+ return getMapper(session).selectGlobalDashboardsForUserLogin(login);
+ } finally {
+ session.close();
+ }
+ }
+
+ private ActiveDashboardMapper getMapper(SqlSession session) {
+ return session.getMapper(ActiveDashboardMapper.class);
+ }
}
package org.sonar.core.dashboard;
+import org.apache.ibatis.annotations.Param;
+
import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+
+import java.util.List;
public interface ActiveDashboardMapper {
@CheckForNull
Integer selectMaxOrderIndexForNullUser();
+ List<DashboardDto> selectGlobalDashboardsForUserLogin(@Nullable @Param("login") String login);
}
import org.apache.ibatis.session.SqlSession;
import org.sonar.api.BatchComponent;
import org.sonar.api.ServerComponent;
+import org.sonar.core.persistence.DaoComponent;
import org.sonar.core.persistence.MyBatis;
-public class DashboardDao implements BatchComponent, ServerComponent {
+public class DashboardDao implements BatchComponent, ServerComponent, DaoComponent {
private MyBatis mybatis;
WHERE user_id IS NULL
</select>
+ <sql id="dashboardColumns">
+ d.id, d.user_id as "userId", d.name, d.description, d.column_layout as "columnLayout",
+ d.shared, d.is_global as "global", d.created_at as "createdAt", d.updated_at as "updatedAt"
+ </sql>
+
+ <select id="selectGlobalDashboardsForUserLogin" parameterType="String" resultType="Dashboard">
+ SELECT <include refid="dashboardColumns" />
+ FROM dashboards d
+ INNER JOIN active_dashboards ad on d.id=ad.dashboard_id
+ LEFT OUTER JOIN users u on u.id=ad.user_id
+ WHERE d.is_global=${_true}
+ <choose>
+ <when test="login == null">
+ AND u.login IS NULL
+ </when>
+ <otherwise>
+ AND u.login=#{login}
+ </otherwise>
+ </choose>
+ ORDER BY order_index ASC
+ </select>
+
</mapper>
package org.sonar.core.dashboard;
import org.junit.Before;
+import org.junit.ClassRule;
import org.junit.Test;
-import org.sonar.core.persistence.AbstractDaoTestCase;
+import org.junit.experimental.categories.Category;
+import org.sonar.core.persistence.DbTester;
+import org.sonar.test.DbTests;
-import static org.hamcrest.Matchers.is;
-import static org.junit.Assert.assertThat;
+import static org.assertj.core.api.Assertions.assertThat;
-public class ActiveDashboardDaoTest extends AbstractDaoTestCase {
+@Category(DbTests.class)
+public class ActiveDashboardDaoTest {
+
+ @ClassRule
+ public static final DbTester dbTester = new DbTester();
private ActiveDashboardDao dao;
@Before
public void createDao() throws Exception {
- dao = new ActiveDashboardDao(getMyBatis());
+ dbTester.truncateTables();
+ dao = new ActiveDashboardDao(dbTester.myBatis());
}
@Test
public void shouldInsert() throws Exception {
- setupData("shouldInsert");
+ dbTester.prepareDbUnit(getClass(), "shouldInsert.xml");
ActiveDashboardDto dashboard = new ActiveDashboardDto();
dashboard.setDashboardId(2L);
dashboard.setOrderIndex(4);
dao.insert(dashboard);
- checkTables("shouldInsert", "active_dashboards");
+ dbTester.assertDbUnit(getClass(), "shouldInsert-result.xml", "active_dashboards");
}
@Test
public void shouldInsertWithNoUser() throws Exception {
- setupData("shouldInsert");
+ dbTester.prepareDbUnit(getClass(), "shouldInsert.xml");
ActiveDashboardDto dashboard = new ActiveDashboardDto();
dashboard.setDashboardId(2L);
dashboard.setOrderIndex(4);
dao.insert(dashboard);
- checkTables("shouldInsertWithNoUser", "active_dashboards");
+ dbTester.assertDbUnit(getClass(), "shouldInsertWithNoUser-result.xml", "active_dashboards");
}
@Test
public void shouldGetMaxOrderIndexForNullUser() throws Exception {
- setupData("shouldGetMaxOrderIndexForNullUser");
+ dbTester.prepareDbUnit(getClass(), "shouldGetMaxOrderIndexForNullUser.xml");
int index = dao.selectMaxOrderIndexForNullUser();
- assertThat(index, is(15));
+ assertThat(index).isEqualTo(15);
}
@Test
public void shouldGetZeroMaxOrderIndex() throws Exception {
- setupData("empty");
+ dbTester.prepareDbUnit(getClass(), "empty.xml");
int index = dao.selectMaxOrderIndexForNullUser();
- assertThat(index, is(0));
+ assertThat(index).isZero();
+ }
+
+ @Test
+ public void should_get_dashboards_for_anonymous() throws Exception {
+ dbTester.prepareDbUnit(getClass(), "shouldSelectDashboardsForAnonymous.xml");
+
+ assertThat(dao.selectGlobalDashboardsForUserLogin(null)).hasSize(2).extracting("id").containsExactly(2L, 1L);
}
+ @Test
+ public void should_get_dashboards_for_user() throws Exception {
+ dbTester.prepareDbUnit(getClass(), "shouldSelectDashboardsForUser.xml");
+
+ assertThat(dao.selectGlobalDashboardsForUserLogin("obiwan")).hasSize(2).extracting("id").containsExactly(2L, 1L);
+ }
}
package org.sonar.core.dashboard;
import org.junit.Before;
+import org.junit.ClassRule;
import org.junit.Test;
-import org.sonar.core.persistence.AbstractDaoTestCase;
+import org.junit.experimental.categories.Category;
+import org.sonar.core.persistence.DbTester;
+import org.sonar.test.DbTests;
import java.util.Date;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
-public class DashboardDaoTest extends AbstractDaoTestCase {
+@Category(DbTests.class)
+public class DashboardDaoTest {
+
+ @ClassRule
+ public static final DbTester dbTester = new DbTester();
private DashboardDao dao;
@Before
public void createDao() {
- dao = new DashboardDao(getMyBatis());
+ dbTester.truncateTables();
+ dao = new DashboardDao(dbTester.myBatis());
}
@Test
public void shouldSelectGlobalDashboard() {
- setupData("shouldSelectGlobalDashboard");
+ dbTester.prepareDbUnit(getClass(), "shouldSelectGlobalDashboard.xml");
DashboardDto dashboard = dao.selectGlobalDashboard("SQALE");
assertThat(dashboard.getId(), is(2L));
assertThat(dashboard.getUserId(), nullValue());
@Test
public void shouldInsert() {
- setupData("shouldInsert");
+ dbTester.prepareDbUnit(getClass(), "shouldInsert.xml");
Date aDate = new Date();
DashboardDto dashboardDto = new DashboardDto();
dao.insert(dashboardDto);
- checkTables("shouldInsert", new String[]{"created_at", "updated_at"}, "dashboards", "widgets", "widget_properties");
+ dbTester.assertDbUnit(getClass(), "shouldInsert-result.xml", new String[] {"created_at", "updated_at"}, "dashboards", "widgets", "widget_properties");
}
@Test
public void shouldInsertWithNullableColumns() throws Exception {
- setupData("shouldInsert");
+ dbTester.prepareDbUnit(getClass(), "shouldInsert.xml");
DashboardDto dashboardDto = new DashboardDto();
dashboardDto.setUserId(null);
dao.insert(dashboardDto);
- checkTables("shouldInsertWithNullableColumns", "dashboards", "widgets", "widget_properties");
+ dbTester.assertDbUnit(getClass(), "shouldInsertWithNullableColumns-result.xml", "dashboards", "widgets", "widget_properties");
}
}
--- /dev/null
+<dataset>
+
+ <users id="42" login="obiwan" name="Obiwan" email="obiwan@keno.bi"
+ created_at="1418215735482" updated_at="1418215735482" active="[true]"/>
+
+ <dashboards
+ id="1"
+ user_id="1"
+ name="My Dashboard"
+ description="Dashboard shared by admin"
+ column_layout="100%"
+ shared="[true]"
+ is_global="[true]"
+ />
+ <dashboards
+ id="2"
+ user_id="[null]"
+ name="Default Dashboard"
+ description="Dashboard provided by system"
+ column_layout="100%"
+ shared="[true]"
+ is_global="[true]"
+ />
+ <dashboards
+ id="3"
+ user_id="[null]"
+ name="Project Dashboard"
+ description="Won't appear, not global"
+ column_layout="100%"
+ shared="[true]"
+ is_global="[false]"
+ />
+ <dashboards
+ id="4"
+ user_id="[null]"
+ name="User Dashboard"
+ description="Won't appear, not anonymous"
+ column_layout="100%"
+ shared="[true]"
+ is_global="[true]"
+ />
+
+ <!-- Dashboard with ID 1 appears after ID 2 -->
+ <active_dashboards
+ id="1"
+ dashboard_id="1"
+ user_id="[null]"
+ order_index="2"/>
+ <!-- Dashboard with ID 2 appears before ID 1 -->
+ <active_dashboards
+ id="2"
+ dashboard_id="2"
+ user_id="[null]"
+ order_index="1"/>
+ <!-- Dashboard with ID 3 does not appear (not global) -->
+ <active_dashboards
+ id="3"
+ dashboard_id="3"
+ user_id="[null]"
+ order_index="1"/>
+ <!-- Dashboard with ID 4 does not appear (not anonymous) -->
+ <active_dashboards
+ id="4"
+ dashboard_id="4"
+ user_id="42"
+ order_index="1"/>
+
+</dataset>
--- /dev/null
+<dataset>
+
+ <users id="24" login="anakin" name="Anakin" email="anakin@skywalk.er"
+ created_at="1418215735482" updated_at="1418215735482" active="[true]"/>
+ <users id="42" login="obiwan" name="Obiwan" email="obiwan@keno.bi"
+ created_at="1418215735482" updated_at="1418215735482" active="[true]"/>
+
+ <dashboards
+ id="1"
+ user_id="1"
+ name="My Dashboard"
+ description="Dashboard shared by admin"
+ column_layout="100%"
+ shared="[true]"
+ is_global="[true]"
+ />
+ <dashboards
+ id="2"
+ user_id="[null]"
+ name="Default Dashboard"
+ description="Dashboard provided by system"
+ column_layout="100%"
+ shared="[true]"
+ is_global="[true]"
+ />
+ <dashboards
+ id="3"
+ user_id="[null]"
+ name="Project Dashboard"
+ description="Won't appear, not global"
+ column_layout="100%"
+ shared="[true]"
+ is_global="[false]"
+ />
+ <dashboards
+ id="4"
+ user_id="[null]"
+ name="Anonymous Dashboard"
+ description="Won't appear, anonymous"
+ column_layout="100%"
+ shared="[true]"
+ is_global="[true]"
+ />
+ <dashboards
+ id="5"
+ user_id="[null]"
+ name="Another User Dashboard"
+ description="Won't appear, different user"
+ column_layout="100%"
+ shared="[true]"
+ is_global="[true]"
+ />
+
+ <!-- Dashboard with ID 1 appears after ID 2 -->
+ <active_dashboards
+ id="1"
+ dashboard_id="1"
+ user_id="42"
+ order_index="2"/>
+ <!-- Dashboard with ID 2 appears before ID 1 -->
+ <active_dashboards
+ id="2"
+ dashboard_id="2"
+ user_id="42"
+ order_index="1"/>
+ <!-- Dashboard with ID 3 does not appear (not global) -->
+ <active_dashboards
+ id="3"
+ dashboard_id="3"
+ user_id="42"
+ order_index="1"/>
+ <!-- Dashboard with ID 4 does not appear (anonymous) -->
+ <active_dashboards
+ id="4"
+ dashboard_id="4"
+ user_id="[null]"
+ order_index="1"/>
+ <!-- Dashboard with ID 5 does not appear (another user) -->
+ <active_dashboards
+ id="5"
+ dashboard_id="5"
+ user_id="24"
+ order_index="1"/>
+
+</dataset>