async def process(self, req: Request[CapFilterArgs], user: User ) -> None: args = req.args typeName = args.restype capMap: ResultKeeper[str, CapInfo] = ResultKeeper(CapInfo) # Always include targets, even if there are no TRs for them. for target in self.project.getTargets(): capMap[target] # pylint: disable=pointless-statement # Determine capabilities required for each task. for taskDefId, taskDef in self.taskDefDB.items(): for record in (taskDef, taskDef.getFramework()): for spec in record.resourceClaim.iterSpecsOfType(typeName): for rcap in spec.capabilities: capMap[rcap].taskDefIds.add(taskDefId) # Determine which resources are necessary for each task. resourceDB = self.resourceDB for resourceId in resourceDB.resourcesOfType(typeName): for cap in resourceDB[resourceId].capabilities: capMap[cap].resourceIds.add(resourceId) cap = args.cap if cap and cap not in capMap: raise ArgsCorrected(args, cap='') # pylint: disable=attribute-defined-outside-init self.capMap = capMap.values()
async def process(self, req: Request[ReportArgsT], user: User) -> None: jobDB = self.jobDB # Set of targets for which jobs have run. targets = cast(AbstractSet[Optional[str]], jobDB.uniqueValues('target')) # Add targets that are available now. targets |= self.project.getTargets() uiTargets = noneToEmpty(targets) # Set of users that have initiated jobs. owners = cast(AbstractSet[Optional[str]], jobDB.uniqueValues('owner')) # Add users that are available now. owners |= self.userDB.keys() uiOwners = noneToEmpty(owners) # Reject unknown targets and/or owners. if req.args.target - uiTargets or req.args.owner - uiOwners: raise ArgsCorrected( req.args.override(target=req.args.target & uiTargets, owner=req.args.owner & uiOwners)) # pylint: disable=attribute-defined-outside-init self.targets = targets self.uiTargets = uiTargets self.owners = owners self.uiOwners = uiOwners
async def process(self, req: Request[TaskMatrixArgs], user: User) -> None: # TODO: It would be useful to have these as method arguments. year = req.args.year week = req.args.week dateRange = self.dateRange # Use week of last report as default. if year is dynamic or week is dynamic: year = dateRange.maxYear week = getWeekNr(localtime(dateRange.maxTime)) else: assert isinstance(year, int) assert isinstance(week, int) # Bring date within a valid range. beginWeek, endWeek = weekRange(year, week) if beginWeek < dateRange.minTime: year = dateRange.minYear week = getWeekNr(localtime(dateRange.minTime)) elif beginWeek > dateRange.maxTime: year = dateRange.maxYear week = getWeekNr(localtime(dateRange.maxTime)) week = max(1, min(week, weeksInYear(year))) if year != req.args.year or week != req.args.week: # Redirect to the selected year and week. raise ArgsCorrected(req.args, year=year, week=week) # pylint: disable=attribute-defined-outside-init self.beginWeek = beginWeek self.endWeek = endWeek self.taskData = groupTasks( filterJobs(self.jobDB, beginWeek, endWeek, req.args.config), beginWeek)
def processSelection(self) -> None: db = self.db args = self.args tagCache = self.tagCache action = args.action selected = {recordId for recordId in args.sel if recordId in db} if action == 'remove': selected -= args.bsk else: for value, label_, page in self.iterActions(): if action == value: raise Redirect(pageURL(page, SelectArgs(sel=selected))) # Determine tag key to filter by. # Empty string as key shows all records (all-pass filter). tagKey = args.tagkey tagKeys = tagCache.getKeys() if tagKey and tagKey not in tagKeys: # Drop non-existing key. tagKey = None if tagKey is None and tagKeys: # Pick default key. tagKey = tagKeys[0] # Determine tag value to filter by. # Empty string as value shows records that are not tagged # with the given tag key. if tagKey: tagValue = args.tagvalue if tagValue: if not tagCache.hasValue(tagKey, tagValue): # Unknown tag; drop non-existing value. tagValue = None if tagValue is None: # Pick default value. # If nothing is tagged with this key, show untagged. tagValue = min(tagCache.getValues(tagKey), default='') else: # A value only has meaning if we have a key. tagValue = None if (selected != args.sel or tagKey != args.tagkey or tagValue != args.tagvalue): raise ArgsCorrected(args, sel=selected, tagkey=tagKey, tagvalue=tagValue) filteredRecords = self.__filterRecords(tagKey, tagValue) self.selected = selected self.selectedRecords = [db[recordId] for recordId in selected] self.filtered = {record.getId() for record in filteredRecords} self.filteredRecords = filteredRecords
async def process(self, req: Request['Logout_GET.Arguments'], user: User) -> None: url = req.args.url if url is not None: # Only accept relative URLs. url = req.relativeURL(url) if url is None: raise ArgsCorrected(req.args, url=None) loggedOut = req.stopSession() # pylint: disable=attribute-defined-outside-init self.loggedOut = loggedOut # If the user still has privileges when logged out, redirect to # where they logged out from. # The privilege we check is semi-arbitrary: listing jobs is needed # to see the Home page, so even guests have this privilege. if self.project.defaultUser.hasPrivilege('j/l'): raise Redirect('Home' if url is None else url)
async def process(self, req: Request[TaskReportArgs], user: User) -> None: self.initTask(req) run = self.task.getLatestRun() reports: Dict[str, Optional[str]] = OrderedDict() reports['Overview'] = None reports['Data'] = None taskReports = tuple(run.reports) reports.update(taskReports) # Find report to display. report = req.args.report if report is None: active = taskReports[0][0] if taskReports else 'Overview' else: report = report.casefold() for label in reports: if label.casefold() == report: active = label break else: raise InvalidRequest(f'unknown report: "{report}"') if report != active.casefold(): raise ArgsCorrected( req.args.override(report=active.casefold())) presenter: Optional[ReportPresenter] = None if reports[active] is not None: opener = run.reportOpener(active) if opener is not None: presenter = createPresenter(opener, active) # pylint: disable=attribute-defined-outside-init self.reports = reports self.active = active self.presenter = presenter
def __init__(self, table: 'DataTable[Record]', proc: PageProcessor): super().__init__() columns = tuple(table.iterColumns(proc=proc, data=None)) dbName = table.dbName db: Optional[Database[Any]] = \ None if dbName is None else getattr(proc, dbName) records = table.getRecordsToQuery(proc) if isinstance(records, SizedABC): unfilteredNrRecords: Optional[int] = len(records) elif db is not None: unfilteredNrRecords = len(db) else: # We could store all records in a list or wrap a counting iterator # around it, but so far that has not been necessary. unfilteredNrRecords = None sortField = table.sortField if sortField is None: # We don't know if getRecordsToQuery() has filtered or not. filtered = None if isinstance(records, list): records = cast(List[Record], records) else: records = list(records) else: sortOrder = cast(Sequence[str], getattr(proc.args, sortField)) cleanSortOrder = self.__cleanSortOrder(columns, sortOrder) if sortOrder != cleanSortOrder: if proc.args.isArgument(sortField): raise ArgsCorrected( proc.args.override(**{sortField: cleanSortOrder})) else: setattr(proc.args, sortField, cleanSortOrder) query: List[RecordProcessor] = list(table.iterFilters(proc)) filtered = bool(query) keyMap = _buildKeyMap(columns, proc) sortKeys = (keyMap.get(key, key) for key in cleanSortOrder) # TODO: Maybe we should have a class (RecordCollection?) for # records that are not DBRecords or to keep track of # a subset of a full DB. Then 'uniqueKeys' could be moved # from DataTable to RecordCollection. getRetriever: Callable[[str], Retriever[Record, Comparable]] if db is None: getRetriever = itemgetter uniqueKeys = table.uniqueKeys or () else: getRetriever = db.retrieverFor assert table.uniqueKeys is None, "table's uniqueKeys is ignored" uniqueKeys = db.uniqueKeys retrievers: List[Retriever[Record, Comparable]] = [] for key in sortKeys: if callable(key): retrievers.append(substMissingForNone(key)) else: retrievers.append(substMissingForNone(getRetriever(key))) if key in uniqueKeys: break else: retrievers.append( cast(Callable[[Record], Comparable], lambda record: record)) query.append(KeySorter(retrievers)) records = runQuery(query, records) totalNrRecords = len(records) tabOffsetField = table.tabOffsetField if tabOffsetField is not None: tabOffset: int = getattr(proc.args, tabOffsetField) recordsPerPage = table.recordsPerPage if tabOffset < 0: # User tried to be funny and entered negative offset in URL. # Clip to first tab. newOffset = 0 elif tabOffset >= totalNrRecords: # URL could be manipulated or were are looking at a database # from which records were recently deleted. # Clip to last tab. newOffset = (totalNrRecords // recordsPerPage) * recordsPerPage else: # Make sure the first record on a tab matches the tab label. # Round down to current tab label. newOffset = (tabOffset // recordsPerPage) * recordsPerPage if newOffset != tabOffset: raise ArgsCorrected( proc.args.override(**{tabOffsetField: newOffset})) records = records[tabOffset:tabOffset + table.recordsPerPage] objectName = table.objectName if objectName is None: assert db is not None objectName = pluralize(db.description, 42) self.objectName = objectName self.columns = columns self.records = records self.unfilteredNrRecords = unfilteredNrRecords self.totalNrRecords = totalNrRecords self.filtered = filtered
def _checkState(self) -> None: args = self.args # Check max job count. if args.maxjobs <= 0: raise PresentableError(xhtml.p[ f'The value for multiple jobs limit ({args.maxjobs:d}) ' f'is invalid; it must be a positive integer.']) # Check timezone. # Since timezone is selected from a drop-down list, under normal # circumstances it will always be valid. timezone = decodeTimezone(args.timezone) knownZones = getKnownTimezones() if knownZones and timezone not in knownZones: raise PresentableError( xhtml.p[f'Unknown timezone "{timezone}".']) # Check site filters. siteFilters = [] for siteFilter in args.embedcustom.split(): if ';' in siteFilter: # Semicolon is used as a separator in the CSP header. raise PresentableError(xhtml.p[ f'Illegal character ";" in site filter "{siteFilter}"'] ) try: url = urlparse(siteFilter) if not url.scheme: # Force scheme-less location to be parsed as a netloc; # otherwise urlparse() treats it as a relative path. url = urlparse('//' + siteFilter) # Force sanity check of port number. _ = url.port except ValueError as ex: raise PresentableError( xhtml.p[f'Invalid site filter "{siteFilter}": {ex}']) for name in ('path', 'params', 'query', 'fragment', 'username', 'password'): if getattr(url, name): raise PresentableError(xhtml.p[ f'Site filter "{siteFilter}" contains {name}, ' f'which is not supported']) scheme = url.scheme netloc = url.netloc if scheme and netloc: siteFilters.append(f'{scheme}://{netloc}') elif scheme: siteFilters.append(f'{scheme}:') elif netloc: siteFilters.append(netloc) else: # Note: I don't know what filter would parse like this, # but handle it just in case. raise PresentableError(xhtml.p[ f'Site filter "{siteFilter}" contains no information']) if not siteFilters and args.embed is EmbeddingPolicy.CUSTOM: raise PresentableError( xhtml.p['Custom embedding policy cannot be empty']) siteFilterStr = ' '.join(siteFilters) if siteFilterStr != args.embedcustom: raise ArgsCorrected(args, embedcustom=siteFilterStr)