Пример #1
0
        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()
Пример #2
0
    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
Пример #3
0
    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)
Пример #4
0
    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
Пример #5
0
        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)
Пример #6
0
        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
Пример #7
0
    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
Пример #8
0
        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)