Ejemplo n.º 1
0
def displayFormattedText(db, req, user, source):
    document = htmlutils.Document(req)
    document.setBase(None)
    document.addExternalStylesheet("resource/tutorial.css")
    document.addInternalStylesheet(
        "div.main table td.text { %s }" %
        user.getPreference(db, "style.tutorialFont"))

    html = document.html()
    head = html.head()
    body = html.body()

    generateHeader(body, db, user)

    if isinstance(source, basestring):
        lines = source.splitlines()
    else:
        lines = source

    import textformatting

    textformatting.renderFormatted(db,
                                   user,
                                   body.div("main").table("paleyellow"),
                                   source,
                                   toc=True)

    generateFooter(body, db, user)

    return document
Ejemplo n.º 2
0
    def process(req, db, user, repository_id, commit_ids, reviewfilters,
                applyfilters, applyparentfilters):
        reviewfilters = parseReviewFilters(db, reviewfilters)

        repository = gitutils.Repository.fromId(db, repository_id)
        commits = [
            gitutils.Commit.fromId(db, repository, commit_id)
            for commit_id in commit_ids
        ]

        all_reviewers, all_watchers = getReviewersAndWatchers(
            db,
            repository,
            commits,
            reviewfilters=reviewfilters,
            applyfilters=applyfilters,
            applyparentfilters=applyparentfilters)
        document = htmlutils.Document(req)

        generateReviewersAndWatchersTable(
            db,
            repository,
            document,
            all_reviewers,
            all_watchers,
            applyfilters=applyfilters,
            applyparentfilters=applyparentfilters)

        return OperationResult(html=document.render(plain=True))
Ejemplo n.º 3
0
def renderNews(req, db, user):
    item_id = req.getParameter("item", None, filter=int)
    display = req.getParameter("display", "unread")

    document = htmlutils.Document(req)

    html = document.html()
    head = html.head()
    body = html.body()

    cursor = db.cursor()

    def renderButtons(target):
        if user.hasRole(db, "newswriter"):
            if item_id is not None:
                target.button("editnewsitem").text("Edit Item")
            target.button("addnewsitem").text("Add News Item")

    page.utils.generateHeader(body,
                              db,
                              user,
                              current_page="news",
                              generate_right=renderButtons)

    document.addExternalStylesheet("resource/tutorial.css")
    document.addExternalStylesheet("resource/comment.css")
    document.addExternalStylesheet("resource/news.css")
    document.addExternalScript("resource/news.js")
    document.addInternalStylesheet(
        "div.main table td.text { %s }" %
        user.getPreference(db, "style.tutorialFont"))

    target = body.div("main")

    if item_id:
        cursor.execute("SELECT text, date FROM newsitems WHERE id=%s",
                       (item_id, ))

        text, date = cursor.fetchone()

        document.addInternalScript("var news_item_id = %d;" % item_id)
        document.addInternalScript("var news_text = %s;" %
                                   htmlutils.jsify(text))

        renderNewsItem(db, user, target, text, date.isoformat())

        if not user.isAnonymous() and user.name == req.user:
            cursor.execute("SELECT 1 FROM newsread WHERE item=%s AND uid=%s",
                           (item_id, user.id))
            if not cursor.fetchone():
                cursor.execute(
                    "INSERT INTO newsread (item, uid) VALUES (%s, %s)",
                    (item_id, user.id))
                db.commit()
    else:
        renderNewsItems(db, user, target, display in ("unread", "all"), display
                        in ("read", "all"))

    return document
Ejemplo n.º 4
0
        def generate(self, page, req, db, user):
            self.setup(page, req, db, user)

            self.document = htmlutils.Document(req)
            self.html = self.document.html()
            self.head = self.html.head()
            self.body = self.html.body()

            self._generateHeader()
            self.generateContent()
            self._generateFooter()

            return self.document
Ejemplo n.º 5
0
def displayMessage(db,
                   req,
                   user,
                   title,
                   review=None,
                   message=None,
                   page_title=None,
                   is_html=False):
    document = htmlutils.Document(req)

    if page_title:
        document.setTitle(page_title)

    document.addExternalStylesheet("resource/message.css")

    html = document.html()
    head = html.head()
    body = html.body()

    if review:
        import reviewing.utils as review_utils

        def generateRight(target):
            review_utils.renderDraftItems(db, user, review, target)

        back_to_review = ("r/%d" % review.id, "Back to Review")

        document.addInternalScript(review.getJS())

        generateHeader(body,
                       db,
                       user,
                       generate_right=generateRight,
                       extra_links=[back_to_review])
    else:
        generateHeader(body, db, user)

    target = body.div("message paleyellow")

    if message:
        target.h1("title").text(title)

        if callable(message): message(target)
        elif is_html: target.innerHTML(message)
        else: target.p().text(message)
    else:
        target.h1("center").text(title)

    return document
Ejemplo n.º 6
0
def renderShowComment(req, db, user):
    chain_id = req.getParameter("chain", filter=int)
    context_lines = req.getParameter("context", user.getPreference(db, "comment.diff.contextLines"), filter=int)

    default_compact = "yes" if user.getPreference(db, "commit.diff.compactMode") else "no"
    compact = req.getParameter("compact", default_compact) == "yes"

    default_tabify = "yes" if user.getPreference(db, "commit.diff.visualTabs") else "no"
    tabify = req.getParameter("tabify", default_tabify) == "yes"

    original = req.getParameter("original", "no") == "yes"

    chain = review_comment.CommentChain.fromId(db, chain_id, user)

    if chain is None or chain.state == "empty":
        raise page.utils.DisplayMessage("Invalid comment chain ID: %d" % chain_id)

    review = chain.review

    document = htmlutils.Document(req)

    html = document.html()
    head = html.head()
    body = html.body()

    document.setTitle("%s in review %s" % (chain.title(False), review.branch.name))

    def renderHeaderItems(target):
        review_utils.renderDraftItems(db, user, review, target)
        target.div("buttons").span("buttonscope buttonscope-global")

    page.utils.generateHeader(body, db, user, renderHeaderItems, extra_links=[("r/%d" % review.id, "Back to Review")])

    document.addExternalScript("resource/showcomment.js")
    document.addInternalScript(user.getJS(db))
    document.addInternalScript(review.repository.getJS())
    document.addInternalScript(review.getJS())
    document.addInternalScript("var contextLines = %d;" % context_lines)
    document.addInternalScript("var keyboardShortcuts = %s;" % (user.getPreference(db, "ui.keyboardShortcuts") and "true" or "false"))

    if not user.isAnonymous() and user.name == req.user:
        document.addInternalScript("$(function () { markChainsAsRead([%d]); });" % chain_id)

    review_html.renderCommentChain(db, body.div("main"), user, review, chain, context_lines=context_lines, compact=compact, tabify=tabify, original=original, linkify=linkify.Context(db=db, request=req, review=review))

    if user.getPreference(db, "ui.keyboardShortcuts"):
        page.utils.renderShortcuts(body, "showcomment", review=review)

    yield document.render(pretty=not compact)
Ejemplo n.º 7
0
def renderTutorial(req, db, user):
    item = req.getParameter("item", None)

    document = htmlutils.Document(req)
    document.setBase(None)
    document.setTitle("Tutorials")

    html = document.html()
    head = html.head()
    body = html.body()

    page.utils.generateHeader(body,
                              db,
                              user,
                              current_page=None if item else "tutorial")

    document.addExternalStylesheet("resource/tutorial.css")
    document.addExternalScript("resource/tutorial.js")
    document.addInternalStylesheet(
        "div.main table td.text { %s }" %
        user.getPreference(db, "style.tutorialFont"))

    target = body.div("main")

    items = {
        "request": "requesting",
        "review": "reviewing",
        "filters": "filters",
        "archival": "archival",
        "viewer": "repository",
        "rebase": "rebasing",
        "reconfigure": "reconfiguring",
        "checkbranch": "checkbranch",
        "administration": "administration",
        "customization": "customization",
        "search": "search",
        "external-authentication": "external-authentication",
        "extensions": "extensions",
        "extensions-api": "extensions-api"
    }

    if item in items:
        renderFromFile(db, user, target, items[item])
    else:
        renderSections(db, user, target)

    return document
Ejemplo n.º 8
0
def renderLogin(req, db, user):
    target_url = req.getParameter("target", "/")

    if not user.isAnonymous():
        raise page.utils.MovedTemporarily(target_url or "/", True)

    document = htmlutils.Document(req)
    document.setTitle("Login")

    html = document.html()
    head = html.head()
    body = html.body()

    page.utils.generateHeader(body, db, user, current_page="login")

    document.addExternalStylesheet("resource/login.css")
    document.addExternalScript("resource/login.js")

    def render(target):
        form = target.form(name="login",
                           method="POST",
                           action="redirect?" +
                           urllib.urlencode({"target": target_url}))
        table = form.table("login", align="center")

        row = table.tr("status disabled")
        row.td(colspan=2).text()

        row = table.tr("username")
        row.td("key").text("Username:"******"value").input("username",
                              name="username",
                              autofocus="autofocus")

        row = table.tr("password")
        row.td("key").text("Password:"******"value").input("password", name="password", type="password")

        row = table.tr("login")
        row.td(colspan=2).input("login", type="submit", value="Sign in")

    paleyellow = page.utils.PaleYellowTable(body, "Sign in")
    paleyellow.addCentered(render)

    return document
Ejemplo n.º 9
0
def renderServices(req, db, user):
    req.content_type = "text/html; charset=utf-8"

    document = htmlutils.Document(req)
    document.setTitle("Services")

    html = document.html()
    head = html.head()
    body = html.body()

    page.utils.generateHeader(body, db, user, current_page="services")

    document.addExternalStylesheet("resource/services.css")
    document.addExternalScript("resource/services.js")
    document.addInternalScript(user.getJS())

    delay = 0.5
    connected = False

    while not connected and delay <= 10:
        connection = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)

        # This loop is for the case where we just restarted the service manager
        # via the /services UI.  The client-side script immediately reloads the
        # page after restart, which typically leads to us trying to connect to
        # the service manager while it's in the process of restarting.  So just
        # try a couple of times if at first the connection fails.

        try:
            connection.connect(
                configuration.services.SERVICEMANAGER["address"])
            connected = True
        except socket.error, error:
            if error[0] in (errno.ENOENT, errno.ECONNREFUSED):
                time.sleep(delay)
                delay += delay
            else:
                raise
Ejemplo n.º 10
0
def renderTutorial(db, user, source):
    document = htmlutils.Document()

    document.addExternalStylesheet("resource/tutorial.css")
    document.addExternalScript("resource/tutorial.js")
    document.addInternalStylesheet(
        "div.main table td.text { %s }" %
        user.getPreference(db, "style.tutorialFont"))

    html = document.html()
    head = html.head()
    body = html.body()

    page.utils.generateHeader(body, db, user)

    table = body.div("main").table("paleyellow", align="center")
    textformatting.renderFormatted(db,
                                   user,
                                   table,
                                   source.splitlines(),
                                   toc=True)

    return str(document)
Ejemplo n.º 11
0
def renderSearch(req, db, user):
    document = htmlutils.Document(req)
    document.setTitle("Review Search")

    html = document.html()
    head = html.head()
    body = html.body()

    page.utils.generateHeader(body, db, user, current_page="search")

    document.addExternalStylesheet("resource/search.css")
    document.addExternalScript("resource/search.js")
    document.addExternalScript("resource/autocomplete.js")
    document.addInternalScript(user.getJS())

    cursor = db.cursor()
    cursor.execute("SELECT name, fullname FROM users")

    users = dict(cursor)

    document.addInternalScript("var users = %s;" %
                               textutils.json_encode(users))

    def renderQuickSearch(target):
        wrap = target.div("quicksearch callout")
        wrap.p().text("""A Quick Search dialog can be opened from any page
                         using the "F" keyboard shortcut.""")
        wrap.p().a(href="/tutorial?item=search").text("More information")

    def renderInput(target, label, name, placeholder=""):
        fieldset = target.fieldset("search-" + name)
        fieldset.label("input-label").text(label)
        fieldset.input(type="text", name=name, placeholder=placeholder)

    def renderInputWithOptions(target, label, name, options, placeholder=""):
        fieldset = target.fieldset("search-" + name)
        fieldset.label("input-label").text(label)
        checkGroup = fieldset.div("input-options checkbox-group")
        for option in options:
            opt_label = checkGroup.label()
            opt_label.input(type="checkbox",
                            name=option["name"],
                            checked="checked" if "checked" in option else None)
            opt_label.text(option["label"])
        fieldset.input(type="text", name=name, placeholder=placeholder)

    def renderFreetext(target):
        options = [{
            "name": "freetextSummary",
            "label": "Summary",
            "checked": True
        }, {
            "name": "freetextDescription",
            "label": "Description",
            "checked": True
        }]
        renderInputWithOptions(target,
                               label="Search term",
                               name="freetext",
                               placeholder="free text search",
                               options=options)

    def renderState(target):
        state = target.fieldset("search-state")
        state.label("input-label").text("State")
        select = state.select(name="state")
        select.option(value="", selected="selected").text("Any state")
        select.option(value="open").text("Open")
        select.option(value="pending").text("Pending")
        select.option(value="accepted").text("Accepted")
        select.option(value="closed").text("Finished")
        select.option(value="dropped").text("Dropped")

    def renderUser(target):
        options = [{
            "name": "userOwner",
            "label": "Owner",
            "checked": True
        }, {
            "name": "userReviewer",
            "label": "Reviewer"
        }]
        renderInputWithOptions(target,
                               label="User",
                               name="user",
                               placeholder="user name(s)",
                               options=options)

    def renderRepository(target):
        fieldset = target.fieldset("search-repository")
        fieldset.label("input-label").text("Repository")
        page.utils.generateRepositorySelect(db,
                                            user,
                                            fieldset,
                                            name="repository",
                                            selected=False,
                                            placeholder_text="Any repository",
                                            allow_selecting_none=True)

    section = body.section("paleyellow section")
    section.h1("section-heading").text("Review Search")

    url_terms = []

    for name, value in urlparse.parse_qsl(req.query):
        if name == "q":
            url_terms.append(value)
        elif name.startswith("q"):
            url_terms.append("%s:%s" % (name[1:], value))

    wrap = section.div("flex")
    search = wrap.form("search", name="search")

    if url_terms:
        row = search.div("flex")
        query = row.fieldset("search-query")
        query.label("input-label").text("Search query")
        query.input(type="text", name="query", value=" ".join(url_terms))

        result = section.div("search-result", style="display: none")
        result.h2().text("Search result")
        result.div("callout")
    else:
        row = search.div("flex")
        renderFreetext(row)
        renderState(row)

        renderUser(search)

        row = search.div("flex")
        renderRepository(row)
        renderInput(row, "Branch", "branch")

        renderInput(search, "Path", "path")

    buttons = search.div("search-buttons")

    if url_terms:
        buttons.button(type="submit").text("Search again")
        buttons.a("button", href="/search").text("Show full search form")
    else:
        buttons.button(type="submit").text("Search")

    renderQuickSearch(wrap)

    return document
Ejemplo n.º 12
0
def renderShowBranch(req, db, user):
    branch_name = req.getParameter("branch")
    base_name = req.getParameter("base", None)

    repository = gitutils.Repository.fromParameter(
        db,
        req.getParameter("repository",
                         user.getPreference(db, "defaultRepository")))

    cursor = db.cursor()

    cursor.execute(
        "SELECT id, type, base, head, tail FROM branches WHERE name=%s AND repository=%s",
        (branch_name, repository.id))

    try:
        branch_id, branch_type, base_id, head_id, tail_id = cursor.fetchone()
    except:
        return page.utils.displayMessage(
            db, req, user, "'%s' doesn't name a branch!" % branch_name)

    branch = dbutils.Branch.fromName(db, repository, branch_name)
    rebased = False

    if base_name:
        base = dbutils.Branch.fromName(db, repository, base_name)

        if base is None:
            return page.utils.displayMessage(
                db, req, user, "'%s' doesn't name a branch!" % branch)

        old_count, new_count, base_old_count, base_new_count = branch.rebase(
            db, base)

        if base_old_count is not None:
            new_base_base_name = base.base.name
        else:
            new_base_base_name = None

        rebased = True

    document = htmlutils.Document(req)

    html = document.html()
    head = html.head()
    body = html.body()

    document.addExternalStylesheet("resource/showbranch.css")

    def renderCreateReview(target):
        if not user.isAnonymous(
        ) and branch and branch.review is None and not rebased:
            target.button(
                onclick="location.href = " +
                htmlutils.jsify("createreview?repository=%d&branch=%s" %
                                (repository.id, branch_name))).text(
                                    "Create Review")

    page.utils.generateHeader(body, db, user, renderCreateReview)

    document.addInternalScript(branch.getJS())

    title_right = None

    if rebased:

        def renderPerformRebase(db, target):
            target.button(
                "perform",
                onclick="rebase(%s, %s, %s, %s, %s, %s, %s)" % tuple(
                    map(htmlutils.jsify, [
                        branch_name, base_name, new_base_base_name, old_count,
                        new_count, base_old_count, base_new_count
                    ]))).text("Perform Rebase")

        title_right = renderPerformRebase
    elif base_id is not None:
        bases = []
        base = branch.base

        if base:
            if base.type == "review":
                bases.append("master")
            else:
                base = base.base
                while base:
                    bases.append(base.name)
                    base = base.base

        cursor.execute("SELECT name FROM branches WHERE base=%s",
                       (branch.id, ))

        for (name, ) in cursor:
            bases.append(name)

        def renderSelectBase(db, target):
            select = target.select("base")
            select.option(value="*").text("Select new base")
            select.option(value="*").text("---------------")

            for name in bases:
                select.option("base", value=name.split(" ")[0]).text(name)

        if not bases and branch.base:
            cursor.execute("SELECT commit FROM reachable WHERE branch=%s",
                           (branch.id, ))

            commit_ids = cursor.fetchall()

            body.comment(repr(commit_ids))

            for commit_id in commit_ids:
                cursor.execute(
                    "SELECT 1 FROM reachable WHERE branch=%s AND commit=%s",
                    (branch.base.id, commit_id))
                if cursor.fetchone():
                    bases.append("%s (trim)" % branch.base.name)
                    break

        if bases:
            title_right = renderSelectBase

    target = body.div("main")

    if branch_type == 'normal':
        cursor.execute("SELECT COUNT(*) FROM reachable WHERE branch=%s",
                       (branch_id, ))

        commit_count = cursor.fetchone()[0]
        if commit_count > configuration.limits.MAXIMUM_REACHABLE_COMMITS:
            offset = req.getParameter("offset", default=0, filter=int)
            limit = req.getParameter("limit", default=200, filter=int)

            head = gitutils.Commit.fromId(db, repository, head_id)
            tail = gitutils.Commit.fromId(db, repository,
                                          tail_id) if tail_id else None

            sha1s = repository.revlist([head], [tail] if tail else [],
                                       "--skip=%d" % offset,
                                       "--max-count=%d" % limit)
            commits = [
                gitutils.Commit.fromSHA1(db, repository, sha1)
                for sha1 in sha1s
            ]

            def moreCommits(db, target):
                target.a(href="/log?branch=%s&offset=%d&limit=%d" %
                         (branch_name, offset + limit,
                          limit)).text("More commits...")

            log_html.renderList(db,
                                target,
                                branch.name,
                                commits,
                                title_right=title_right,
                                bottom_right=moreCommits)

            return document

    branch.loadCommits(db)

    log_html.render(db,
                    target,
                    branch.name,
                    branch=branch,
                    title_right=title_right)

    return document
