Loading client/src/components/Tour/Tour.vue +73 −9 Original line number Diff line number Diff line <template> <CurrentUser v-slot="{ user }" class="d-flex flex-column"> <UserHistories v-if="user" v-slot="{ currentHistory, handlers, historiesLoading }" :user="user"> <div v-if="historiesLoading">computing tour requirements...</div> <b-modal id="tour-requirement-unment" v-model="showRequirementDialog" static ok-only hide-header v-else-if="loginRequired(user)"> <b-alert show variant="danger"> You must login to Galaxy to use this tour. </b-alert> </b-modal> <b-modal id="tour-requirement-unment" v-model="showRequirementDialog" static ok-only hide-header v-else-if="adminRequired(user)"> <b-alert show variant="danger"> You must be an admin user to use this tour. </b-alert> </b-modal> <b-modal id="tour-requirement-unment" v-model="showRequirementDialog" static ok-only hide-header v-else-if="newHistoryRequired(currentHistory, handlers)"> <b-alert show variant="danger"> This tour is designed to run on a new history, please create a new history before running it. <a @click.prevent="handlers.createNewHistory()">Click here</a> to create a new history. </b-alert> </b-modal> <TourStep v-if="currentStep" v-else-if="currentStep" :key="currentIndex" :step="currentStep" :is-playing="isPlaying" Loading @@ -8,10 +41,14 @@ @next="next" @end="end" @play="play" /> </UserHistories> </CurrentUser> </template> <script> import TourStep from "./TourStep"; import CurrentUser from "components/providers/CurrentUser"; import UserHistories from "components/providers/UserHistories"; // popup display duration when auto-playing the tour const playDelay = 3000; Loading @@ -19,17 +56,24 @@ const playDelay = 3000; export default { components: { TourStep, CurrentUser, UserHistories, }, props: { steps: { type: Array, required: true, }, requirements: { type: Array, required: true, }, }, data() { return { currentIndex: -1, isPlaying: false, showRequirementDialog: true, }; }, computed: { Loading Loading @@ -60,6 +104,26 @@ export default { this.next(); } }, loginRequired(user) { return this.requirements.indexOf("logged_in") >= 0 && user.isAnonymous; }, adminRequired(user) { return this.requirements.indexOf("admin") >= 0 && !user.is_admin; }, newHistoryRequired(history) { const hasNewNistoryRequirement = this.requirements.indexOf("new_history") >= 0; if (!hasNewNistoryRequirement) { return false; } else if (history && history.size != 0) { // TODO: better estimate for whether the history is new. return true; } else { return false; } }, createNewHistory(handlers) { handlers.createNewHistory(); }, async next() { // do post-actions if (this.currentStep && this.currentStep.onNext) { Loading client/src/components/Tour/runTour.js +2 −1 Original line number Diff line number Diff line Loading @@ -121,5 +121,6 @@ export async function runTour(tourId, tourData = null) { }, }); }); return mountTour({ steps }); const requirements = tourData.requirements || []; return mountTour({ steps, requirements }); } lib/galaxy/tours/_impl.py +7 −0 Original line number Diff line number Diff line Loading @@ -11,6 +11,7 @@ from typing import ( import yaml from pydantic import parse_obj_as from galaxy.exceptions import ObjectNotFound from galaxy.util import config_directories_from_setting from ._interface import ToursRegistry from ._schema import TourList Loading @@ -32,6 +33,9 @@ def load_tour_steps(contents_dict, warn=None): warn = warn or noop_warn # Some of this can be done on the clientside. Maybe even should? title_default = contents_dict.get("title_default") if "requirements" not in contents_dict: contents_dict["requirements"] = [] for step in contents_dict["steps"]: # Remove attributes no longer used, so they are attempted to be # validated. Loading Loading @@ -94,6 +98,7 @@ class ToursRegistryImpl: "name": self.tours[k].get("name"), "description": self.tours[k].get("description"), "tags": self.tours[k].get("tags"), "requirements": self.tours[k].get("requirements"), } tours.append(tourdata) return parse_obj_as(TourList, tours) Loading @@ -102,6 +107,8 @@ class ToursRegistryImpl: """Return tour contents.""" # Extra format translation could happen here (like the previous intro_to_tour) # For now just return the loaded contents. if tour_id not in self.tours: raise ObjectNotFound(f"tour {tour_id} not found") return self.tours.get(tour_id) def load_tour(self, tour_id): Loading lib/galaxy/tours/_schema.py +13 −0 Original line number Diff line number Diff line from enum import Enum from typing import ( List, Optional, Loading @@ -10,10 +11,22 @@ from pydantic import ( ) class Requirement(str, Enum): """Available types of job sources (model classes) that produce dataset collections.""" LOGGED_IN = "logged_in" NEW_HISTORY = "new_history" ADMIN = "admin" class TourCore(BaseModel): name: str = Field(title="Name", description="Name of tour") description: str = Field(title="Description", description="Tour description") tags: List[str] = Field(title="Tags", description="Topic topic tags") requirements: List[Requirement] = Field(title="Requirements", description="Requirements to run the tour.") class Config: use_enum_values = True # when using .dict() class Tour(TourCore): Loading Loading
client/src/components/Tour/Tour.vue +73 −9 Original line number Diff line number Diff line <template> <CurrentUser v-slot="{ user }" class="d-flex flex-column"> <UserHistories v-if="user" v-slot="{ currentHistory, handlers, historiesLoading }" :user="user"> <div v-if="historiesLoading">computing tour requirements...</div> <b-modal id="tour-requirement-unment" v-model="showRequirementDialog" static ok-only hide-header v-else-if="loginRequired(user)"> <b-alert show variant="danger"> You must login to Galaxy to use this tour. </b-alert> </b-modal> <b-modal id="tour-requirement-unment" v-model="showRequirementDialog" static ok-only hide-header v-else-if="adminRequired(user)"> <b-alert show variant="danger"> You must be an admin user to use this tour. </b-alert> </b-modal> <b-modal id="tour-requirement-unment" v-model="showRequirementDialog" static ok-only hide-header v-else-if="newHistoryRequired(currentHistory, handlers)"> <b-alert show variant="danger"> This tour is designed to run on a new history, please create a new history before running it. <a @click.prevent="handlers.createNewHistory()">Click here</a> to create a new history. </b-alert> </b-modal> <TourStep v-if="currentStep" v-else-if="currentStep" :key="currentIndex" :step="currentStep" :is-playing="isPlaying" Loading @@ -8,10 +41,14 @@ @next="next" @end="end" @play="play" /> </UserHistories> </CurrentUser> </template> <script> import TourStep from "./TourStep"; import CurrentUser from "components/providers/CurrentUser"; import UserHistories from "components/providers/UserHistories"; // popup display duration when auto-playing the tour const playDelay = 3000; Loading @@ -19,17 +56,24 @@ const playDelay = 3000; export default { components: { TourStep, CurrentUser, UserHistories, }, props: { steps: { type: Array, required: true, }, requirements: { type: Array, required: true, }, }, data() { return { currentIndex: -1, isPlaying: false, showRequirementDialog: true, }; }, computed: { Loading Loading @@ -60,6 +104,26 @@ export default { this.next(); } }, loginRequired(user) { return this.requirements.indexOf("logged_in") >= 0 && user.isAnonymous; }, adminRequired(user) { return this.requirements.indexOf("admin") >= 0 && !user.is_admin; }, newHistoryRequired(history) { const hasNewNistoryRequirement = this.requirements.indexOf("new_history") >= 0; if (!hasNewNistoryRequirement) { return false; } else if (history && history.size != 0) { // TODO: better estimate for whether the history is new. return true; } else { return false; } }, createNewHistory(handlers) { handlers.createNewHistory(); }, async next() { // do post-actions if (this.currentStep && this.currentStep.onNext) { Loading
client/src/components/Tour/runTour.js +2 −1 Original line number Diff line number Diff line Loading @@ -121,5 +121,6 @@ export async function runTour(tourId, tourData = null) { }, }); }); return mountTour({ steps }); const requirements = tourData.requirements || []; return mountTour({ steps, requirements }); }
lib/galaxy/tours/_impl.py +7 −0 Original line number Diff line number Diff line Loading @@ -11,6 +11,7 @@ from typing import ( import yaml from pydantic import parse_obj_as from galaxy.exceptions import ObjectNotFound from galaxy.util import config_directories_from_setting from ._interface import ToursRegistry from ._schema import TourList Loading @@ -32,6 +33,9 @@ def load_tour_steps(contents_dict, warn=None): warn = warn or noop_warn # Some of this can be done on the clientside. Maybe even should? title_default = contents_dict.get("title_default") if "requirements" not in contents_dict: contents_dict["requirements"] = [] for step in contents_dict["steps"]: # Remove attributes no longer used, so they are attempted to be # validated. Loading Loading @@ -94,6 +98,7 @@ class ToursRegistryImpl: "name": self.tours[k].get("name"), "description": self.tours[k].get("description"), "tags": self.tours[k].get("tags"), "requirements": self.tours[k].get("requirements"), } tours.append(tourdata) return parse_obj_as(TourList, tours) Loading @@ -102,6 +107,8 @@ class ToursRegistryImpl: """Return tour contents.""" # Extra format translation could happen here (like the previous intro_to_tour) # For now just return the loaded contents. if tour_id not in self.tours: raise ObjectNotFound(f"tour {tour_id} not found") return self.tours.get(tour_id) def load_tour(self, tour_id): Loading
lib/galaxy/tours/_schema.py +13 −0 Original line number Diff line number Diff line from enum import Enum from typing import ( List, Optional, Loading @@ -10,10 +11,22 @@ from pydantic import ( ) class Requirement(str, Enum): """Available types of job sources (model classes) that produce dataset collections.""" LOGGED_IN = "logged_in" NEW_HISTORY = "new_history" ADMIN = "admin" class TourCore(BaseModel): name: str = Field(title="Name", description="Name of tour") description: str = Field(title="Description", description="Tour description") tags: List[str] = Field(title="Tags", description="Topic topic tags") requirements: List[Requirement] = Field(title="Requirements", description="Requirements to run the tour.") class Config: use_enum_values = True # when using .dict() class Tour(TourCore): Loading