From f397b77c1a6933e6f65f66e63f05b8b6f9e5949a Mon Sep 17 00:00:00 2001
From: RachelElysia <71795832+RachelElysia@users.noreply.github.com>
Date: Thu, 30 Sep 2021 10:30:44 -0400
Subject: [PATCH] Schedule Page: Team maintainer can modify their team's
schedule (#2243)
* Change permissions to schedule UI to include team maintainers
* Update e2e tests
---
...sue-2058-team-maintainer-can-schedule-team | 1 +
cypress/integration/all/app/queryflow.spec.ts | 75 +---------
cypress/integration/free/maintainer.spec.ts | 2 +-
cypress/integration/premium/admin.spec.ts | 137 +++++++++++++++++-
.../premium/team_maintainer_observer.spec.ts | 17 ++-
docs/01-Using-Fleet/09-Permissions.md | 9 +-
.../side_panels/SiteTopNav/SiteTopNav.jsx | 48 +++---
.../side_panels/SiteTopNav/navItems.js | 22 ++-
.../ManageSchedulePage/ManageSchedulePage.tsx | 85 ++++++++---
.../ScheduleListWrapper.tsx | 23 +--
frontend/router/index.tsx | 2 +
11 files changed, 276 insertions(+), 145 deletions(-)
create mode 100644 changes/issue-2058-team-maintainer-can-schedule-team
diff --git a/changes/issue-2058-team-maintainer-can-schedule-team b/changes/issue-2058-team-maintainer-can-schedule-team
new file mode 100644
index 0000000000..ef694bf8b4
--- /dev/null
+++ b/changes/issue-2058-team-maintainer-can-schedule-team
@@ -0,0 +1 @@
+- Team maintainer now has permissions to modify their team's schedule
\ No newline at end of file
diff --git a/cypress/integration/all/app/queryflow.spec.ts b/cypress/integration/all/app/queryflow.spec.ts
index 6ccb20127a..3677ec4126 100644
--- a/cypress/integration/all/app/queryflow.spec.ts
+++ b/cypress/integration/all/app/queryflow.spec.ts
@@ -38,6 +38,7 @@ describe(
cy.findByText(/query all/i).click();
+ cy.wait(2000); // eslint-disable-line cypress/no-unnecessary-waiting
cy.findByText(/run query/i).should("exist");
cy.get(".ace_scroller")
@@ -48,79 +49,7 @@ describe(
cy.findByText(/query updated/i).should("be.visible");
- // // Start e2e test for schedules
- // cy.visit("/schedule/manage");
-
- // cy.wait(1000); // eslint-disable-line cypress/no-unnecessary-waiting
-
- // cy.findByRole("button", { name: /schedule a query/i }).click();
-
- // cy.findByText(/select query/i).click();
-
- // cy.findByText(/query all window crashes/i).click();
-
- // cy.get(
- // ".schedule-editor-modal__form-field--frequency > .dropdown__select"
- // ).click();
-
- // cy.findByText(/every week/i).click();
-
- // cy.findByText(/show advanced options/i).click();
-
- // cy.get(
- // ".schedule-editor-modal__form-field--logging > .dropdown__select"
- // ).click();
-
- // cy.findByText(/ignore removals/i).click();
-
- // cy.get(".schedule-editor-modal__form-field--shard > .input-field")
- // .click()
- // .type("50");
-
- // cy.get(".schedule-editor-modal__btn-wrap")
- // .contains("button", /schedule/i)
- // .click();
-
- // cy.visit("/schedule/manage");
-
- // cy.wait(2000); // eslint-disable-line cypress/no-unnecessary-waiting
- // cy.findByText(/query all window crashes/i).should("exist");
-
- // cy.findByText(/actions/i).click();
- // cy.findByText(/edit/i).click();
-
- // cy.get(
- // ".schedule-editor-modal__form-field--frequency > .dropdown__select"
- // ).click();
-
- // cy.findByText(/every 6 hours/i).click();
-
- // cy.findByText(/show advanced options/i).click();
-
- // cy.findByText(/ignore removals/i).click();
- // cy.findByText(/snapshot/i).click();
-
- // cy.get(".schedule-editor-modal__form-field--shard > .input-field")
- // .click()
- // .type("{selectall}{backspace}10");
-
- // cy.get(".schedule-editor-modal__btn-wrap")
- // .contains("button", /schedule/i)
- // .click();
-
- // cy.visit("/schedule/manage");
-
- // cy.wait(2000); // eslint-disable-line cypress/no-unnecessary-waiting
- // cy.findByText(/actions/i).click();
- // cy.findByText(/remove/i).click();
-
- // cy.get(".remove-scheduled-query-modal__btn-wrap")
- // .contains("button", /remove/i)
- // .click();
-
- // cy.findByText(/query all window crashes/i).should("not.exist");
-
- // // End e2e test for schedules
+ // E2e Test for schedules moved to premium/admin
cy.visit("/queries/manage");
diff --git a/cypress/integration/free/maintainer.spec.ts b/cypress/integration/free/maintainer.spec.ts
index 5f50c2e835..4c24e4506f 100644
--- a/cypress/integration/free/maintainer.spec.ts
+++ b/cypress/integration/free/maintainer.spec.ts
@@ -22,7 +22,7 @@ describe(
cy.visit("/");
// Ensure page is loaded
- cy.wait(3000); // eslint-disable-line cypress/no-unnecessary-waiting
+ // cy.wait(3000); // eslint-disable-line cypress/no-unnecessary-waiting
cy.contains("All hosts");
// Settings restrictions
diff --git a/cypress/integration/premium/admin.spec.ts b/cypress/integration/premium/admin.spec.ts
index 0e455f558b..f61c4262e6 100644
--- a/cypress/integration/premium/admin.spec.ts
+++ b/cypress/integration/premium/admin.spec.ts
@@ -78,12 +78,143 @@ describe(
// cy.findByText(/teams/i).should("exist");
// });
+ cy.visit("/queries/manage");
+
+ cy.findByRole("button", { name: /create new query/i }).click();
+
+ // Using class selector because third party element doesn't work with Cypress Testing Selector Library
+ cy.get(".ace_scroller")
+ .click({ force: true })
+ .type("{selectall}SELECT * FROM windows_crashes;");
+
+ cy.findByRole("button", { name: /save/i }).click();
+
+ // save modal
+ cy.get(".query-form__query-save-modal-name")
+ .click()
+ .type("Query all window crashes");
+
+ cy.get(".query-form__query-save-modal-description")
+ .click()
+ .type("See all window crashes");
+
+ cy.findByRole("button", { name: /save query/i }).click();
+
+ cy.findByText(/query created/i).should("exist");
+ cy.findByText(/back to queries/i).should("exist");
+ cy.visit("/queries/manage");
+
+ cy.findByText(/query all/i).click();
+
+ cy.wait(2000); // eslint-disable-line cypress/no-unnecessary-waiting
+ cy.findByText(/run query/i).should("exist");
+
+ cy.get(".ace_scroller")
+ .click({ force: true })
+ .type("{selectall}SELECT datetime, username FROM windows_crashes;");
+
+ cy.findByRole("button", { name: /^Save$/ }).click();
+
+ cy.findByText(/query updated/i).should("be.visible");
+
+ // Start e2e test for schedules
+ cy.visit("/schedule/manage");
+
+ cy.wait(2000); // eslint-disable-line cypress/no-unnecessary-waiting
+
+ cy.findByRole("button", { name: /schedule a query/i }).click();
+
+ cy.findByText(/select query/i).click();
+
+ cy.findByText(/query all window crashes/i).click();
+
+ cy.get(
+ ".schedule-editor-modal__form-field--frequency > .dropdown__select"
+ ).click();
+
+ cy.findByText(/every week/i).click();
+
+ cy.findByText(/show advanced options/i).click();
+
+ cy.get(
+ ".schedule-editor-modal__form-field--logging > .dropdown__select"
+ ).click();
+
+ cy.findByText(/ignore removals/i).click();
+
+ cy.get(".schedule-editor-modal__form-field--shard > .input-field")
+ .click()
+ .type("50");
+
+ cy.get(".schedule-editor-modal__btn-wrap")
+ .contains("button", /schedule/i)
+ .click();
+
+ cy.visit("/schedule/manage");
+
+ cy.wait(2000); // eslint-disable-line cypress/no-unnecessary-waiting
+ cy.findByText(/query all window crashes/i).should("exist");
+
+ cy.findByText(/actions/i).click();
+ cy.findByText(/edit/i).click();
+
+ cy.get(
+ ".schedule-editor-modal__form-field--frequency > .dropdown__select"
+ ).click();
+
+ cy.findByText(/every 6 hours/i).click();
+
+ cy.findByText(/show advanced options/i).click();
+
+ cy.findByText(/ignore removals/i).click();
+ cy.findByText(/snapshot/i).click();
+
+ cy.get(".schedule-editor-modal__form-field--shard > .input-field")
+ .click()
+ .type("{selectall}{backspace}10");
+
+ cy.get(".schedule-editor-modal__btn-wrap")
+ .contains("button", /schedule/i)
+ .click();
+
+ cy.visit("/schedule/manage");
+
+ cy.wait(2000); // eslint-disable-line cypress/no-unnecessary-waiting
+ cy.findByText(/actions/i).click();
+ cy.findByText(/remove/i).click();
+
+ cy.get(".remove-scheduled-query-modal__btn-wrap")
+ .contains("button", /remove/i)
+ .click();
+
+ cy.findByText(/query all window crashes/i).should("not.exist");
+
+ // End e2e test for schedules
+
+ cy.visit("/queries/manage");
+
+ cy.findByText(/query all window crashes/i)
+ .parent()
+ .parent()
+ .within(() => {
+ cy.get(".fleet-checkbox__input").check({ force: true });
+ });
+
+ cy.findByRole("button", { name: /delete/i }).click();
+
+ // Can't figure out how attach findByRole onto modal button
+ // Can't use findByText because delete button under modal
+ cy.get(".remove-query-modal")
+ .contains("button", /delete/i)
+ .click();
+
+ cy.findByText(/successfully removed query/i).should("be.visible");
+
+ cy.findByText(/query all/i).should("not.exist");
+
// On the Packs pages (manage, new, and edit), they should…
// ^^General admin functionality for packs page is being tested in app/packflow.spec.ts
- // On the Schedule pages (manage, new, and edit), they should…
- // ^^General admin functionality for packs page is being tested in app/queryflow.spec.ts
-
// On the Settings pages, they should…
// See the “Teams” navigation item and access the Settings - Teams page
cy.visit("/settings/organization");
diff --git a/cypress/integration/premium/team_maintainer_observer.spec.ts b/cypress/integration/premium/team_maintainer_observer.spec.ts
index 725af24e4c..87298ae71f 100644
--- a/cypress/integration/premium/team_maintainer_observer.spec.ts
+++ b/cypress/integration/premium/team_maintainer_observer.spec.ts
@@ -34,13 +34,11 @@ describe(
// Nav restrictions
cy.findByText(/settings/i).should("not.exist");
- cy.findByText(/schedule/i).should("not.exist");
+ cy.findByText(/schedule/i).should("exist");
cy.visit("/settings/organization");
cy.findByText(/you do not have permissions/i).should("exist");
cy.visit("/packs/manage");
cy.findByText(/you do not have permissions/i).should("exist");
- cy.visit("/schedule/manage");
- cy.findByText(/you do not have permissions/i).should("exist");
// NOT see and select "add label"
cy.findByRole("button", { name: /new label/i }).should("not.exist");
@@ -120,7 +118,7 @@ describe(
cy.get("nav").within(() => {
cy.findByText(/hosts/i).should("exist");
cy.findByText(/queries/i).should("exist");
- cy.findByText(/schedule/i).should("not.exist");
+ cy.findByText(/schedule/i).should("exist");
cy.findByText(/settings/i).should("not.exist");
});
@@ -140,7 +138,7 @@ describe(
// ^^TODO hosts table is not rendering because we need new forEach script/command for admin to assign team after the host is added
// See and select the “Add new host” button
- cy.findByText(/add new host/i).click();
+ cy.findByRole("button", { name: /add new host/i }).click();
// See the “Select a team for this new host” in the Add new host modal. This modal appears after the user selects the “Add new host” button
cy.get(".add-host-modal__team-dropdown-wrapper .Select-control").click();
@@ -191,6 +189,15 @@ describe(
// });
// });
+ // On the Schedule page, they should
+ // See Oranges (team they maintain) only, not able to reach packs, able to schedule a query
+ cy.visit("/schedule/manage");
+ cy.findByText(/oranges/i).click();
+ cy.findByText(/apples/i).should("not.exist");
+ cy.findByText(/advanced/i).should("not.exist");
+ cy.findByRole("button", { name: /schedule a query/i }).click();
+ // TODO: Write e2e test for team maintainer to schedule a query
+
// On the Profile page, they should…
// See 2 Teams in the Team section and Various in the Role section
cy.visit("/profile");
diff --git a/docs/01-Using-Fleet/09-Permissions.md b/docs/01-Using-Fleet/09-Permissions.md
index 2315c56a6a..aadc0c7075 100644
--- a/docs/01-Using-Fleet/09-Permissions.md
+++ b/docs/01-Using-Fleet/09-Permissions.md
@@ -75,12 +75,15 @@ The following table depicts various permissions levels in a team.
| Filter hosts assigned to team using labels | ✅ | ✅ |
| Target hosts assigned to team using labels | ✅ | ✅ |
| Run saved queries as live queries on hosts assigned to team | ✅ | ✅ |
-| Create new team policies | | ✅ |
-| Delete team policies | | ✅ |
| Run custom queries as live queries on hosts assigned to team | | ✅ |
| Enroll hosts to member team | | ✅ |
| Delete hosts belonging to member team | | ✅ |
| Edit queries they authored | | ✅ |
| Delete queries they authored | | ✅ |
-| Browse global policies | | ✅ |
+| Create new team schedules | | ✅ |
+| Delete team schedules | | ✅ |
| Browse global schedules | | ✅ |
+| Create new team policies | | ✅ |
+| Delete team policies | | ✅ |
+| Browse global policies | | ✅ |
+
diff --git a/frontend/components/side_panels/SiteTopNav/SiteTopNav.jsx b/frontend/components/side_panels/SiteTopNav/SiteTopNav.jsx
index 497a7867fb..9d3240001c 100644
--- a/frontend/components/side_panels/SiteTopNav/SiteTopNav.jsx
+++ b/frontend/components/side_panels/SiteTopNav/SiteTopNav.jsx
@@ -48,34 +48,6 @@ class SiteTopNav extends Component {
const iconClasses = classnames([`${navItemBaseClass}__icon`]);
- let icon = (
-
- );
- if (iconName === "queries")
- icon = (
-
- );
- else if (iconName === "packs")
- icon = (
-
- );
- else if (iconName === "policies")
- icon = (
-
- );
- else if (iconName === "settings")
- icon = (
-
- );
-
if (iconName === "logo") {
return (
- Schedule a query, or go to your osquery packs via the - ‘Advanced’ button. + {!isTeamMaintainer + ? "Schedule a query, or go to your osquery packs via the ‘Advanced’ button." + : "Schedule a query to run on hosts assigned to this team."}