Ejemplo n.º 13
0
def renderCreateReview(req, db, user):
    if user.isAnonymous(): raise page.utils.NeedLogin(req)

    repository = req.getParameter("repository", filter=gitutils.Repository.FromParameter(db), default=None)
    applyparentfilters = req.getParameter("applyparentfilters", "yes" if user.getPreference(db, 'review.applyUpstreamFilters') else "no") == "yes"

    cursor = db.cursor()

    if req.method == "POST":
        data = json_decode(req.read())

        summary = data.get("summary")
        description = data.get("description")
        review_branch_name = data.get("review_branch_name")
        commit_ids = data.get("commit_ids")
        commit_sha1s = data.get("commit_sha1s")
    else:
        summary = req.getParameter("summary", None)
        description = req.getParameter("description", None)
        review_branch_name = req.getParameter("reviewbranchname", None)

        commit_ids = None
        commit_sha1s = None

        commits_arg = req.getParameter("commits", None)
        remote = req.getParameter("remote", None)
        upstream = req.getParameter("upstream", "master")
        branch_name = req.getParameter("branch", None)

        if commits_arg:
            try: commit_ids = map(int, commits_arg.split(","))
            except: commit_sha1s = [repository.revparse(ref) for ref in commits_arg.split(",")]
        elif branch_name:
            cursor.execute("""SELECT commit
                                FROM reachable
                                JOIN branches ON (branch=id)
                               WHERE repository=%s
                                 AND name=%s""",
                           (repository.id, branch_name))
            commit_ids = [commit_id for (commit_id,) in cursor]

            if len(commit_ids) > configuration.limits.MAXIMUM_REVIEW_COMMITS:
                raise page.utils.DisplayMessage(
                    "Too many commits!",
                    (("<p>The branch <code>%s</code> contains %d commits.  Reviews can"
                      "be created from branches that contain at most %d commits.</p>"
                      "<p>This limit can be adjusted by modifying the system setting"
                      "<code>configuration.limits.MAXIMUM_REVIEW_COMMITS</code>.</p>")
                     % (htmlutils.htmlify(branch_name), len(commit_ids),
                        configuration.limits.MAXIMUM_REVIEW_COMMITS)),
                    html=True)
        else:
            return renderSelectSource(req, db, user)

    req.content_type = "text/html; charset=utf-8"

    if commit_ids:
        commits = [gitutils.Commit.fromId(db, repository, commit_id) for commit_id in commit_ids]
    elif commit_sha1s:
        commits = [gitutils.Commit.fromSHA1(db, repository, commit_sha1) for commit_sha1 in commit_sha1s]
    else:
        commits = []

    if not commit_ids:
        commit_ids = [commit.getId(db) for commit in commits]
    if not commit_sha1s:
        commit_sha1s = [commit.sha1 for commit in commits]

    if summary is None:
        if len(commits) == 1:
            summary = commits[0].summary()
        else:
            summary = ""

    if review_branch_name:
        invalid_branch_name = "false"
        default_branch_name = review_branch_name
    else:
        invalid_branch_name = htmlutils.jsify(user.name + "/")
        default_branch_name = user.name + "/"

        match = re.search("(?:^|[Ff]ix(?:e[ds])?(?: +for)?(?: +bug)? +)([A-Z][A-Z0-9]+-[0-9]+)", summary)
        if match:
            invalid_branch_name = "false"
            default_branch_name = htmlutils.htmlify(match.group(1))

    changesets = []
    changeset_utils.createChangesets(db, repository, commits)
    for commit in commits:
        changesets.extend(changeset_utils.createChangeset(db, None, repository, commit, do_highlight=False))
    changeset_ids = [changeset.id for changeset in changesets]

    all_reviewers, all_watchers = reviewing.utils.getReviewersAndWatchers(
        db, repository, changesets=changesets, applyparentfilters=applyparentfilters)

    document = htmlutils.Document(req)
    html = document.html()
    head = html.head()

    document.addInternalScript(user.getJS(db))

    if branch_name:
        document.addInternalScript("var fromBranch = %s;" % htmlutils.jsify(branch_name))

    if remote:
        document.addInternalScript("var trackedbranch = { remote: %s, name: %s };" % (htmlutils.jsify(remote), htmlutils.jsify(branch_name)))

    head.title().text("Create Review")

    body = html.body(onload="document.getElementById('branch_name').focus()")

    page.utils.generateHeader(body, db, user, lambda target: target.button(onclick="submitReview();").text("Submit Review"))

    document.addExternalStylesheet("resource/createreview.css")
    document.addExternalScript("resource/createreview.js")
    document.addExternalScript("resource/reviewfilters.js")
    document.addExternalScript("resource/autocomplete.js")

    document.addInternalScript("""
var invalid_branch_name = %s;
var review_data = { commit_ids: %r,
                    commit_sha1s: %r,
                    changeset_ids: %r };""" % (invalid_branch_name,
                                               commit_ids,
                                               commit_sha1s,
                                               changeset_ids))
    document.addInternalScript(repository.getJS())

    main = body.div("main")

    table = main.table("basic paleyellow", align="center")
    table.tr().td("h1", colspan=3).h1().text("Create Review")

    row = table.tr("line")
    row.td("heading").text("Branch Name:")
    row.td("value").text("r/").input("value", id="branch_name", value=default_branch_name)
    row.td("status")

    row = table.tr()

    if not remote:
        row.td("help", colspan=3).div().text("""\
This is the main identifier of the review.  It will be created in the review
repository to contain the commits below.  Reviewers can fetch it from there, and
additional commits can be added to the review later by pushing them to this
branch in the review repository.""")
    else:
        row.td("help", colspan=3).div().text("""\
This is the main identifier of the review.  It will be created in the review
repository to contain the commits below, and reviewers can fetch it from there.""")

    if remote:
        row = table.tr("line")
        row.td("heading").text("Tracked Branch:")
        value = row.td("value")
        value.code("branch inset").text(branch_name, linkify=linkify.Context(remote=remote))
        value.text(" in ")
        value.code("remote inset").text(remote, linkify=linkify.Context())
        row.td("status")

        row = table.tr()
        row.td("help", colspan=3).div().text("""\
Rather than pushing directly to the review branch in Critic's repository to add
commits to the review, you will be pushing to this branch (in a separate
repository,) from which Critic will fetch commits and add them to the review
automatically.""")

    row = table.tr("line")
    row.td("heading").text("Summary:")
    row.td("value").input("value", id="summary", value=summary)
    row.td("status")

    row = table.tr()
    row.td("help", colspan=3).div().text("""\
The summary should be a short summary of the changes in the review.  It will
appear in the subject of all emails sent about the review.
""")

    row = table.tr("line description")
    row.td("heading").text("Description:")
    textarea = row.td("value").textarea(id="description", rows=12)
    textarea.preformatted()
    if description: textarea.text(description)
    row.td("status")

    row = table.tr()
    row.td("help", colspan=3).div().text("""\
The description should describe the changes to be reviewed.  It is usually fine
to leave the description empty, since the commit messages are also available in
the review.
""")

    generateReviewersAndWatchersTable(db, repository, main, all_reviewers, all_watchers, applyparentfilters=applyparentfilters)

    row = table.tr("line recipients")
    row.td("heading").text("Recipient List:")
    cell = row.td("value", colspan=2).preformatted()
    cell.span("mode").text("Everyone")
    cell.span("users")
    cell.text(".")
    buttons = cell.div("buttons")
    buttons.button(onclick="editRecipientList();").text("Edit Recipient List")

    row = table.tr()
    row.td("help", colspan=3).div().text("""\
The basic recipient list for e-mails sent about the review.
""")

    log.html.render(db, main, "Commits", commits=commits)

    return document
Ejemplo n.º 14
0
def renderSelectSource(req, db, user):
    cursor = db.cursor()

    document = htmlutils.Document(req)
    html = document.html()
    head = html.head()
    body = html.body()

    page.utils.generateHeader(body, db, user, current_page="createreview")

    document.addExternalStylesheet("resource/createreview.css")
    document.addExternalScript("resource/createreview.js")
    document.addExternalScript("resource/autocomplete.js")

    document.addInternalScript(user.getJS(db))
    document.setTitle("Create Review")

    target = body.div("main")
    table = page.utils.PaleYellowTable(target, "Create Review")
    table.titleRight.innerHTML("Step 1")

    default_repository = user.getPreference(db, "defaultRepository")
    default_remotes = {}
    default_branches = {}

    def renderLocalRepository(target):
        page.utils.generateRepositorySelect(db, user, target)

        cursor.execute("""SELECT repositories.id, repositories.name, repositories.path
                            FROM repositories
                        ORDER BY repositories.id""")

        for repository_id, name, path in cursor.fetchall():
            def findRemote(local_name):
                cursor.execute("""SELECT remote
                                    FROM trackedbranches
                                   WHERE repository=%s
                                     AND local_name=%s""",
                               (repository_id, local_name))
                row = cursor.fetchone()
                if row:
                    return row[0]

            repository = gitutils.Repository.fromId(db, repository_id)
            remote = branch_name = None

            for branch in repository.getSignificantBranches(db):
                remote = findRemote(branch.name)
                if remote:
                    branch_name = branch.name
                    break

            if not remote:
                remote = findRemote("*")

            default_remotes[name] = remote
            default_branches[name] = branch_name

        document.addInternalScript("var default_remotes = %s;" % json_encode(default_remotes))
        document.addInternalScript("var default_branches = %s;" % json_encode(default_branches))

    def renderRemoteRepository(target):
        target.input("remote", value=default_remotes.get(default_repository))

    def renderWorkBranch(target):
        target.text("refs/heads/")
        target.input("workbranch")

    def renderUpstreamCommit(target):
        default_branch = default_branches.get(default_repository)
        target.input("upstreamcommit", value=("refs/heads/%s" % default_branch) if default_branch else "")

    table.addItem("Local Repository", renderLocalRepository, "Critic repository to create review in.")
    table.addItem("Remote Repository", renderRemoteRepository, "Remote repository to fetch commits from.")
    table.addItem("Work Branch", renderWorkBranch, "Work branch (in remote repository) containing commits to create review of.")
    table.addItem("Upstream Commit", renderUpstreamCommit, "Upstream commit from which the work branch was branched.")

    def renderButtons(target):
        target.button("fetchbranch").text("Fetch Branch")

    table.addCentered(renderButtons)

    return document
Ejemplo n.º 15
0
def renderSelectSource(req, db, user):
    cursor = db.cursor()

    document = htmlutils.Document(req)
    html = document.html()
    head = html.head()
    body = html.body()

    page.utils.generateHeader(body, db, user, current_page="createreview")

    document.addExternalStylesheet("resource/createreview.css")
    document.addExternalScript("resource/createreview.js")

    document.addInternalScript(user.getJS(db))
    document.setTitle("Create Review")

    target = body.div("main")
    table = page.utils.PaleYellowTable(target, "Create Review")
    table.titleRight.innerHTML("Step 1")

    default_repository = user.getPreference(db, "defaultRepository")
    default_remotes = {}
    default_branches = {}

    def renderLocalRepository(target):
        repositories = target.select("repository")

        cursor.execute(
            """SELECT repositories.id, repositories.name, repositories.path, branches.name
                            FROM repositories
                 LEFT OUTER JOIN branches ON (branches.id=repositories.branch)
                        ORDER BY id""")

        for repository_id, name, path, branch_name in cursor:
            option = repositories.option(
                "repository",
                value=name,
                selected="selected" if name == default_repository else None)
            option.text("%s [%s:%s]" %
                        (name, configuration.base.HOSTNAME, path))

            local_names = ["*"]

            if branch_name:
                local_names.append(branch_name)

            cursor.execute(
                """SELECT remote
                                FROM trackedbranches
                               WHERE repository=%s
                                 AND local_name=ANY (%s)
                            ORDER BY local_name
                               LIMIT 1""", (repository_id, local_names))

            def splitRemote(remote):
                if remote.startswith("git://"):
                    host, path = remote[6:].split("/", 1)
                    host = "git://" + host
                else:
                    host, path = remote.split(":", 1)
                return host, path

            row = cursor.fetchone()

            if row: default_remotes[name] = splitRemote(row[0])
            else: default_remotes[name] = None

            default_branches[name] = branch_name

        document.addInternalScript("var default_remotes = %s;" %
                                   json_encode(default_remotes))
        document.addInternalScript("var default_branches = %s;" %
                                   json_encode(default_branches))

    def renderRemoteRepository(target):
        host = target.p("remotehost")
        host.text("Host: ")
        hosts = host.select("remotehost")

        cursor.execute("SELECT name, path FROM knownhosts ORDER BY id")

        default_remote = default_remotes.get(default_repository)

        for name, path in cursor:
            option = hosts.option("remotehost",
                                  value=name,
                                  critic_default_path=path,
                                  selected="selected" if default_remote
                                  and default_remote[0] == name else None)
            option.text(name)

        path = target.p("remotepath")
        path.text("Path: ")
        path.input("remotepath",
                   value=default_remote[1] if default_remote else None)

    def renderWorkBranch(target):
        target.input("workbranch")

    def renderUpstreamCommit(target):
        default_branch = default_branches.get(default_repository)
        target.input("upstreamcommit",
                     value=("refs/heads/%s" %
                            default_branch) if default_branch else "")

    table.addItem("Local Repository", renderLocalRepository,
                  "Critic repository to create review in.")
    table.addItem("Remote Repository", renderRemoteRepository,
                  "Remote repository to fetch commits from.")
    table.addItem(
        "Work Branch", renderWorkBranch,
        "Work branch (in remote repository) containing commits to create review of."
    )
    table.addItem("Upstream Commit", renderUpstreamCommit,
                  "Upstream commit from which the work branch was branched.")

    def renderButtons(target):
        target.button("fetchbranch").text("Fetch Branch")

    table.addCentered(renderButtons)

    return document
Ejemplo n.º 16
0
def renderServices(req, db, user):
    req.content_type = "text/html; charset=utf-8"

    document = htmlutils.Document(req)
    document.setTitle("Services")

    html = document.html()
    head = html.head()
    body = html.body()

    page.utils.generateHeader(body, db, user, current_page="services")

    document.addExternalStylesheet("resource/services.css")
    document.addExternalScript("resource/services.js")
    document.addInternalScript(user.getJS())

    delay = 0.5
    connected = False

    while not connected and delay <= 10:
        connection = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)

        # This loop is for the case where we just restarted the service manager
        # via the /services UI.  The client-side script immediately reloads the
        # page after restart, which typically leads to us trying to connect to
        # the service manager while it's in the process of restarting.  So just
        # try a couple of times if at first the connection fails.

        try:
            connection.connect(
                configuration.services.SERVICEMANAGER["address"])
            connected = True
        except socket.error as error:
            if error[0] in (errno.ENOENT, errno.ECONNREFUSED):
                time.sleep(delay)
                delay += delay
            else:
                raise

    if not connected:
        raise page.utils.DisplayMessage("Service manager not responding!")

    connection.send(textutils.json_encode({"query": "status"}))
    connection.shutdown(socket.SHUT_WR)

    data = ""
    while True:
        received = connection.recv(4096)
        if not received: break
        data += received

    result = textutils.json_decode(data)

    if result["status"] == "error":
        raise page.utils.DisplayMessage(result["error"])

    paleyellow = page.utils.PaleYellowTable(body, "Services")

    def render(target):
        table = target.table("services callout")

        headings = table.tr("headings")
        headings.th("name").text("Name")
        headings.th("module").text("Module")
        headings.th("pid").text("PID")
        headings.th("rss").text("RSS")
        headings.th("cpu").text("CPU")
        headings.th("uptime").text("Uptime")
        headings.th("commands").text()

        table.tr("spacer").td("spacer", colspan=4)

        def formatUptime(seconds):
            def inner(seconds):
                if seconds < 60: return "%d seconds" % seconds
                elif seconds < 60 * 60: return "%d minutes" % (seconds / 60)
                elif seconds < 60 * 60 * 24:
                    return "%d hours" % (seconds / (60 * 60))
                else:
                    return "%d days" % (seconds / (60 * 60 * 24))

            return inner(int(seconds)).replace(" ", "&nbsp;")

        def formatRSS(bytes):
            if bytes < 1024: return "%d B" % bytes
            elif bytes < 1024**2: return "%.1f kB" % (float(bytes) / 1024)
            elif bytes < 1024**3: return "%.1f MB" % (float(bytes) / 1024**2)
            else: return "%.1f GB" % (float(bytes) / 1024**3)

        def formatCPU(seconds):
            minutes = int(seconds / 60)
            seconds = seconds - minutes * 60
            seconds = "%2.2f" % seconds
            if seconds.find(".") == 1: seconds = "0" + seconds
            return "%d:%s" % (minutes, seconds)

        def getProcessData(pid):
            try:
                items = open("/proc/%d/stat" % pid).read().split()

                return {
                    "cpu":
                    formatCPU(
                        float(int(items[13]) + int(items[14])) /
                        os.sysconf("SC_CLK_TCK")),
                    "rss":
                    formatRSS(int(items[23]) * os.sysconf("SC_PAGE_SIZE"))
                }
            except:
                return {"cpu": "N/A", "rss": "N/A"}

        for service_name, service_data in sorted(result["services"].items()):
            process_data = getProcessData(service_data["pid"])

            row = table.tr("service")
            row.td("name").text(service_name)
            row.td("module").text(service_data["module"])
            row.td("pid").text(service_data["pid"] if service_data["pid"] != -1
                               else "(not running)")
            row.td("rss").text(process_data["rss"])
            row.td("cpu").text(process_data["cpu"])
            row.td("uptime").innerHTML(formatUptime(service_data["uptime"]))

            commands = row.td("commands")
            commands.a(href="javascript:void(restartService(%s));" %
                       htmlutils.jsify(service_name)).text("[restart]")
            commands.a(href="javascript:void(getServiceLog(%s));" %
                       htmlutils.jsify(service_name)).text("[log]")

        for index, pid in enumerate(
                os.listdir(configuration.paths.WSGI_PIDFILE_DIR)):
            startup = float(
                open(os.path.join(configuration.paths.WSGI_PIDFILE_DIR,
                                  pid)).read())
            uptime = time.time() - startup

            process_data = getProcessData(int(pid))

            row = table.tr("service")
            row.td("name").text("wsgi:%d" % index)
            row.td("module").text()
            row.td("pid").text(pid)
            row.td("rss").text(process_data["rss"])
            row.td("cpu").text(process_data["cpu"])
            row.td("uptime").innerHTML(formatUptime(uptime))

            commands = row.td("commands")
            commands.a(href="javascript:void(restartService('wsgi'));").text(
                "[restart]")

    paleyellow.addCentered(render)

    return document
Ejemplo n.º 17
0
def renderShowCommit(req, db, user):
    profiler = profiling.Profiler()

    file_ids = req.getParameter("file", None)
    if file_ids: file_ids = set(map(int, filter(None, file_ids.split(","))))

    review_id = req.getParameter("review", None, filter=int)
    review_filter = req.getParameter("filter", None)
    context = req.getParameter("context", None, int)
    style = req.getParameter("style", "horizontal", str)
    rescan = req.getParameter("rescan", "no", str) == "yes"
    reanalyze = req.getParameter("reanalyze", None)
    wrap = req.getParameter("wrap", "yes", str) == "yes"
    conflicts = req.getParameter("conflicts", "no") == "yes"
    moves = req.getParameter("moves", "no") == "yes"
    full = req.getParameter("full", "no") == "yes"

    default_tabify = "yes" if user.getPreference(db, "commit.diff.visualTabs") else "no"
    tabify = req.getParameter("tabify", default_tabify) == "yes"

    if user.getPreference(db, "commit.diff.compactMode"): default_compact = "yes"
    else: default_compact = "no"

    compact = req.getParameter("compact", default_compact) == "yes"

    if moves:
        move_source_file_ids = req.getParameter("sourcefiles", None)
        move_target_file_ids = req.getParameter("targetfiles", None)

        if move_source_file_ids:
            move_source_file_ids = set(map(int, move_source_file_ids.split(",")))
        if move_target_file_ids:
            move_target_file_ids = set(map(int, move_target_file_ids.split(",")))

    all_commits = None
    listed_commits = None
    first_sha1 = None
    last_sha1 = None

    repository = None

    document = htmlutils.Document(req)

    if review_id is None:
        review = None
    else:
        review = dbutils.Review.fromId(db, review_id)
        if not review: raise page.utils.DisplayMessage, "Invalid review ID: %d" % review_id
        branch = review.branch
        repository = review.repository

    title = ""

    if review:
        title += "[r/%d] " % review.id

        if review_filter == "pending":
            title += "Pending: "
        elif review_filter == "reviewable":
            title += "Reviewable: "
        elif review_filter == "relevant":
            title += "Relevant: "

    if not repository:
        parameter = req.getParameter("repository", None)
        if parameter:
            repository = gitutils.Repository.fromParameter(db, parameter)
            if not repository:
                yield page.utils.displayMessage(db, req, user, "'%s' is not a valid repository!" % repository.name, review=review)
                return

    cursor = db.cursor()

    def expand_sha1(sha1):
        if review and re.match("^[0-9a-f]+$", sha1):
            cursor.execute("""SELECT sha1
                                FROM commits
                                JOIN changesets ON (changesets.child=commits.id)
                                JOIN reviewchangesets ON (reviewchangesets.changeset=changesets.id)
                               WHERE reviewchangesets.review=%s
                                 AND commits.sha1 LIKE %s""",
                           (review.id, sha1 + "%"))
            try: return cursor.fetchone()[0]
            except: pass

        if len(sha1) == 40: return sha1
        else: return repository.revparse(sha1)

    sha1 = req.getParameter("sha1", None, filter=expand_sha1)

    if sha1 is None:
        from_sha1 = req.getParameter("from", None, filter=expand_sha1)
        to_sha1 = req.getParameter("to", None, filter=expand_sha1)

        if (from_sha1 is None) != (to_sha1 is None):
            raise page.utils.DisplayMessage, "invalid parameters; one of 'from'/'to' specified but not both"

        if from_sha1 is None:
            first_sha1 = req.getParameter("first", None, filter=expand_sha1)
            last_sha1 = req.getParameter("last", None, filter=expand_sha1)

            if (first_sha1 is None) != (last_sha1 is None):
                raise page.utils.DisplayMessage, "invalid parameters; one of 'first'/'last' specified but not both"

            if first_sha1 is None:
                if review_id and review_filter:
                    from_sha1, to_sha1, all_commits, listed_commits = commitRangeFromReview(db, user, review, review_filter, file_ids)
                    if from_sha1 is None:
                        sha1 = to_sha1
                        to_sha1 = None
                else:
                    raise page.utils.DisplayMessage, "invalid parameters; need 'sha1', 'from'/'to' or 'first'/'last'"
    else:
        from_sha1 = None
        to_sha1 = None

    if context is None: context = user.getPreference(db, "commit.diff.contextLines")

    one_sha1 = filter(None, (sha1, from_sha1, to_sha1, first_sha1, last_sha1))[0]

    if repository:
        if not repository.iscommit(one_sha1):
            yield page.utils.displayMessage(db, req, user, "'%s' is not a valid commit in the repository '%s'!" % (one_sha1, repository.name), review=review)
            return
    else:
        default = user.getPreference(db, "defaultRepository")
        if default:
            repository = gitutils.Repository.fromName(db, default)
            if repository and not repository.iscommit(one_sha1):
                repository = None
        if not repository:
            repository = gitutils.Repository.fromSHA1(db, one_sha1)
            if not repository:
                yield page.utils.displayMessage(db, req, user, "'%s' is not a valid commit in any repository!" % one_sha1, review=review)
                return

    if first_sha1 is not None:
        try:
            first_commit = gitutils.Commit.fromSHA1(db, repository, first_sha1)
        except gitutils.GitError, error:
            raise page.utils.DisplayMessage("Invalid SHA-1", "%s is not a commit in %s" % (error.sha1, repository.path))

        if len(first_commit.parents) != 1:
            yield page.utils.displayMessage(db, req, user, "Invalid parameters; 'first' must be a commit with a single parent.", review=review)
            return

        from_sha1 = first_commit.parents[0]
        to_sha1 = last_sha1
Ejemplo n.º 18
0
def renderFilterChanges(req, db, user):
    review_id = page.utils.getParameter(req, "review", filter=int)
    first_sha1 = page.utils.getParameter(req, "first", None)
    last_sha1 = page.utils.getParameter(req, "last", None)

    cursor = db.cursor()

    review = dbutils.Review.fromId(db, review_id)

    root_directories = {}
    root_files = {}

    def processFile(file_id):
        components = dbutils.describe_file(db, file_id).split("/")
        directories, files = root_directories, root_files
        for directory_name in components[:-1]:
            directories, files = directories.setdefault(
                directory_name, ({}, {}))
        files[components[-1]] = file_id

    if first_sha1 and last_sha1:
        sha1 = last_sha1
        changesets = []

        cursor.execute(
            """SELECT commits.sha1
                            FROM commits
                            JOIN changesets ON (changesets.child=commits.id)
                            JOIN reviewchangesets ON (reviewchangesets.changeset=changesets.id)
                           WHERE reviewchangesets.review=%s""", (review.id, ))

        first_commit = gitutils.Commit.fromSHA1(db, review.repository,
                                                first_sha1)
        last_commit = gitutils.Commit.fromSHA1(db, review.repository,
                                               last_sha1)

        if len(first_commit.parents) != 1:
            raise page.utils.DisplayMessage(
                "Filtering failed!",
                "First selected commit is a merge commit.  Please go back and select a different range of commits.",
                review=review)

        from_commit = gitutils.Commit.fromSHA1(db, review.repository,
                                               first_commit.parents[0])
        to_commit = last_commit

        commits = log.commitset.CommitSet.fromRange(db, from_commit, to_commit)

        if not commits:
            raise page.utils.DisplayMessage(
                "Filtering failed!",
                "The range of commits selected includes merges with ancestors not included in the range.  Please go back and select a different range of commits.",
                review=review)

        cursor.execute(
            """SELECT DISTINCT reviewfiles.file
                            FROM reviewfiles
                            JOIN changesets ON (changesets.id=reviewfiles.changeset)
                            JOIN commits ON (commits.id=changesets.child)
                           WHERE reviewfiles.review=%s
                             AND commits.sha1=ANY (%s)""",
            (review.id, [commit.sha1 for commit in commits]))
    else:
        cursor.execute("SELECT DISTINCT file FROM reviewfiles WHERE review=%s",
                       (review.id, ))

    for (file_id, ) in cursor:
        processFile(file_id)

    document = htmlutils.Document(req)

    html = document.html()
    head = html.head()
    body = html.body()

    page.utils.generateHeader(
        body,
        db,
        user,
        lambda target: review_utils.renderDraftItems(db, user, review, target),
        extra_links=[("r/%d" % review.id, "Back to Review", True)])

    document.addExternalStylesheet("resource/filterchanges.css")
    document.addExternalScript("resource/filterchanges.js")
    document.addInternalScript(user.getJS())
    document.addInternalScript(review.getJS())

    if first_sha1 and last_sha1:
        document.addInternalScript(
            "var commitRange = { first: %s, last: %s };" %
            (htmlutils.jsify(first_sha1), htmlutils.jsify(last_sha1)))
    else:
        document.addInternalScript("var commitRange = null;")

    target = body.div("main")

    basic = target.table('filter paleyellow', align='center', cellspacing=0)
    basic.col(width='10%')
    basic.col(width='60%')
    basic.col(width='30%')
    row = basic.tr("header")
    row.td('h1', colspan=2).h1().text("Filter Changes")
    row.td('h1 button').button("display").text("Display Diff")

    row = basic.tr("headings")
    row.td("select").text("Include")
    row.td("path").text("Path")
    row.td().text()

    def outputDirectory(base, name, directories, files):
        if name:
            level = base.count("/")
            row = basic.tr("directory", critic_level=level)
            row.td("select").input(type="checkbox")
            if level > 1:
                row.td("path").preformatted().innerHTML((" " *
                                                         (len(base) - 2)) +
                                                        "&#8230;/" + name +
                                                        "/")
            else:
                row.td("path").preformatted().innerHTML(base + name + "/")
            row.td().text()
        else:
            level = 0

        for directory_name in sorted(directories.keys()):
            outputDirectory(base + name + "/" if name else "", directory_name,
                            directories[directory_name][0],
                            directories[directory_name][1])

        for file_name in sorted(files.keys()):
            row = basic.tr("file",
                           critic_file_id=files[file_name],
                           critic_level=level + 1)
            row.td("select").input(type="checkbox")
            row.td("path").preformatted().innerHTML(
                (" " * (len(base + name) - 1)) + "&#8230;/" +
                htmlutils.htmlify(file_name))
            row.td().text()

    outputDirectory("", "", root_directories, root_files)

    row = basic.tr("footer")
    row.td('spacer', colspan=3)

    row = basic.tr("footer")
    row.td('button', colspan=3).button("display").text("Display Diff")

    if user.getPreference(db, "ui.keyboardShortcuts"):
        page.utils.renderShortcuts(body, "filterchanges")

    return document
Ejemplo n.º 19
0
def renderManageExtensions(req, db, user):
    if not configuration.extensions.ENABLED:
        administrators = dbutils.getAdministratorContacts(db, as_html=True)
        raise page.utils.DisplayMessage(
            title="Extension support not enabled",
            body=(("<p>This Critic system does not support extensions.</p>"
                   "<p>Contact %s to have it enabled, or see the "
                   "<a href='/tutorial?item=administration#extensions'>"
                   "section on extensions</a> in the system administration "
                   "tutorial for more information.</p>")
                  % administrators),
            html=True)

    cursor = db.cursor()

    what = req.getParameter("what", "available")
    selected_versions = page.utils.json_decode(req.getParameter("select", "{}"))
    focused = req.getParameter("focus", None)

    if what == "installed":
        title = "Installed Extensions"
        listed_extensions = []
        for extension_id, _, _, _ in Extension.getInstalls(db, user):
            try:
                listed_extensions.append(Extension.fromId(db, extension_id))
            except ExtensionError as error:
                listed_extensions.append(error)
    else:
        title = "Available Extensions"
        listed_extensions = Extension.find(db)

    req.content_type = "text/html; charset=utf-8"

    document = htmlutils.Document(req)
    document.setTitle("Manage Extensions")

    html = document.html()
    head = html.head()
    body = html.body()

    def generateRight(target):
        target.a("button", href="tutorial?item=extensions").text("Tutorial")
        target.text(" ")
        target.a("button", href="tutorial?item=extensions-api").text("API Documentation")

    page.utils.generateHeader(body, db, user, current_page="extensions", generate_right=generateRight)

    document.addExternalStylesheet("resource/manageextensions.css")
    document.addExternalScript("resource/manageextensions.js")
    document.addInternalScript(user.getJS())

    table = page.utils.PaleYellowTable(body, title)

    def addTitleRightLink(url, label):
        if user.name != req.user:
            url += "&user=%s" % user.name
        table.titleRight.text(" ")
        table.titleRight.a(href=url).text("[" + label + " extensions]")

    if what != "installed" or focused:
        addTitleRightLink("/manageextensions?what=installed", "installed")
    if what != "available" or focused:
        addTitleRightLink("/manageextensions?what=available", "available")

    for item in listed_extensions:
        if isinstance(item, ExtensionError):
            extension_error = item
            extension = item.extension
        else:
            extension_error = None
            extension = item

        if focused and extension.getKey() != focused:
            continue

        extension_path = extension.getPath()

        if extension.isSystemExtension():
            hosting_user = None
        else:
            hosting_user = extension.getAuthor(db)

        selected_version = selected_versions.get(extension.getKey(), False)
        installed_sha1, installed_version = extension.getInstalledVersion(db, user)
        universal_sha1, universal_version = extension.getInstalledVersion(db, None)
        installed_upgradeable = universal_upgradeable = False

        if extension_error is None:
            if installed_sha1:
                current_sha1 = extension.getCurrentSHA1(installed_version)
                installed_upgradeable = installed_sha1 != current_sha1
            if universal_sha1:
                current_sha1 = extension.getCurrentSHA1(universal_version)
                universal_upgradeable = universal_sha1 != current_sha1

        def massage_version(version):
            if version is None:
                return "live"
            elif version:
                return "version/%s" % version
            else:
                return None

        if selected_version is False:
            selected_version = installed_version
        if selected_version is False:
            selected_version = universal_version

        install_version = massage_version(selected_version)
        installed_version = massage_version(installed_version)
        universal_version = massage_version(universal_version)

        manifest = None

        if extension_error is None:
            try:
                if selected_version is False:
                    manifest = extension.getManifest()
                else:
                    manifest = extension.getManifest(selected_version)
            except ManifestError as error:
                pass
        elif installed_sha1:
            manifest = extension.getManifest(installed_version, installed_sha1)
        elif universal_sha1:
            manifest = extension.getManifest(universal_version, universal_sha1)

        if manifest:
            if what == "available" and manifest.hidden:
                # Hide from view unless the user is hosting the extension, or is
                # an administrator and the extension is a system extension.
                if extension.isSystemExtension():
                    if not user.hasRole(db, "administrator"):
                        continue
                elif hosting_user != user:
                    continue
        else:
            if hosting_user != user:
                continue

        extension_id = extension.getExtensionID(db, create=False)

        if not user.isAnonymous():
            buttons = []

            if extension_id is not None:
                cursor.execute("""SELECT 1
                                    FROM extensionstorage
                                   WHERE extension=%s
                                     AND uid=%s""",
                               (extension_id, user.id))

                if cursor.fetchone():
                    buttons.append(("Clear storage",
                                    ("clearExtensionStorage(%s, %s)"
                                     % (htmlutils.jsify(extension.getAuthorName()),
                                        htmlutils.jsify(extension.getName())))))

            if not installed_version:
                if manifest and install_version and install_version != universal_version:
                    buttons.append(("Install",
                                    ("installExtension(%s, %s, %s)"
                                     % (htmlutils.jsify(extension.getAuthorName()),
                                        htmlutils.jsify(extension.getName()),
                                        htmlutils.jsify(install_version)))))
            else:
                buttons.append(("Uninstall",
                                ("uninstallExtension(%s, %s)"
                                 % (htmlutils.jsify(extension.getAuthorName()),
                                    htmlutils.jsify(extension.getName())))))

                if manifest and (install_version != installed_version
                                 or (installed_sha1 and installed_upgradeable)):
                    if install_version == installed_version:
                        label = "Upgrade"
                    else:
                        label = "Install"

                    buttons.append(("Upgrade",
                                    ("reinstallExtension(%s, %s, %s)"
                                     % (htmlutils.jsify(extension.getAuthorName()),
                                        htmlutils.jsify(extension.getName()),
                                        htmlutils.jsify(install_version)))))

            if user.hasRole(db, "administrator"):
                if not universal_version:
                    if manifest and install_version:
                        buttons.append(("Install (universal)",
                                        ("installExtension(%s, %s, %s, true)"
                                         % (htmlutils.jsify(extension.getAuthorName()),
                                            htmlutils.jsify(extension.getName()),
                                            htmlutils.jsify(install_version)))))
                else:
                    buttons.append(("Uninstall (universal)",
                                    ("uninstallExtension(%s, %s, true)"
                                     % (htmlutils.jsify(extension.getAuthorName()),
                                        htmlutils.jsify(extension.getName())))))

                    if manifest and (install_version != universal_version
                                     or (universal_sha1 and universal_upgradeable)):
                        if install_version == universal_version:
                            label = "Upgrade (universal)"
                        else:
                            label = "Install (universal)"

                        buttons.append((label,
                                        ("reinstallExtension(%s, %s, %s, true)"
                                         % (htmlutils.jsify(extension.getAuthorName()),
                                            htmlutils.jsify(extension.getName()),
                                            htmlutils.jsify(universal_version)))))
        else:
            buttons = None

        def renderItem(target):
            target.span("name").innerHTML(extension.getTitle(db, html=True))

            if hosting_user:
                is_author = manifest and manifest.isAuthor(db, hosting_user)
                is_sole_author = is_author and len(manifest.authors) == 1
            else:
                is_sole_author = False

            if extension_error is None:
                span = target.span("details")
                span.b().text("Details: ")
                select = span.select("details", critic_author=extension.getAuthorName(), critic_extension=extension.getName())
                select.option(value='', selected="selected" if selected_version is False else None).text("Select version")
                versions = extension.getVersions()
                if versions:
                    optgroup = select.optgroup(label="Official Versions")
                    for version in versions:
                        optgroup.option(value="version/%s" % version, selected="selected" if selected_version == version else None).text("%s" % version.upper())
                optgroup = select.optgroup(label="Development")
                optgroup.option(value='live', selected="selected" if selected_version is None else None).text("LIVE")

            if manifest:
                is_installed = bool(installed_version)

                if is_installed:
                    target.span("installed").text(" [installed]")
                else:
                    is_installed = bool(universal_version)

                    if is_installed:
                        target.span("installed").text(" [installed (universal)]")

                target.div("description").preformatted().text(manifest.description, linkify=True)

                if not is_sole_author:
                    authors = target.div("authors")
                    authors.b().text("Author%s:" % ("s" if len(manifest.authors) > 1 else ""))
                    authors.text(", ".join(author.name for author in manifest.getAuthors()))
            else:
                is_installed = False

                div = target.div("description broken").preformatted()

                if extension_error is None:
                    anchor = div.a(href="loadmanifest?key=%s" % extension.getKey())
                    anchor.text("[This extension has an invalid MANIFEST file]")
                else:
                    div.text("[This extension has been deleted or has become inaccessible]")

            if selected_version is False:
                return

            pages = []
            injects = []
            processcommits = []
            filterhooks = []
            scheduled = []

            if manifest:
                for role in manifest.roles:
                    if isinstance(role, PageRole):
                        pages.append(role)
                    elif isinstance(role, InjectRole):
                        injects.append(role)
                    elif isinstance(role, ProcessCommitsRole):
                        processcommits.append(role)
                    elif isinstance(role, FilterHookRole):
                        filterhooks.append(role)
                    elif isinstance(role, ScheduledRole):
                        scheduled.append(role)

            role_table = target.table("roles")

            if pages:
                role_table.tr().th(colspan=2).text("Pages")

                for role in pages:
                    row = role_table.tr()
                    url = "%s/%s" % (dbutils.getURLPrefix(db, user), role.pattern)
                    if is_installed and "*" not in url:
                        row.td("pattern").a(href=url).text(url)
                    else:
                        row.td("pattern").text(url)
                    td = row.td("description")
                    td.text(role.description)

            if injects:
                role_table.tr().th(colspan=2).text("Page Injections")

                for role in injects:
                    row = role_table.tr()
                    row.td("pattern").text("%s/%s" % (dbutils.getURLPrefix(db, user), role.pattern))
                    td = row.td("description")
                    td.text(role.description)

            if processcommits:
                role_table.tr().th(colspan=2).text("ProcessCommits hooks")
                ul = role_table.tr().td(colspan=2).ul()

                for role in processcommits:
                    li = ul.li()
                    li.text(role.description)

            if filterhooks:
                role_table.tr().th(colspan=2).text("FilterHook hooks")

                for role in filterhooks:
                    row = role_table.tr()
                    row.td("title").text(role.title)
                    row.td("description").text(role.description)

            if scheduled:
                role_table.tr().th(colspan=2).text("Scheduled hooks")

                for role in scheduled:
                    row = role_table.tr()
                    row.td("pattern").text("%s @ %s" % (role.frequency, role.at))
                    td = row.td("description")
                    td.text(role.description)

        installed_by = ""

        if extension_id is not None:
            cursor.execute("""SELECT uid
                                FROM extensioninstalls
                                JOIN extensions ON (extensions.id=extensioninstalls.extension)
                               WHERE extensions.id=%s""",
                           (extension.getExtensionID(db, create=False),))

            user_ids = set(user_id for user_id, in cursor.fetchall())
            if user_ids:
                installed_by = " (installed"
                if None in user_ids:
                    installed_by += " universally"
                    user_ids.remove(None)
                    if user_ids:
                        installed_by += " and"
                if user_ids:
                    installed_by += (" by %d user%s"
                                  % (len(user_ids),
                                     "s" if len(user_ids) > 1 else ""))
                installed_by += ")"

        table.addItem("Extension", renderItem, extension_path + "/" + installed_by, buttons)

    document.addInternalScript("var selected_versions = %s;" % page.utils.json_encode(selected_versions))

    return document
Ejemplo n.º 20
0
def renderShowComments(req, db, user):
    context_lines = req.getParameter("context",
                                     user.getPreference(
                                         db, "comment.diff.contextLines"),
                                     filter=int)

    default_compact = "yes" if user.getPreference(
        db, "commit.diff.compactMode") else "no"
    compact = req.getParameter("compact", default_compact) == "yes"

    default_tabify = "yes" if user.getPreference(
        db, "commit.diff.visualTabs") else "no"
    tabify = req.getParameter("tabify", default_tabify) == "yes"

    original = req.getParameter("original", "no") == "yes"

    review_id = req.getParameter("review", filter=int)
    batch_id = req.getParameter("batch", None, filter=int)
    filter = req.getParameter("filter", "all")
    blame = req.getParameter("blame", None)

    profiler = profiling.Profiler()

    review = dbutils.Review.fromId(db, review_id)
    review.repository.enableBlobCache()

    cursor = db.cursor()

    profiler.check("create review")

    if blame is not None:
        blame_user = dbutils.User.fromName(db, blame)

        cursor.execute(
            """SELECT commentchains.id
                            FROM commentchains
                            JOIN commentchainlines ON (commentchainlines.chain=commentchains.id)
                            JOIN fileversions ON (fileversions.new_sha1=commentchainlines.sha1)
                            JOIN changesets ON (changesets.id=fileversions.changeset)
                            JOIN commits ON (commits.id=changesets.child)
                            JOIN gitusers ON (gitusers.id=commits.author_gituser)
                            JOIN usergitemails USING (email)
                            JOIN reviewchangesets ON (reviewchangesets.changeset=changesets.id AND reviewchangesets.review=commentchains.review)
                           WHERE commentchains.review=%s
                             AND usergitemails.uid=%s
                             AND commentchains.state!='empty'
                             AND (commentchains.state!='draft' OR commentchains.uid=%s)
                        ORDER BY commentchains.file, commentchainlines.first_line""",
            (review.id, blame_user.id, user.id))

        include_chain_ids = set([chain_id for (chain_id, ) in cursor])

        profiler.check("initial blame filtering")
    else:
        include_chain_ids = None

    if filter == "toread":
        query = """SELECT commentchains.id
                     FROM commentchains
                     JOIN comments ON (comments.chain=commentchains.id)
                     JOIN commentstoread ON (commentstoread.comment=comments.id)
          LEFT OUTER JOIN commentchainlines ON (commentchainlines.chain=commentchains.id)
                    WHERE review=%s
                      AND commentstoread.uid=%s
                 ORDER BY file, first_line"""

        cursor.execute(query, (review.id, user.id))
    else:
        query = """SELECT commentchains.id
                     FROM commentchains
          LEFT OUTER JOIN commentchainlines ON (chain=id)
                    WHERE review=%s
                      AND commentchains.state!='empty'"""

        arguments = [review.id]

        if filter == "issues":
            query += " AND type='issue' AND (commentchains.state!='draft' OR commentchains.uid=%s)"
            arguments.append(user.id)
        elif filter == "draft-issues":
            query += " AND type='issue' AND commentchains.state='draft' AND commentchains.uid=%s"
            arguments.append(user.id)
        elif filter == "open-issues":
            query += " AND type='issue' AND commentchains.state='open'"
        elif filter == "addressed-issues":
            query += " AND type='issue' AND commentchains.state='addressed'"
        elif filter == "closed-issues":
            query += " AND type='issue' AND commentchains.state='closed'"
        elif filter == "notes":
            query += " AND type='note' AND (commentchains.state!='draft' OR commentchains.uid=%s)"
            arguments.append(user.id)
        elif filter == "draft-notes":
            query += " AND type='note' AND commentchains.state='draft' AND commentchains.uid=%s"
            arguments.append(user.id)
        elif filter == "open-notes":
            query += " AND type='note' AND commentchains.state='open'"
        else:
            query += " AND (commentchains.state!='draft' OR commentchains.uid=%s)"
            arguments.append(user.id)

        if batch_id is not None:
            query += " AND batch=%s"
            arguments.append(batch_id)

        # This ordering is inaccurate if comments apply to the same file but
        # different commits, but then, in that case there isn't really a
        # well-defined natural order either.  Two comments that apply to the
        # same file and commit will at least be order by line number, and that's
        # better than nothing.
        query += " ORDER BY file, first_line"

        cursor.execute(query, arguments)

    profiler.check("main query")

    if include_chain_ids is None:
        chain_ids = [chain_id for (chain_id, ) in cursor]
    else:
        chain_ids = [
            chain_id for (chain_id, ) in cursor
            if chain_id in include_chain_ids
        ]

    profiler.check("query result")

    document = htmlutils.Document(req)

    html = document.html()
    head = html.head()
    body = html.body()

    document.addInternalScript(user.getJS(db))
    document.addInternalScript(review.getJS())

    page.utils.generateHeader(
        body,
        db,
        user,
        lambda target: review_utils.renderDraftItems(db, user, review, target),
        extra_links=[("r/%d" % review.id, "Back to Review")])

    profiler.check("page header")

    target = body.div("main")

    if chain_ids and not user.isAnonymous() and user.name == req.user:
        document.addInternalScript(
            "$(function () { markChainsAsRead([%s]); });" %
            ", ".join(map(str, chain_ids)))

    if chain_ids:
        processed = set()

        chains = []
        file_ids = set()
        changesets_files = {}
        changesets = {}

        if blame is not None:
            annotators = {}
            review.branch.loadCommits(db)
            commits = log.commitset.CommitSet(review.branch.commits)

        for chain_id in chain_ids:
            if chain_id in processed:
                continue
            else:
                processed.add(chain_id)

                chain = review_comment.CommentChain.fromId(db,
                                                           chain_id,
                                                           user,
                                                           review=review)
                chains.append(chain)

                if chain.file_id is not None:
                    file_ids.add(chain.file_id)
                    parent, child = review_html.getCodeCommentChainChangeset(
                        db, chain, original)
                    if parent and child:
                        changesets_files.setdefault((parent, child),
                                                    set()).add(chain.file_id)

        profiler.check("load chains")

        changeset_cache = {}

        for (from_commit,
             to_commit), filtered_file_ids in changesets_files.items():
            changesets[(from_commit,
                        to_commit)] = changeset_utils.createChangeset(
                            db,
                            user,
                            review.repository,
                            from_commit=from_commit,
                            to_commit=to_commit,
                            filtered_file_ids=filtered_file_ids)[0]
            profiler.check("create changesets")

            if blame is not None:
                annotators[(from_commit,
                            to_commit)] = operation.blame.LineAnnotator(
                                db,
                                from_commit,
                                to_commit,
                                file_ids=file_ids,
                                commits=commits,
                                changeset_cache=changeset_cache)
                profiler.check("create annotators")

        for chain in chains:
            if blame is not None and chain.file_id is not None:
                try:
                    changeset = changesets[(chain.first_commit,
                                            chain.last_commit)]
                    annotator = annotators[(chain.first_commit,
                                            chain.last_commit)]
                except KeyError:
                    # Most likely a comment created via /showfile.  Such a
                    # comment could be in code that 'blame_user' modified in the
                    # review, but for now, let's skip the comment.
                    continue
                else:
                    file_in_changeset = changeset.getFile(chain.file_id)

                    if not file_in_changeset:
                        continue

                    try:
                        offset, count = chain.lines_by_sha1[
                            file_in_changeset.new_sha1]
                    except KeyError:
                        # Probably a chain raised against the "old" side of the diff.
                        continue
                    else:
                        if not annotator.annotate(chain.file_id,
                                                  offset,
                                                  offset + count - 1,
                                                  check_user=blame_user):
                            continue

            profiler.check("detailed blame filtering")

            if chain.file_id is not None:
                from_commit, to_commit = review_html.getCodeCommentChainChangeset(
                    db, chain, original)
                changeset = changesets.get((from_commit, to_commit))
            else:
                changeset = None

            review_html.renderCommentChain(db,
                                           target,
                                           user,
                                           review,
                                           chain,
                                           context_lines=context_lines,
                                           compact=compact,
                                           tabify=tabify,
                                           original=original,
                                           changeset=changeset,
                                           linkify=linkify.Context(
                                               db=db,
                                               request=req,
                                               review=review))

            profiler.check("rendering")

            yield document.render(
                stop=target, pretty=not compact
            ) + "<script>console.log((new Date).toString());</script>"

            profiler.check("transfer")

        page.utils.renderShortcuts(target, "showcomments", review=review)
    else:
        target.h1(align="center").text("No comments.")

    profiler.output(db, user, document)

    yield document.render(pretty=not compact)
Ejemplo n.º 21
0
def renderDashboard(req, db, user):
    if user.isAnonymous(): default_show = "open"
    else: default_show = user.getPreference(db, "dashboard.defaultGroups")

    show = req.getParameter("show", default_show)

    if user.isAnonymous():

        def possible(group):
            return group in ("open", "closed")
    else:

        def possible(group):
            return True

    showlist = filter(possible, show.split(","))
    showset = set(showlist)

    if user.getPreference(db, "commit.diff.compactMode"):
        default_compact = "yes"
    else:
        default_compact = "no"

    repository_arg = req.getParameter("repository", None)
    repository = gitutils.Repository.fromParameter(
        db, repository_arg) if repository_arg else None
    compact = req.getParameter("compact", default_compact) == "yes"

    cursor = db.cursor()

    profiler = profiling.Profiler()
    document = htmlutils.Document(req)

    document.setTitle("Dashboard")

    html = document.html()
    head = html.head()
    body = html.body()

    def generateRight(target):
        def addLink(key, title=None):
            if not title: title = key
            if key not in showset:
                target.text("[")
                target.a(href="dashboard?show=%s" %
                         ",".join(showlist + [key])).text("show %s" % title)
                target.text("]")

        if user.isAnonymous():
            addLink("open", "open")
            addLink("closed")
        else:
            target.text("[")
            target.a(href="config?highlight=dashboard.defaultGroups").text(
                "configure defaults")
            target.text("]")

            addLink("owned")
            addLink("draft")
            addLink("active")
            addLink("watched")
            addLink("open", "other open")
            addLink("closed")

    page.utils.generateHeader(body,
                              db,
                              user,
                              current_page="dashboard",
                              generate_right=generateRight,
                              profiler=profiler)

    document.addExternalStylesheet("resource/dashboard.css")
    document.addExternalScript("resource/dashboard.js")
    document.addInternalScript(user.getJS())

    target = body.div("main")

    def flush(target):
        return document.render(stop=target, pretty=not compact)

    def includeReview(review_id):
        if repository:
            cursor = db.cursor()
            cursor.execute(
                "SELECT branches.repository FROM branches JOIN reviews ON (reviews.branch=branches.id) WHERE reviews.id=%s",
                (review_id, ))
            return cursor.fetchone()[0] == repository.id
        else:
            return True

    def sortedReviews(data):
        reviews = []
        for review_id in sorted(data.keys()):
            reviews.append((review_id, data[review_id]))
        return reviews

    def isAccepted(review_ids):
        cursor.execute(
            """SELECT reviews.id, COUNT(reviewfiles.id)=0 AND COUNT(commentchains.id)=0
                            FROM reviews
                 LEFT OUTER JOIN reviewfiles ON (reviewfiles.review=reviews.id
                                             AND reviewfiles.state='pending')
                 LEFT OUTER JOIN commentchains ON (commentchains.review=reviews.id
                                               AND commentchains.type='issue'
                                               AND commentchains.state='open')
                           WHERE reviews.id=ANY (%s)
                        GROUP BY reviews.id""", (review_ids, ))

        return dict(cursor)

    checked_repositories = {}

    def accessRepository(repository_id):
        already_checked = checked_repositories.get(repository_id)
        if already_checked is not None:
            return already_checked
        is_allowed = auth.AccessControlProfile.isAllowedRepository(
            db.profiles, "read", repository_id)
        checked_repositories[repository_id] = is_allowed
        return is_allowed

    def renderReviews(target, reviews, lines_and_comments=True, links=True):
        cursor.execute(
            "SELECT id, repository, name FROM branches WHERE id=ANY (%s)",
            (list(branch_id for _, (_, branch_id, _, _) in reviews), ))

        branch_data = {
            branch_id: (repository_id, name)
            for branch_id, repository_id, name in cursor
        }

        for review_id, (summary, branch_id, lines, comments) in reviews:
            repository_id, branch_name = branch_data[branch_id]
            if not accessRepository(repository_id):
                continue
            row = target.tr("review")
            row.td("name").text(branch_name)
            row.td("title").a(href="r/%d" % review_id).text(summary)

            if lines_and_comments:
                if lines:
                    if links:
                        row.td("lines").a(
                            href="showcommit?review=%d&filter=pending" %
                            review_id).text("%d lines" % (sum(lines)))
                    else:
                        row.td("lines").text("%d lines" % (sum(lines)))
                else:
                    row.td("lines").text()
                if comments:
                    if links:
                        row.td("comments").a(
                            href="showcomments?review=%s&filter=toread" %
                            review_id).text(
                                "%d comment%s" %
                                (comments, "s" if comments > 1 else ""))
                    else:
                        row.td("comments").text(
                            "%d comment%s" %
                            (comments, "s" if comments > 1 else ""))
                else:
                    row.td("comments").text()

    def hidden(what):
        new_show = ",".join(filter(lambda item: item != what, showlist))
        if new_show: return "dashboard?show=%s" % new_show
        else: return "dashboard"

    profiler.check("generate: prologue")

    def renderOwned():
        owned_accepted = []
        owned_open = []

        cursor.execute(
            """SELECT id, summary, branch
                            FROM reviews
                            JOIN reviewusers ON (review=id AND reviewusers.owner)
                           WHERE state='open'
                             AND uid=%s
                        ORDER BY id DESC""", (user.id, ))

        owned = cursor.fetchall()

        profiler.check("query: owned")

        is_accepted = isAccepted(list(review_id for review_id, _, _ in owned))

        for review_id, summary, branch_id in owned:
            if includeReview(review_id):
                if is_accepted[review_id]:
                    owned_accepted.append(
                        (review_id, (summary, branch_id, None, None)))
                else:
                    owned_open.append(
                        (review_id, (summary, branch_id, None, None)))

        profiler.check("processing: owned")

        if owned_accepted or owned_open:
            table = target.table("paleyellow reviews",
                                 id="owned",
                                 align="center",
                                 cellspacing=0)
            table.col(width="15%")
            table.col(width="55%")
            table.col(width="15%")
            table.col(width="15%")
            header = table.tr().td("h1", colspan=4).h1()
            header.text("Owned By You")
            header.span("right").a(href=hidden("owned")).text("[hide]")

            if owned_accepted:
                table.tr(id="accepted").td("h2",
                                           colspan=4).h2().text("Accepted")
                renderReviews(table, owned_accepted)

            if owned_open:
                table.tr(id="open").td("h2", colspan=4).h2().text("Pending")
                renderReviews(table, owned_open)

            profiler.check("generate: owned")
            return True

    def renderDraft():
        draft_changes = {}
        draft_comments = {}
        draft_both = {}

        cursor.execute(
            """SELECT reviews.id, reviews.summary, reviews.branch, SUM(reviewfiles.deleted), SUM(reviewfiles.inserted)
                            FROM reviews
                            JOIN reviewfiles ON (reviewfiles.review=reviews.id)
                            JOIN reviewfilechanges ON (reviewfilechanges.file=reviewfiles.id)
                           WHERE reviews.state='open'
                             AND reviewfiles.state=reviewfilechanges.from_state
                             AND reviewfilechanges.state='draft'
                             AND reviewfilechanges.uid=%s
                        GROUP BY reviews.id, reviews.summary, reviews.branch""",
            (user.id, ))

        profiler.check("query: draft lines")

        for review_id, summary, branch_id, deleted_count, inserted_count in cursor:
            if includeReview(review_id):
                draft_changes[review_id] = (summary, branch_id,
                                            (deleted_count,
                                             inserted_count), None)

        profiler.check("processing: draft lines")

        cursor.execute(
            """SELECT reviews.id, reviews.summary, reviews.branch, COUNT(comments.id)
                            FROM reviews
                            JOIN commentchains ON (commentchains.review=reviews.id)
                            JOIN comments ON (comments.chain=commentchains.id)
                           WHERE comments.state='draft'
                             AND comments.uid=%s
                        GROUP BY reviews.id, reviews.summary, reviews.branch""",
            [user.id])

        profiler.check("query: draft comments")

        for review_id, summary, branch_id, comments_count in cursor:
            if includeReview(review_id):
                if draft_changes.has_key(review_id):
                    draft_both[review_id] = (summary, branch_id,
                                             draft_changes[review_id][2],
                                             comments_count)
                    del draft_changes[review_id]
                else:
                    draft_comments[review_id] = (summary, branch_id, None,
                                                 comments_count)

        profiler.check("processing: draft comments")

        if draft_both or draft_changes or draft_comments:
            table = target.table("paleyellow reviews",
                                 id="draft",
                                 align="center",
                                 cellspacing=0)
            table.col(width="15%")
            table.col(width="55%")
            table.col(width="15%")
            table.col(width="15%")
            header = table.tr().td("h1", colspan=4).h1()
            header.text("Reviews With Unsubmitted Work")
            header.span("right").a(href=hidden("draft")).text("[hide]")

            if draft_both:
                table.tr(id="draft-changes-comments").td(
                    "h2", colspan=4).h2().text("Draft Changes And Comments")
                renderReviews(table, sortedReviews(draft_both), links=False)

            if draft_changes:
                table.tr(id="draft-changes").td(
                    "h2", colspan=4).h2().text("Draft Changes")
                renderReviews(table, sortedReviews(draft_changes), links=False)

            if draft_comments:
                table.tr(id="draft-comments").td(
                    "h2", colspan=4).h2().text("Draft Comments")
                renderReviews(table,
                              sortedReviews(draft_comments),
                              links=False)

            profiler.check("generate: draft")
            return True

    active = {}

    def fetchActive():
        if not active:
            with_changes = {}
            with_comments = {}
            with_both = {}

            cursor.execute(
                """SELECT reviews.id, reviews.summary, reviews.branch, SUM(reviewfiles.deleted), SUM(reviewfiles.inserted)
                                FROM reviews
                                JOIN reviewusers ON (reviewusers.review=reviews.id
                                                 AND reviewusers.uid=%s)
                                JOIN reviewfiles ON (reviewfiles.review=reviews.id)
                                JOIN reviewuserfiles ON (reviewuserfiles.file=reviewfiles.id
                                                     AND reviewuserfiles.uid=%s)
                               WHERE reviews.state='open'
                                 AND reviewfiles.state='pending'
                            GROUP BY reviews.id, reviews.summary, reviews.branch""",
                (user.id, user.id))

            profiler.check("query: active lines")

            for review_id, summary, branch_id, deleted_count, inserted_count in cursor:
                if includeReview(review_id):
                    with_changes[review_id] = (summary, branch_id,
                                               (deleted_count,
                                                inserted_count), None)

            profiler.check("processing: active lines")

            cursor.execute(
                """SELECT reviews.id, reviews.summary, reviews.branch, unread.count
                                FROM (SELECT commentchains.review AS review, COUNT(commentstoread.comment) AS count
                                        FROM commentchains
                                        JOIN comments ON (comments.chain=commentchains.id)
                                        JOIN commentstoread ON (commentstoread.comment=comments.id
                                                            AND commentstoread.uid=%s)
                                    GROUP BY commentchains.review) AS unread
                                JOIN reviews ON (reviews.id=unread.review)
                               WHERE reviews.state='open'""", (user.id, ))

            profiler.check("query: active comments")

            for review_id, summary, branch_id, comments_count in cursor:
                if includeReview(review_id):
                    if with_changes.has_key(review_id):
                        with_both[review_id] = (summary, branch_id,
                                                with_changes[review_id][2],
                                                comments_count)
                        del with_changes[review_id]
                    else:
                        with_comments[review_id] = (summary, branch_id, None,
                                                    comments_count)

            profiler.check("processing: active comments")

            active["changes"] = with_changes
            active["comments"] = with_comments
            active["both"] = with_both

    def renderActive():
        fetchActive()

        if active["both"] or active["changes"] or active["comments"]:
            table = target.table("paleyellow reviews",
                                 id="active",
                                 align="center",
                                 cellspacing=0)
            table.col(width="15%")
            table.col(width="55%")
            table.col(width="15%")
            table.col(width="15%")
            header = table.tr().td("h1", colspan=4).h1()
            header.text("Active Reviews")
            header.span("right").a(href=hidden("active")).text("[hide]")

            if active["both"]:
                review_ids = ",".join(map(str, active["both"].keys()))
                h2 = table.tr(id="active-changes-comments").td(
                    "h2", colspan=4).h2().text("Has Changes And Comments")
                h2.a(href="javascript:void(0);",
                     onclick="markChainsAsRead([%s]);" %
                     review_ids).text("[mark all as read]")
                renderReviews(table, sortedReviews(active["both"]))

            if active["changes"]:
                table.tr(id="active-changes").td(
                    "h2", colspan=4).h2().text("Has Changes")
                renderReviews(table, sortedReviews(active["changes"]))

            if active["comments"]:
                review_ids = ",".join(map(str, active["comments"].keys()))
                h2 = table.tr(id="active-comments").td(
                    "h2", colspan=4).h2().text("Has Comments")
                h2.a(href="javascript:void(0);",
                     onclick="markChainsAsRead([%s]);" %
                     review_ids).text("[mark all as read]")
                renderReviews(table, sortedReviews(active["comments"]))

            profiler.check("generate: active")
            return True

    other = {}

    def fetchWatchedAndClosed():
        if not other:
            if "watched" not in showset:
                state_filter = " WHERE reviews.state='closed'"
            elif "closed" not in showset:
                state_filter = " WHERE reviews.state='open'"
            else:
                state_filter = ""

            profiler.check("query: watched/closed")

            watched = {}
            owned_closed = {}
            other_closed = {}

            if "watched" in showset: fetchActive()

            cursor.execute(
                """SELECT reviews.id, reviews.summary, reviews.branch, reviews.state, reviewusers.owner, reviewusers.uid IS NULL
                                FROM reviews
                     LEFT OUTER JOIN reviewusers ON (reviewusers.review=reviews.id AND reviewusers.uid=%s)"""
                + state_filter, (user.id, ))

            for review_id, summary, branch_id, review_state, is_owner, not_associated in cursor:
                if includeReview(review_id):
                    if review_state == 'open':
                        if is_owner or not_associated:
                            continue

                        fetchActive()

                        if active["both"].has_key(
                                review_id) or active["changes"].has_key(
                                    review_id) or active["comments"].has_key(
                                        review_id):
                            continue

                        watched[review_id] = summary, branch_id, None, None
                    elif is_owner:
                        owned_closed[
                            review_id] = summary, branch_id, None, None
                    else:
                        other_closed[
                            review_id] = summary, branch_id, None, None

            profiler.check("processing: watched/closed")

            other["watched"] = watched
            other["owned-closed"] = owned_closed
            other["other-closed"] = other_closed

    def renderWatched():
        fetchWatchedAndClosed()

        watched = other["watched"]
        accepted = []
        pending = []

        is_accepted = isAccepted(watched.keys())

        for review_id, (summary, branch_id, lines,
                        comments) in sortedReviews(watched):
            if is_accepted[review_id]:
                accepted.append(
                    (review_id, (summary, branch_id, lines, comments)))
            else:
                pending.append(
                    (review_id, (summary, branch_id, lines, comments)))

        if accepted or pending:
            table = target.table("paleyellow reviews",
                                 id="watched",
                                 align="center",
                                 cellspacing=0)
            table.col(width="30%")
            table.col(width="70%")
            header = table.tr().td("h1", colspan=4).h1()
            header.text("Watched Reviews")
            header.span("right").a(href=hidden("watched")).text("[hide]")

            if accepted:
                table.tr(id="active-changes-comments").td(
                    "h2", colspan=4).h2().text("Accepted")
                renderReviews(table, accepted, False)

            if pending:
                table.tr(id="active-changes-comments").td(
                    "h2", colspan=4).h2().text("Pending")
                renderReviews(table, pending, False)

            profiler.check("generate: watched")
            return True

    def renderClosed():
        fetchWatchedAndClosed()

        owned_closed = other["owned-closed"]
        other_closed = other["other-closed"]

        if owned_closed or other_closed:
            table = target.table("paleyellow reviews",
                                 id="closed",
                                 align="center",
                                 cellspacing=0)
            table.col(width="30%")
            table.col(width="70%")
            header = table.tr().td("h1", colspan=4).h1()
            header.text("Closed Reviews")
            header.span("right").a(href=hidden("closed")).text("[hide]")

            if not user.isAnonymous():
                if owned_closed:
                    table.tr().td("h2", colspan=4).h2().text("Owned")
                    renderReviews(table, sortedReviews(owned_closed), False)

                if other_closed:
                    table.tr().td("h2", colspan=4).h2().text("Other")
                    renderReviews(table, sortedReviews(other_closed), False)
            else:
                renderReviews(table, sortedReviews(other_closed), False)

            profiler.check("generate: closed")
            return True

    def renderOpen():
        other_open = {}

        cursor.execute(
            """SELECT reviews.id, reviews.summary, reviews.branch
                            FROM reviews
                 LEFT OUTER JOIN reviewusers ON (reviewusers.review=reviews.id AND reviewusers.uid=%s)
                           WHERE reviews.state='open'
                             AND reviewusers.uid IS NULL""", [user.id])

        profiler.check("query: open")

        for review_id, summary, branch_id in cursor:
            if includeReview(review_id):
                other_open[review_id] = summary, branch_id, None, None

        profiler.check("processing: open")

        if other_open:
            accepted = []
            pending = []

            for review_id, (summary, branch_id, lines,
                            comments) in sortedReviews(other_open):
                if dbutils.Review.isAccepted(db, review_id):
                    accepted.append(
                        (review_id, (summary, branch_id, lines, comments)))
                else:
                    pending.append(
                        (review_id, (summary, branch_id, lines, comments)))

            table = target.table("paleyellow reviews",
                                 id="open",
                                 align="center",
                                 cellspacing=0)
            table.col(width="30%")
            table.col(width="70%")
            header = table.tr().td("h1", colspan=4).h1()
            header.text(
                "Open Reviews" if user.isAnonymous() else "Other Open Reviews")
            header.span("right").a(href=hidden("open")).text("[hide]")

            if accepted:
                table.tr().td("h2", colspan=4).h2().text("Accepted")
                renderReviews(table, accepted, False)

            if pending:
                table.tr().td("h2", colspan=4).h2().text("Pending")
                renderReviews(table, pending, False)

            profiler.check("generate: open")
            return True

    render = {
        "owned": renderOwned,
        "draft": renderDraft,
        "active": renderActive,
        "watched": renderWatched,
        "closed": renderClosed,
        "open": renderOpen
    }

    empty = True

    for item in showlist:
        if item in render:
            target.comment(repr(item))
            if render[item]():
                empty = False
                yield flush(target)

    if empty:
        document.addExternalStylesheet("resource/message.css")
        body.div("message paleyellow").h1("center").text("No reviews!")

    profiler.output(db=db, user=user, target=document)

    yield flush(None)
Ejemplo n.º 22
0
def renderShowReviewLog(req, db, user):
    review_id = page.utils.getParameter(req, "review", filter=int)
    granularity = page.utils.getParameter(req, "granularity")
    unassigned = page.utils.getParameter(req, "unassigned", "no") == "yes"

    cursor = db.cursor()

    review = dbutils.Review.fromId(db, review_id)

    document = htmlutils.Document(req)
    html = document.html()
    head = html.head()
    body = html.body()

    head.title("Review Log: %s" % review.branch.name)

    page.utils.generateHeader(
        body,
        db,
        user,
        lambda target: review_utils.renderDraftItems(db, user, review, target),
        extra_links=[("r/%d" % review.id, "Back to Review", True)])

    document.addExternalStylesheet("resource/showreviewlog.css")
    document.addExternalStylesheet("resource/review.css")
    document.addExternalScript("resource/review.js")
    document.addInternalScript(review.getJS())

    target = body.div("main")

    reviewed_reviewers = review_utils.getReviewedReviewers(db, review)

    def formatFiles(files):
        paths = sorted(
            [dbutils.describe_file(db, file_id) for file_id in files])
        if granularity == "file":
            return diff.File.eliminateCommonPrefixes(paths)
        else:
            modules = set()
            files = []
            for path in paths:
                match = re_module.match(path)
                if match: modules.add(match.group(1))
                else: files.append(path)
            return sorted(modules) + diff.File.eliminateCommonPrefixes(files)

    if reviewed_reviewers and not unassigned:
        reviewed = target.table('changes', align='center')
        reviewed.col(width="30%")
        reviewed.col(width="10%")
        reviewed.col(width="60%")
        reviewed.tr().td('h1', colspan=3).h1().text("Reviewed Changes")

        teams = review_utils.collectReviewTeams(reviewed_reviewers)

        for team in teams:
            row = reviewed.tr("reviewers")

            cell = row.td("reviewers")
            users = sorted([
                dbutils.User.fromId(db, user_id).fullname for user_id in team
            ])
            for user in users:
                cell.text(user).br()
            row.td("willreview").innerHTML("reviewed")

            cell = row.td("files")
            for file in formatFiles(teams[team]):
                cell.span("file").innerHTML(file).br()

    pending_reviewers = review_utils.getPendingReviewers(db, review)

    if pending_reviewers:
        pending = target.table('changes', align='center')
        pending.col(width="30%")
        pending.col(width="10%")
        pending.col(width="60%")
        pending.tr().td('h1', colspan=3).h1().text("Pending Changes")

        teams = review_utils.collectReviewTeams(pending_reviewers)

        if not unassigned:
            for team in teams:
                if team is not None:
                    row = pending.tr("reviewers")

                    cell = row.td("reviewers")
                    users = sorted([
                        dbutils.User.fromId(db, user_id).fullname
                        for user_id in team
                    ])
                    for user in users:
                        cell.text(user).br()
                    row.td("willreview").innerHTML("should&nbsp;review")

                    cell = row.td("files")
                    for file in formatFiles(teams[team]):
                        cell.span("file").innerHTML(file).br()

        if None in teams:
            row = pending.tr("reviewers")
            row.td("no-one",
                   colspan=2).text("No reviewers found for changes in")

            cell = row.td("files")
            for file in formatFiles(teams[None]):
                cell.span("file").innerHTML(file).br()

    return document
Ejemplo n.º 23
0
def renderManageReviewers(req, db, user):
    review_id = req.getParameter("review", filter=int)

    cursor = db.cursor()

    review = dbutils.Review.fromId(db, review_id)

    root_directories = {}
    root_files = {}

    def processFile(file_id):
        components = dbutils.describe_file(db, file_id).split("/")
        directories, files = root_directories, root_files
        for directory_name in components[:-1]:
            directories, files = directories.setdefault(
                directory_name, ({}, {}))
        files[components[-1]] = file_id

    cursor.execute("SELECT file FROM reviewfiles WHERE review=%s",
                   (review.id, ))

    for (file_id, ) in cursor:
        processFile(file_id)

    cursor.execute("SELECT name FROM users WHERE name IS NOT NULL")
    users = [user_name for (user_name, ) in cursor if user_name]

    document = htmlutils.Document(req)

    html = document.html()
    head = html.head()
    body = html.body()

    page.utils.generateHeader(
        body,
        db,
        user,
        lambda target: review_utils.renderDraftItems(db, user, review, target),
        extra_links=[("r/%d" % review.id, "Back to Review")])

    document.addExternalStylesheet("resource/managereviewers.css")
    document.addExternalScript("resource/managereviewers.js")
    document.addInternalScript(user.getJS())
    document.addInternalScript(review.getJS())
    document.addInternalScript(
        "var users = [ %s ];" %
        ", ".join([htmlutils.jsify(user_name) for user_name in sorted(users)]))

    target = body.div("main")

    basic = target.table('manage paleyellow', align='center')
    basic.col(width='10%')
    basic.col(width='60%')
    basic.col(width='30%')
    basic.tr().td('h1', colspan=3).h1().text("Manage Reviewers")

    row = basic.tr("current")
    row.td("select").text("Current:")
    cell = row.td("value")
    for index, reviewer in enumerate(review.reviewers):
        if index != 0: cell.text(", ")
        cell.span("reviewer", critic_username=reviewer.name).innerHTML(
            htmlutils.htmlify(reviewer.fullname).replace(" ", "&nbsp;"))
    row.td("right").text()

    row = basic.tr("reviewer")
    row.td("select").text("Reviewer:")
    row.td("value").input("reviewer").span("message")
    row.td("right").button("save").text("Save")

    row = basic.tr("help")
    row.td("help", colspan=3).text(
        "Enter the name of a current reviewer to edit assignments (or unassign.)  Enter the name of another user to add a new reviewer."
    )

    row = basic.tr("headings")
    row.td("select").text("Assigned")
    row.td("path").text("Path")
    row.td().text()

    def outputDirectory(base, name, directories, files):
        if name:
            level = base.count("/")
            row = basic.tr("directory", critic_level=level)
            row.td("select").input(type="checkbox")
            if level > 1:
                row.td("path").preformatted().innerHTML((" " *
                                                         (len(base) - 2)) +
                                                        "&#8230;/" + name +
                                                        "/")
            else:
                row.td("path").preformatted().innerHTML(base + name + "/")
            row.td().text()
        else:
            level = 0

        for directory_name in sorted(directories.keys()):
            outputDirectory(base + name + "/" if name else "", directory_name,
                            directories[directory_name][0],
                            directories[directory_name][1])

        for file_name in sorted(files.keys()):
            row = basic.tr("file",
                           critic_file_id=files[file_name],
                           critic_level=level + 1)
            row.td("select").input(type="checkbox")
            row.td("path").preformatted().innerHTML(
                (" " * (len(base + name) - 1)) + "&#8230;/" +
                htmlutils.htmlify(file_name))
            row.td().text()

    outputDirectory("", "", root_directories, root_files)

    return document
Ejemplo n.º 24
0
def renderConfirmMerge(req, db, user):
    confirmation_id = req.getParameter("id", filter=int)
    tail_sha1 = req.getParameter("tail", None)
    do_confirm = req.getParameter("confirm", "no") == "yes"
    do_cancel = req.getParameter("cancel", "no") == "yes"

    cursor = db.cursor()

    cursor.execute(
        "SELECT review, uid, merge, confirmed, tail FROM reviewmergeconfirmations WHERE id=%s",
        (confirmation_id, ))
    row = cursor.fetchone()
    if not row:
        raise page.utils.DisplayMessage("No pending merge with that id.")
    review_id, user_id, merge_id, confirmed, tail_id = row

    review = dbutils.Review.fromId(db, review_id)
    merge = gitutils.Commit.fromId(db, review.repository, merge_id)

    if confirmed and tail_id is not None:
        tail_sha1 = gitutils.Commit.fromId(db, review.repository, tail_id).sha1

    cursor.execute("SELECT merged FROM reviewmergecontributions WHERE id=%s",
                   (confirmation_id, ))

    merged = [
        gitutils.Commit.fromId(db, review.repository, merged_id)
        for (merged_id, ) in cursor
    ]
    merged_set = log.commitset.CommitSet(merged)

    if tail_sha1 is not None:
        tail = gitutils.Commit.fromSHA1(db, review.repository, tail_sha1)
        tail_id = tail.getId(db)

        cut = [
            gitutils.Commit.fromSHA1(db, review.repository, sha1)
            for sha1 in tail.parents if sha1 in merged_set
        ]
        merged_set = merged_set.without(cut)
        merged = list(merged_set)
    else:
        tail_id = None

    if do_confirm:
        cursor.execute(
            "UPDATE reviewmergeconfirmations SET confirmed=TRUE, tail=%s WHERE id=%s",
            (tail_id, confirmation_id))
        db.commit()
    elif do_cancel:
        cursor.execute("DELETE FROM reviewmergeconfirmations WHERE id=%s",
                       (confirmation_id, ))
        db.commit()

    document = htmlutils.Document(req)

    html = document.html()
    head = html.head()
    body = html.body()

    def renderButtons(target):
        if not do_confirm and not do_cancel:
            target.button("confirmAll").text("Confirm (merge + contributed)")
            target.button("confirmNone").text("Confirm (merge only)")
            target.button("cancel").text("Cancel")

    page.utils.generateHeader(body,
                              db,
                              user,
                              renderButtons,
                              extra_links=[("r/%d" % review.id,
                                            "Back to Review")])

    document.addExternalStylesheet("resource/confirmmerge.css")
    document.addExternalScript("resource/log.js")
    document.addExternalScript("resource/confirmmerge.js")
    document.addInternalScript(user.getJS())
    document.addInternalScript(review.getJS())
    document.addInternalScript("var confirmation_id = %d;" % confirmation_id)
    document.addInternalScript("var merge_sha1 = %s;" %
                               htmlutils.jsify(merge.sha1))

    if tail_sha1 is not None:
        document.addInternalScript("var tail_sha1 = %s;" %
                                   htmlutils.jsify(tail_sha1))

    if not do_confirm and not do_cancel:
        heads = merged_set.getHeads()
        if heads:
            document.addInternalScript("var automaticAnchorCommit = %s;" %
                                       htmlutils.jsify(heads.pop().sha1))
        else:
            document.addInternalScript("var automaticAnchorCommit = null;")

    if do_confirm:
        document.addInternalScript("var confirmed = true;")
    else:
        document.addInternalScript("var confirmed = false;")

    target = body.div("main")

    basic = target.table("paleyellow")
    basic.col(width='15%')
    basic.col(width='55%')
    basic.col(width='30%')
    h1 = basic.tr().td('h1', colspan=3).h1()

    if do_confirm:
        h1.text("CONFIRMED MERGE")
    elif do_cancel:
        h1.text("CANCELLED MERGE")
    else:
        h1.text("Confirm Merge")

    row = basic.tr("sha1")
    row.td("heading").text("SHA-1:")
    row.td("value").preformatted().text(merge.sha1)
    row.td().text()

    row = basic.tr("message")
    row.td("heading").text("Message:")
    row.td("value").preformatted().text(merge.message)
    row.td().text()

    if not do_confirm and not do_cancel:
        row = basic.tr("instructions")
        row.td("heading").text("Instructions:")
        row.td("value").preformatted().text("""\
Use the top right buttons to confirm the merge with or without the contributed commits that it brings.

By clicking 'Confirm (merge + contributed)' you will bring the merge commit plus all commits that it contributes into the this code review.

By clicking 'Confirm (merge only)' you will bring only the merge commit itself into the code review and not the contributed commits."

By clicking 'Cancel' you will abort the merge. The code review will not be modified at all from its current state."""
                                            )
        row.td().text()

    if merged:
        columns = [(10, log.html.WhenColumn()), (60, log.html.SummaryColumn()),
                   (16, log.html.AuthorColumn())]

        log.html.render(db,
                        target,
                        "Contributed Commits",
                        commits=merged,
                        columns=columns)

    return document
Ejemplo n.º 25
0
def renderEditResource(req, db, user):
    name = page.utils.getParameter(req, "name", None)

    document = htmlutils.Document(req)

    html = document.html()
    head = html.head()
    body = html.body()

    page.utils.generateHeader(body, db, user)

    document.addExternalStylesheet("resource/editresource.css")
    document.addExternalScript("resource/editresource.js")

    target = body.div("main")

    table = target.table('paleyellow', align='center')
    table.col(width='10%')
    table.col(width='60%')
    table.tr().td('h1', colspan=2).h1().text("Resource Editor")

    select_row = table.tr('select')
    select_row.td('heading').text('Resource:')
    select = select_row.td('value').select()
    if name is None: select.option(selected="selected").text("Select resource")
    select.option(value="diff.css", selected="selected" if name=="diff.css" else None).text("Diff coloring")
    select.option(value="syntax.css", selected="selected" if name=="syntax.css" else None).text("Syntax highlighting")

    help_row = table.tr('help')
    help_row.td('help', colspan=2).text("Select the resource to edit.")

    is_edited = False
    is_reset = False
    source = None

    if name is None:
        document.addInternalScript("var resource_name = null;");
        source = ""
    else:
        if name not in ("diff.css", "syntax.css"):
            raise page.utils.DisplayMessage("Invalid resource name", body="Must be one of 'diff.css' and 'syntax.css'.")

        document.addInternalScript("var resource_name = %s;" % htmlutils.jsify(name));

        cursor = db.cursor()
        cursor.execute("SELECT source FROM userresources WHERE uid=%s AND name=%s ORDER BY revision DESC FETCH FIRST ROW ONLY", (user.id, name))
        row = cursor.fetchone()

        if row:
            is_edited = True
            source = row[0]

        if source is None:
            is_reset = is_edited
            source = open(configuration.paths.INSTALL_DIR + "/resources/" + name).read()

        document.addInternalScript("var original_source = %s;" % htmlutils.jsify(source));

    table.tr('value').td('value', colspan=2).textarea(rows=source.count("\n") + 10).preformatted().text(source)

    buttons = table.tr('buttons').td('buttons', colspan=2)
    buttons.button('save').text("Save changes")

    if is_edited and not is_reset:
        buttons.button('reset').text("Reset to built-in version")

    if is_reset:
        buttons.button('restore').text("Restore last edited version")

    return document
Ejemplo n.º 26
0
def renderShowTree(req, db, user):
    cursor = db.cursor()

    sha1 = req.getParameter("sha1")
    path = req.getParameter("path", "/")
    review_id = req.getParameter("review", None, filter=int)

    if path[0] == '/':
        full_path = path
        if path != "/": path = path[1:]
    else:
        full_path = "/" + path
        if not path: path = "/"

    if review_id is None:
        review = None
        review_arg = ""
        repository_arg = req.getParameter("repository", "")
        if repository_arg:
            repository = gitutils.Repository.fromParameter(db, repository_arg)
        else:
            repository = gitutils.Repository.fromSHA1(db, sha1)
        repository_arg = "&repository=%d" % repository.id
    else:
        review = dbutils.Review.fromId(db, review_id)
        review_arg = "&review=%d" % review_id
        repository_arg = ""
        repository = review.repository

    document = htmlutils.Document(req)

    html = document.html()
    head = html.head()
    body = html.body()

    extra_links = []

    if review:
        extra_links.append(("r/%d" % review.id, "Back to Review", True))

    page.utils.generateHeader(body, db, user, extra_links=extra_links)

    document.addExternalStylesheet("resource/showtree.css")
    #document.addExternalScript("resource/showtree.js")

    target = body.div("main")

    table = target.table("tree paleyellow", align="center", cellspacing=0)
    table.col(width="10%")
    table.col(width="60%")
    table.col(width="20%")

    thead = table.thead()
    h1 = thead.tr().td('h1', colspan=3).h1()

    if path == "/":
        h1.text("/")
    else:
        h1.a("root",
             href="showtree?sha1=%s&path=/%s%s" %
             (sha1, review_arg, repository_arg)).text("root")
        h1.span().text('/')

        components = path.split("/")
        for index, component in enumerate(components[:-1]):
            h1.a(href="showtree?sha1=%s&path=%s%s%s" %
                 (sha1, "/".join(components[:index + 1]), review_arg,
                  repository_arg)).text(component)
            h1.span().text('/')

        h1.text(components[-1])

    row = thead.tr()
    row.td('mode').text("Mode")
    row.td('name').text("Name")
    row.td('size').text("Size")

    tree = gitutils.Tree.fromPath(
        gitutils.Commit.fromSHA1(db, repository, sha1), full_path)

    def compareEntries(a, b):
        if a.type != b.type:
            if a.type == "tree": return -1
            else: return 1
        else:
            return cmp(a.name, b.name)

    tbody = table.tbody()

    for entry in sorted(tree, cmp=compareEntries):
        if entry.type == "blob":
            url = "showfile?sha1=%s&path=%s%s%s" % (
                sha1, os.path.join(path,
                                   entry.name), review_arg, repository_arg)
        else:
            url = "showtree?sha1=%s&path=%s%s%s" % (
                sha1, os.path.join(path,
                                   entry.name), review_arg, repository_arg)

        row = tbody.tr(entry.type)
        row.td('mode').text(str(entry.mode))

        if stat.S_ISLNK(entry.mode):
            cell = row.td('link', colspan=2)
            cell.span('name').text(entry.name)
            cell.text(' -> ')
            cell.span('target').text(repository.fetch(entry.sha1).data)
        elif entry.type == "commit":
            row.td('name').text("%s (%s)" % (entry.name, entry.sha1))
            row.td('size').text(entry.size)
        else:
            row.td('name').a(href=url).text(entry.name)
            row.td('size').text(entry.size)

    return document
Ejemplo n.º 27
0
def renderShowBatch(req, db, user):
    batch_id = page.utils.getParameter(req, "batch", None, filter=int)
    review_id = page.utils.getParameter(req, "review", None, filter=int)

    cursor = db.cursor()

    if batch_id is None and review_id is None:
        return page.utils.displayMessage(db, req, user,
                                         "Missing argument: 'batch'")

    if batch_id:
        cursor.execute("SELECT review, uid, comment FROM batches WHERE id=%s",
                       (batch_id, ))

        row = cursor.fetchone()
        if not row:
            raise page.utils.DisplayMessage("Invalid batch ID: %d" % batch_id)

        review_id, author_id, chain_id = row
        author = dbutils.User.fromId(db, author_id)
    else:
        chain_id = None
        author = user

    review = dbutils.Review.fromId(db, review_id)

    if chain_id:
        batch_chain = review_comment.CommentChain.fromId(db,
                                                         chain_id,
                                                         user,
                                                         review=review)
        batch_chain.loadComments(db, user)
    else:
        batch_chain = None

    document = htmlutils.Document(req)

    html = document.html()
    head = html.head()
    body = html.body()

    page.utils.generateHeader(
        body,
        db,
        user,
        lambda target: review_utils.renderDraftItems(db, user, review, target),
        extra_links=[("r/%d" % review.id, "Back to Review")])

    document.addExternalStylesheet("resource/showreview.css")
    document.addExternalStylesheet("resource/showbatch.css")
    document.addExternalStylesheet("resource/review.css")
    document.addExternalStylesheet("resource/comment.css")
    document.addExternalScript("resource/review.js")
    document.addExternalScript("resource/comment.js")
    document.addInternalScript(user.getJS())
    document.addInternalScript(review.getJS())

    if batch_chain:
        document.addInternalScript(
            "commentChainById[%d] = %s;" %
            (batch_chain.id, batch_chain.getJSConstructor()))

    target = body.div("main")

    table = target.table('paleyellow basic comments', align='center')
    table.col(width='10%')
    table.col(width='80%')
    table.col(width='10%')
    table.tr().td('h1', colspan=3).h1().text("Review by %s" %
                                             htmlify(author.fullname))

    if batch_chain:
        batch_chain.loadComments(db, user)

        row = table.tr("line")
        row.td("heading").text("Comment:")
        row.td("value").preformatted().div("text").text(
            htmlify(batch_chain.comments[0].comment))
        row.td("status").text()

    def renderFiles(title, cursor):
        files = []

        for file_id, delete_count, insert_count in cursor.fetchall():
            files.append(
                (dbutils.describe_file(db,
                                       file_id), delete_count, insert_count))

        paths = []
        deleted = []
        inserted = []

        for path, delete_count, insert_count in sorted(files):
            paths.append(path)
            deleted.append(delete_count)
            inserted.append(insert_count)

        if paths:
            diff.File.eliminateCommonPrefixes(paths)

            row = table.tr("line")
            row.td("heading").text(title)

            files_table = row.td().table("files callout")
            headers = files_table.thead().tr()
            headers.th("path").text("Changed Files")
            headers.th("lines", colspan=2).text("Lines")

            files = files_table.tbody()
            for path, delete_count, insert_count in zip(
                    paths, deleted, inserted):
                file = files.tr()
                file.td("path").preformatted().innerHTML(path)
                file.td("lines").preformatted().text(
                    "-%d" % delete_count if delete_count else None)
                file.td("lines").preformatted().text(
                    "+%d" % insert_count if insert_count else None)

            row.td("status").text()

    def condition(table_name):
        if batch_id:
            return "%s.batch=%d" % (table_name, batch_id)
        else:
            return "review=%d AND %s.batch IS NULL AND %s.uid=%d" % (
                review.id, table_name, table_name, author.id)

    cursor.execute("""SELECT reviewfiles.file, SUM(deleted), SUM(inserted)
                        FROM reviewfiles
                        JOIN reviewfilechanges ON (reviewfilechanges.file=reviewfiles.id)
                       WHERE %s
                         AND reviewfilechanges.to_state='reviewed'
                    GROUP BY reviewfiles.file""" %
                   condition("reviewfilechanges"))
    renderFiles("Reviewed:", cursor)

    cursor.execute("""SELECT reviewfiles.file, SUM(deleted), SUM(inserted)
                        FROM reviewfiles
                        JOIN reviewfilechanges ON (reviewfilechanges.file=reviewfiles.id)
                       WHERE %s
                         AND reviewfilechanges.to_state='pending'
                    GROUP BY reviewfiles.file""" %
                   condition("reviewfilechanges"))
    renderFiles("Unreviewed:", cursor)

    def renderChains(title, cursor, replies):
        all_chains = [
            review_comment.CommentChain.fromId(db,
                                               chain_id,
                                               user,
                                               review=review)
            for (chain_id, ) in cursor
        ]

        if not all_chains:
            return

        for chain in all_chains:
            chain.loadComments(db, user)

        issue_chains = filter(lambda chain: chain.type == "issue", all_chains)
        draft_issues = filter(lambda chain: chain.state == "draft",
                              issue_chains)
        open_issues = filter(lambda chain: chain.state == "open", issue_chains)
        addressed_issues = filter(lambda chain: chain.state == "addressed",
                                  issue_chains)
        closed_issues = filter(lambda chain: chain.state == "closed",
                               issue_chains)
        note_chains = filter(lambda chain: chain.type == "note", all_chains)
        draft_notes = filter(
            lambda chain: chain.state == "draft" and chain != batch_chain,
            note_chains)
        open_notes = filter(
            lambda chain: chain.state == "open" and chain != batch_chain,
            note_chains)

        def renderChains(target, chains):
            for chain in chains:
                row = target.tr("comment")
                row.td("author").text(chain.user.fullname)
                row.td("title").a(href="showcomment?chain=%d" %
                                  chain.id).innerHTML(chain.leader())
                row.td("when").text(chain.when())

        def showcomments(filter_param):
            params = {"review": review.id, "filter": filter_param}
            if batch_id:
                params["batch"] = batch_id
            return htmlutils.URL("/showcomments", **params)

        if draft_issues or open_issues or addressed_issues or closed_issues:
            h2 = table.tr().td("h2", colspan=3).h2().text(title)
            if len(draft_issues) + len(open_issues) + len(
                    addressed_issues) + len(closed_issues) > 1:
                h2.a(href=showcomments("issues")).text("[display all]")

            if draft_issues:
                h3 = table.tr(id="draft-issues").td(
                    "h3", colspan=3).h3().text("Draft issues")
                if len(draft_issues) > 1:
                    h3.a(href=showcomments("draft-issues")).text(
                        "[display all]")
                renderChains(table, draft_issues)

            if batch_id is not None or replies:
                if open_issues:
                    h3 = table.tr(id="open-issues").td(
                        "h3", colspan=3).h3().text("Still open issues")
                    if batch_id and len(open_issues) > 1:
                        h3.a(href=showcomments("open-issues")).text(
                            "[display all]")
                    renderChains(table, open_issues)

                if addressed_issues:
                    h3 = table.tr(id="addressed-issues").td(
                        "h3", colspan=3).h3().text("Now addressed issues")
                    if batch_id and len(addressed_issues) > 1:
                        h3.a(href=showcomments("addressed-issues")).text(
                            "[display all]")
                    renderChains(table, addressed_issues)

                if closed_issues:
                    h3 = table.tr(id="closed-issues").td(
                        "h3", colspan=3).h3().text("Now closed issues")
                    if batch_id and len(closed_issues) > 1:
                        h3.a(href=showcomments("closed-issues")).text(
                            "[display all]")
                    renderChains(table, closed_issues)

        if draft_notes or open_notes:
            h2 = table.tr().td("h2", colspan=3).h2().text(title)
            if len(draft_notes) + len(open_notes) > 1:
                h2.a(href=showcomments("notes")).text("[display all]")

            if draft_notes:
                h3 = table.tr(id="draft-notes").td(
                    "h3", colspan=3).h3().text("Draft notes")
                if len(draft_notes) > 1:
                    h3.a(
                        href=showcomments("draft-notes")).text("[display all]")
                renderChains(table, draft_notes)

            if open_notes:
                h3 = table.tr(id="notes").td("h3",
                                             colspan=3).h3().text("Notes")
                if batch_id and len(open_notes) > 1:
                    h3.a(href=showcomments("open-notes")).text("[display all]")
                renderChains(table, open_notes)

    cursor.execute("SELECT id FROM commentchains WHERE %s AND type='issue'" %
                   condition("commentchains"))

    renderChains("Raised issues", cursor, False)

    cursor.execute("""SELECT commentchains.id
                        FROM commentchains
                        JOIN commentchainchanges ON (commentchainchanges.chain=commentchains.id)
                       WHERE %s
                         AND to_state='closed'""" %
                   condition("commentchainchanges"))

    renderChains("Resolved issues", cursor, False)

    cursor.execute("""SELECT commentchains.id
                        FROM commentchains
                        JOIN commentchainchanges ON (commentchainchanges.chain=commentchains.id)
                       WHERE %s
                         AND to_state='open'""" %
                   condition("commentchainchanges"))

    renderChains("Reopened issues", cursor, False)

    cursor.execute("""SELECT commentchains.id
                        FROM commentchains
                        JOIN commentchainchanges ON (commentchainchanges.chain=commentchains.id)
                       WHERE %s
                         AND to_type='issue'""" %
                   condition("commentchainchanges"))

    renderChains("Converted into issues", cursor, False)

    cursor.execute("""SELECT commentchains.id
                        FROM commentchains
                        JOIN commentchainchanges ON (commentchainchanges.chain=commentchains.id)
                       WHERE %s
                         AND to_type='note'""" %
                   condition("commentchainchanges"))

    renderChains("Converted into notes", cursor, False)

    cursor.execute("SELECT id FROM commentchains WHERE %s AND type='note'" %
                   condition("commentchains"))

    renderChains("Written notes", cursor, False)

    cursor.execute("""SELECT commentchains.id
                        FROM commentchains
                        JOIN comments ON (comments.chain=commentchains.id)
                       WHERE %s
                         AND comments.id!=commentchains.first_comment""" %
                   condition("comments"))

    renderChains("Replied to", cursor, True)

    return document
Ejemplo n.º 28
0
def renderShowFile(req, db, user):
    cursor = db.cursor()

    sha1 = req.getParameter("sha1")
    path = req.getParameter("path")
    line = req.getParameter("line", None)
    review_id = req.getParameter("review", None, filter=int)

    default_tabify = "yes" if user.getPreference(
        db, "commit.diff.visualTabs") else "no"
    tabify = req.getParameter("tabify", default_tabify) == "yes"

    if line is None:
        first, last = None, None
    else:
        if "-" in line:
            first, last = map(int, line.split("-"))
        else:
            first = last = int(line)

        context = req.getParameter(
            "context", user.getPreference(db, "commit.diff.contextLines"), int)

        first_with_context = max(1, first - context)
        last_with_context = last + context

    if user.getPreference(db, "commit.diff.compactMode"):
        default_compact = "yes"
    else:
        default_compact = "no"

    compact = req.getParameter("compact", default_compact) == "yes"

    if len(path) == 0 or path[-1:] == "/":
        raise page.utils.DisplayMessage(
            title="Invalid path parameter",
            body=
            "<p>The path must be non-empty and must not end with a <code>/</code>.</p>",
            html=True)
    if path[0] == '/':
        full_path = path
        if path != "/": path = path[1:]
    else:
        full_path = "/" + path
        if not path: path = "/"

    if review_id is None:
        review = None
        repository_arg = req.getParameter("repository", "")
        if repository_arg:
            repository = gitutils.Repository.fromParameter(db, repository_arg)
        else:
            repository = gitutils.Repository.fromSHA1(db, sha1)
    else:
        review = dbutils.Review.fromId(db, review_id)
        repository = review.repository

    document = htmlutils.Document(req)

    html = document.html()
    head = html.head()
    body = html.body()

    if review:
        page.utils.generateHeader(body,
                                  db,
                                  user,
                                  lambda target: review_utils.renderDraftItems(
                                      db, user, review, target),
                                  extra_links=[("r/%d" % review.id,
                                                "Back to Review")])
    else:
        page.utils.generateHeader(body, db, user)

    document.addExternalStylesheet("resource/showfile.css")
    document.addInternalStylesheet(
        htmlutils.stripStylesheet(
            user.getResource(db, "syntax.css")[1], compact))

    commit = gitutils.Commit.fromSHA1(db, repository, sha1)
    file_sha1 = commit.getFileSHA1(full_path)
    file_id = dbutils.find_file(db, path=path)

    if file_sha1 is None:
        raise page.utils.DisplayMessage(
            title="File does not exist",
            body=("<p>There is no file named <code>%s</code> in the commit "
                  "<a href='/showcommit?repository=%s&amp;sha1=%s'>"
                  "<code>%s</code></a>.</p>" %
                  (htmlutils.htmlify(textutils.escape(full_path)),
                   htmlutils.htmlify(repository.name), htmlutils.htmlify(sha1),
                   htmlutils.htmlify(sha1[:8]))),
            html=True)

    file = diff.File(file_id, path, None, file_sha1, repository)

    # A new file ID might have been added to the database, so need to commit.
    db.commit()

    if file.canHighlight():
        requestHighlights(repository,
                          {file.new_sha1: (file.path, file.getLanguage())})

    file.loadNewLines(True, request_highlight=True)

    if review:
        document.addInternalScript(user.getJS())
        document.addInternalScript(review.getJS())
        document.addInternalScript(
            "var changeset = { parent: { id: %(id)d, sha1: %(sha1)r }, child: { id: %(id)d, sha1: %(sha1)r } };"
            % {
                'id': commit.getId(db),
                'sha1': commit.sha1
            })
        document.addInternalScript(
            "var files = { %(id)d: { new_sha1: %(sha1)r }, %(sha1)r: { id: %(id)d, side: 'n' } };"
            % {
                'id': file_id,
                'sha1': file_sha1
            })
        document.addExternalStylesheet("resource/review.css")
        document.addExternalScript("resource/review.js")

        cursor.execute(
            """SELECT DISTINCT commentchains.id
                            FROM commentchains
                            JOIN commentchainlines ON (commentchainlines.chain=commentchains.id)
                           WHERE commentchains.review=%s
                             AND commentchains.file=%s
                             AND commentchainlines.sha1=%s
                             AND ((commentchains.state!='draft' OR commentchains.uid=%s)
                              AND commentchains.state!='empty')
                        GROUP BY commentchains.id""",
            (review.id, file_id, file_sha1, user.id))

        comment_chain_script = ""

        for (chain_id, ) in cursor.fetchall():
            chain = review_comment.CommentChain.fromId(db,
                                                       chain_id,
                                                       user,
                                                       review=review)
            chain.loadComments(db, user)

            comment_chain_script += "commentChains.push(%s);\n" % chain.getJSConstructor(
                file_sha1)

        if comment_chain_script:
            document.addInternalScript(comment_chain_script)

    document.addExternalStylesheet("resource/comment.css")
    document.addExternalScript("resource/comment.js")
    document.addExternalScript("resource/showfile.js")

    if tabify:
        document.addExternalStylesheet("resource/tabify.css")
        document.addExternalScript("resource/tabify.js")
        tabwidth = file.getTabWidth()
        indenttabsmode = file.getIndentTabsMode()

    if user.getPreference(db, "commit.diff.highlightIllegalWhitespace"):
        document.addInternalStylesheet(
            user.getResource(db, "whitespace.css")[1], compact)

    if first is not None:
        document.addInternalScript(
            "var firstSelectedLine = %d, lastSelectedLine = %d;" %
            (first, last))

    target = body.div("main")

    if tabify:
        target.script(type="text/javascript").text("calculateTabWidth();")

    table = target.table('file show expanded paleyellow',
                         align='center',
                         cellspacing=0)

    columns = table.colgroup()
    columns.col('edge')
    columns.col('linenr')
    columns.col('line')
    columns.col('middle')
    columns.col('middle')
    columns.col('line')
    columns.col('linenr')
    columns.col('edge')

    thead = table.thead()
    cell = thead.tr().td('h1', colspan=8)
    h1 = cell.h1()

    def make_url(url_path, path):
        params = {"sha1": sha1, "path": path}
        if review is None:
            params["repository"] = str(repository.id)
        else:
            params["review"] = str(review.id)
        return "%s?%s" % (url_path, urllib.urlencode(params))

    h1.a("root", href=make_url("showtree", "/")).text("root")
    h1.span().text('/')

    components = path.split("/")
    for index, component in enumerate(components[:-1]):
        h1.a(href=make_url("showtree", "/".join(components[:index + 1]))).text(
            component, escape=True)
        h1.span().text('/')

    if first is not None:
        h1.a(href=make_url("showfile", "/".join(components))).text(
            components[-1], escape=True)
    else:
        h1.text(components[-1], escape=True)

    h1.span("right").a(href=("/download/%s?repository=%s&sha1=%s" %
                             (urllib.quote(path), repository.name, file_sha1)),
                       download=urllib.quote(path)).text("[download]")
    h1.span("right").a(
        href=("/download/%s?repository=%s&sha1=%s" %
              (urllib.quote(path), repository.name, file_sha1))).text("[view]")

    table.tbody('spacer top').tr('spacer top').td(colspan=8).text()

    tbody = table.tbody("lines")

    yield document.render(stop=tbody, pretty=not compact)

    for linenr, line in enumerate(file.newLines(True)):
        linenr = linenr + 1
        highlight_class = ""

        if first is not None:
            if not (first_with_context <= linenr <= last_with_context):
                continue
            if linenr == first:
                highlight_class += " first-selected"
            if linenr == last:
                highlight_class += " last-selected"

        if tabify:
            line = htmlutils.tabify(line, tabwidth, indenttabsmode)

        line = line.replace("\r", "<i class='cr'></i>")

        row = tbody.tr("line context single",
                       id="f%do%dn%d" % (file.id, linenr, linenr))
        row.td("edge").text()
        row.td("linenr old").text(linenr)
        row.td("line single whole%s" % highlight_class,
               id="f%dn%d" % (file.id, linenr),
               colspan=4).innerHTML(line)
        row.td("linenr new").text(linenr)
        row.td("edge").text()

        if linenr % 500:
            yield document.render(stop=tbody, pretty=not compact)

    table.tbody('spacer bottom').tr('spacer bottom').td(colspan=8).text()

    yield document.render(pretty=not compact)
Ejemplo n.º 29
0
def renderHome(req, db, user):
    if user.isAnonymous(): raise page.utils.NeedLogin(req)

    profiler = profiling.Profiler()

    cursor = db.cursor()

    readonly = req.getParameter(
        "readonly", "yes" if user.name != req.user else "no") == "yes"
    repository = req.getParameter("repository", None,
                                  gitutils.Repository.FromParameter(db))
    verified_email_id = req.getParameter("email_verified", None, int)

    if not repository:
        repository = user.getDefaultRepository(db)

    title_fullname = user.fullname

    if title_fullname[-1] == 's': title_fullname += "'"
    else: title_fullname += "'s"

    cursor.execute(
        "SELECT email FROM usergitemails WHERE uid=%s ORDER BY email ASC",
        (user.id, ))
    gitemails = ", ".join([email for (email, ) in cursor])

    document = htmlutils.Document(req)

    html = document.html()
    head = html.head()
    body = html.body()

    if user.name == req.user:
        actual_user = None
    else:
        actual_user = req.getUser(db)

    def renderHeaderItems(target):
        if readonly and actual_user and actual_user.hasRole(
                db, "administrator"):
            target.a("button",
                     href="/home?user=%s&readonly=no" % user.name).text("Edit")

    page.utils.generateHeader(body,
                              db,
                              user,
                              generate_right=renderHeaderItems,
                              current_page="home")

    document.addExternalStylesheet("resource/home.css")
    document.addExternalScript("resource/home.js")
    document.addExternalScript("resource/autocomplete.js")
    if repository: document.addInternalScript(repository.getJS())
    else: document.addInternalScript("var repository = null;")
    if actual_user and actual_user.hasRole(db, "administrator"):
        document.addInternalScript("var administrator = true;")
    else:
        document.addInternalScript("var administrator = false;")
    document.addInternalScript(user.getJS())
    document.addInternalScript("user.gitEmails = %s;" % jsify(gitemails))
    document.addInternalScript(
        "var verifyEmailAddresses = %s;" %
        jsify(configuration.base.VERIFY_EMAIL_ADDRESSES))
    document.setTitle("%s Home" % title_fullname)

    target = body.div("main")

    basic = target.table('paleyellow basic', align='center')
    basic.tr().td('h1', colspan=3).h1().text("%s Home" % title_fullname)

    def row(heading, value, help=None, extra_class=None):
        if extra_class:
            row_class = "line " + extra_class
        else:
            row_class = "line"
        main_row = basic.tr(row_class)
        main_row.td('heading').text("%s:" % heading)
        value_cell = main_row.td('value', colspan=2)
        if callable(value): value(value_cell)
        else: value_cell.text(value)
        basic.tr('help').td('help', colspan=3).text(help)

    def renderFullname(target):
        if readonly: target.text(user.fullname)
        else:
            target.input("value", id="user_fullname", value=user.fullname)
            target.span("status", id="status_fullname")
            buttons = target.span("buttons")
            buttons.button(onclick="saveFullname();").text("Save")
            buttons.button(onclick="resetFullname();").text("Reset")

    def renderEmail(target):
        if not actual_user or actual_user.hasRole(db, "administrator"):
            cursor.execute(
                """SELECT id, email, verified
                                FROM useremails
                               WHERE uid=%s
                            ORDER BY id ASC""", (user.id, ))
            rows = cursor.fetchall()
            if rows:
                if len(rows) > 1:
                    target.addClass("multiple")
                addresses = target.div("addresses")
                for email_id, email, verified in rows:
                    checked = "checked" if email == user.email else None
                    selected = " selected" if email == user.email else ""

                    label = addresses.label("address inset flex" + selected,
                                            data_email_id=email_id)
                    if len(rows) > 1:
                        label.input(name="email",
                                    type="radio",
                                    value=email,
                                    checked=checked)
                    label.span("value").text(email)
                    actions = label.span("actions")

                    if verified is False:
                        actions.a("action unverified",
                                  href="#").text("[unverified]")
                    elif verified is True:
                        now = " now" if email_id == verified_email_id else ""
                        actions.span("action verified" +
                                     now).text("[verified]")
                    actions.a("action delete", href="#").text("[delete]")
            else:
                target.i().text("No email address")
            target.span("buttons").button("addemail").text("Add email address")
        elif user.email is None:
            target.i().text("No email address")
        elif user.email_verified is False:
            # Pending verification: don't show to other users.
            target.i().text("Email address not verified")
        else:
            target.span("inset").text(user.email)

    def renderGitEmails(target):
        if readonly: target.text(gitemails)
        else:
            target.input("value", id="user_gitemails", value=gitemails)
            target.span("status", id="status_gitemails")
            buttons = target.span("buttons")
            buttons.button(onclick="saveGitEmails();").text("Save")
            buttons.button(onclick="resetGitEmails();").text("Reset")

    def renderPassword(target):
        cursor.execute("SELECT password IS NOT NULL FROM users WHERE id=%s",
                       (user.id, ))
        has_password = cursor.fetchone()[0]
        if not has_password:
            target.text("not set")
        else:
            target.text("****")
        if not readonly:
            if not has_password or (actual_user and actual_user.hasRole(
                    db, "administrator")):
                target.span("buttons").button(
                    onclick="setPassword();").text("Set password")
            else:
                target.span("buttons").button(
                    onclick="changePassword();").text("Change password")

    row("User ID", str(user.id))
    row("User Name", user.name)
    row("Display Name", renderFullname,
        "This is the name used when displaying commits or comments.")
    row("Primary Email",
        renderEmail,
        "This is the primary email address, to which emails are sent.",
        extra_class="email")
    row("Git Emails", renderGitEmails,
        "These email addresses are used to map Git commits to the user.")

    if configuration.base.AUTHENTICATION_MODE == "critic":
        row("Password", renderPassword, extra_class="password")

    cursor.execute(
        """SELECT provider, account
                        FROM externalusers
                       WHERE uid=%s""", (user.id, ))

    external_accounts = [(auth.PROVIDERS[provider_name], account)
                         for provider_name, account in cursor
                         if provider_name in auth.PROVIDERS]

    if external_accounts:
        basic.tr().td('h2', colspan=3).h2().text("External Accounts")

        for provider, account in external_accounts:

            def renderExternalAccount(target):
                url = provider.getAccountURL(account)
                target.a("external", href=url).text(account)

            row(provider.getTitle(), renderExternalAccount)

    profiler.check("user information")

    filters = page.utils.PaleYellowTable(body, "Filters")
    filters.titleRight.a("button",
                         href="/tutorial?item=filters").text("Tutorial")

    cursor.execute(
        """SELECT repositories.id, repositories.name, repositories.path,
                             filters.id, filters.type, filters.path, NULL, filters.delegate
                        FROM repositories
                        JOIN filters ON (filters.repository=repositories.id)
                       WHERE filters.uid=%s""", (user.id, ))

    rows = cursor.fetchall()

    if configuration.extensions.ENABLED:
        cursor.execute(
            """SELECT repositories.id, repositories.name, repositories.path,
                                 filters.id, 'extensionhook', filters.path, filters.name, filters.data
                            FROM repositories
                            JOIN extensionhookfilters AS filters ON (filters.repository=repositories.id)
                           WHERE filters.uid=%s""", (user.id, ))

        rows.extend(cursor.fetchall())

    FILTER_TYPES = ["reviewer", "watcher", "ignored", "extensionhook"]

    def rowSortKey(row):
        (repository_id, repository_name, repository_path, filter_id,
         filter_type, filter_path, filter_name, filter_data) = row

        # Rows are grouped by repository first and type second, so sort by
        # repository name and filter type primarily.
        #
        # Secondarily sort by filter name (only for extension hook filters; is
        # None for regular filters) and filter path.  This sorting is mostly to
        # achieve a stable order; it has no greater meaning.

        return (repository_name, FILTER_TYPES.index(filter_type), filter_name,
                filter_path)

    rows.sort(key=rowSortKey)

    if rows:
        repository = None
        repository_filters = None
        tbody_reviewer = None
        tbody_watcher = None
        tbody_ignored = None
        tbody_extensionhook = None

        count_matched_files = {}

        for (repository_id, repository_name, repository_path, filter_id,
             filter_type, filter_path, filter_name, filter_data) in rows:
            if not repository or repository.id != repository_id:
                repository = gitutils.Repository.fromId(db, repository_id)
                repository_url = repository.getURL(db, user)
                filters.addSection(repository_name, repository_url)
                repository_filters = filters.addCentered().table(
                    "filters callout")
                tbody_reviewer = tbody_watcher = tbody_ignored = tbody_extensionhook = None

            if filter_type == "reviewer":
                if not tbody_reviewer:
                    tbody_reviewer = repository_filters.tbody()
                    tbody_reviewer.tr().th(colspan=5).text("Reviewer")
                tbody = tbody_reviewer
            elif filter_type == "watcher":
                if not tbody_watcher:
                    tbody_watcher = repository_filters.tbody()
                    tbody_watcher.tr().th(colspan=5).text("Watcher")
                tbody = tbody_watcher
            elif filter_type == "ignored":
                if not tbody_ignored:
                    tbody_ignored = repository_filters.tbody()
                    tbody_ignored.tr().th(colspan=5).text("Ignored")
                tbody = tbody_ignored
            else:
                if not tbody_extensionhook:
                    tbody_extensionhook = repository_filters.tbody()
                    tbody_extensionhook.tr().th(
                        colspan=5).text("Extension hooks")
                tbody = tbody_extensionhook

            row = tbody.tr()
            row.td("path").text(filter_path)

            if filter_type != "extensionhook":
                delegates = row.td("delegates", colspan=2)
                if filter_data:
                    delegates.i().text("Delegates: ")
                    delegates.span("names").text(", ".join(
                        filter_data.split(",")))
            else:
                role = extensions.role.filterhook.getFilterHookRole(
                    db, filter_id)
                if role:
                    title = row.td("title")
                    title.text(role.title)

                    data = row.td("data")
                    data.text(filter_data)
                else:
                    row.td(colspan=2).i().text("Invalid filter")

            if filter_path == "/":
                row.td("files").text("all files")
            else:
                href = "javascript:void(showMatchedFiles(%s, %s));" % (jsify(
                    repository.name), jsify(filter_path))
                row.td("files").a(href=href,
                                  id=("f%d" % filter_id)).text("? files")
                count_matched_files.setdefault(repository_id,
                                               []).append(filter_id)

            links = row.td("links")

            arguments = (jsify(repository.name), filter_id, jsify(filter_type),
                         jsify(filter_path), jsify(filter_data))
            links.a(href="javascript:void(editFilter(%s, %d, %s, %s, %s));" %
                    arguments).text("[edit]")

            if filter_type != "extensionhook":
                links.a(
                    href=
                    "javascript:if (deleteFilterById(%d)) location.reload(); void(0);"
                    % filter_id).text("[delete]")
                links.a(href="javascript:location.href='/config?filter=%d';" %
                        filter_id).text("[preferences]")
            else:
                links.a(
                    href=
                    "javascript:if (deleteExtensionHookFilterById(%d)) location.reload(); void(0);"
                    % filter_id).text("[delete]")

        document.addInternalScript("var count_matched_files = %s;" %
                                   json_encode(count_matched_files.values()))
    else:
        filters.addCentered().p().b().text("No filters")

        # Additionally check if there are in fact no repositories.
        cursor.execute("SELECT 1 FROM repositories")
        if not cursor.fetchone():
            document.addInternalScript("var no_repositories = true;")

    if not readonly:
        filters.addSeparator()
        filters.addCentered().button(
            onclick="editFilter();").text("Add filter")

    profiler.check("filters")

    hidden = body.div("hidden", style="display: none")

    if configuration.extensions.ENABLED:
        filterhooks = extensions.role.filterhook.listFilterHooks(db, user)
    else:
        filterhooks = []

    with hidden.div("filterdialog") as dialog:
        paragraph = dialog.p()
        paragraph.b().text("Repository:")
        paragraph.br()
        page.utils.generateRepositorySelect(db,
                                            user,
                                            paragraph,
                                            name="repository")

        paragraph = dialog.p()
        paragraph.b().text("Filter type:")
        paragraph.br()
        filter_type = paragraph.select(name="type")
        filter_type.option(value="reviewer").text("Reviewer")
        filter_type.option(value="watcher").text("Watcher")
        filter_type.option(value="ignored").text("Ignored")

        for extension, manifest, roles in filterhooks:
            optgroup = filter_type.optgroup(label=extension.getTitle(db))
            for role in roles:
                option = optgroup.option(
                    value="extensionhook",
                    data_extension_id=extension.getExtensionID(db),
                    data_filterhook_name=role.name)
                option.text(role.title)

        paragraph = dialog.p()
        paragraph.b().text("Path:")
        paragraph.br()
        paragraph.input(name="path", type="text")
        paragraph.span("matchedfiles")

        regular_div = dialog.div("regular")

        paragraph = regular_div.p()
        paragraph.b().text("Delegates:")
        paragraph.br()
        paragraph.input(name="delegates", type="text")

        paragraph = regular_div.p()
        label = paragraph.label()
        label.input(name="apply", type="checkbox", checked="checked")
        label.b().text("Apply to existing reviews")

        for extension, manifest, roles in filterhooks:
            for role in roles:
                if not role.data_description:
                    continue

                filterhook_id = "%d_%s" % (extension.getExtensionID(db),
                                           role.name)

                extensionhook_div = dialog.div("extensionhook " +
                                               filterhook_id,
                                               style="display: none")
                extensionhook_div.innerHTML(role.data_description)

                paragraph = extensionhook_div.p()
                paragraph.b().text("Data:")
                paragraph.br()
                paragraph.input(type="text")

    profiler.output(db, user, document)

    return document
Ejemplo n.º 30
0
def renderBranches(req, db, user):
    offset = req.getParameter("offset", 0, filter=int)
    count = req.getParameter("count", 50, filter=int)

    cursor = db.cursor()

    repository = req.getParameter("repository", None,
                                  gitutils.Repository.FromParameter(db))

    if not repository:
        repository = user.getDefaultRepository(db)

    all_branches = []
    commit_times = []

    if repository:
        cursor.execute(
            """SELECT branches.id, branches.name, commits.commit_time
                            FROM branches
                            JOIN repositories ON (repositories.id=branches.repository)
                            JOIN commits ON (commits.id=branches.head)
                           WHERE branches.type='normal'
                             AND branches.name NOT LIKE 'replay/%%'
                             AND repositories.id=%s
                        ORDER BY commits.commit_time DESC LIMIT %s""",
            (repository.id, count))

        for branch_id, branch_name, commit_time in cursor.fetchall():
            all_branches.append(branch_id)
            commit_times.append(commit_time.timetuple())

    document = htmlutils.Document(req)

    if repository:
        document.setTitle("Branches in %s" % repository.name)
    else:
        document.setTitle("Branches")

    html = document.html()
    head = html.head()
    body = html.body()

    page.utils.generateHeader(body, db, user, current_page="branches")

    document.addExternalScript("resource/branches.js")

    if repository:
        document.addInternalScript(repository.getJS())

    extraColumns = [
        ExtraColumn(
            "when", "When", lambda target, index: log.html.renderWhen(
                target, commit_times[index]))
    ]

    render(db,
           body.div("main"),
           "All Branches",
           repository,
           all_branches[offset:],
           extraColumns=extraColumns)

    return document