Merge "Bazel: Switch to using toolchain resolution for java rules"
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index a1dc27c..ace6995 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -2216,6 +2216,9 @@
other project managed by the running server. The name is
relative to `gerrit.basePath`.
+
+The link:#cache_names[persisted_projects cache] must be
+flushed after this setting is changed.
++
Defaults to `All-Projects` if not set.
[[gerrit.defaultBranch]]gerrit.defaultBranch::
diff --git a/Documentation/config-labels.txt b/Documentation/config-labels.txt
index f6d52e8..55aa9bd 100644
--- a/Documentation/config-labels.txt
+++ b/Documentation/config-labels.txt
@@ -6,7 +6,10 @@
link:access-control.html#category_review_labels[access controls]. Gerrit
comes pre-configured with the Code-Review label that can be granted to
groups within projects, enabling functionality for that group's members.
-
+Project owners and admins might also need to configure rules which require
+labels to be voted before a change can be submittable. See the
+link:config-submit-requirements.html[submit requirements] documentation to
+configure such rules.
[[label_Code-Review]]
== Label: Code-Review
@@ -205,7 +208,12 @@
[[label_function]]
-=== `label.Label-Name.function`
+=== `label.Label-Name.function (deprecated)`
+
+Label functions dictate the rules for requiring certain label votes before a
+change is allowed for submission. Label functions are **deprecated**, favour
+using link:config-submit-requirements.html[submit requirements] instead,
+except if it's needed to set the value to `PatchSetLock`.
The name of a function for evaluating multiple votes for a label. This
function is only applied if the default submit rule is used for a label.
@@ -486,7 +494,7 @@
`${shardeduserid}`.
[[label_ignoreSelfApproval]]
-=== `label.Label-Name.ignoreSelfApproval`
+=== `label.Label-Name.ignoreSelfApproval (deprecated)`
If true, the label may be voted on by the uploader of the latest patch set,
but their approval does not make a change submittable. Instead, a
@@ -494,6 +502,13 @@
Defaults to false.
+The `ignoreSelfApproval` attribute is **deprecated**, favour
+using link:config-submit-requirements.html[submit requirements] and
+define the `submittableIf` expression with the `label` operator and
+the `user=non_uploader` argument. See the
+link:config-submit-requirements.html#code-review-example[Code Review] submit
+requirement example.
+
[[label_example]]
=== Example
diff --git a/Documentation/config-project-config.txt b/Documentation/config-project-config.txt
index 4dff685..0a6e945e 100644
--- a/Documentation/config-project-config.txt
+++ b/Documentation/config-project-config.txt
@@ -481,6 +481,12 @@
Please refer to link:config-labels.html#label_custom[Custom Labels] documentation.
+[[submit-requirement-section]]
+=== Submit Requirement section
+
+Please refer to link:config-submit-requirements.html[Configuring Submit
+Requirements] documentation.
+
[[branchOrder-section]]
=== branchOrder section
diff --git a/Documentation/config-submit-requirements.txt b/Documentation/config-submit-requirements.txt
new file mode 100644
index 0000000..465cb53
--- /dev/null
+++ b/Documentation/config-submit-requirements.txt
@@ -0,0 +1,240 @@
+= Gerrit Code Review - Submit Requirements
+
+As part of the code review process, project owners need to configure rules that
+govern when changes become submittable. For example, an admin might want to
+prevent changes from being submittable until at least a “+2” vote on the
+“Code-Review” label is granted on a change. Admins can define submit
+requirements to enforce submittability rules for changes.
+
+[[configuring_submit_requirements]]
+== Configuring Submit Requirements
+
+Site administrators and project owners can define submit requirements in the
+link:config-project-config.html[project config]. A submit requirement has the
+following fields:
+
+
+[[submit_requirement_name]]
+=== submit-requirement.Name
+
+A name that uniquely identifies the submit requirement. Submit requirements
+can be overridden in child projects if they are defined with the same name in
+the child project. See the link:#inheritance[inheritance] section for more
+details.
+
+[[submit_requirement_description]]
+=== submit-requirement.Name.description
+
+A detailed description of what the submit requirement is supposed to do. This
+field is optional. The description is visible to the users in the change page
+upon hovering on the submit requirement to help them understand what the
+requirement is about and how it can be fulfilled.
+
+[[submit_requirement_applicable_if]]
+=== submit-requirement.Name.applicableIf
+
+A link:#query_expression_syntax[query expression] that determines if the submit
+requirement is applicable for a change. For example, administrators can exclude
+submit requirements for certain branch patterns. See the
+link:#exempt-branch-example[exempt branch] example.
+
+This field is optional, and if not specified, the submit requirement is
+considered applicable for all changes in the project.
+
+[[submit_requirement_submittable_if]]
+=== submit-requirement.Name.submittableIf
+
+A link:#query_expression_syntax[query expression] that determines when the
+change becomes submittable. This field is mandatory.
+
+
+[[submit_requirement_override_if]]
+=== submit-requirement.Name.overrideIf
+
+A link:#query_expression_syntax[query expression] that controls when the
+submit requirement is overridden. When this expression is evaluated to true,
+the submit requirement state becomes `OVERRIDDEN` and the submit requirement
+is no longer blocking the change submission.
+This expression can be used to enable bypassing the requirement in some
+circumstances, for example if the change owner is a power user or to allow
+change submission in case of emergencies. +
+
+This field is optional.
+
+[[submit_requirement_can_override_in_child_projects]]
+=== submit-requirement.Name.canOverrideInChildProjects
+
+A boolean (true, false) that determines if child projects can override the
+submit requirement. +
+
+The default value is `false`.
+
+[[evaluation_results]]
+== Evaluation Results
+
+When submit requirements are configured, their results are returned for all
+changes requested by the REST API with the
+link:rest-api-changes.html#submit-requirement-result-info[SubmitRequirementResultInfo]
+entity. +
+
+Submit requirement results are produced from the evaluation of the submit
+requirements in the project config (
+See link:#configuring_submit_requirements[Configuring Submit Requirements])
+as well as the conversion of the results of the legacy submit rules to submit
+requirement results. Legacy submit rules are label functions
+(see link:config-labels.html[config labels]), custom and
+link:prolog-cookbook.html[prolog] submit rules.
+
+The `status` field can be one of:
+
+* `NOT_APPLICABLE`
++
+The link:#submit_requirement_applicable_if[applicableIf] expression evaluates
+to false for the change.
+
+* `UNSATISFIED`
++
+The submit requirement is applicable
+(link:#submit_requirement_applicable_if[applicableIf] evaluates to true), but
+the evaluation of the link:#submit_requirement_submittable_if[submittableIf] and
+link:#submit_requirement_override_if[overrideIf] expressions return false for
+the change.
+
+* `SATISFIED`
++
+The submit requirement is applicable
+(link:#submit_requirement_applicable_if[applicableIf] evaluates to true), the
+link:#submit_requirement_submittable_if[submittableIf] expression evaluates to
+true, and the link:#submit_requirement_override_if[overrideIf] evaluates to
+false for the change.
+
+* `OVERRIDDEN`
++
+The submit requirement is applicable
+(link:#submit_requirement_applicable_if[applicableIf] evaluates to true) and the
+link:#submit_requirement_override_if[overrideIf] expression evaluates to true.
++
+Note that in this case, the change is overridden whether the
+link:#submit_requirement_submittable_if[submittableIf] expression evaluates to
+true or not.
+
+* `BYPASSED`
++
+The change was merged directly bypassing code review by supplying the
+link:user-upload.html#auto_merge[submit] push option while doing a git push.
+
+* `ERROR`
++
+The evaluation of any of the
+link:#submit_requirement_applicable_if[applicableIf],
+link:#submit_requirement_submittable_if[submittableIf] or
+link:#submit_requirement_override_if[overrideIf] expressions resulted in an
+error.
+
+
+[[query_expression_syntax]]
+== Query Expression Syntax
+
+All applicableIf, submittableIf and overrideIf expressions use the same syntax
+and operators available for link:user-search.html[searching changes]. In
+addition to that, submit requirements support extra operators.
+
+
+[[submit_requirements_operators]]
+=== Submit Requirements Operators
+
+[[operator_is_true]]
+is:true::
++
+An operator that always returns true for all changes. An example usage is to
+redefine a submit requirement in a child project and make the submit requirement
+always applicable.
+
+[[operator_is_false]]
+is:false::
++
+An operator that always returns false for all changes. An example usage is to
+redefine a submit requirement in a child project and make the submit requirement
+always non-applicable.
+
+[[unsupported_operators]]
+=== Unsupported Operators
+
+Some operators are not supported with submit requirement expressions.
+
+[[operator_is_submittable]]
+is:submittable::
++
+Cannot be used since it will result in recursive evaluation of expressions.
+
+[[inheritance]]
+== Inheritance
+
+Child projects can override a submit requirement defined in any of their parent
+projects. Overriding a submit requirement overrides all of its properties and
+values. The overriding project needs to define all mandatory fields.
+
+Submit requirements are looked up from the current project up the inheritance
+hierarchy to “All-Projects”. The first project in the hierarchy chain that sets
+link:#submit_requirement_can_override_in_child_projects[canOverrideInChildProjects]
+to false prevents all descendant projects from overriding it.
+
+If a project disallows a submit requirement from being overridden in child
+projects, all definitions of this submit requirement in descendant projects are
+ignored.
+
+To remove a submit requirement in a child project, administrators can redefine
+the requirement with the same name in the child project and set the
+link:#submit_requirement_applicable_if[applicableIf] expression to `is:false`.
+Since the link:#submit_requirement_submittable_if[submittableIf] field is
+mandatory, administrators need to provide it in the child project but can set it
+to anything, for example `is:false` but it will have no effect anyway.
+
+
+[[trigger-votes]]
+== Trigger Votes
+
+Trigger votes are label votes that are not associated with any submit
+requirement expressions. Trigger votes are displayed in a separate section in
+the change page. For more about configuring labels, see the
+link:config-labels.html[config labels] documentation.
+
+
+[[examples]]
+== Examples
+
+[[code-review-example]]
+=== Code-Review Example
+
+To define a submit requirement for code-review that requires a maximum vote for
+the “Code-Review” label from a non-uploader without a maximum negative vote:
+
+----
+[submit-requirement "Code-Review"]
+ description = A maximum vote from a non-uploader is required for the \
+ 'Code-Review' label. A minimum vote is blocking.
+ submittableIf = label:Code-Review=MAX,user=non_uploader AND -label:Code-Review=MIN
+ canOverrideInChildProjects = true
+----
+
+[[exempt-branch-example]]
+=== Exempt a branch Example
+
+We could exempt a submit requirement from certain branches. For example,
+project owners might want to skip the 'Code-Style' requirement from the
+refs/meta/config branch.
+
+----
+[submit-requirement "Code-Style"]
+ description = Code is properly styled and formatted
+ applicableIf = -branch:refs/meta/config
+ submittableIf = label:Code-Style=+1 AND -label:Code-Style=-1
+ canOverrideInChildProjects = true
+----
+
+GERRIT
+------
+Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/dev-plugins.txt b/Documentation/dev-plugins.txt
index 38ce7b3..8592205 100644
--- a/Documentation/dev-plugins.txt
+++ b/Documentation/dev-plugins.txt
@@ -2188,6 +2188,8 @@
DiffWebLinks will appear in the side-by-side and unified diff screen in
the header next to the navigation icons.
+EditWebLinks will appear in the top-right part of the file diff page.
+
ProjectWebLinks will appear in the project list in the
`Repository Browser` column.
diff --git a/Documentation/index.txt b/Documentation/index.txt
index 782a6a9..9afd6e3 100644
--- a/Documentation/index.txt
+++ b/Documentation/index.txt
@@ -43,6 +43,7 @@
== Project Management
. link:project-configuration.html[Project Configuration]
.. link:config-labels.html[Review Labels]
+.. link:config-submit-requirements.html[Submit Requirements]
.. link:config-project-config.html[Project Configuration File Format]
. link:access-control.html[Access Controls]
. Multi-project management
diff --git a/Documentation/user-attention-set.txt b/Documentation/user-attention-set.txt
index 1f67fc7..512f784 100644
--- a/Documentation/user-attention-set.txt
+++ b/Documentation/user-attention-set.txt
@@ -2,8 +2,6 @@
Report a bug or send feedback using
link:https://bugs.chromium.org/p/gerrit/issues/entry?template=Attention+Set[this Monorail template].
-You can also report a bug through the bug icon in the user hovercard and in the
-reply dialog.
[[whose-turn]]
== Whose turn is it?
@@ -133,24 +131,6 @@
Gerrit-Attention: Marian Harbach <mharbach@google.com>
----
-=== Assignee
-
-While the "Assignee" feature can still be used together with the attention set,
-we do not recommend doing so. Using both features is likely confusing. The
-distinct feature of the "Assignee" compared to the attention set is that only
-one user can be the assignee at the same time. So the assignee can be used to
-single out one person or escalate, if there are multiple reviewers. Since
-*every* reviewer in the attention set is expected to take action, singling out
-is not likely to be important and also still achievable with the attention set.
-Otherwise "Assignee" and "Attention Set" are very much overlapping, so we
-recommend to only use one of them.
-
-If you don't expect action from reviewers, then consider adding them to CC
-instead.
-
-The "Assignee" feature can be turned on/off with the
-link:config-gerrit.html#change.enableAssignee[enableAssignee] config option.
-
=== Bold Changes / Mark Reviewed
Before the attention set feature, changes were bolded in the dashboard when
diff --git a/Documentation/user-search.txt b/Documentation/user-search.txt
index f314e46..7e8a7e0 100644
--- a/Documentation/user-search.txt
+++ b/Documentation/user-search.txt
@@ -419,6 +419,12 @@
current patch set. 'FOOTER' can be specified verbatim ('<key>: <value>', must
be quoted) or as '<key>=<value>'. The matching is done case-insensitive.
+[[hasfooter-operator]]
+hasfooter:'FOOTERNAME'::
++
+Matches any change that has a commit message with a footer where the footer
+name is equal to 'FOOTERNAME'.The matching is done case-sensitive.
+
[[star]]
star:'LABEL'::
+
diff --git a/java/com/google/gerrit/entities/AccessSection.java b/java/com/google/gerrit/entities/AccessSection.java
index 69a234a..8ae0a5d 100644
--- a/java/com/google/gerrit/entities/AccessSection.java
+++ b/java/com/google/gerrit/entities/AccessSection.java
@@ -18,12 +18,14 @@
import static java.util.Objects.requireNonNull;
import com.google.auto.value.AutoValue;
+import com.google.auto.value.extension.memoized.Memoized;
import com.google.common.collect.ImmutableList;
import com.google.gerrit.common.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;
+import java.util.regex.Pattern;
/** Portion of a {@link Project} describing access rules. */
@AutoValue
@@ -42,6 +44,20 @@
/** Name of the access section. It could be a ref pattern or something else. */
public abstract String getName();
+ /**
+ * A compiled regular expression in case {@link #getName()} is a regular expression. This is
+ * memoized to save callers from compiling patterns for every use.
+ */
+ @Memoized
+ public Optional<Pattern> getNamePattern() {
+ if (isValidRefSectionName(getName())
+ && getName().startsWith(REGEX_PREFIX)
+ && !getName().contains("${")) {
+ return Optional.of(Pattern.compile(getName()));
+ }
+ return Optional.empty();
+ }
+
public abstract ImmutableList<Permission> getPermissions();
public static AccessSection create(String name) {
diff --git a/java/com/google/gerrit/entities/CachedProjectConfig.java b/java/com/google/gerrit/entities/CachedProjectConfig.java
index cd65efc..be4a1cf 100644
--- a/java/com/google/gerrit/entities/CachedProjectConfig.java
+++ b/java/com/google/gerrit/entities/CachedProjectConfig.java
@@ -18,6 +18,7 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSortedMap;
import com.google.common.flogger.FluentLogger;
import java.util.Collection;
import java.util.List;
@@ -69,7 +70,7 @@
public abstract AccountsSection getAccountsSection();
/** Returns a map of {@link AccessSection}s keyed by their name. */
- public abstract ImmutableMap<String, AccessSection> getAccessSections();
+ public abstract ImmutableSortedMap<String, AccessSection> getAccessSections();
/** Returns the {@link AccessSection} with to the given name. */
public Optional<AccessSection> getAccessSection(String refName) {
@@ -235,7 +236,7 @@
protected abstract ImmutableMap.Builder<AccountGroup.UUID, GroupReference> groupsBuilder();
- protected abstract ImmutableMap.Builder<String, AccessSection> accessSectionsBuilder();
+ protected abstract ImmutableSortedMap.Builder<String, AccessSection> accessSectionsBuilder();
protected abstract ImmutableMap.Builder<String, ContributorAgreement>
contributorAgreementsBuilder();
diff --git a/java/com/google/gerrit/entities/LabelType.java b/java/com/google/gerrit/entities/LabelType.java
index 5b51dc9..3541aac 100644
--- a/java/com/google/gerrit/entities/LabelType.java
+++ b/java/com/google/gerrit/entities/LabelType.java
@@ -306,7 +306,7 @@
for (LabelValue v : valueList) {
byValue.put(v.getValue(), v);
}
- setByValue(byValue.buildOrThrow());
+ setByValue(byValue.build());
setCopyValues(ImmutableList.sortedCopyOf(getCopyValues()));
diff --git a/java/com/google/gerrit/entities/Project.java b/java/com/google/gerrit/entities/Project.java
index 2521231..617b827 100644
--- a/java/com/google/gerrit/entities/Project.java
+++ b/java/com/google/gerrit/entities/Project.java
@@ -146,7 +146,7 @@
ImmutableMap.builder();
Arrays.stream(BooleanProjectConfig.values())
.forEach(b -> booleans.put(b, InheritableBoolean.INHERIT));
- builder.setBooleanConfigs(booleans.buildOrThrow());
+ builder.setBooleanConfigs(booleans.build());
return builder;
}
diff --git a/java/com/google/gerrit/entities/SubmitRequirementResult.java b/java/com/google/gerrit/entities/SubmitRequirementResult.java
index c81b43f..a97560b 100644
--- a/java/com/google/gerrit/entities/SubmitRequirementResult.java
+++ b/java/com/google/gerrit/entities/SubmitRequirementResult.java
@@ -16,6 +16,7 @@
import com.google.auto.value.AutoValue;
import com.google.auto.value.extension.memoized.Memoized;
+import com.google.gerrit.common.Nullable;
import com.google.gson.Gson;
import com.google.gson.TypeAdapter;
import java.util.Optional;
@@ -33,6 +34,7 @@
/**
* Result of evaluating a {@link SubmitRequirement#submittabilityExpression()} ()} on a change.
*/
+ @Nullable
public abstract SubmitRequirementExpressionResult submittabilityExpressionResult();
/** Result of evaluating a {@link SubmitRequirement#overrideExpression()} ()} on a change. */
@@ -69,7 +71,8 @@
"Applicability expression result has an error: "
+ applicabilityExpressionResult().get().errorMessage().get());
}
- if (submittabilityExpressionResult().errorMessage().isPresent()) {
+ if (submittabilityExpressionResult() != null
+ && submittabilityExpressionResult().errorMessage().isPresent()) {
return Optional.of(
"Submittability expression result has an error: "
+ submittabilityExpressionResult().errorMessage().get());
@@ -164,7 +167,8 @@
public abstract Builder applicabilityExpressionResult(
Optional<SubmitRequirementExpressionResult> value);
- public abstract Builder submittabilityExpressionResult(SubmitRequirementExpressionResult value);
+ public abstract Builder submittabilityExpressionResult(
+ @Nullable SubmitRequirementExpressionResult value);
public abstract Builder overrideExpressionResult(
Optional<SubmitRequirementExpressionResult> value);
@@ -182,7 +186,7 @@
return assertStatus(expressionResult, SubmitRequirementExpressionResult.Status.PASS);
}
- private boolean assertPass(SubmitRequirementExpressionResult expressionResult) {
+ private boolean assertPass(@Nullable SubmitRequirementExpressionResult expressionResult) {
return assertStatus(expressionResult, SubmitRequirementExpressionResult.Status.PASS);
}
@@ -194,14 +198,14 @@
return assertStatus(expressionResult, SubmitRequirementExpressionResult.Status.ERROR);
}
- private boolean assertError(SubmitRequirementExpressionResult expressionResult) {
+ private boolean assertError(@Nullable SubmitRequirementExpressionResult expressionResult) {
return assertStatus(expressionResult, SubmitRequirementExpressionResult.Status.ERROR);
}
private boolean assertStatus(
- SubmitRequirementExpressionResult expressionResult,
+ @Nullable SubmitRequirementExpressionResult expressionResult,
SubmitRequirementExpressionResult.Status status) {
- return expressionResult.status() == status;
+ return expressionResult != null && expressionResult.status() == status;
}
private boolean assertStatus(
diff --git a/java/com/google/gerrit/extensions/common/ChangeInfoDiffer.java b/java/com/google/gerrit/extensions/common/ChangeInfoDiffer.java
index 3084a42..ad112d3 100644
--- a/java/com/google/gerrit/extensions/common/ChangeInfoDiffer.java
+++ b/java/com/google/gerrit/extensions/common/ChangeInfoDiffer.java
@@ -177,7 +177,7 @@
additionsBuilder.put(entry.getKey(), added);
}
}
- ImmutableMap<Object, Object> additions = additionsBuilder.buildOrThrow();
+ ImmutableMap<Object, Object> additions = additionsBuilder.build();
return additions.isEmpty() ? null : additions;
}
diff --git a/java/com/google/gerrit/httpd/GetUserFilter.java b/java/com/google/gerrit/httpd/GetUserFilter.java
index 943fd29..68db98a 100644
--- a/java/com/google/gerrit/httpd/GetUserFilter.java
+++ b/java/com/google/gerrit/httpd/GetUserFilter.java
@@ -61,7 +61,7 @@
if (resEnabled) {
initParams.put("resEnabled", "");
}
- filter("/*").through(GetUserFilter.class, initParams.buildOrThrow());
+ filter("/*").through(GetUserFilter.class, initParams.build());
}
}
}
diff --git a/java/com/google/gerrit/httpd/raw/IndexHtmlUtil.java b/java/com/google/gerrit/httpd/raw/IndexHtmlUtil.java
index ec7071e..ce22ae8 100644
--- a/java/com/google/gerrit/httpd/raw/IndexHtmlUtil.java
+++ b/java/com/google/gerrit/httpd/raw/IndexHtmlUtil.java
@@ -73,7 +73,7 @@
if (!enabledExperiments.isEmpty()) {
data.put("enabledExperiments", serializeObject(GSON, enabledExperiments).toString());
}
- return data.buildOrThrow();
+ return data.build();
}
/** Returns dynamic parameters of {@code index.html}. */
@@ -131,7 +131,7 @@
}
data.put("gerritInitialData", initialData);
- return data.buildOrThrow();
+ return data.build();
}
/** Returns experimentData to be used in {@code index.html}. */
@@ -183,7 +183,7 @@
data.put("useGoogleFonts", "true");
}
- return data.buildOrThrow();
+ return data.build();
}
private static String computeCanonicalPath(@Nullable String canonicalURL)
diff --git a/java/com/google/gerrit/index/Schema.java b/java/com/google/gerrit/index/Schema.java
index a68d012..91c3f70 100644
--- a/java/com/google/gerrit/index/Schema.java
+++ b/java/com/google/gerrit/index/Schema.java
@@ -108,8 +108,8 @@
sb.put(f.getName(), f);
}
}
- this.fields = b.buildOrThrow();
- this.storedFields = sb.buildOrThrow();
+ this.fields = b.build();
+ this.storedFields = sb.build();
this.useLegacyNumericFields = useLegacyNumericFields;
}
diff --git a/java/com/google/gerrit/index/query/QueryBuilder.java b/java/com/google/gerrit/index/query/QueryBuilder.java
index b8462fd..ffa7ce4 100644
--- a/java/com/google/gerrit/index/query/QueryBuilder.java
+++ b/java/com/google/gerrit/index/query/QueryBuilder.java
@@ -133,7 +133,7 @@
}
c = c.getSuperclass();
}
- opFactories = b.buildOrThrow();
+ opFactories = b.build();
}
}
@@ -205,7 +205,7 @@
String name = e.getExportName() + "_" + e.getPluginName();
opFactoriesBuilder.put(name, e.getProvider().get());
}
- opFactories = opFactoriesBuilder.buildOrThrow();
+ opFactories = opFactoriesBuilder.build();
} else {
opFactories = def.opFactories;
}
diff --git a/java/com/google/gerrit/index/testing/AbstractFakeIndex.java b/java/com/google/gerrit/index/testing/AbstractFakeIndex.java
index f530630..36e9e52 100644
--- a/java/com/google/gerrit/index/testing/AbstractFakeIndex.java
+++ b/java/com/google/gerrit/index/testing/AbstractFakeIndex.java
@@ -222,7 +222,7 @@
doc.put(field.getName(), field.get(value));
}
}
- return doc.buildOrThrow();
+ return doc.build();
}
@Override
diff --git a/java/com/google/gerrit/json/BUILD b/java/com/google/gerrit/json/BUILD
index 3c6bec6..7b2fe2f 100644
--- a/java/com/google/gerrit/json/BUILD
+++ b/java/com/google/gerrit/json/BUILD
@@ -5,7 +5,10 @@
srcs = glob(["*.java"]),
visibility = ["//visibility:public"],
deps = [
+ "//java/com/google/gerrit/common:annotations",
+ "//java/com/google/gerrit/entities",
"//lib:gson",
"//lib:guava",
+ "//lib/guice",
],
)
diff --git a/java/com/google/gerrit/json/OptionalSubmitRequirementExpressionResultAdapterFactory.java b/java/com/google/gerrit/json/OptionalSubmitRequirementExpressionResultAdapterFactory.java
new file mode 100644
index 0000000..d35b8fb
--- /dev/null
+++ b/java/com/google/gerrit/json/OptionalSubmitRequirementExpressionResultAdapterFactory.java
@@ -0,0 +1,150 @@
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.json;
+
+import com.google.gerrit.common.Nullable;
+import com.google.gerrit.entities.SubmitRequirementExpressionResult;
+import com.google.gerrit.entities.SubmitRequirementResult;
+import com.google.gson.Gson;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonParser;
+import com.google.gson.TypeAdapter;
+import com.google.gson.TypeAdapterFactory;
+import com.google.gson.reflect.TypeToken;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonWriter;
+import com.google.inject.TypeLiteral;
+import java.io.IOException;
+import java.util.Optional;
+
+/**
+ * A {@code TypeAdapterFactory} for Optional {@code SubmitRequirementExpressionResult}.
+ *
+ * <p>{@link SubmitRequirementResult#submittabilityExpressionResult} was previously serialized as a
+ * mandatory field, but was later on migrated to an optional field. The server needs to handle
+ * deserializing of both formats.
+ */
+public class OptionalSubmitRequirementExpressionResultAdapterFactory implements TypeAdapterFactory {
+
+ private static final TypeToken<?> OPTIONAL_SR_EXPRESSION_RESULT_TOKEN =
+ TypeToken.get(new TypeLiteral<Optional<SubmitRequirementExpressionResult>>() {}.getType());
+
+ private static final TypeToken<?> SR_EXPRESSION_RESULT_TOKEN =
+ TypeToken.get(SubmitRequirementExpressionResult.class);
+
+ @SuppressWarnings({"unchecked"})
+ @Override
+ public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
+ if (typeToken.equals(OPTIONAL_SR_EXPRESSION_RESULT_TOKEN)) {
+ return (TypeAdapter<T>)
+ new OptionalSubmitRequirementExpressionResultTypeAdapter(
+ SubmitRequirementExpressionResult.typeAdapter(gson));
+ } else if (typeToken.equals(SR_EXPRESSION_RESULT_TOKEN)) {
+ return (TypeAdapter<T>)
+ new SubmitRequirementExpressionResultTypeAdapter(
+ SubmitRequirementExpressionResult.typeAdapter(gson));
+ }
+ return null;
+ }
+
+ /**
+ * Reads json representation of either {@code Optional<SubmitRequirementExpressionResult>} or
+ * {@code SubmitRequirementExpressionResult}, converting it to {@code Nullable} {@code
+ * SubmitRequirementExpressionResult}.
+ */
+ @Nullable
+ private static SubmitRequirementExpressionResult readOptionalOrMandatory(
+ TypeAdapter<SubmitRequirementExpressionResult> submitRequirementExpressionResultAdapter,
+ JsonReader in) {
+ JsonElement parsed = JsonParser.parseReader(in);
+ if (parsed == null) {
+ return null;
+ }
+ // If it does not have 'value' field, then it was serialized as
+ // SubmitRequirementExpressionResult directly
+ if (parsed.getAsJsonObject().has("value")) {
+ parsed = parsed.getAsJsonObject().get("value");
+ }
+ if (parsed == null || parsed.isJsonNull() || parsed.getAsJsonObject().entrySet().isEmpty()) {
+ return null;
+ }
+ return submitRequirementExpressionResultAdapter.fromJsonTree(parsed);
+ }
+
+ /**
+ * A {@code TypeAdapter} that provides backward compatibility for reading previously non-optional
+ * {@code SubmitRequirementExpressionResult} field.
+ */
+ private static class OptionalSubmitRequirementExpressionResultTypeAdapter
+ extends TypeAdapter<Optional<SubmitRequirementExpressionResult>> {
+
+ private final TypeAdapter<SubmitRequirementExpressionResult>
+ submitRequirementExpressionResultAdapter;
+
+ public OptionalSubmitRequirementExpressionResultTypeAdapter(
+ TypeAdapter<SubmitRequirementExpressionResult> submitRequirementResultAdapter) {
+ this.submitRequirementExpressionResultAdapter = submitRequirementResultAdapter;
+ }
+
+ @Override
+ public Optional<SubmitRequirementExpressionResult> read(JsonReader in) throws IOException {
+ return Optional.ofNullable(
+ readOptionalOrMandatory(submitRequirementExpressionResultAdapter, in));
+ }
+
+ @Override
+ public void write(JsonWriter out, Optional<SubmitRequirementExpressionResult> value)
+ throws IOException {
+ // Serialize the field using the same format used by the AutoValue's default Gson serializer.
+ out.beginObject();
+ out.name("value");
+ if (value.isPresent()) {
+ out.jsonValue(submitRequirementExpressionResultAdapter.toJson(value.get()));
+ } else {
+ out.nullValue();
+ }
+ out.endObject();
+ }
+ }
+
+ /**
+ * A {@code TypeAdapter} that provides forward compatibility for reading the optional {@code
+ * SubmitRequirementExpressionResult} field.
+ *
+ * <p>TODO(mariasavtchouk): Remove once updated to read the new format only.
+ */
+ private static class SubmitRequirementExpressionResultTypeAdapter
+ extends TypeAdapter<SubmitRequirementExpressionResult> {
+
+ private final TypeAdapter<SubmitRequirementExpressionResult>
+ submitRequirementExpressionResultAdapter;
+
+ public SubmitRequirementExpressionResultTypeAdapter(
+ TypeAdapter<SubmitRequirementExpressionResult> submitRequirementResultAdapter) {
+ this.submitRequirementExpressionResultAdapter = submitRequirementResultAdapter;
+ }
+
+ @Override
+ public SubmitRequirementExpressionResult read(JsonReader in) throws IOException {
+ return readOptionalOrMandatory(submitRequirementExpressionResultAdapter, in);
+ }
+
+ @Override
+ public void write(JsonWriter out, SubmitRequirementExpressionResult value) throws IOException {
+ // Serialize the field using the same format used by the AutoValue's default Gson serializer.
+ out.jsonValue(submitRequirementExpressionResultAdapter.toJson(value));
+ }
+ }
+}
diff --git a/java/com/google/gerrit/server/PropertyMap.java b/java/com/google/gerrit/server/PropertyMap.java
index 6806597..da3a2495 100644
--- a/java/com/google/gerrit/server/PropertyMap.java
+++ b/java/com/google/gerrit/server/PropertyMap.java
@@ -61,7 +61,7 @@
/** Builds and returns an immutable {@link PropertyMap}. */
public PropertyMap build() {
- return new PropertyMap(mutableMap.buildOrThrow());
+ return new PropertyMap(mutableMap.build());
}
}
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/server/StarredChangesUtil.java
index 087630b..33f2ad1 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/server/StarredChangesUtil.java
@@ -282,7 +282,7 @@
Account.Id accountId = Account.id(id);
builder.put(accountId, readLabels(repo, RefNames.refsStarredChanges(changeId, accountId)));
}
- return builder.buildOrThrow();
+ return builder.build();
} catch (IOException e) {
throw new StorageException(
String.format("Get accounts that starred change %d failed", changeId.get()), e);
diff --git a/java/com/google/gerrit/server/account/AccountCacheImpl.java b/java/com/google/gerrit/server/account/AccountCacheImpl.java
index 6944e27..1d9150d 100644
--- a/java/com/google/gerrit/server/account/AccountCacheImpl.java
+++ b/java/com/google/gerrit/server/account/AccountCacheImpl.java
@@ -132,7 +132,7 @@
account.getKey().accountId(),
AccountState.forCachedAccount(account.getValue(), defaultPreferences, externalIds));
}
- return result.buildOrThrow();
+ return result.build();
}
} catch (IOException | ExecutionException e) {
throw new StorageException(e);
diff --git a/java/com/google/gerrit/server/account/CachedAccountDetails.java b/java/com/google/gerrit/server/account/CachedAccountDetails.java
index cd7ca26..2ab6174 100644
--- a/java/com/google/gerrit/server/account/CachedAccountDetails.java
+++ b/java/com/google/gerrit/server/account/CachedAccountDetails.java
@@ -165,7 +165,7 @@
return CachedAccountDetails.create(
account,
- projectWatches.buildOrThrow(),
+ projectWatches.build(),
CachedPreferences.fromString(proto.getUserPreferences()));
}
}
diff --git a/java/com/google/gerrit/server/account/CapabilityCollection.java b/java/com/google/gerrit/server/account/CapabilityCollection.java
index 280d0e3..7621929 100644
--- a/java/com/google/gerrit/server/account/CapabilityCollection.java
+++ b/java/com/google/gerrit/server/account/CapabilityCollection.java
@@ -91,7 +91,7 @@
}
m.put(e.getKey(), ImmutableList.copyOf(rules));
}
- permissions = m.buildOrThrow();
+ permissions = m.build();
administrateServer = getPermission(GlobalCapability.ADMINISTRATE_SERVER);
batchChangesLimit = getPermission(GlobalCapability.BATCH_CHANGES_LIMIT);
diff --git a/java/com/google/gerrit/server/account/ProjectWatches.java b/java/com/google/gerrit/server/account/ProjectWatches.java
index 10f4018..42137c1 100644
--- a/java/com/google/gerrit/server/account/ProjectWatches.java
+++ b/java/com/google/gerrit/server/account/ProjectWatches.java
@@ -196,7 +196,7 @@
ImmutableMap.builder();
projectWatches.entrySet().stream()
.forEach(e -> b.put(e.getKey(), ImmutableSet.copyOf(e.getValue())));
- return b.buildOrThrow();
+ return b.build();
}
@AutoValue
diff --git a/java/com/google/gerrit/server/account/UniversalGroupBackend.java b/java/com/google/gerrit/server/account/UniversalGroupBackend.java
index bf5f6b1..5bd9bea 100644
--- a/java/com/google/gerrit/server/account/UniversalGroupBackend.java
+++ b/java/com/google/gerrit/server/account/UniversalGroupBackend.java
@@ -104,7 +104,7 @@
private UniversalGroupMembership(CurrentUser user) {
ImmutableMap.Builder<GroupBackend, GroupMembership> builder = ImmutableMap.builder();
backends.runEach(g -> builder.put(g, g.membershipsOf(user)));
- this.memberships = builder.buildOrThrow();
+ this.memberships = builder.build();
}
@Nullable
diff --git a/java/com/google/gerrit/server/account/externalids/AllExternalIds.java b/java/com/google/gerrit/server/account/externalids/AllExternalIds.java
index 4a040da..e718bcb 100644
--- a/java/com/google/gerrit/server/account/externalids/AllExternalIds.java
+++ b/java/com/google/gerrit/server/account/externalids/AllExternalIds.java
@@ -42,7 +42,7 @@
}
});
- return new AutoValue_AllExternalIds(byKey.buildOrThrow(), byAccount.build(), byEmail.build());
+ return new AutoValue_AllExternalIds(byKey.build(), byAccount.build(), byEmail.build());
}
public abstract ImmutableMap<ExternalId.Key, ExternalId> byKey();
diff --git a/java/com/google/gerrit/server/account/externalids/ExternalIdCacheLoader.java b/java/com/google/gerrit/server/account/externalids/ExternalIdCacheLoader.java
index 3ee2708..27672bd 100644
--- a/java/com/google/gerrit/server/account/externalids/ExternalIdCacheLoader.java
+++ b/java/com/google/gerrit/server/account/externalids/ExternalIdCacheLoader.java
@@ -253,7 +253,7 @@
}
}
}
- return new AutoValue_AllExternalIds(byKey.buildOrThrow(), byAccount.build(), byEmail.build());
+ return new AutoValue_AllExternalIds(byKey.build(), byAccount.build(), byEmail.build());
}
private AllExternalIds reloadAllExternalIds(ObjectId notesRev)
diff --git a/java/com/google/gerrit/server/cache/h2/H2CacheImpl.java b/java/com/google/gerrit/server/cache/h2/H2CacheImpl.java
index e181022..0403408 100644
--- a/java/com/google/gerrit/server/cache/h2/H2CacheImpl.java
+++ b/java/com/google/gerrit/server/cache/h2/H2CacheImpl.java
@@ -152,7 +152,7 @@
asLoadingCache.refresh(entry.getKey());
}
}
- return result.buildOrThrow();
+ return result.build();
}
throw new UnsupportedOperationException();
}
diff --git a/java/com/google/gerrit/server/change/SubmitRequirementsJson.java b/java/com/google/gerrit/server/change/SubmitRequirementsJson.java
index 31d8a15..7793b76 100644
--- a/java/com/google/gerrit/server/change/SubmitRequirementsJson.java
+++ b/java/com/google/gerrit/server/change/SubmitRequirementsJson.java
@@ -40,18 +40,20 @@
result.applicabilityExpressionResult().get(),
/* hide= */ true); // Always hide applicability expressions on the API
}
- if (req.overrideExpression().isPresent()) {
+ if (req.overrideExpression().isPresent() && result.overrideExpressionResult().isPresent()) {
info.overrideExpressionResult =
submitRequirementExpressionToInfo(
req.overrideExpression().get(),
result.overrideExpressionResult().get(),
/* hide= */ false);
}
- info.submittabilityExpressionResult =
- submitRequirementExpressionToInfo(
- req.submittabilityExpression(),
- result.submittabilityExpressionResult(),
- /* hide= */ false);
+ if (result.submittabilityExpressionResult() != null) {
+ info.submittabilityExpressionResult =
+ submitRequirementExpressionToInfo(
+ req.submittabilityExpression(),
+ result.submittabilityExpressionResult(),
+ /* hide= */ false);
+ }
info.status = SubmitRequirementResultInfo.Status.valueOf(result.status().toString());
info.isLegacy = result.isLegacy();
return info;
diff --git a/java/com/google/gerrit/server/comment/CommentContextCacheImpl.java b/java/com/google/gerrit/server/comment/CommentContextCacheImpl.java
index fe8ae9f..5be41d4 100644
--- a/java/com/google/gerrit/server/comment/CommentContextCacheImpl.java
+++ b/java/com/google/gerrit/server/comment/CommentContextCacheImpl.java
@@ -121,7 +121,7 @@
CommentContextKey cacheKey = inputKeysToCacheKeys.get(inputKey);
result.put(inputKey, allContext.get(cacheKey));
}
- return result.buildOrThrow();
+ return result.build();
} catch (ExecutionException e) {
throw new StorageException("Failed to retrieve comments' context", e);
}
@@ -170,7 +170,7 @@
AllCommentContextProto proto = Protos.parseUnchecked(AllCommentContextProto.parser(), in);
proto.getContextList().stream()
.forEach(c -> contextLinesMap.put(c.getLineNumber(), c.getContextLine()));
- return CommentContext.create(contextLinesMap.buildOrThrow(), proto.getContentType());
+ return CommentContext.create(contextLinesMap.build(), proto.getContentType());
}
}
diff --git a/java/com/google/gerrit/server/config/ConfigSection.java b/java/com/google/gerrit/server/config/ConfigSection.java
deleted file mode 100644
index 057ce99..0000000
--- a/java/com/google/gerrit/server/config/ConfigSection.java
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright (C) 2012 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.server.config;
-
-import org.eclipse.jgit.lib.Config;
-
-/** Provides access to one section from {@link Config} */
-public class ConfigSection {
-
- private final Config cfg;
- private final String section;
-
- public ConfigSection(Config cfg, String section) {
- this.cfg = cfg;
- this.section = section;
- }
-
- public String optional(String name) {
- return cfg.getString(section, null, name);
- }
-
- public String required(String name) {
- return ConfigUtil.getRequired(cfg, section, name);
- }
-}
diff --git a/java/com/google/gerrit/server/config/GitwebConfig.java b/java/com/google/gerrit/server/config/GitwebConfig.java
index 5632978..38a86f0 100644
--- a/java/com/google/gerrit/server/config/GitwebConfig.java
+++ b/java/com/google/gerrit/server/config/GitwebConfig.java
@@ -27,7 +27,6 @@
import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.extensions.restapi.Url;
import com.google.gerrit.extensions.webui.BranchWebLink;
-import com.google.gerrit.extensions.webui.EditWebLink;
import com.google.gerrit.extensions.webui.FileHistoryWebLink;
import com.google.gerrit.extensions.webui.FileWebLink;
import com.google.gerrit.extensions.webui.ParentWebLink;
@@ -73,7 +72,6 @@
}
if (!isNullOrEmpty(type.getFile()) || !isNullOrEmpty(type.getRootTree())) {
- DynamicSet.bind(binder(), EditWebLink.class).to(GitwebLinks.class);
DynamicSet.bind(binder(), FileWebLink.class).to(GitwebLinks.class);
}
@@ -257,7 +255,6 @@
@Singleton
static class GitwebLinks
implements BranchWebLink,
- EditWebLink,
FileHistoryWebLink,
FileWebLink,
PatchSetWebLink,
@@ -333,12 +330,6 @@
}
@Override
- public WebLinkInfo getEditWebLink(String projectName, String revision, String fileName) {
- // For Gitweb treat edit links the same as file links
- return getFileWebLink(projectName, revision, fileName);
- }
-
- @Override
public WebLinkInfo getPatchSetWebLink(
String projectName, String commit, String commitMessage, String branchName) {
if (revision != null) {
diff --git a/java/com/google/gerrit/server/config/PluginConfig.java b/java/com/google/gerrit/server/config/PluginConfig.java
index ac5fa12..2b363f1 100644
--- a/java/com/google/gerrit/server/config/PluginConfig.java
+++ b/java/com/google/gerrit/server/config/PluginConfig.java
@@ -58,10 +58,7 @@
groupReferences.putAll(projectConfig.getGroups());
}
return new AutoValue_PluginConfig(
- pluginName,
- copyConfig(cfg),
- Optional.ofNullable(projectConfig),
- groupReferences.buildOrThrow());
+ pluginName, copyConfig(cfg), Optional.ofNullable(projectConfig), groupReferences.build());
}
public static PluginConfig createFromGerritConfig(String pluginName, Config cfg) {
diff --git a/java/com/google/gerrit/server/git/receive/ReceiveCommitsResult.java b/java/com/google/gerrit/server/git/receive/ReceiveCommitsResult.java
index 19022ba..ecbdcbc 100644
--- a/java/com/google/gerrit/server/git/receive/ReceiveCommitsResult.java
+++ b/java/com/google/gerrit/server/git/receive/ReceiveCommitsResult.java
@@ -70,7 +70,7 @@
ImmutableMap.Builder<ChangeStatus, ImmutableSet<Change.Id>> changesBuilder =
ImmutableMap.builder();
changes.entrySet().forEach(e -> changesBuilder.put(e.getKey(), e.getValue().build()));
- changes(changesBuilder.buildOrThrow());
+ changes(changesBuilder.build());
return autoBuild();
}
diff --git a/java/com/google/gerrit/server/group/SystemGroupBackend.java b/java/com/google/gerrit/server/group/SystemGroupBackend.java
index f4e4052..5a9b9e5 100644
--- a/java/com/google/gerrit/server/group/SystemGroupBackend.java
+++ b/java/com/google/gerrit/server/group/SystemGroupBackend.java
@@ -116,7 +116,7 @@
names =
ImmutableSet.copyOf(
namesToGroups.values().stream().map(GroupReference::getName).collect(toSet()));
- uuids = u.buildOrThrow();
+ uuids = u.build();
externalUserMemberships =
cfg.getBoolean("groups", null, "includeExternalUsersInRegisteredUsersGroup", true)
? ImmutableSet.of(ANONYMOUS_USERS, REGISTERED_USERS)
diff --git a/java/com/google/gerrit/server/index/change/ChangeField.java b/java/com/google/gerrit/server/index/change/ChangeField.java
index 3d5dca8..baac95b 100644
--- a/java/com/google/gerrit/server/index/change/ChangeField.java
+++ b/java/com/google/gerrit/server/index/change/ChangeField.java
@@ -271,6 +271,14 @@
.collect(toSet());
}
+ /** Footers from the commit message of the current patch set. */
+ public static final FieldDef<ChangeData, Iterable<String>> FOOTER_NAME =
+ exact(ChangeQueryBuilder.FIELD_FOOTER_NAME).buildRepeatable(ChangeField::getFootersNames);
+
+ public static Set<String> getFootersNames(ChangeData cd) {
+ return cd.commitFooters().stream().map(f -> f.getKey()).collect(toSet());
+ }
+
/** Folders that are touched by the current patch set. */
public static final FieldDef<ChangeData, Iterable<String>> DIRECTORY =
exact(ChangeQueryBuilder.FIELD_DIRECTORY).buildRepeatable(ChangeField::getDirectories);
diff --git a/java/com/google/gerrit/server/index/change/ChangeSchemaDefinitions.java b/java/com/google/gerrit/server/index/change/ChangeSchemaDefinitions.java
index 9ff806d..9776584 100644
--- a/java/com/google/gerrit/server/index/change/ChangeSchemaDefinitions.java
+++ b/java/com/google/gerrit/server/index/change/ChangeSchemaDefinitions.java
@@ -205,6 +205,7 @@
* Added new field {@link ChangeField#PREFIX_HASHTAG} and {@link ChangeField#PREFIX_TOPIC} to
* allow easier search for topics.
*/
+ @Deprecated
static final Schema<ChangeData> V75 =
new Schema.Builder<ChangeData>()
.add(V74)
@@ -212,6 +213,10 @@
.add(ChangeField.PREFIX_TOPIC)
.build();
+ /** Added new field {@link ChangeField#FOOTER_NAME}. */
+ static final Schema<ChangeData> V76 =
+ new Schema.Builder<ChangeData>().add(V75).add(ChangeField.FOOTER_NAME).build();
+
/**
* Name of the change index to be used when contacting index backends or loading configurations.
*/
diff --git a/java/com/google/gerrit/server/logging/TraceContext.java b/java/com/google/gerrit/server/logging/TraceContext.java
index cac8858..487e0af 100644
--- a/java/com/google/gerrit/server/logging/TraceContext.java
+++ b/java/com/google/gerrit/server/logging/TraceContext.java
@@ -252,7 +252,7 @@
public ImmutableMap<String, String> getTags() {
ImmutableMap.Builder<String, String> tagMap = ImmutableMap.builder();
tags.cellSet().forEach(c -> tagMap.put(c.getRowKey(), c.getColumnKey()));
- return tagMap.buildOrThrow();
+ return tagMap.build();
}
public TraceContext addPluginTag(String pluginName) {
diff --git a/java/com/google/gerrit/server/mail/send/ChangeEmail.java b/java/com/google/gerrit/server/mail/send/ChangeEmail.java
index 33f2810..63b9c70 100644
--- a/java/com/google/gerrit/server/mail/send/ChangeEmail.java
+++ b/java/com/google/gerrit/server/mail/send/ChangeEmail.java
@@ -653,7 +653,7 @@
break;
}
}
- result.add(lineData.buildOrThrow());
+ result.add(lineData.build());
}
return result.build();
}
diff --git a/java/com/google/gerrit/server/mime/DefaultFileExtensionRegistry.java b/java/com/google/gerrit/server/mime/DefaultFileExtensionRegistry.java
index 2e2059b..1814e54 100644
--- a/java/com/google/gerrit/server/mime/DefaultFileExtensionRegistry.java
+++ b/java/com/google/gerrit/server/mime/DefaultFileExtensionRegistry.java
@@ -50,7 +50,7 @@
b.put((String) e.getKey(), type);
MimeUtil.addKnownMimeType(type);
}
- TYPES = b.buildOrThrow();
+ TYPES = b.build();
}
@Override
diff --git a/java/com/google/gerrit/server/notedb/ChangeNoteJson.java b/java/com/google/gerrit/server/notedb/ChangeNoteJson.java
index 44bb244..8b3ab5a 100644
--- a/java/com/google/gerrit/server/notedb/ChangeNoteJson.java
+++ b/java/com/google/gerrit/server/notedb/ChangeNoteJson.java
@@ -17,6 +17,7 @@
import com.google.common.collect.ImmutableList;
import com.google.gerrit.entities.EntitiesAdapterFactory;
import com.google.gerrit.json.EnumTypeAdapterFactory;
+import com.google.gerrit.json.OptionalSubmitRequirementExpressionResultAdapterFactory;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
@@ -49,6 +50,7 @@
.registerTypeAdapter(
new TypeLiteral<Optional<Boolean>>() {}.getType(),
new OptionalBooleanAdapter().nullSafe())
+ .registerTypeAdapterFactory(new OptionalSubmitRequirementExpressionResultAdapterFactory())
.registerTypeAdapter(ObjectId.class, new ObjectIdAdapter())
.setPrettyPrinting()
.create();
diff --git a/java/com/google/gerrit/server/notedb/RevisionNoteMap.java b/java/com/google/gerrit/server/notedb/RevisionNoteMap.java
index 248e922..98c9873 100644
--- a/java/com/google/gerrit/server/notedb/RevisionNoteMap.java
+++ b/java/com/google/gerrit/server/notedb/RevisionNoteMap.java
@@ -50,7 +50,7 @@
result.put(note.copy(), rn);
}
- return new RevisionNoteMap<>(noteMap, result.buildOrThrow());
+ return new RevisionNoteMap<>(noteMap, result.build());
}
static RevisionNoteMap<RobotCommentsRevisionNote> parseRobotComments(
@@ -63,7 +63,7 @@
rn.parse();
result.put(note.copy(), rn);
}
- return new RevisionNoteMap<>(noteMap, result.buildOrThrow());
+ return new RevisionNoteMap<>(noteMap, result.build());
}
static <T extends RevisionNote<? extends Comment>> RevisionNoteMap<T> emptyMap() {
diff --git a/java/com/google/gerrit/server/notedb/SubmitRequirementProtoConverter.java b/java/com/google/gerrit/server/notedb/SubmitRequirementProtoConverter.java
index dac71ea..e655d92 100644
--- a/java/com/google/gerrit/server/notedb/SubmitRequirementProtoConverter.java
+++ b/java/com/google/gerrit/server/notedb/SubmitRequirementProtoConverter.java
@@ -32,6 +32,8 @@
private static final FieldDescriptor SR_APPLICABILITY_EXPR_RESULT_FIELD =
SubmitRequirementResultProto.getDescriptor().findFieldByNumber(2);
+ private static final FieldDescriptor SR_SUBMITTABILITY_EXPR_RESULT_FIELD =
+ SubmitRequirementResultProto.getDescriptor().findFieldByNumber(3);
private static final FieldDescriptor SR_OVERRIDE_EXPR_RESULT_FIELD =
SubmitRequirementResultProto.getDescriptor().findFieldByNumber(4);
private static final FieldDescriptor SR_LEGACY_FIELD =
@@ -56,8 +58,11 @@
SubmitRequirementExpressionResultSerializer.serialize(
r.applicabilityExpressionResult().get()));
}
- builder.setSubmittabilityExpressionResult(
- SubmitRequirementExpressionResultSerializer.serialize(r.submittabilityExpressionResult()));
+ if (r.submittabilityExpressionResult() != null) {
+ builder.setSubmittabilityExpressionResult(
+ SubmitRequirementExpressionResultSerializer.serialize(
+ r.submittabilityExpressionResult()));
+ }
if (r.overrideExpressionResult().isPresent()) {
builder.setOverrideExpressionResult(
SubmitRequirementExpressionResultSerializer.serialize(
@@ -85,9 +90,11 @@
SubmitRequirementExpressionResultSerializer.deserialize(
proto.getApplicabilityExpressionResult())));
}
- builder.submittabilityExpressionResult(
- SubmitRequirementExpressionResultSerializer.deserialize(
- proto.getSubmittabilityExpressionResult()));
+ if (proto.hasField(SR_SUBMITTABILITY_EXPR_RESULT_FIELD)) {
+ builder.submittabilityExpressionResult(
+ SubmitRequirementExpressionResultSerializer.deserialize(
+ proto.getSubmittabilityExpressionResult()));
+ }
if (proto.hasField(SR_OVERRIDE_EXPR_RESULT_FIELD)) {
builder.overrideExpressionResult(
Optional.of(
diff --git a/java/com/google/gerrit/server/patch/DiffOperationsImpl.java b/java/com/google/gerrit/server/patch/DiffOperationsImpl.java
index 9bad339..a5b4d0a 100644
--- a/java/com/google/gerrit/server/patch/DiffOperationsImpl.java
+++ b/java/com/google/gerrit/server/patch/DiffOperationsImpl.java
@@ -346,7 +346,7 @@
diffs.put(fileDiffOutput.newPath().get(), fileDiffOutput);
}
}
- return diffs.buildOrThrow();
+ return diffs.build();
}
private static boolean allDueToRebase(FileDiffOutput fileDiffOutput) {
diff --git a/java/com/google/gerrit/server/patch/filediff/FileDiffCacheImpl.java b/java/com/google/gerrit/server/patch/filediff/FileDiffCacheImpl.java
index 44ef566..92c3b39 100644
--- a/java/com/google/gerrit/server/patch/filediff/FileDiffCacheImpl.java
+++ b/java/com/google/gerrit/server/patch/filediff/FileDiffCacheImpl.java
@@ -195,7 +195,7 @@
logger.atWarning().log("Failed to open the repository %s: %s", project, e.getMessage());
}
}
- return result.buildOrThrow();
+ return result.build();
}
}
diff --git a/java/com/google/gerrit/server/permissions/PermissionBackend.java b/java/com/google/gerrit/server/permissions/PermissionBackend.java
index 27c6793..1191db8 100644
--- a/java/com/google/gerrit/server/permissions/PermissionBackend.java
+++ b/java/com/google/gerrit/server/permissions/PermissionBackend.java
@@ -173,7 +173,13 @@
return ref(notes.getChange().getDest()).change(notes);
}
- /** Verify scoped user can {@code perm}, throwing if denied. */
+ /**
+ * Verify scoped user can {@code perm}, throwing if denied.
+ *
+ * <p>Should be used in REST API handlers where the thrown {@link AuthException} can be
+ * propagated. In business logic, where the exception would have to be caught, prefer using
+ * {@link #test(GlobalOrPluginPermission)}.
+ */
public abstract void check(GlobalOrPluginPermission perm)
throws AuthException, PermissionBackendException;
@@ -280,7 +286,13 @@
return ref(notes.getChange().getDest().branch()).change(notes);
}
- /** Verify scoped user can {@code perm}, throwing if denied. */
+ /**
+ * Verify scoped user can {@code perm}, throwing if denied.
+ *
+ * <p>Should be used in REST API handlers where the thrown {@link AuthException} can be
+ * propagated. In business logic, where the exception would have to be caught, prefer using
+ * {@link #test(CoreOrPluginProjectPermission)}.
+ */
public abstract void check(CoreOrPluginProjectPermission perm)
throws AuthException, PermissionBackendException;
@@ -368,7 +380,13 @@
/** Returns an instance scoped to change. */
public abstract ForChange change(ChangeNotes notes);
- /** Verify scoped user can {@code perm}, throwing if denied. */
+ /**
+ * Verify scoped user can {@code perm}, throwing if denied.
+ *
+ * <p>Should be used in REST API handlers where the thrown {@link AuthException} can be
+ * propagated. In business logic, where the exception would have to be caught, prefer using
+ * {@link #test(RefPermission)}.
+ */
public abstract void check(RefPermission perm) throws AuthException, PermissionBackendException;
/** Filter {@code permSet} to permissions scoped user might be able to perform. */
@@ -406,7 +424,13 @@
/** Returns the fully qualified resource path that this instance is scoped to. */
public abstract String resourcePath();
- /** Verify scoped user can {@code perm}, throwing if denied. */
+ /**
+ * Verify scoped user can {@code perm}, throwing if denied.
+ *
+ * <p>Should be used in REST API handlers where the thrown {@link AuthException} can be
+ * propagated. In business logic, where the exception would have to be caught, prefer using
+ * {@link #test(ChangePermissionOrLabel)}.
+ */
public abstract void check(ChangePermissionOrLabel perm)
throws AuthException, PermissionBackendException;
diff --git a/java/com/google/gerrit/server/permissions/PluginPermissionsUtil.java b/java/com/google/gerrit/server/permissions/PluginPermissionsUtil.java
index 68e7717..b147926 100644
--- a/java/com/google/gerrit/server/permissions/PluginPermissionsUtil.java
+++ b/java/com/google/gerrit/server/permissions/PluginPermissionsUtil.java
@@ -102,7 +102,7 @@
permissionIdNames.put(id, extension.get().getDescription());
}
- return permissionIdNames.buildOrThrow();
+ return permissionIdNames.build();
}
/**
diff --git a/java/com/google/gerrit/server/plugins/AbstractPreloadedPluginScanner.java b/java/com/google/gerrit/server/plugins/AbstractPreloadedPluginScanner.java
index e7f254d..c6133ea 100644
--- a/java/com/google/gerrit/server/plugins/AbstractPreloadedPluginScanner.java
+++ b/java/com/google/gerrit/server/plugins/AbstractPreloadedPluginScanner.java
@@ -103,7 +103,7 @@
}
}
}
- return result.buildOrThrow();
+ return result.build();
}
private void appendIfNotNull(StringBuilder string, String header, Class<?> guiceModuleClass) {
diff --git a/java/com/google/gerrit/server/plugins/JarScanner.java b/java/com/google/gerrit/server/plugins/JarScanner.java
index cd7b524..e119bf1 100644
--- a/java/com/google/gerrit/server/plugins/JarScanner.java
+++ b/java/com/google/gerrit/server/plugins/JarScanner.java
@@ -119,7 +119,7 @@
transform(values, cd -> new ExtensionMetaData(cd.className, cd.annotationValue)));
}
- return result.buildOrThrow();
+ return result.build();
}
public List<String> findSubClassesOf(Class<?> superClass) throws IOException {
diff --git a/java/com/google/gerrit/server/project/ProjectCacheImpl.java b/java/com/google/gerrit/server/project/ProjectCacheImpl.java
index 31bbff5..3aa3783 100644
--- a/java/com/google/gerrit/server/project/ProjectCacheImpl.java
+++ b/java/com/google/gerrit/server/project/ProjectCacheImpl.java
@@ -130,7 +130,7 @@
.keySerializer(new ProtobufSerializer<>(Cache.ProjectCacheKeyProto.parser()))
.valueSerializer(PersistedProjectConfigSerializer.INSTANCE)
.diskLimit(1 << 30) // 1 GiB
- .version(3)
+ .version(4)
.maximumWeight(0);
cache(CACHE_LIST, ListKey.class, new TypeLiteral<ImmutableSortedSet<Project.NameKey>>() {})
diff --git a/java/com/google/gerrit/server/project/ProjectConfig.java b/java/com/google/gerrit/server/project/ProjectConfig.java
index 4a17b5c..123a14c 100644
--- a/java/com/google/gerrit/server/project/ProjectConfig.java
+++ b/java/com/google/gerrit/server/project/ProjectConfig.java
@@ -224,7 +224,8 @@
projectName,
projectName.equals(allProjectsName)
? allProjectsConfigProvider.get(allProjectsName)
- : Optional.empty());
+ : Optional.empty(),
+ allProjectsName);
}
public ProjectConfig read(MetaDataUpdate update) throws IOException, ConfigInvalidException {
@@ -250,6 +251,7 @@
}
private final Optional<StoredConfig> baseConfig;
+ private final AllProjectsName allProjectsName;
private Project project;
private AccountsSection accountsSection;
@@ -287,7 +289,6 @@
.setCheckReceivedObjects(checkReceivedObjects)
.setExtensionPanelSections(extensionPanelSections);
groupList.byUUID().values().forEach(g -> builder.addGroup(g));
- accessSections.values().forEach(a -> builder.addAccessSection(a));
contributorAgreements.values().forEach(c -> builder.addContributorAgreement(c));
notifySections.values().forEach(n -> builder.addNotifySection(n));
subscribeSections.values().forEach(s -> builder.addSubscribeSection(s));
@@ -300,6 +301,28 @@
projectLevelConfigs
.entrySet()
.forEach(c -> builder.addProjectLevelConfig(c.getKey(), c.getValue().toText()));
+
+ if (projectName.equals(allProjectsName)) {
+ // Filter out permissions that aren't allowed to be set on All-Projects
+ accessSections
+ .values()
+ .forEach(
+ a -> {
+ List<Permission.Builder> copy = new ArrayList<>();
+ for (Permission p : a.getPermissions()) {
+ if (Permission.canBeOnAllProjects(a.getName(), p.getName())) {
+ copy.add(p.toBuilder());
+ }
+ }
+ AccessSection section =
+ AccessSection.builder(a.getName())
+ .modifyPermissions(permissions -> permissions.addAll(copy))
+ .build();
+ builder.addAccessSection(section);
+ });
+ } else {
+ accessSections.values().forEach(a -> builder.addAccessSection(a));
+ }
return builder.build();
}
@@ -355,9 +378,13 @@
requireNonNull(commentLinkSections.remove(name));
}
- private ProjectConfig(Project.NameKey projectName, Optional<StoredConfig> baseConfig) {
+ private ProjectConfig(
+ Project.NameKey projectName,
+ Optional<StoredConfig> baseConfig,
+ AllProjectsName allProjectsName) {
this.projectName = projectName;
this.baseConfig = baseConfig;
+ this.allProjectsName = allProjectsName;
}
public void load(Repository repo) throws IOException, ConfigInvalidException {
diff --git a/java/com/google/gerrit/server/project/ProjectState.java b/java/com/google/gerrit/server/project/ProjectState.java
index 69e6036..6352f66 100644
--- a/java/com/google/gerrit/server/project/ProjectState.java
+++ b/java/com/google/gerrit/server/project/ProjectState.java
@@ -15,9 +15,7 @@
package com.google.gerrit.server.project;
import static com.google.common.base.Preconditions.checkState;
-import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.gerrit.entities.PermissionRule.Action.ALLOW;
-import static java.util.Comparator.comparing;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.FluentIterable;
@@ -268,35 +266,18 @@
/** Get the sections that pertain only to this project. */
List<SectionMatcher> getLocalAccessSections() {
- List<SectionMatcher> sm = localAccessSections;
- if (sm == null) {
- ImmutableList<AccessSection> fromConfig =
- cachedConfig.getAccessSections().values().stream()
- .sorted(comparing(AccessSection::getName))
- .collect(toImmutableList());
- sm = new ArrayList<>(fromConfig.size());
- for (AccessSection section : fromConfig) {
- if (isAllProjects) {
- List<Permission.Builder> copy = new ArrayList<>();
- for (Permission p : section.getPermissions()) {
- if (Permission.canBeOnAllProjects(section.getName(), p.getName())) {
- copy.add(p.toBuilder());
- }
- }
- section =
- AccessSection.builder(section.getName())
- .modifyPermissions(permissions -> permissions.addAll(copy))
- .build();
- }
-
- SectionMatcher matcher = SectionMatcher.wrap(getNameKey(), section);
- if (matcher != null) {
- sm.add(matcher);
- }
- }
- localAccessSections = sm;
+ if (localAccessSections != null) {
+ return localAccessSections;
}
- return sm;
+ List<SectionMatcher> sm = new ArrayList<>(cachedConfig.getAccessSections().values().size());
+ for (AccessSection section : cachedConfig.getAccessSections().values()) {
+ SectionMatcher matcher = SectionMatcher.wrap(getNameKey(), section);
+ if (matcher != null) {
+ sm.add(matcher);
+ }
+ }
+ localAccessSections = sm;
+ return localAccessSections;
}
/**
diff --git a/java/com/google/gerrit/server/project/RefPatternMatcher.java b/java/com/google/gerrit/server/project/RefPatternMatcher.java
index b9076b3..be840b5 100644
--- a/java/com/google/gerrit/server/project/RefPatternMatcher.java
+++ b/java/com/google/gerrit/server/project/RefPatternMatcher.java
@@ -22,6 +22,7 @@
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Streams;
import com.google.gerrit.common.data.ParameterizedString;
+import com.google.gerrit.entities.AccessSection;
import com.google.gerrit.entities.Account;
import com.google.gerrit.entities.RefNames;
import com.google.gerrit.server.CurrentUser;
@@ -32,6 +33,13 @@
import java.util.stream.Stream;
public abstract class RefPatternMatcher {
+ public static RefPatternMatcher getMatcher(AccessSection section) {
+ if (section.getNamePattern().isPresent()) {
+ return new Regexp(section.getNamePattern().get());
+ }
+ return getMatcher(section.getName());
+ }
+
public static RefPatternMatcher getMatcher(String pattern) {
if (containsParameters(pattern)) {
return new ExpandParameters(pattern);
@@ -79,6 +87,10 @@
pattern = Pattern.compile(re);
}
+ Regexp(Pattern re) {
+ pattern = re;
+ }
+
@Override
public boolean match(String ref, CurrentUser user) {
return pattern.matcher(ref).matches() || (isRE(ref) && pattern.pattern().equals(ref));
diff --git a/java/com/google/gerrit/server/project/SectionMatcher.java b/java/com/google/gerrit/server/project/SectionMatcher.java
index 763957e..3d7175f 100644
--- a/java/com/google/gerrit/server/project/SectionMatcher.java
+++ b/java/com/google/gerrit/server/project/SectionMatcher.java
@@ -28,7 +28,7 @@
static SectionMatcher wrap(Project.NameKey project, AccessSection section) {
String ref = section.getName();
if (AccessSection.isValidRefSectionName(ref)) {
- return new SectionMatcher(project, section, getMatcher(ref));
+ return new SectionMatcher(project, section, getMatcher(section));
}
return null;
}
diff --git a/java/com/google/gerrit/server/project/SubmitRequirementsAdapter.java b/java/com/google/gerrit/server/project/SubmitRequirementsAdapter.java
index 69b35aa..177ce0d 100644
--- a/java/com/google/gerrit/server/project/SubmitRequirementsAdapter.java
+++ b/java/com/google/gerrit/server/project/SubmitRequirementsAdapter.java
@@ -99,7 +99,7 @@
}
}
}
- return result.buildOrThrow();
+ return result.build();
}
static List<SubmitRequirementResult> createResult(
diff --git a/java/com/google/gerrit/server/project/SubmitRequirementsEvaluatorImpl.java b/java/com/google/gerrit/server/project/SubmitRequirementsEvaluatorImpl.java
index 905719c..00f6876 100644
--- a/java/com/google/gerrit/server/project/SubmitRequirementsEvaluatorImpl.java
+++ b/java/com/google/gerrit/server/project/SubmitRequirementsEvaluatorImpl.java
@@ -170,7 +170,7 @@
for (SubmitRequirement requirement : requirements.values()) {
results.put(requirement, evaluateRequirement(requirement, cd));
}
- return results.buildOrThrow();
+ return results.build();
}
/**
diff --git a/java/com/google/gerrit/server/query/change/ChangePredicates.java b/java/com/google/gerrit/server/query/change/ChangePredicates.java
index 355f9de..ce17b31 100644
--- a/java/com/google/gerrit/server/query/change/ChangePredicates.java
+++ b/java/com/google/gerrit/server/query/change/ChangePredicates.java
@@ -270,6 +270,14 @@
}
/**
+ * Returns a predicate that matches changes with the provided {@code footer} name in their commit
+ * message.
+ */
+ public static Predicate<ChangeData> hasFooter(String footerName) {
+ return new ChangeIndexPredicate(ChangeField.FOOTER_NAME, footerName);
+ }
+
+ /**
* Returns a predicate that matches changes that modified files in the provided {@code directory}.
*/
public static Predicate<ChangeData> directory(String directory) {
diff --git a/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java b/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
index c828d4d..4491aa6 100644
--- a/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
+++ b/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
@@ -163,6 +163,7 @@
public static final String FIELD_EXTENSION = "extension";
public static final String FIELD_ONLY_EXTENSIONS = "onlyextensions";
public static final String FIELD_FOOTER = "footer";
+ public static final String FIELD_FOOTER_NAME = "footernames";
public static final String FIELD_CONFLICTS = "conflicts";
public static final String FIELD_DELETED = "deleted";
public static final String FIELD_DELTA = "delta";
@@ -955,6 +956,12 @@
}
@Operator
+ public Predicate<ChangeData> hasfooter(String footerName) throws QueryParseException {
+ checkFieldAvailable(ChangeField.FOOTER_NAME, "hasfooter");
+ return ChangePredicates.hasFooter(footerName);
+ }
+
+ @Operator
public Predicate<ChangeData> dir(String directory) {
return directory(directory);
}
diff --git a/java/com/google/gerrit/server/restapi/change/AllowedFormats.java b/java/com/google/gerrit/server/restapi/change/AllowedFormats.java
index 4ee483a..e3ab135 100644
--- a/java/com/google/gerrit/server/restapi/change/AllowedFormats.java
+++ b/java/com/google/gerrit/server/restapi/change/AllowedFormats.java
@@ -38,7 +38,7 @@
}
exts.put(format.name().toLowerCase(), format);
}
- extensions = exts.buildOrThrow();
+ extensions = exts.build();
// Zip is not supported because it may be interpreted by a Java plugin as a
// valid JAR file, whose code would have access to cookies on the domain.
diff --git a/java/com/google/gerrit/testing/SystemPropertiesTestRule.java b/java/com/google/gerrit/testing/SystemPropertiesTestRule.java
index a2861dd..c7a3542 100644
--- a/java/com/google/gerrit/testing/SystemPropertiesTestRule.java
+++ b/java/com/google/gerrit/testing/SystemPropertiesTestRule.java
@@ -45,7 +45,7 @@
for (String key : properties.keySet()) {
previousValuesBuilder.put(key, Optional.ofNullable(System.getProperty(key)));
}
- previousValues = previousValuesBuilder.buildOrThrow();
+ previousValues = previousValuesBuilder.build();
properties.entrySet().stream().forEach(this::setSystemProperty);
}
diff --git a/java/com/google/gerrit/util/cli/OptionHandlers.java b/java/com/google/gerrit/util/cli/OptionHandlers.java
index e487a13..9547410 100644
--- a/java/com/google/gerrit/util/cli/OptionHandlers.java
+++ b/java/com/google/gerrit/util/cli/OptionHandlers.java
@@ -62,7 +62,7 @@
}
}
}
- return map.buildOrThrow();
+ return map.build();
}
private static Class<?> getType(TypeLiteral<?> t) {
diff --git a/javatests/com/google/gerrit/prettify/common/SparseFileContentBuilderTest.java b/javatests/com/google/gerrit/prettify/common/SparseFileContentBuilderTest.java
index ae00dbf..a751d50 100644
--- a/javatests/com/google/gerrit/prettify/common/SparseFileContentBuilderTest.java
+++ b/javatests/com/google/gerrit/prettify/common/SparseFileContentBuilderTest.java
@@ -165,6 +165,6 @@
.put(5, "Fifth line")
.put(6, "Sixth line")
.put(10, "Seventh line")
- .buildOrThrow());
+ .build());
}
}
diff --git a/javatests/com/google/gerrit/server/cache/serialize/entities/SubmitRequirementJsonSerializerTest.java b/javatests/com/google/gerrit/server/cache/serialize/entities/SubmitRequirementJsonSerializerTest.java
index 5cd43af..2418d1c 100644
--- a/javatests/com/google/gerrit/server/cache/serialize/entities/SubmitRequirementJsonSerializerTest.java
+++ b/javatests/com/google/gerrit/server/cache/serialize/entities/SubmitRequirementJsonSerializerTest.java
@@ -196,12 +196,47 @@
}
@Test
- public void submitRequirementResult_deserialize() throws Exception {
+ public void submitRequirementResult_deserialize_nonOptionalSubmittabilityExpressionResultField()
+ throws Exception {
assertThat(SubmitRequirementResult.typeAdapter(gson).fromJson(srReqResultSerial))
.isEqualTo(srReqResult);
}
@Test
+ public void submitRequirementResult_deserialize_optionalSubmittabilityExpressionResultField()
+ throws Exception {
+ String newFormatSrReqResultSerial =
+ srReqResultSerial.replace(
+ "\"submittabilityExpressionResult\":{"
+ + "\"expression\":{\"expressionString\":\"label:\\\"Code-Review=+2\\\"\"},"
+ + "\"status\":\"PASS\",\"errorMessage\":{\"value\":null},"
+ + "\"passingAtoms\":[\"label:\\\"Code-Review=+2\\\"\"],"
+ + "\"failingAtoms\":[]},",
+ "\"submittabilityExpressionResult\":{\"value\":{"
+ + "\"expression\":{\"expressionString\":\"label:\\\"Code-Review=+2\\\"\"},"
+ + "\"status\":\"PASS\",\"errorMessage\":{\"value\":null},"
+ + "\"passingAtoms\":[\"label:\\\"Code-Review=+2\\\"\"],"
+ + "\"failingAtoms\":[]}},");
+ assertThat(SubmitRequirementResult.typeAdapter(gson).fromJson(newFormatSrReqResultSerial))
+ .isEqualTo(srReqResult);
+ }
+
+ @Test
+ public void submitRequirementResult_deserialize_emptyOptionalSubmittabilityExpressionResultField()
+ throws Exception {
+ String newFormatSrReqResultSerial =
+ srReqResultSerial.replace(
+ "\"submittabilityExpressionResult\":{"
+ + "\"expression\":{\"expressionString\":\"label:\\\"Code-Review=+2\\\"\"},"
+ + "\"status\":\"PASS\",\"errorMessage\":{\"value\":null},"
+ + "\"passingAtoms\":[\"label:\\\"Code-Review=+2\\\"\"],"
+ + "\"failingAtoms\":[]},",
+ "\"submittabilityExpressionResult\":{\"value\":null},");
+ assertThat(SubmitRequirementResult.typeAdapter(gson).fromJson(newFormatSrReqResultSerial))
+ .isEqualTo(srReqResult.toBuilder().submittabilityExpressionResult(null).build());
+ }
+
+ @Test
public void submitRequirementResult_roundTrip() throws Exception {
TypeAdapter<SubmitRequirementResult> adapter = SubmitRequirementResult.typeAdapter(gson);
assertThat(adapter.fromJson(adapter.toJson(srReqResult))).isEqualTo(srReqResult);
diff --git a/javatests/com/google/gerrit/server/events/EventJsonTest.java b/javatests/com/google/gerrit/server/events/EventJsonTest.java
index bdd447d..3c9a355 100644
--- a/javatests/com/google/gerrit/server/events/EventJsonTest.java
+++ b/javatests/com/google/gerrit/server/events/EventJsonTest.java
@@ -108,11 +108,11 @@
.put("name", event.submitter.get().name)
.put("email", event.submitter.get().email)
.put("username", event.submitter.get().username)
- .buildOrThrow())
+ .build())
.put("refUpdate", ImmutableMap.of("refName", REF))
.put("type", "ref-updated")
.put("eventCreatedOn", TS1)
- .buildOrThrow());
+ .build());
}
@Test
@@ -131,7 +131,7 @@
.put("name", event.uploader.get().name)
.put("email", event.uploader.get().email)
.put("username", event.uploader.get().username)
- .buildOrThrow())
+ .build())
.put(
"change",
ImmutableMap.builder()
@@ -143,13 +143,13 @@
.put("commitMessage", COMMIT_MESSAGE)
.put("createdOn", TS1)
.put("status", NEW.name())
- .buildOrThrow())
+ .build())
.put("project", PROJECT)
.put("refName", REF)
.put("changeKey", map("id", CHANGE_ID))
.put("type", "patchset-created")
.put("eventCreatedOn", TS2)
- .buildOrThrow());
+ .build());
}
@Test
@@ -169,14 +169,14 @@
.put("name", event.changer.get().name)
.put("email", event.changer.get().email)
.put("username", event.changer.get().username)
- .buildOrThrow())
+ .build())
.put(
"oldAssignee",
ImmutableMap.builder()
.put("name", event.oldAssignee.get().name)
.put("email", event.oldAssignee.get().email)
.put("username", event.oldAssignee.get().username)
- .buildOrThrow())
+ .build())
.put(
"change",
ImmutableMap.builder()
@@ -188,13 +188,13 @@
.put("commitMessage", COMMIT_MESSAGE)
.put("createdOn", TS1)
.put("status", NEW.name())
- .buildOrThrow())
+ .build())
.put("project", PROJECT)
.put("refName", REF)
.put("changeKey", map("id", CHANGE_ID))
.put("type", "assignee-changed")
.put("eventCreatedOn", TS2)
- .buildOrThrow());
+ .build());
}
@Test
@@ -213,7 +213,7 @@
.put("name", event.deleter.get().name)
.put("email", event.deleter.get().email)
.put("username", event.deleter.get().username)
- .buildOrThrow())
+ .build())
.put(
"change",
ImmutableMap.builder()
@@ -225,13 +225,13 @@
.put("commitMessage", COMMIT_MESSAGE)
.put("createdOn", TS1)
.put("status", NEW.name())
- .buildOrThrow())
+ .build())
.put("project", PROJECT)
.put("refName", REF)
.put("changeKey", map("id", CHANGE_ID))
.put("type", "change-deleted")
.put("eventCreatedOn", TS2)
- .buildOrThrow());
+ .build());
}
@Test
@@ -253,7 +253,7 @@
.put("name", event.editor.get().name)
.put("email", event.editor.get().email)
.put("username", event.editor.get().username)
- .buildOrThrow())
+ .build())
.put("added", list("added"))
.put("removed", list("removed"))
.put("hashtags", list("hashtags"))
@@ -268,13 +268,13 @@
.put("commitMessage", COMMIT_MESSAGE)
.put("createdOn", TS1)
.put("status", NEW.name())
- .buildOrThrow())
+ .build())
.put("project", PROJECT)
.put("refName", REF)
.put("changeKey", map("id", CHANGE_ID))
.put("type", "hashtags-changed")
.put("eventCreatedOn", TS2)
- .buildOrThrow());
+ .build());
}
@Test
@@ -294,7 +294,7 @@
.put("name", event.abandoner.get().name)
.put("email", event.abandoner.get().email)
.put("username", event.abandoner.get().username)
- .buildOrThrow())
+ .build())
.put("reason", "some reason")
.put(
"change",
@@ -307,13 +307,13 @@
.put("commitMessage", COMMIT_MESSAGE)
.put("createdOn", TS1)
.put("status", NEW.name())
- .buildOrThrow())
+ .build())
.put("project", PROJECT)
.put("refName", REF)
.put("changeKey", map("id", CHANGE_ID))
.put("type", "change-abandoned")
.put("eventCreatedOn", TS2)
- .buildOrThrow());
+ .build());
}
@Test
@@ -336,13 +336,13 @@
.put("commitMessage", COMMIT_MESSAGE)
.put("createdOn", TS1)
.put("status", NEW.name())
- .buildOrThrow())
+ .build())
.put("project", PROJECT)
.put("refName", REF)
.put("changeKey", map("id", CHANGE_ID))
.put("type", "change-merged")
.put("eventCreatedOn", TS2)
- .buildOrThrow());
+ .build());
}
@Test
@@ -365,13 +365,13 @@
.put("commitMessage", COMMIT_MESSAGE)
.put("createdOn", TS1)
.put("status", NEW.name())
- .buildOrThrow())
+ .build())
.put("project", PROJECT)
.put("refName", REF)
.put("changeKey", map("id", CHANGE_ID))
.put("type", "change-restored")
.put("eventCreatedOn", TS2)
- .buildOrThrow());
+ .build());
}
@Test
@@ -394,13 +394,13 @@
.put("commitMessage", COMMIT_MESSAGE)
.put("createdOn", TS1)
.put("status", NEW.name())
- .buildOrThrow())
+ .build())
.put("project", PROJECT)
.put("refName", REF)
.put("changeKey", map("id", CHANGE_ID))
.put("type", "comment-added")
.put("eventCreatedOn", TS2)
- .buildOrThrow());
+ .build());
}
@Test
@@ -423,13 +423,13 @@
.put("commitMessage", COMMIT_MESSAGE)
.put("createdOn", TS1)
.put("status", NEW.name())
- .buildOrThrow())
+ .build())
.put("project", PROJECT)
.put("refName", REF)
.put("changeKey", map("id", CHANGE_ID))
.put("type", "private-state-changed")
.put("eventCreatedOn", TS2)
- .buildOrThrow());
+ .build());
}
@Test
@@ -452,13 +452,13 @@
.put("commitMessage", COMMIT_MESSAGE)
.put("createdOn", TS1)
.put("status", NEW.name())
- .buildOrThrow())
+ .build())
.put("project", PROJECT)
.put("refName", REF)
.put("changeKey", map("id", CHANGE_ID))
.put("type", "reviewer-added")
.put("eventCreatedOn", TS2)
- .buildOrThrow());
+ .build());
}
@Test
@@ -481,13 +481,13 @@
.put("commitMessage", COMMIT_MESSAGE)
.put("createdOn", TS1)
.put("status", NEW.name())
- .buildOrThrow())
+ .build())
.put("project", PROJECT)
.put("refName", REF)
.put("changeKey", map("id", CHANGE_ID))
.put("type", "reviewer-deleted")
.put("eventCreatedOn", TS2)
- .buildOrThrow());
+ .build());
}
@Test
@@ -510,13 +510,13 @@
.put("commitMessage", COMMIT_MESSAGE)
.put("createdOn", TS1)
.put("status", NEW.name())
- .buildOrThrow())
+ .build())
.put("project", PROJECT)
.put("refName", REF)
.put("changeKey", map("id", CHANGE_ID))
.put("type", "vote-deleted")
.put("eventCreatedOn", TS2)
- .buildOrThrow());
+ .build());
}
@Test
@@ -539,13 +539,13 @@
.put("commitMessage", COMMIT_MESSAGE)
.put("createdOn", TS1)
.put("status", NEW.name())
- .buildOrThrow())
+ .build())
.put("project", PROJECT)
.put("refName", REF)
.put("changeKey", map("id", CHANGE_ID))
.put("type", "wip-state-changed")
.put("eventCreatedOn", TS2)
- .buildOrThrow());
+ .build());
}
@Test
@@ -568,13 +568,13 @@
.put("commitMessage", COMMIT_MESSAGE)
.put("createdOn", TS1)
.put("status", NEW.name())
- .buildOrThrow())
+ .build())
.put("project", PROJECT)
.put("refName", REF)
.put("changeKey", map("id", CHANGE_ID))
.put("type", "topic-changed")
.put("eventCreatedOn", TS2)
- .buildOrThrow());
+ .build());
}
@Test
@@ -590,7 +590,7 @@
.put("headName", REF)
.put("type", "project-created")
.put("eventCreatedOn", TS1)
- .buildOrThrow());
+ .build());
}
private Supplier<AccountAttribute> newAccount(String name) {
diff --git a/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java b/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
index e916147..c851e64 100644
--- a/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
+++ b/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
@@ -1675,6 +1675,27 @@
}
@Test
+ public void byFooterName() throws Exception {
+ assume().that(getSchema().hasField(ChangeField.FOOTER_NAME)).isTrue();
+ TestRepository<Repo> repo = createProject("repo");
+ RevCommit commit1 = repo.parseBody(repo.commit().message("Test\n\nfoo: bar").create());
+ Change change1 = insert(repo, newChangeForCommit(repo, commit1));
+ RevCommit commit2 = repo.parseBody(repo.commit().message("Test\n\nBaR: baz").create());
+ Change change2 = insert(repo, newChangeForCommit(repo, commit2));
+
+ // create a changes with lines that look like footers, but which are not
+ RevCommit commit6 = repo.parseBody(repo.commit().message("Test\n\na=b: c").create());
+ insert(repo, newChangeForCommit(repo, commit6));
+
+ // matching by 'key=value' works
+ assertQuery("hasfooter:foo", change1);
+
+ // case matters
+ assertQuery("hasfooter:BaR", change2);
+ assertQuery("hasfooter:Bar");
+ }
+
+ @Test
public void byDirectory() throws Exception {
TestRepository<Repo> repo = createProject("repo");
Change change1 = insert(repo, newChangeWithFiles(repo, "src/foo.h", "src/foo.cc"));
diff --git a/lib/BUILD b/lib/BUILD
index 7d0e2e1..ce83ba1 100644
--- a/lib/BUILD
+++ b/lib/BUILD
@@ -511,7 +511,6 @@
":icu4j",
":jsr305",
":protobuf",
- "//lib/flogger:api",
"//lib/guice",
"//lib/guice:guice-assistedinject",
"//lib/guice:javax_inject",
diff --git a/lib/flogger/BUILD b/lib/flogger/BUILD
index a335586..35c3c62 100644
--- a/lib/flogger/BUILD
+++ b/lib/flogger/BUILD
@@ -5,7 +5,6 @@
data = ["//lib:LICENSE-Apache2.0"],
visibility = ["//visibility:public"],
exports = [
- "@flogger-google-extensions//jar",
"@flogger-log4j-backend//jar",
"@flogger-system-backend//jar",
"@flogger//jar",
diff --git a/lib/nongoogle_test.sh b/lib/nongoogle_test.sh
index 21710f4..957c33d 100755
--- a/lib/nongoogle_test.sh
+++ b/lib/nongoogle_test.sh
@@ -18,7 +18,6 @@
eddsa
error-prone-annotations
flogger
-flogger-google-extensions
flogger-log4j-backend
flogger-system-backend
guava
diff --git a/modules/jgit b/modules/jgit
index 4d34cdf..56f45e3 160000
--- a/modules/jgit
+++ b/modules/jgit
@@ -1 +1 @@
-Subproject commit 4d34cdf3459022d0878dfbd099c6f7b7ea03ea73
+Subproject commit 56f45e36dc2ccb0803ad810098bc4a0ac8f4a675
diff --git a/plugins/package.json b/plugins/package.json
index e5d245c..94c8be9 100644
--- a/plugins/package.json
+++ b/plugins/package.json
@@ -3,10 +3,10 @@
"description": "Gerrit Code Review - frontend plugin dependencies, each plugin may depend on a subset of these",
"browser": true,
"dependencies": {
- "@polymer/decorators": "^3.0.0",
- "@polymer/polymer": "^3.4.1",
- "@gerritcodereview/typescript-api": "3.4.4",
- "lit": "^2.0.2"
+ "@gerritcodereview/typescript-api": "3.4.4",
+ "@polymer/decorators": "^3.0.0",
+ "@polymer/polymer": "^3.4.1",
+ "lit": "^2.1.1"
},
"license": "Apache-2.0",
"private": true
diff --git a/plugins/yarn.lock b/plugins/yarn.lock
index 4cbe489..5dccf83 100644
--- a/plugins/yarn.lock
+++ b/plugins/yarn.lock
@@ -7,10 +7,10 @@
resolved "https://registry.yarnpkg.com/@gerritcodereview/typescript-api/-/typescript-api-3.4.4.tgz#9f09687038088dd7edd3b4e30d249502eb21bfbc"
integrity sha512-MAiQwntcQ59b92yYDsVIXj3oBbAB4C7HELkLFFbYs4ZjzC43XqqtR9VF0dh5OUC8wzFZttgUiOmGehk9edpPuw==
-"@lit/reactive-element@^1.0.0":
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/@lit/reactive-element/-/reactive-element-1.0.1.tgz#853cacd4d78d79059f33f66f8e7b0e5c34bee294"
- integrity sha512-nSD5AA2AZkKuXuvGs8IK7K5ZczLAogfDd26zT9l6S7WzvqALdVWcW5vMUiTnZyj5SPcNwNNANj0koeV1ieqTFQ==
+"@lit/reactive-element@^1.1.0":
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/@lit/reactive-element/-/reactive-element-1.2.0.tgz#c62444a0e3d3f8d3a6875ad56f867279aa89fa88"
+ integrity sha512-7i/Fz8enAQ2AN5DyJ2i2AFERufjP6x1NjuHoNgDyJkjjHxEoo8kVyyHxu1A9YyeShlksjt5FvpvENBDuivQHLA==
"@polymer/decorators@^3.0.0":
version "3.0.0"
@@ -36,26 +36,26 @@
resolved "https://registry.yarnpkg.com/@webcomponents/shadycss/-/shadycss-1.11.0.tgz#73e289996c002d8be694cd3be0e83c46ad25e7e0"
integrity sha512-L5O/+UPum8erOleNjKq6k58GVl3fNsEQdSOyh0EUhNmi7tHUyRuCJy1uqJiWydWcLARE5IPsMoPYMZmUGrz1JA==
-lit-element@^3.0.0:
- version "3.0.1"
- resolved "https://registry.yarnpkg.com/lit-element/-/lit-element-3.0.1.tgz#3c545af17d8a46268bc1dd5623a47486e6ff76f4"
- integrity sha512-vs9uybH9ORyK49CFjoNGN85HM9h5bmisU4TQ63phe/+GYlwvY/3SIFYKdjV6xNvzz8v2MnVC+9+QOkPqh+Q3Ew==
+lit-element@^3.1.0:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/lit-element/-/lit-element-3.1.1.tgz#562d5ccbc8ba0c01d8ba4a0ac3576263167d2ccb"
+ integrity sha512-14ClnMAU8EXnzC+M2/KDd3SFmNUn1QUw1+GxWkEMwGV3iaH8ObunMlO5svzvaWlkSV0WlxJCi40NGnDVJ2XZKQ==
dependencies:
- "@lit/reactive-element" "^1.0.0"
- lit-html "^2.0.0"
+ "@lit/reactive-element" "^1.1.0"
+ lit-html "^2.1.0"
-lit-html@^2.0.0:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/lit-html/-/lit-html-2.0.1.tgz#63241015efa07bc9259b6f96f04abd052d2a1f95"
- integrity sha512-KF5znvFdXbxTYM/GjpdOOnMsjgRcFGusTnB54ixnCTya5zUR0XqrDRj29ybuLS+jLXv1jji6Y8+g4W7WP8uL4w==
+lit-html@^2.1.0:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/lit-html/-/lit-html-2.1.1.tgz#f4da485798a0d967514d31730d387350fafb79f7"
+ integrity sha512-E4BImK6lopAYanJpvcGaAG8kQFF1ccIulPu2BRNZI7acFB6i4ujjjsnaPVFT1j/4lD9r8GKih0Y8d7/LH8SeyQ==
dependencies:
"@types/trusted-types" "^2.0.2"
-lit@^2.0.2:
- version "2.0.2"
- resolved "https://registry.yarnpkg.com/lit/-/lit-2.0.2.tgz#5e6f422924e0732258629fb379556b6d23f7179c"
- integrity sha512-hKA/1YaSB+P+DvKWuR2q1Xzy/iayhNrJ3aveD0OQ9CKn6wUjsdnF/7LavDOJsKP/K5jzW/kXsuduPgRvTFrFJw==
+lit@^2.1.1:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/lit/-/lit-2.1.1.tgz#65f43abca945988f696391f762c645ba51966b0b"
+ integrity sha512-yqDqf36IhXwOxIQSFqCMgpfvDCRdxLCLZl7m/+tO5C9W/OBHUj17qZpiMBT35v97QMVKcKEi1KZ3hZRyTwBNsQ==
dependencies:
- "@lit/reactive-element" "^1.0.0"
- lit-element "^3.0.0"
- lit-html "^2.0.0"
+ "@lit/reactive-element" "^1.1.0"
+ lit-element "^3.1.0"
+ lit-html "^2.1.0"
diff --git a/polygerrit-ui/app/BUILD b/polygerrit-ui/app/BUILD
index 14f1e95..d66c18e 100644
--- a/polygerrit-ui/app/BUILD
+++ b/polygerrit-ui/app/BUILD
@@ -111,14 +111,14 @@
"elements/change/gr-reply-dialog/gr-reply-dialog_html.ts",
"elements/change/gr-reviewer-list/gr-reviewer-list_html.ts",
"elements/change/gr-thread-list/gr-thread-list_html.ts",
- "elements/diff/gr-diff-builder/gr-diff-builder-element_html.ts",
- "elements/diff/gr-diff-host/gr-diff-host_html.ts",
"elements/diff/gr-diff-preferences-dialog/gr-diff-preferences-dialog_html.ts",
- "elements/diff/gr-diff-view/gr-diff-view_html.ts",
- "elements/diff/gr-diff/gr-diff_html.ts",
"elements/gr-app-element_html.ts",
"elements/settings/gr-watched-projects-editor/gr-watched-projects-editor_html.ts",
"elements/shared/gr-account-list/gr-account-list_html.ts",
+ "embed/diff/gr-diff-builder/gr-diff-builder-element_html.ts",
+ "embed/diff/gr-diff-host/gr-diff-host_html.ts",
+ "embed/diff/gr-diff-view/gr-diff-view_html.ts",
+ "embed/diff/gr-diff/gr-diff_html.ts",
"models/dependency.ts",
]
diff --git a/polygerrit-ui/app/api/rest-api.ts b/polygerrit-ui/app/api/rest-api.ts
index 314ea8b..9eb8aa4 100644
--- a/polygerrit-ui/app/api/rest-api.ts
+++ b/polygerrit-ui/app/api/rest-api.ts
@@ -558,7 +558,7 @@
export declare interface ContributorAgreementInfo {
name: string;
description: string;
- url: string;
+ url?: string;
auto_verify_group?: GroupInfo;
}
@@ -1079,7 +1079,7 @@
description?: string;
status: SubmitRequirementStatus;
applicability_expression_result?: SubmitRequirementExpressionInfo;
- submittability_expression_result: SubmitRequirementExpressionInfo;
+ submittability_expression_result?: SubmitRequirementExpressionInfo;
override_expression_result?: SubmitRequirementExpressionInfo;
is_legacy?: boolean;
}
@@ -1092,8 +1092,9 @@
export declare interface SubmitRequirementExpressionInfo {
expression: string;
fulfilled: boolean;
- passing_atoms: string[];
- failing_atoms: string[];
+ passing_atoms?: string[];
+ failing_atoms?: string[];
+ error_message?: string;
}
/**
diff --git a/polygerrit-ui/app/constants/reporting.ts b/polygerrit-ui/app/constants/reporting.ts
index adddf39..cc7ff29 100644
--- a/polygerrit-ui/app/constants/reporting.ts
+++ b/polygerrit-ui/app/constants/reporting.ts
@@ -84,14 +84,12 @@
DIFF_CONTENT = 'Diff Content Render',
// Time to compute and render the syntax highlighting of a diff.
DIFF_SYNTAX = 'Diff Syntax Render',
+ // Time to load diff and prepare before gr-diff rendering begins.
+ DIFF_LOAD = 'Diff Load Render',
// Time to render a batch of rows in the file list. If there are very many files, this may be the first batch of rows that are rendered by default. If there are many files and the user clicks [Show More], this may be the batch of additional files that appear as a result.
FILE_RENDER = 'FileListRenderTime',
- // This measures the same interval as FileListRenderTime, but the result is divided by the number of rows in the batch.
- FILE_RENDER_AVG = 'FileListRenderTimePerFile',
// The time to expand some number of diffs in the file list (i.e. render their diffs, including syntax highlighting).
FILE_EXPAND_ALL = 'ExpandAllDiffs',
- // This measures the same interval as ExpandAllDiffs, but the result is divided by the number of diffs expanded.
- FILE_EXPAND_ALL_AVG = 'ExpandAllPerDiff',
// Time for making the REST API call of creating a draft comment.
DRAFT_CREATE = 'CreateDraftComment',
// Time for making the REST API call of update a draft comment.
diff --git a/polygerrit-ui/app/elements/admin/gr-create-repo-dialog/gr-create-repo-dialog.ts b/polygerrit-ui/app/elements/admin/gr-create-repo-dialog/gr-create-repo-dialog.ts
index ea9495c..4c26f89 100644
--- a/polygerrit-ui/app/elements/admin/gr-create-repo-dialog/gr-create-repo-dialog.ts
+++ b/polygerrit-ui/app/elements/admin/gr-create-repo-dialog/gr-create-repo-dialog.ts
@@ -37,7 +37,7 @@
declare global {
interface HTMLElementEventMap {
- 'text-changed': CustomEvent;
+ 'text-changed': CustomEvent<string>;
'value-changed': CustomEvent;
}
interface HTMLElementTagNameMap {
diff --git a/polygerrit-ui/app/elements/admin/gr-group/gr-group.ts b/polygerrit-ui/app/elements/admin/gr-group/gr-group.ts
index 8faa5e7..6054d9c 100644
--- a/polygerrit-ui/app/elements/admin/gr-group/gr-group.ts
+++ b/polygerrit-ui/app/elements/admin/gr-group/gr-group.ts
@@ -57,7 +57,7 @@
declare global {
interface HTMLElementEventMap {
- 'text-changed': CustomEvent;
+ 'text-changed': CustomEvent<string>;
'value-changed': CustomEvent;
}
interface HTMLElementTagNameMap {
diff --git a/polygerrit-ui/app/elements/admin/gr-repo/gr-repo.ts b/polygerrit-ui/app/elements/admin/gr-repo/gr-repo.ts
index 5fc7bc8..a3f7374 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo/gr-repo.ts
+++ b/polygerrit-ui/app/elements/admin/gr-repo/gr-repo.ts
@@ -246,7 +246,10 @@
)}
.schemes=${this.schemes}
.selectedScheme=${this.selectedScheme}
- @selected-scheme-changed=${this.handleSelectedSchemeValueChanged}
+ @selected-scheme-changed=${(e: BindValueChangeEvent) => {
+ if (this.loading) return;
+ this.selectedScheme = e.detail.value;
+ }}
></gr-download-commands>
</fieldset>
</div>
@@ -1121,12 +1124,7 @@
}
}
- private handleSelectedSchemeValueChanged(e: CustomEvent) {
- if (this.loading) return;
- this.selectedScheme = e.detail.value;
- }
-
- private handleDescriptionTextChanged(e: CustomEvent) {
+ private handleDescriptionTextChanged(e: BindValueChangeEvent) {
if (!this.repoConfig || this.loading) return;
this.repoConfig = {
...this.repoConfig,
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-column-requirement/gr-change-list-column-requirement.ts b/polygerrit-ui/app/elements/change-list/gr-change-list-column-requirement/gr-change-list-column-requirement.ts
new file mode 100644
index 0000000..2a614a7
--- /dev/null
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-column-requirement/gr-change-list-column-requirement.ts
@@ -0,0 +1,164 @@
+/**
+ * @license
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import '../../change/gr-submit-requirement-dashboard-hovercard/gr-submit-requirement-dashboard-hovercard';
+import '../../shared/gr-change-status/gr-change-status';
+import {LitElement, css, html} from 'lit';
+import {customElement, property} from 'lit/decorators';
+import {
+ ApprovalInfo,
+ ChangeInfo,
+ isDetailedLabelInfo,
+ LabelInfo,
+ SubmitRequirementResultInfo,
+ SubmitRequirementStatus,
+} from '../../../api/rest-api';
+import {submitRequirementsStyles} from '../../../styles/gr-submit-requirements-styles';
+import {
+ extractAssociatedLabels,
+ getAllUniqueApprovals,
+ getRequirements,
+ hasNeutralStatus,
+ iconForStatus,
+} from '../../../utils/label-util';
+import {sharedStyles} from '../../../styles/shared-styles';
+
+@customElement('gr-change-list-column-requirement')
+export class GrChangeListColumnRequirement extends LitElement {
+ @property({type: Object})
+ change?: ChangeInfo;
+
+ @property()
+ labelName?: string;
+
+ static override get styles() {
+ return [
+ submitRequirementsStyles,
+ sharedStyles,
+ css`
+ iron-icon {
+ vertical-align: top;
+ }
+ .container {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ }
+ .container.not-applicable {
+ background-color: var(--table-header-background-color);
+ height: calc(var(--line-height-normal) + var(--spacing-m));
+ }
+ `,
+ ];
+ }
+
+ override render() {
+ return html`<div class="container ${this.computeClass()}">
+ ${this.renderContent()}
+ </div>`;
+ }
+
+ private renderContent() {
+ if (!this.labelName) return;
+ const requirements = this.getRequirement(this.labelName);
+ if (requirements.length === 0) return;
+
+ const requirement = requirements[0];
+ if (requirement.status === SubmitRequirementStatus.UNSATISFIED) {
+ return this.renderUnsatisfiedState(requirement);
+ } else {
+ return this.renderStatusIcon(requirement.status);
+ }
+ }
+
+ private renderUnsatisfiedState(requirement: SubmitRequirementResultInfo) {
+ const requirementLabels = extractAssociatedLabels(
+ requirement,
+ 'onlySubmittability'
+ );
+ const allLabels = this.change?.labels ?? {};
+ const associatedLabels = Object.keys(allLabels).filter(label =>
+ requirementLabels.includes(label)
+ );
+
+ let worstVote: ApprovalInfo | undefined;
+ let labelInfo: LabelInfo | undefined;
+ for (const label of associatedLabels) {
+ const votes = this.getSortedVotes(label);
+ if (votes.length === 0) break;
+ // votes are already sorted from worst e.g -2 to best e.g +2
+ if (!worstVote || (worstVote.value ?? 0) > (votes[0].value ?? 0)) {
+ worstVote = votes[0];
+ labelInfo = allLabels[label];
+ }
+ }
+ if (worstVote === undefined) {
+ return this.renderStatusIcon(requirement.status);
+ } else {
+ return html`<gr-vote-chip
+ .vote="${worstVote}"
+ .label="${labelInfo}"
+ ></gr-vote-chip>`;
+ }
+ }
+
+ private renderStatusIcon(status: SubmitRequirementStatus) {
+ const icon = iconForStatus(status ?? SubmitRequirementStatus.ERROR);
+ return html`<iron-icon
+ class="${icon}"
+ icon="gr-icons:${icon}"
+ ></iron-icon>`;
+ }
+
+ private computeClass(): string {
+ if (!this.labelName) return '';
+ const requirements = this.getRequirement(this.labelName);
+ if (requirements.length === 0) {
+ return 'not-applicable';
+ }
+ return '';
+ }
+
+ private getRequirement(labelName: string) {
+ const requirements = getRequirements(this.change).filter(
+ sr => sr.name === labelName
+ );
+ // TODO(milutin): Remove this after migration from legacy requirements.
+ if (requirements.length > 1) {
+ return requirements.filter(sr => !sr.is_legacy);
+ } else {
+ return requirements;
+ }
+ }
+
+ private getSortedVotes(label: string) {
+ const allLabels = this.change?.labels ?? {};
+ const labelInfo = allLabels[label];
+ if (isDetailedLabelInfo(labelInfo)) {
+ return getAllUniqueApprovals(labelInfo)
+ .filter(approval => !hasNeutralStatus(labelInfo, approval))
+ .sort((a, b) => (a.value ?? 0) - (b.value ?? 0));
+ }
+ return [];
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ 'gr-change-list-column-requirement': GrChangeListColumnRequirement;
+ }
+}
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-column-requirement/gr-change-list-column-requirement_test.ts b/polygerrit-ui/app/elements/change-list/gr-change-list-column-requirement/gr-change-list-column-requirement_test.ts
new file mode 100644
index 0000000..1bf4d88
--- /dev/null
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-column-requirement/gr-change-list-column-requirement_test.ts
@@ -0,0 +1,125 @@
+/**
+ * @license
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import '../../../test/common-test-setup-karma';
+import {fixture} from '@open-wc/testing-helpers';
+import {html} from 'lit';
+import './gr-change-list-column-requirement';
+import {GrChangeListColumnRequirement} from './gr-change-list-column-requirement';
+import {
+ createSubmitRequirementExpressionInfo,
+ createSubmitRequirementResultInfo,
+ createNonApplicableSubmitRequirementResultInfo,
+ createChange,
+} from '../../../test/test-data-generators';
+import {
+ AccountId,
+ ChangeInfo,
+ DetailedLabelInfo,
+ SubmitRequirementResultInfo,
+ SubmitRequirementStatus,
+} from '../../../api/rest-api';
+import {StandardLabels} from '../../../utils/label-util';
+import {queryAndAssert, stubFlags} from '../../../test/test-utils';
+
+suite('gr-change-list-column-requirement tests', () => {
+ let element: GrChangeListColumnRequirement;
+ let change: ChangeInfo;
+ setup(() => {
+ stubFlags('isEnabled').returns(true);
+ const submitRequirement: SubmitRequirementResultInfo = {
+ ...createSubmitRequirementResultInfo(),
+ name: StandardLabels.CODE_REVIEW,
+ submittability_expression_result: {
+ ...createSubmitRequirementExpressionInfo(),
+ expression: 'label:Verified=MAX -label:Verified=MIN',
+ },
+ };
+ change = {
+ ...createChange(),
+ submit_requirements: [
+ submitRequirement,
+ createNonApplicableSubmitRequirementResultInfo(),
+ ],
+ unresolved_comment_count: 1,
+ };
+ });
+
+ test('renders', async () => {
+ element = await fixture<GrChangeListColumnRequirement>(
+ html`<gr-change-list-column-requirement
+ .change=${change}
+ .labelName=${StandardLabels.CODE_REVIEW}
+ >
+ </gr-change-list-column-requirement>`
+ );
+ expect(element).shadowDom.to.equal(`<div class="container">
+ <iron-icon
+ class="check-circle-filled"
+ icon="gr-icons:check-circle-filled"
+ >
+ </iron-icon>
+ </div>`);
+ });
+
+ test('show worst vote when state is not satisfied', async () => {
+ const VALUES_2 = {
+ '-2': 'blocking',
+ '-1': 'bad',
+ '0': 'neutral',
+ '+1': 'good',
+ '+2': 'perfect',
+ };
+ const label: DetailedLabelInfo = {
+ values: VALUES_2,
+ all: [
+ {value: -1, _account_id: 777 as AccountId},
+ {value: 1, _account_id: 324 as AccountId},
+ ],
+ };
+ const submitRequirement: SubmitRequirementResultInfo = {
+ ...createSubmitRequirementResultInfo(),
+ name: StandardLabels.CODE_REVIEW,
+ status: SubmitRequirementStatus.UNSATISFIED,
+ submittability_expression_result: {
+ ...createSubmitRequirementExpressionInfo(),
+ expression: 'label:Verified=MAX -label:Verified=MIN',
+ },
+ };
+ change = {
+ ...change,
+ submit_requirements: [submitRequirement],
+ labels: {
+ Verified: label,
+ },
+ };
+ element = await fixture<GrChangeListColumnRequirement>(
+ html`<gr-change-list-column-requirement
+ .change=${change}
+ .labelName=${StandardLabels.CODE_REVIEW}
+ >
+ </gr-change-list-column-requirement>`
+ );
+ expect(element).shadowDom.to.equal(`<div class="container">
+ <gr-vote-chip></gr-vote-chip>
+ </div>`);
+ const voteChip = queryAndAssert(element, 'gr-vote-chip');
+ expect(voteChip).shadowDom.to.equal(`<span class="container">
+ <div class="negative vote-chip">-1</div>
+ </span>`);
+ });
+});
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-column-requirements-summary/gr-change-list-column-requirements-summary_test.ts b/polygerrit-ui/app/elements/change-list/gr-change-list-column-requirements-summary/gr-change-list-column-requirements-summary_test.ts
index c8f5813..9ce0da8 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-column-requirements-summary/gr-change-list-column-requirements-summary_test.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-column-requirements-summary/gr-change-list-column-requirements-summary_test.ts
@@ -26,6 +26,7 @@
createParsedChange,
createSubmitRequirementExpressionInfo,
createSubmitRequirementResultInfo,
+ createNonApplicableSubmitRequirementResultInfo,
} from '../../../test/test-data-generators';
import {
SubmitRequirementResultInfo,
@@ -48,7 +49,10 @@
};
change = {
...createParsedChange(),
- submit_requirements: [submitRequirement],
+ submit_requirements: [
+ submitRequirement,
+ createNonApplicableSubmitRequirementResultInfo(),
+ ],
labels: {
Verified: {
...createDetailedLabelInfo(),
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.ts b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.ts
index 9e16fc0..73b043a 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.ts
@@ -25,6 +25,7 @@
import '../../plugins/gr-endpoint-decorator/gr-endpoint-decorator';
import '../../plugins/gr-endpoint-param/gr-endpoint-param';
import '../gr-change-list-column-requirements-summary/gr-change-list-column-requirements-summary';
+import '../gr-change-list-column-requirement/gr-change-list-column-requirement';
import '../../shared/gr-tooltip-content/gr-tooltip-content';
import {GerritNav} from '../../core/gr-navigation/gr-navigation';
import {getDisplayName} from '../../../utils/display-name-util';
@@ -44,17 +45,14 @@
} from '../../../types/common';
import {hasOwnProperty} from '../../../utils/common-util';
import {pluralize} from '../../../utils/string-util';
-import {
- getRequirements,
- iconForStatus,
- showNewSubmitRequirements,
-} from '../../../utils/label-util';
+import {showNewSubmitRequirements} from '../../../utils/label-util';
import {changeListStyles} from '../../../styles/gr-change-list-styles';
import {sharedStyles} from '../../../styles/shared-styles';
import {LitElement, css, html} from 'lit';
import {customElement, property, state} from 'lit/decorators';
import {submitRequirementsStyles} from '../../../styles/gr-submit-requirements-styles';
import {ifDefined} from 'lit/directives/if-defined';
+import {KnownExperimentId} from '../../../services/flags/flags';
enum ChangeSize {
XS = 10,
@@ -250,12 +248,20 @@
.placeholder {
color: var(--deemphasized-text-color);
}
+ .cell.selection input {
+ vertical-align: middle;
+ }
.cell.label {
font-weight: var(--font-weight-normal);
}
.cell.label iron-icon {
vertical-align: top;
}
+ /* Requirement child needs whole area */
+ .cell.requirement {
+ padding: 0;
+ margin: 0;
+ }
@media only screen and (max-width: 50em) {
:host {
display: flex;
@@ -269,13 +275,14 @@
const changeUrl = this.computeChangeURL();
return html`
<td aria-hidden="true" class="cell leftPadding"></td>
- ${this.renderCellStar()} ${this.renderCellNumber(changeUrl)}
- ${this.renderCellSubject(changeUrl)} ${this.renderCellStatus()}
- ${this.renderCellOwner()} ${this.renderCellReviewers()}
- ${this.renderCellComments()} ${this.renderCellRepo()}
- ${this.renderCellBranch()} ${this.renderCellUpdated()}
- ${this.renderCellSubmitted()} ${this.renderCellWaiting()}
- ${this.renderCellSize()} ${this.renderCellRequirements()}
+ ${this.renderCellSelectionBox()} ${this.renderCellStar()}
+ ${this.renderCellNumber(changeUrl)} ${this.renderCellSubject(changeUrl)}
+ ${this.renderCellStatus()} ${this.renderCellOwner()}
+ ${this.renderCellReviewers()} ${this.renderCellComments()}
+ ${this.renderCellRepo()} ${this.renderCellBranch()}
+ ${this.renderCellUpdated()} ${this.renderCellSubmitted()}
+ ${this.renderCellWaiting()} ${this.renderCellSize()}
+ ${this.renderCellRequirements()}
${this.labelNames?.map(labelNames => this.renderChangeLabels(labelNames))}
${this.dynamicCellEndpoints?.map(pluginEndpointName =>
this.renderChangePluginEndpoint(pluginEndpointName)
@@ -283,6 +290,15 @@
`;
}
+ private renderCellSelectionBox() {
+ if (!this.flagsService.isEnabled(KnownExperimentId.BULK_ACTIONS)) return;
+ return html`
+ <td class="cell selection">
+ <input type="checkbox" />
+ </td>
+ `;
+ }
+
private renderCellStar() {
if (!this.showStar) return;
@@ -383,7 +399,6 @@
return html`
<gr-account-link
hideAvatar
- hideStatus
firstName
highlightAttention
.change=${this.change}
@@ -536,6 +551,15 @@
}
private renderChangeLabels(labelName: string) {
+ if (showNewSubmitRequirements(this.flagsService, this.change)) {
+ return html` <td class="cell label requirement">
+ <gr-change-list-column-requirement
+ .change=${this.change}
+ .labelName=${labelName}
+ >
+ </gr-change-list-column-requirement>
+ </td>`;
+ }
return html`
<td
title="${this.computeLabelTitle(labelName)}"
@@ -547,22 +571,6 @@
}
private renderChangeHasLabelIcon(labelName: string) {
- if (showNewSubmitRequirements(this.flagsService, this.change)) {
- let requirements = getRequirements(this.change).filter(
- sr => sr.name === labelName
- );
- // TODO(milutin): Remove this after migration from legacy requirements.
- if (requirements.length > 1) {
- requirements = requirements.filter(sr => !sr.is_legacy);
- }
- if (requirements.length === 1) {
- const icon = iconForStatus(requirements[0].status);
- return html`<iron-icon
- class="${icon}"
- icon="gr-icons:${icon}"
- ></iron-icon>`;
- }
- }
if (this.computeLabelIcon(labelName) === '')
return html`<span>${this.computeLabelValue(labelName)}</span>`;
@@ -618,16 +626,6 @@
// private but used in test
computeLabelClass(labelName: string) {
const classes = ['cell', 'label'];
- if (showNewSubmitRequirements(this.flagsService, this.change)) {
- const requirements = getRequirements(this.change).filter(
- sr => sr.name === labelName
- );
- if (requirements.length === 1) {
- classes.push('requirement');
- // Do not add label category classes.
- return classes.sort().join(' ');
- }
- }
const category = this.computeLabelCategory(labelName);
switch (category) {
case LabelCategory.NOT_APPLICABLE:
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_test.ts b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_test.ts
index 8932d52..cd43151 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_test.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_test.ts
@@ -25,8 +25,14 @@
createChange,
createSubmitRequirementExpressionInfo,
createSubmitRequirementResultInfo,
+ createNonApplicableSubmitRequirementResultInfo,
} from '../../../test/test-data-generators';
-import {query, queryAndAssert, stubRestApi} from '../../../test/test-utils';
+import {
+ query,
+ queryAndAssert,
+ stubRestApi,
+ stubFlags,
+} from '../../../test/test-utils';
import {
AccountId,
BranchName,
@@ -287,6 +293,14 @@
}
});
+ test('selection checkbox is only shown if experiment is enabled', async () => {
+ assert.isNotOk(query(element, '.selection'));
+ stubFlags('isEnabled').returns(true);
+ element = basicFixture.instantiate();
+ await element.updateComplete;
+ assert.isOk(query(element, '.selection'));
+ });
+
test('repo column hidden', async () => {
element.visibleChangeTableColumns = [
'Subject',
@@ -483,7 +497,10 @@
};
const change: ChangeInfo = {
...createChange(),
- submit_requirements: [submitRequirement],
+ submit_requirements: [
+ submitRequirement,
+ createNonApplicableSubmitRequirementResultInfo(),
+ ],
unresolved_comment_count: 1,
};
const element = await fixture<GrChangeListItem>(
@@ -494,9 +511,7 @@
);
const requirement = queryAndAssert(element, '.requirement');
- expect(requirement).dom.to.equal(`<iron-icon
- class="check-circle-filled"
- icon="gr-icons:check-circle-filled">
- </iron-icon>`);
+ expect(requirement).dom.to.equal(`<gr-change-list-column-requirement>
+ </gr-change-list-column-requirement>`);
});
});
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.ts b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.ts
index f276260..f8b1a2b 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.ts
@@ -252,6 +252,11 @@
`;
}
+ private renderSelectionHeader() {
+ if (!this.flagsService.isEnabled(KnownExperimentId.BULK_ACTIONS)) return;
+ return html`<td aria-hidden="true" class="selection"></td>`;
+ }
+
private renderSectionHeader(
changeSection: ChangeListSection,
labelNames: string[]
@@ -262,6 +267,7 @@
<tbody>
<tr class="groupHeader">
<td aria-hidden="true" class="leftPadding"></td>
+ ${this.renderSelectionHeader()}
<td aria-hidden="true" class="star" ?hidden=${!this.showStar}></td>
<td
class="cell"
@@ -317,6 +323,7 @@
return html`
<tr class="groupTitle">
<td class="leftPadding" aria-hidden="true"></td>
+ ${this.renderSelectionHeader()}
<td
class="star"
aria-label="Star status column"
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_test.ts b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_test.ts
index 50708c0..29204b6 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_test.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_test.ts
@@ -292,6 +292,41 @@
});
});
+ test('selection checkbox is only shown if experiment is enabled', async () => {
+ function propertiesSetup(element: GrChangeList) {
+ element.sections = [{results: [{...createChange()}]}];
+ element.account = {_account_id: 1001 as AccountId};
+ element.preferences = {
+ legacycid_in_change_table: true,
+ time_format: TimeFormat.HHMM_12,
+ change_table: [
+ 'Subject',
+ 'Status',
+ 'Owner',
+ 'Reviewers',
+ 'Comments',
+ 'Repo',
+ 'Branch',
+ 'Updated',
+ 'Size',
+ ' Status ',
+ ],
+ };
+ element.config = createServerInfo();
+ }
+
+ element = basicFixture.instantiate();
+ propertiesSetup(element);
+ await element.updateComplete;
+ assert.isNotOk(query(element, '.selection'));
+
+ stubFlags('isEnabled').returns(true);
+ element = basicFixture.instantiate();
+ propertiesSetup(element);
+ await element.updateComplete;
+ assert.isOk(query(element, '.selection'));
+ });
+
suite('empty column preference', () => {
let element: GrChangeList;
diff --git a/polygerrit-ui/app/elements/change-list/gr-create-destination-dialog/gr-create-destination-dialog.ts b/polygerrit-ui/app/elements/change-list/gr-create-destination-dialog/gr-create-destination-dialog.ts
index ff9666b..c686d70 100644
--- a/polygerrit-ui/app/elements/change-list/gr-create-destination-dialog/gr-create-destination-dialog.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-create-destination-dialog/gr-create-destination-dialog.ts
@@ -18,73 +18,91 @@
import '../../shared/gr-dialog/gr-dialog';
import '../../shared/gr-overlay/gr-overlay';
import '../../shared/gr-repo-branch-picker/gr-repo-branch-picker';
-import {PolymerElement} from '@polymer/polymer/polymer-element';
-import {htmlTemplate} from './gr-create-destination-dialog_html';
-import {customElement, property} from '@polymer/decorators';
import {GrOverlay} from '../../shared/gr-overlay/gr-overlay';
import {RepoName, BranchName} from '../../../types/common';
+import {sharedStyles} from '../../../styles/shared-styles';
+import {LitElement, html} from 'lit';
+import {customElement, state, query} from 'lit/decorators';
+import {assertIsDefined} from '../../../utils/common-util';
+import {BindValueChangeEvent} from '../../../types/events';
export interface CreateDestinationConfirmDetail {
repo?: RepoName;
branch?: BranchName;
}
-/**
- * Fired when a destination has been picked. Event details contain the repo
- * name and the branch name.
- *
- * @event confirm
- */
-export interface GrCreateDestinationDialog {
- $: {
- createOverlay: GrOverlay;
- };
-}
-
@customElement('gr-create-destination-dialog')
-export class GrCreateDestinationDialog extends PolymerElement {
- static get template() {
- return htmlTemplate;
+export class GrCreateDestinationDialog extends LitElement {
+ /**
+ * Fired when a destination has been picked. Event details contain the repo
+ * name and the branch name.
+ *
+ * @event confirm
+ */
+
+ @query('#createOverlay') private createOverlay?: GrOverlay;
+
+ @state() private repo?: RepoName;
+
+ @state() private branch?: BranchName;
+
+ static override get styles() {
+ return [sharedStyles];
}
- @property({type: String})
- _repo?: RepoName;
-
- @property({type: String})
- _branch?: BranchName;
-
- @property({
- type: Boolean,
- computed: '_computeRepoAndBranchSelected(_repo, _branch)',
- })
- _repoAndBranchSelected = false;
+ override render() {
+ return html`
+ <gr-overlay id="createOverlay" with-backdrop>
+ <gr-dialog
+ confirm-label="View commands"
+ @confirm=${this.pickerConfirm}
+ @cancel=${() => {
+ assertIsDefined(this.createOverlay, 'createOverlay');
+ this.createOverlay.close();
+ }}
+ ?disabled=${!(this.repo && this.branch)}
+ >
+ <div class="header" slot="header">Create change</div>
+ <div class="main" slot="main">
+ <gr-repo-branch-picker
+ .repo=${this.repo}
+ .branch=${this.branch}
+ @repo-changed=${(e: BindValueChangeEvent) => {
+ this.repo = e.detail.value as RepoName;
+ }}
+ @branch-changed=${(e: BindValueChangeEvent) => {
+ this.branch = e.detail.value as BranchName;
+ }}
+ ></gr-repo-branch-picker>
+ <p>
+ If you haven't done so, you will need to clone the repository.
+ </p>
+ </div>
+ </gr-dialog>
+ </gr-overlay>
+ `;
+ }
open() {
- this._repo = '' as RepoName;
- this._branch = '' as BranchName;
- this.$.createOverlay.open();
+ assertIsDefined(this.createOverlay, 'createOverlay');
+ this.repo = '' as RepoName;
+ this.branch = '' as BranchName;
+ this.createOverlay.open();
}
- _handleClose() {
- this.$.createOverlay.close();
- }
-
- _pickerConfirm(e: Event) {
- this.$.createOverlay.close();
+ private pickerConfirm = (e: Event) => {
+ assertIsDefined(this.createOverlay, 'createOverlay');
+ this.createOverlay.close();
const detail: CreateDestinationConfirmDetail = {
- repo: this._repo,
- branch: this._branch,
+ repo: this.repo,
+ branch: this.branch,
};
// e is a 'confirm' event from gr-dialog. We want to fire a more detailed
// 'confirm' event here, so let's stop propagation of the bare event.
e.preventDefault();
e.stopPropagation();
this.dispatchEvent(new CustomEvent('confirm', {detail, bubbles: false}));
- }
-
- _computeRepoAndBranchSelected(repo?: RepoName, branch?: BranchName) {
- return !!(repo && branch);
- }
+ };
}
declare global {
diff --git a/polygerrit-ui/app/elements/change-list/gr-create-destination-dialog/gr-create-destination-dialog_html.ts b/polygerrit-ui/app/elements/change-list/gr-create-destination-dialog/gr-create-destination-dialog_html.ts
deleted file mode 100644
index 0aed75b..0000000
--- a/polygerrit-ui/app/elements/change-list/gr-create-destination-dialog/gr-create-destination-dialog_html.ts
+++ /dev/null
@@ -1,38 +0,0 @@
-/**
- * @license
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import {html} from '@polymer/polymer/lib/utils/html-tag';
-
-export const htmlTemplate = html`
- <style include="shared-styles"></style>
- <gr-overlay id="createOverlay" with-backdrop="">
- <gr-dialog
- confirm-label="View commands"
- on-confirm="_pickerConfirm"
- on-cancel="_handleClose"
- disabled="[[!_repoAndBranchSelected]]"
- >
- <div class="header" slot="header">Create change</div>
- <div class="main" slot="main">
- <gr-repo-branch-picker
- repo="{{_repo}}"
- branch="{{_branch}}"
- ></gr-repo-branch-picker>
- <p>If you haven't done so, you will need to clone the repository.</p>
- </div>
- </gr-dialog>
- </gr-overlay>
-`;
diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.ts b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.ts
index c0bbb17..9412a66 100644
--- a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.ts
@@ -46,7 +46,7 @@
GpgKeyInfoStatus,
SubmitType,
} from '../../../constants/constants';
-import {changeIsOpen} from '../../../utils/change-util';
+import {changeIsOpen, isOwner} from '../../../utils/change-util';
import {customElement, property, observe} from '@polymer/decorators';
import {
AccountDetailInfo,
@@ -304,6 +304,14 @@
return !hasTopic && !settingTopic && topicReadOnly === false;
}
+ _showTopic(
+ changeRecord?: ElementPropertyDeepChange<GrChangeMetadata, 'change'>,
+ topicReadOnly?: boolean
+ ) {
+ const hasTopic = !!changeRecord?.base?.topic;
+ return hasTopic || !topicReadOnly;
+ }
+
_showTopicChip(
changeRecord?: ElementPropertyDeepChange<GrChangeMetadata, 'change'>,
settingTopic?: boolean
@@ -517,8 +525,21 @@
_computeDisplayState(
showAllSections: boolean,
change: ParsedChangeInfo | undefined,
- section: Metadata
+ section: Metadata,
+ account?: AccountDetailInfo
) {
+ // special case for Topic - show always for owners, others when set
+ if (section === Metadata.TOPIC) {
+ if (
+ showAllSections ||
+ isOwner(change, account) ||
+ isSectionSet(section, change)
+ ) {
+ return '';
+ } else {
+ return 'hideDisplay';
+ }
+ }
if (
showAllSections ||
DisplayRules.ALWAYS_SHOW.includes(section) ||
diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_html.ts b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_html.ts
index 7ca34d2..d5f63c9 100644
--- a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_html.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_html.ts
@@ -379,39 +379,44 @@
</span>
</section>
</template>
- <section
- class$="topic [[_computeDisplayState(_showAllSections, change, _SECTION.TOPIC)]]"
- >
- <span class="title">Topic</span>
- <span class="value">
- <template is="dom-if" if="[[_showTopicChip(change.*, _settingTopic)]]">
- <gr-linked-chip
- text="[[change.topic]]"
- limit="40"
- href="[[_computeTopicUrl(change.topic)]]"
- removable="[[!_topicReadOnly]]"
- on-remove="_handleTopicRemoved"
- ></gr-linked-chip>
- </template>
- <template
- is="dom-if"
- if="[[_showAddTopic(change.*, _settingTopic, _topicReadOnly)]]"
- >
- <gr-editable-label
- class="topicEditableLabel"
- label-text="Add a topic"
- value="[[change.topic]]"
- max-length="1024"
- placeholder="[[_computeTopicPlaceholder(_topicReadOnly)]]"
- read-only="[[_topicReadOnly]]"
- on-changed="_handleTopicChanged"
- show-as-edit-pencil="true"
- autocomplete="true"
- query="[[queryTopic]]"
- ></gr-editable-label>
- </template>
- </span>
- </section>
+ <template is="dom-if" if="[[_showTopic(change.*, _topicReadOnly)]]">
+ <section
+ class$="topic [[_computeDisplayState(_showAllSections, change, _SECTION.TOPIC, account)]]"
+ >
+ <span class="title">Topic</span>
+ <span class="value">
+ <template
+ is="dom-if"
+ if="[[_showTopicChip(change.*, _settingTopic)]]"
+ >
+ <gr-linked-chip
+ text="[[change.topic]]"
+ limit="40"
+ href="[[_computeTopicUrl(change.topic)]]"
+ removable="[[!_topicReadOnly]]"
+ on-remove="_handleTopicRemoved"
+ ></gr-linked-chip>
+ </template>
+ <template
+ is="dom-if"
+ if="[[_showAddTopic(change.*, _settingTopic, _topicReadOnly)]]"
+ >
+ <gr-editable-label
+ class="topicEditableLabel"
+ label-text="Add a topic"
+ value="[[change.topic]]"
+ max-length="1024"
+ placeholder="[[_computeTopicPlaceholder(_topicReadOnly)]]"
+ read-only="[[_topicReadOnly]]"
+ on-changed="_handleTopicChanged"
+ show-as-edit-pencil="true"
+ autocomplete="true"
+ query="[[queryTopic]]"
+ ></gr-editable-label>
+ </template>
+ </span>
+ </section>
+ </template>
<template is="dom-if" if="[[_showCherryPickOf(change.*)]]">
<section
class$="[[_computeDisplayState(_showAllSections, change, _SECTION.CHERRY_PICK_OF)]]"
diff --git a/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog.ts b/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog.ts
index 92f4a87..a72749b 100644
--- a/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog.ts
+++ b/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog.ts
@@ -14,46 +14,37 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import '../../../styles/gr-font-styles';
-import '../../../styles/shared-styles';
import '../../shared/gr-download-commands/gr-download-commands';
-import {PolymerElement} from '@polymer/polymer/polymer-element';
-import {htmlTemplate} from './gr-download-dialog_html';
import {changeBaseURL, getRevisionKey} from '../../../utils/change-util';
-import {customElement, property, computed, observe} from '@polymer/decorators';
-import {
- ChangeInfo,
- DownloadInfo,
- PatchSetNum,
- RevisionInfo,
-} from '../../../types/common';
+import {ChangeInfo, DownloadInfo, PatchSetNum} from '../../../types/common';
import {GrDownloadCommands} from '../../shared/gr-download-commands/gr-download-commands';
import {GrButton} from '../../shared/gr-button/gr-button';
-import {hasOwnProperty} from '../../../utils/common-util';
+import {hasOwnProperty, queryAndAssert} from '../../../utils/common-util';
import {GrOverlayStops} from '../../shared/gr-overlay/gr-overlay';
import {fireAlert, fireEvent} from '../../../utils/event-util';
import {addShortcut} from '../../../utils/dom-util';
-
-export interface GrDownloadDialog {
- $: {
- download: HTMLAnchorElement;
- downloadCommands: GrDownloadCommands;
- closeButton: GrButton;
- };
-}
+import {fontStyles} from '../../../styles/gr-font-styles';
+import {sharedStyles} from '../../../styles/shared-styles';
+import {LitElement, PropertyValues, html, css} from 'lit';
+import {customElement, property, state, query} from 'lit/decorators';
+import {assertIsDefined} from '../../../utils/common-util';
+import {PaperTabsElement} from '@polymer/paper-tabs/paper-tabs';
+import {BindValueChangeEvent} from '../../../types/events';
@customElement('gr-download-dialog')
-export class GrDownloadDialog extends PolymerElement {
- static get template() {
- return htmlTemplate;
- }
-
+export class GrDownloadDialog extends LitElement {
/**
* Fired when the user presses the close button.
*
* @event close
*/
+ @query('#download') protected download?: HTMLAnchorElement;
+
+ @query('#downloadCommands') protected downloadCommands?: GrDownloadCommands;
+
+ @query('#closeButton') protected closeButton?: GrButton;
+
@property({type: Object})
change: ChangeInfo | undefined;
@@ -63,8 +54,7 @@
@property({type: String})
patchNum: PatchSetNum | undefined;
- @property({type: String})
- _selectedScheme?: string;
+ @state() private selectedScheme?: string;
/** Called in disconnectedCallback. */
private cleanups: (() => void)[] = [];
@@ -79,14 +69,159 @@
super.connectedCallback();
for (const key of ['1', '2', '3', '4', '5']) {
this.cleanups.push(
- addShortcut(this, {key}, e => this._handleNumberKey(e))
+ addShortcut(this, {key}, e => this.handleNumberKey(e))
);
}
}
- @computed('change', 'patchNum')
- get _schemes() {
- // Polymer 2: check for undefined
+ static override get styles() {
+ return [
+ fontStyles,
+ sharedStyles,
+ css`
+ :host {
+ display: block;
+ padding: var(--spacing-m) 0;
+ }
+ section {
+ display: flex;
+ padding: var(--spacing-m) var(--spacing-xl);
+ }
+ .flexContainer {
+ display: flex;
+ justify-content: space-between;
+ padding-top: var(--spacing-m);
+ }
+ .footer {
+ justify-content: flex-end;
+ }
+ .closeButtonContainer {
+ align-items: flex-end;
+ display: flex;
+ flex: 0;
+ justify-content: flex-end;
+ }
+ .patchFiles,
+ .archivesContainer {
+ padding-bottom: var(--spacing-m);
+ }
+ .patchFiles {
+ margin-right: var(--spacing-xxl);
+ }
+ .patchFiles a,
+ .archives a {
+ display: inline-block;
+ margin-right: var(--spacing-l);
+ }
+ .patchFiles a:last-of-type,
+ .archives a:last-of-type {
+ margin-right: 0;
+ }
+ gr-download-commands {
+ width: min(80vw, 1200px);
+ }
+ `,
+ ];
+ }
+
+ override render() {
+ const revisions = this.change?.revisions;
+ return html`
+ <section>
+ <h3 class="heading-3">
+ Patch set ${this.patchNum} of
+ ${revisions ? Object.keys(revisions).length : 0}
+ </h3>
+ </section>
+ ${this.renderDownloadCommands()}
+ <section class="flexContainer">
+ ${this.renderPatchFiles()} ${this.renderArchives()}
+ </section>
+ <section class="footer">
+ <span class="closeButtonContainer">
+ <gr-button
+ id="closeButton"
+ link
+ @click=${(e: Event) => {
+ this.handleCloseTap(e);
+ }}
+ >Close</gr-button
+ >
+ </span>
+ </section>
+ `;
+ }
+
+ private renderDownloadCommands() {
+ if (!this.schemes.length) return;
+
+ return html`
+ <section>
+ <gr-download-commands
+ id="downloadCommands"
+ .commands=${this.computeDownloadCommands()}
+ .schemes=${this.schemes}
+ .selectedScheme=${this.selectedScheme}
+ show-keyboard-shortcut-tooltips
+ @selected-scheme-changed=${(e: BindValueChangeEvent) => {
+ this.selectedScheme = e.detail.value;
+ }}
+ ></gr-download-commands>
+ </section>
+ `;
+ }
+
+ private renderPatchFiles() {
+ if (this.computeHidePatchFile()) return;
+
+ return html`
+ <div class="patchFiles">
+ <label>Patch file</label>
+ <div>
+ <a id="download" .href="${this.computeDownloadLink()}" download>
+ ${this.computeDownloadFilename()}
+ </a>
+ <a .href="${this.computeDownloadLink(true)}" download>
+ ${this.computeDownloadFilename(true)}
+ </a>
+ </div>
+ </div>
+ `;
+ }
+
+ private renderArchives() {
+ if (!this.config?.archives.length) return;
+
+ return html`
+ <div class="archivesContainer">
+ <label>Archive</label>
+ <div id="archives" class="archives">
+ ${this.config.archives.map(format => this.renderArchivesLink(format))}
+ </div>
+ </div>
+ `;
+ }
+
+ private renderArchivesLink(format: string) {
+ return html`
+ <a .href=${this.computeArchiveDownloadLink(format)} download>
+ ${format}
+ </a>
+ `;
+ }
+
+ override firstUpdated(changedProperties: PropertyValues) {
+ super.firstUpdated(changedProperties);
+ if (!this.getAttribute('role')) this.setAttribute('role', 'dialog');
+ }
+
+ override willUpdate(changedProperties: PropertyValues) {
+ if (changedProperties.has('schemes')) {
+ this.schemesChanged();
+ }
+ }
+
+ get schemes() {
if (this.change === undefined || this.patchNum === undefined) {
return [];
}
@@ -103,13 +238,9 @@
return [];
}
- _handleNumberKey(e: KeyboardEvent) {
+ private handleNumberKey(e: KeyboardEvent) {
const index = Number(e.key) - 1;
- const commands = this._computeDownloadCommands(
- this.change,
- this.patchNum,
- this._selectedScheme
- );
+ const commands = this.computeDownloadCommands();
if (index > commands.length) return;
navigator.clipboard.writeText(commands[index].command).then(() => {
fireAlert(this, `${commands[index].title} command copied to clipboard`);
@@ -117,41 +248,40 @@
});
}
- override ready() {
- super.ready();
- this._ensureAttribute('role', 'dialog');
- }
-
override focus() {
- if (this._schemes.length) {
- this.$.downloadCommands.focusOnCopy();
+ if (this.schemes.length) {
+ assertIsDefined(this.downloadCommands, 'downloadCommands');
+ this.downloadCommands.focusOnCopy();
} else {
- this.$.download.focus();
+ assertIsDefined(this.download, 'download');
+ this.download.focus();
}
}
getFocusStops(): GrOverlayStops {
+ assertIsDefined(this.downloadCommands, 'downloadCommands');
+ assertIsDefined(this.closeButton, 'closeButton');
+ const downloadTabs = queryAndAssert<PaperTabsElement>(
+ this.downloadCommands,
+ '#downloadTabs'
+ );
return {
- start: this.$.downloadCommands.$.downloadTabs,
- end: this.$.closeButton,
+ start: downloadTabs,
+ end: this.closeButton,
};
}
- _computeDownloadCommands(
- change?: ChangeInfo,
- patchNum?: PatchSetNum,
- selectedScheme?: string
- ) {
+ private computeDownloadCommands() {
let commandObj;
- if (!change || !selectedScheme) return [];
- for (const rev of Object.values(change.revisions || {})) {
+ if (!this.change || !this.selectedScheme) return [];
+ for (const rev of Object.values(this.change.revisions || {})) {
if (
- rev._number === patchNum &&
+ rev._number === this.patchNum &&
rev &&
rev.fetch &&
- hasOwnProperty(rev.fetch, selectedScheme)
+ hasOwnProperty(rev.fetch, this.selectedScheme)
) {
- commandObj = rev.fetch[selectedScheme].commands;
+ commandObj = rev.fetch[this.selectedScheme].commands;
break;
}
}
@@ -162,53 +292,35 @@
return commands;
}
- _computeZipDownloadLink(change?: ChangeInfo, patchNum?: PatchSetNum) {
- return this._computeDownloadLink(change, patchNum, true);
- }
-
- _computeZipDownloadFilename(change?: ChangeInfo, patchNum?: PatchSetNum) {
- return this._computeDownloadFilename(change, patchNum, true);
- }
-
- _computeDownloadLink(
- change?: ChangeInfo,
- patchNum?: PatchSetNum,
- zip?: boolean
- ) {
- // Polymer 2: check for undefined
- if (change === undefined || patchNum === undefined) {
+ private computeDownloadLink(zip?: boolean) {
+ if (this.change === undefined || this.patchNum === undefined) {
return '';
}
return (
- changeBaseURL(change.project, change._number, patchNum) +
+ changeBaseURL(this.change.project, this.change._number, this.patchNum) +
'/patch?' +
(zip ? 'zip' : 'download')
);
}
- _computeDownloadFilename(
- change?: ChangeInfo,
- patchNum?: PatchSetNum,
- zip?: boolean
- ) {
- // Polymer 2: check for undefined
- if (change === undefined || patchNum === undefined) {
+ private computeDownloadFilename(zip?: boolean) {
+ if (this.change === undefined || this.patchNum === undefined) {
return '';
}
- const rev = getRevisionKey(change, patchNum) ?? '';
+ const rev = getRevisionKey(this.change, this.patchNum) ?? '';
const shortRev = rev.substr(0, 7);
return shortRev + '.diff.' + (zip ? 'zip' : 'base64');
}
- _computeHidePatchFile(change?: ChangeInfo, patchNum?: PatchSetNum) {
- // Polymer 2: check for undefined
- if (change === undefined || patchNum === undefined) {
+ // private but used in test
+ computeHidePatchFile() {
+ if (this.change === undefined || this.patchNum === undefined) {
return false;
}
- for (const rev of Object.values(change.revisions || {})) {
- if (rev._number === patchNum) {
+ for (const rev of Object.values(this.change.revisions || {})) {
+ if (rev._number === this.patchNum) {
const parentLength =
rev.commit && rev.commit.parents ? rev.commit.parents.length : 0;
return parentLength === 0 || parentLength > 1;
@@ -217,52 +329,36 @@
return false;
}
- _computeArchiveDownloadLink(
- change?: ChangeInfo,
- patchNum?: PatchSetNum,
- format?: string
- ) {
- // Polymer 2: check for undefined
+ // private but used in test
+ computeArchiveDownloadLink(format?: string) {
if (
- change === undefined ||
- patchNum === undefined ||
+ this.change === undefined ||
+ this.patchNum === undefined ||
format === undefined
) {
return '';
}
return (
- changeBaseURL(change.project, change._number, patchNum) +
+ changeBaseURL(this.change.project, this.change._number, this.patchNum) +
'/archive?format=' +
format
);
}
- _computePatchSetQuantity(revisions?: {[revisionId: string]: RevisionInfo}) {
- if (!revisions) {
- return 0;
- }
- return Object.keys(revisions).length;
- }
-
- _handleCloseTap(e: Event) {
+ private handleCloseTap(e: Event) {
e.preventDefault();
e.stopPropagation();
fireEvent(this, 'close');
}
- @observe('_schemes')
- _schemesChanged(schemes: string[]) {
- if (schemes.length === 0) {
+ private schemesChanged() {
+ if (this.schemes.length === 0) {
return;
}
- if (!this._selectedScheme || !schemes.includes(this._selectedScheme)) {
- this._selectedScheme = schemes.sort()[0];
+ if (!this.selectedScheme || !this.schemes.includes(this.selectedScheme)) {
+ this.selectedScheme = this.schemes.sort()[0];
}
}
-
- _computeShowDownloadCommands(schemes: string[]) {
- return schemes.length ? '' : 'hidden';
- }
}
declare global {
diff --git a/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog_html.ts b/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog_html.ts
deleted file mode 100644
index 097fb0e..0000000
--- a/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog_html.ts
+++ /dev/null
@@ -1,127 +0,0 @@
-/**
- * @license
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import {html} from '@polymer/polymer/lib/utils/html-tag';
-
-export const htmlTemplate = html`
- <style include="gr-font-styles">
- /* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
- </style>
- <style include="shared-styles">
- :host {
- display: block;
- padding: var(--spacing-m) 0;
- }
- section {
- display: flex;
- padding: var(--spacing-m) var(--spacing-xl);
- }
- .flexContainer {
- display: flex;
- justify-content: space-between;
- padding-top: var(--spacing-m);
- }
- .footer {
- justify-content: flex-end;
- }
- .closeButtonContainer {
- align-items: flex-end;
- display: flex;
- flex: 0;
- justify-content: flex-end;
- }
- .patchFiles,
- .archivesContainer {
- padding-bottom: var(--spacing-m);
- }
- .patchFiles {
- margin-right: var(--spacing-xxl);
- }
- .patchFiles a,
- .archives a {
- display: inline-block;
- margin-right: var(--spacing-l);
- }
- .patchFiles a:last-of-type,
- .archives a:last-of-type {
- margin-right: 0;
- }
- .hidden {
- display: none;
- }
- gr-download-commands {
- width: min(80vw, 1200px);
- }
- </style>
- <section>
- <h3 class="heading-3">
- Patch set [[patchNum]] of [[_computePatchSetQuantity(change.revisions)]]
- </h3>
- </section>
- <section class$="[[_computeShowDownloadCommands(_schemes)]]">
- <gr-download-commands
- id="downloadCommands"
- commands="[[_computeDownloadCommands(change, patchNum, _selectedScheme)]]"
- schemes="[[_schemes]]"
- selected-scheme="{{_selectedScheme}}"
- show-keyboard-shortcut-tooltips
- ></gr-download-commands>
- </section>
- <section class="flexContainer">
- <div
- class="patchFiles"
- hidden="[[_computeHidePatchFile(change, patchNum)]]"
- >
- <label>Patch file</label>
- <div>
- <a
- id="download"
- href$="[[_computeDownloadLink(change, patchNum)]]"
- download=""
- >
- [[_computeDownloadFilename(change, patchNum)]]
- </a>
- <a href$="[[_computeZipDownloadLink(change, patchNum)]]" download="">
- [[_computeZipDownloadFilename(change, patchNum)]]
- </a>
- </div>
- </div>
- <div
- class="archivesContainer"
- hidden$="[[!config.archives.length]]"
- hidden=""
- >
- <label>Archive</label>
- <div id="archives" class="archives">
- <template is="dom-repeat" items="[[config.archives]]" as="format">
- <a
- href$="[[_computeArchiveDownloadLink(change, patchNum, format)]]"
- download=""
- >
- [[format]]
- </a>
- </template>
- </div>
- </div>
- </section>
- <section class="footer">
- <span class="closeButtonContainer">
- <gr-button id="closeButton" link="" on-click="_handleCloseTap"
- >Close</gr-button
- >
- </span>
- </section>
-`;
diff --git a/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog_test.ts b/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog_test.ts
index f61bb68..4c5a3ee 100644
--- a/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog_test.ts
@@ -22,7 +22,6 @@
createCommit,
createDownloadInfo,
createRevision,
- createRevisions,
} from '../../../test/test-data-generators';
import {
CommitId,
@@ -30,8 +29,10 @@
PatchSetNum,
RepoName,
} from '../../../types/common';
+import './gr-download-dialog';
import {GrDownloadDialog} from './gr-download-dialog';
-import {mockPromise} from '../../../test/test-utils';
+import {mockPromise, queryAll, queryAndAssert} from '../../../test/test-utils';
+import {GrDownloadCommands} from '../../shared/gr-download-commands/gr-download-commands';
const basicFixture = fixtureFromElement('gr-download-dialog');
@@ -103,37 +104,43 @@
};
}
-function getChangeObjectNoFetch() {
- return {
- ...createChange(),
- current_revision: '34685798fe548b6d17d1e8e5edc43a26d055cc72' as CommitId,
- revisions: createRevisions(1),
- };
-}
-
suite('gr-download-dialog', () => {
let element: GrDownloadDialog;
- setup(() => {
+ setup(async () => {
element = basicFixture.instantiate();
element.patchNum = 1 as PatchSetNum;
element.config = createDownloadInfo();
- flush();
+ await element.updateComplete;
});
test('anchors use download attribute', () => {
- const anchors = Array.from(element.root!.querySelectorAll('a'));
+ const anchors = Array.from(queryAll(element, 'a'));
assert.isTrue(!anchors.some(a => !a.hasAttribute('download')));
});
suite('gr-download-dialog tests with no fetch options', () => {
- setup(() => {
- element.change = getChangeObjectNoFetch();
- flush();
+ setup(async () => {
+ element.change = {
+ ...createChange(),
+ revisions: {
+ r1: {
+ ...createRevision(),
+ commit: {
+ ...createCommit(),
+ parents: [{commit: 'p1' as CommitId, subject: 'subject1'}],
+ },
+ },
+ },
+ };
+ await element.updateComplete;
});
test('focuses on first download link if no copy links', () => {
- const focusStub = sinon.stub(element.$.download, 'focus');
+ const focusStub = sinon.stub(
+ queryAndAssert<HTMLAnchorElement>(element, '#download'),
+ 'focus'
+ );
element.focus();
assert.isTrue(focusStub.called);
focusStub.restore();
@@ -141,30 +148,31 @@
});
suite('gr-download-dialog with fetch options', () => {
- setup(() => {
+ setup(async () => {
element.change = getChangeObject();
- flush();
+ await element.updateComplete;
});
- test('focuses on first copy link', () => {
- const focusStub = sinon.stub(element.$.downloadCommands, 'focusOnCopy');
+ test('focuses on first copy link', async () => {
+ const focusStub = sinon.stub(
+ queryAndAssert<GrDownloadCommands>(element, '#downloadCommands'),
+ 'focusOnCopy'
+ );
element.focus();
- flush();
+ await element.updateComplete;
assert.isTrue(focusStub.called);
focusStub.restore();
});
test('computed fields', () => {
+ element.change = {
+ ...createChange(),
+ project: 'test/project' as RepoName,
+ _number: 123 as NumericChangeId,
+ };
+ element.patchNum = 2 as PatchSetNum;
assert.equal(
- element._computeArchiveDownloadLink(
- {
- ...createChange(),
- project: 'test/project' as RepoName,
- _number: 123 as NumericChangeId,
- },
- 2 as PatchSetNum,
- 'tgz'
- ),
+ element.computeArchiveDownloadLink('tgz'),
'/changes/test%2Fproject~123/revisions/2/archive?format=tgz'
);
});
@@ -174,7 +182,8 @@
element.addEventListener('close', () => {
closeCalled.resolve();
});
- const closeButton = element.shadowRoot!.querySelector(
+ const closeButton = queryAndAssert(
+ element,
'.closeButtonContainer gr-button'
);
tap(closeButton!);
@@ -182,23 +191,18 @@
});
});
- test('_computeShowDownloadCommands', () => {
- assert.equal(element._computeShowDownloadCommands([]), 'hidden');
- assert.equal(element._computeShowDownloadCommands(['test']), '');
- });
+ test('computeHidePatchFile', () => {
+ element.patchNum = 1 as PatchSetNum;
- test('_computeHidePatchFile', () => {
- const patchNum = 1 as PatchSetNum;
-
- const changeWithNoParent = {
+ element.change = {
...createChange(),
revisions: {
r1: {...createRevision(), commit: createCommit()},
},
};
- assert.isTrue(element._computeHidePatchFile(changeWithNoParent, patchNum));
+ assert.isTrue(element.computeHidePatchFile());
- const changeWithOneParent = {
+ element.change = {
...createChange(),
revisions: {
r1: {
@@ -210,11 +214,9 @@
},
},
};
- assert.isFalse(
- element._computeHidePatchFile(changeWithOneParent, patchNum)
- );
+ assert.isFalse(element.computeHidePatchFile());
- const changeWithMultipleParents = {
+ element.change = {
...createChange(),
revisions: {
r1: {
@@ -229,8 +231,6 @@
},
},
};
- assert.isTrue(
- element._computeHidePatchFile(changeWithMultipleParents, patchNum)
- );
+ assert.isTrue(element.computeHidePatchFile());
});
});
diff --git a/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.ts b/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.ts
index 766b0c3..56949fa 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.ts
+++ b/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.ts
@@ -15,7 +15,7 @@
* limitations under the License.
*/
import '../../../styles/shared-styles';
-import '../../diff/gr-diff-mode-selector/gr-diff-mode-selector';
+import '../../../embed/diff/gr-diff-mode-selector/gr-diff-mode-selector';
import '../../diff/gr-patch-range-select/gr-patch-range-select';
import '../../edit/gr-edit-controls/gr-edit-controls';
import '../../shared/gr-select/gr-select';
@@ -39,7 +39,7 @@
BasePatchSetNum,
} from '../../../types/common';
import {DiffPreferencesInfo} from '../../../types/diff';
-import {GrDiffModeSelector} from '../../diff/gr-diff-mode-selector/gr-diff-mode-selector';
+import {GrDiffModeSelector} from '../../../embed/diff/gr-diff-mode-selector/gr-diff-mode-selector';
import {GrButton} from '../../shared/gr-button/gr-button';
import {fireEvent} from '../../../utils/event-util';
import {
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.ts b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.ts
index 8eb435d..d52281c 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.ts
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.ts
@@ -17,7 +17,7 @@
import {Subscription} from 'rxjs';
import '../../../styles/gr-a11y-styles';
import '../../../styles/shared-styles';
-import '../../diff/gr-diff-cursor/gr-diff-cursor';
+import '../../../embed/diff/gr-diff-cursor/gr-diff-cursor';
import '../../diff/gr-diff-host/gr-diff-host';
import '../../diff/gr-diff-preferences-dialog/gr-diff-preferences-dialog';
import '../../edit/gr-edit-file-controls/gr-edit-file-controls';
@@ -76,7 +76,7 @@
import {DiffPreferencesInfo} from '../../../types/diff';
import {GrDiffHost} from '../../diff/gr-diff-host/gr-diff-host';
import {GrDiffPreferencesDialog} from '../../diff/gr-diff-preferences-dialog/gr-diff-preferences-dialog';
-import {GrDiffCursor} from '../../diff/gr-diff-cursor/gr-diff-cursor';
+import {GrDiffCursor} from '../../../embed/diff/gr-diff-cursor/gr-diff-cursor';
import {GrCursorManager} from '../../shared/gr-cursor-manager/gr-cursor-manager';
import {PolymerSpliceChange} from '@polymer/polymer/interfaces';
import {ChangeComments} from '../../diff/gr-comment-api/gr-comment-api';
@@ -612,7 +612,6 @@
return;
}
// Re-render all expanded diffs sequentially.
- this.reporting.time(Timing.FILE_EXPAND_ALL);
this._renderInOrder(
this._expandedFiles,
this.diffs,
@@ -1324,8 +1323,6 @@
// Required so that the newly created diff view is included in this.diffs.
flush();
- this.reporting.time(Timing.FILE_EXPAND_ALL);
-
if (newFiles.length) {
this._renderInOrder(newFiles, this.diffs, newFiles.length);
}
@@ -1349,11 +1346,13 @@
* @param initialCount The total number of paths in the pass. This
* is used to generate log messages.
*/
- private _renderInOrder(
+ private async _renderInOrder(
files: PatchSetFile[],
diffElements: GrDiffHost[],
initialCount: number
) {
+ this.reporting.time(Timing.FILE_EXPAND_ALL);
+
for (const file of files) {
const path = file.path;
const diffElem = this._findDiffByPath(path, diffElements);
@@ -1362,7 +1361,7 @@
}
}
- asyncForeach(files, (file, cancel) => {
+ await asyncForeach(files, (file, cancel) => {
const path = file.path;
this._cancelForEachDiff = cancel;
@@ -1382,27 +1381,26 @@
promises.push(this._reviewFile(path, true));
}
return Promise.all(promises);
- }).then(() => {
- this._cancelForEachDiff = undefined;
- this.reporting.timeEndWithAverage(
- Timing.FILE_EXPAND_ALL,
- Timing.FILE_EXPAND_ALL_AVG,
- initialCount
- );
- /* Block diff cursor from auto scrolling after files are done rendering.
- * This prevents the bug where the screen jumps to the first diff chunk
- * after files are done being rendered after the user has already begun
- * scrolling.
- * This also however results in the fact that the cursor does not auto
- * focus on the first diff chunk on a small screen. This is however, a use
- * case we are willing to not support for now.
-
- * Using handleDiffUpdate resulted in diffCursor.row being set which
- * prevented the issue of scrolling to top when we expand the second
- * file individually.
- */
- this.diffCursor.reInitAndUpdateStops();
});
+
+ this._cancelForEachDiff = undefined;
+ this.reporting.timeEnd(Timing.FILE_EXPAND_ALL, {
+ count: initialCount,
+ height: this.clientHeight,
+ });
+ /* Block diff cursor from auto scrolling after files are done rendering.
+ * This prevents the bug where the screen jumps to the first diff chunk
+ * after files are done being rendered after the user has already begun
+ * scrolling.
+ * This also however results in the fact that the cursor does not auto
+ * focus on the first diff chunk on a small screen. This is however, a use
+ * case we are willing to not support for now.
+
+ * Using handleDiffUpdate resulted in diffCursor.row being set which
+ * prevented the issue of scrolling to top when we expand the second
+ * file individually.
+ */
+ this.diffCursor.reInitAndUpdateStops();
}
/** Cancel the rendering work of every diff in the list */
@@ -1615,11 +1613,9 @@
_reportRenderedRow(index: number) {
if (index === this._shownFiles.length - 1) {
setTimeout(() => {
- this.reporting.timeEndWithAverage(
- Timing.FILE_RENDER,
- Timing.FILE_RENDER_AVG,
- this._reportinShownFilesIncrement
- );
+ this.reporting.timeEnd(Timing.FILE_RENDER, {
+ count: this._reportinShownFilesIncrement,
+ });
}, 1);
}
return '';
diff --git a/polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog.ts b/polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog.ts
index 8d515da..a6c67d7 100644
--- a/polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog.ts
+++ b/polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog.ts
@@ -15,14 +15,14 @@
* limitations under the License.
*/
import '@polymer/iron-input/iron-input';
-import '../../../styles/shared-styles';
-import '../../../styles/gr-font-styles';
import '../../shared/gr-button/gr-button';
-import {PolymerElement} from '@polymer/polymer/polymer-element';
-import {htmlTemplate} from './gr-included-in-dialog_html';
-import {customElement, property} from '@polymer/decorators';
import {IncludedInInfo, NumericChangeId} from '../../../types/common';
import {getAppContext} from '../../../services/app-context';
+import {fontStyles} from '../../../styles/gr-font-styles';
+import {sharedStyles} from '../../../styles/shared-styles';
+import {LitElement, PropertyValues, html, css} from 'lit';
+import {customElement, property, state} from 'lit/decorators';
+import {BindValueChangeEvent} from '../../../types/events';
interface DisplayGroup {
title: string;
@@ -30,77 +30,183 @@
}
@customElement('gr-included-in-dialog')
-export class GrIncludedInDialog extends PolymerElement {
- static get template() {
- return htmlTemplate;
- }
-
+export class GrIncludedInDialog extends LitElement {
/**
* Fired when the user presses the close button.
*
* @event close
*/
- @property({type: Object, observer: '_resetData'})
+ @property({type: Object})
changeNum?: NumericChangeId;
- @property({type: Object})
- _includedIn?: IncludedInInfo;
+ // private but used in test
+ @state() includedIn?: IncludedInInfo;
- @property({type: Boolean})
- _loaded = false;
+ @state() private loaded = false;
- @property({type: String})
- _filterText = '';
+ // private but used in test
+ @state() filterText = '';
private readonly restApiService = getAppContext().restApiService;
+ static override get styles() {
+ return [
+ fontStyles,
+ sharedStyles,
+ css`
+ :host {
+ background-color: var(--dialog-background-color);
+ display: block;
+ max-height: 80vh;
+ overflow-y: auto;
+ padding: 4.5em var(--spacing-l) var(--spacing-l) var(--spacing-l);
+ }
+ header {
+ background-color: var(--dialog-background-color);
+ border-bottom: 1px solid var(--border-color);
+ left: 0;
+ padding: var(--spacing-l);
+ position: absolute;
+ right: 0;
+ top: 0;
+ }
+ #title {
+ display: inline-block;
+ font-family: var(--header-font-family);
+ font-size: var(--font-size-h3);
+ font-weight: var(--font-weight-h3);
+ line-height: var(--line-height-h3);
+ margin-top: var(--spacing-xs);
+ }
+ #filterInput {
+ display: inline-block;
+ float: right;
+ margin: 0 var(--spacing-l);
+ padding: var(--spacing-xs);
+ }
+ .closeButtonContainer {
+ float: right;
+ }
+ ul {
+ margin-bottom: var(--spacing-l);
+ }
+ ul li {
+ border: 1px solid var(--border-color);
+ border-radius: var(--border-radius);
+ background: var(--chip-background-color);
+ display: inline-block;
+ margin: 0 var(--spacing-xs) var(--spacing-s) var(--spacing-xs);
+ padding: var(--spacing-xs) var(--spacing-s);
+ }
+ `,
+ ];
+ }
+
+ override render() {
+ return html`
+ <header>
+ <h1 id="title" class="heading-1">Included In:</h1>
+ <span class="closeButtonContainer">
+ <gr-button
+ id="closeButton"
+ link
+ @click=${(e: Event) => {
+ this.handleCloseTap(e);
+ }}
+ >Close</gr-button
+ >
+ </span>
+ <iron-input
+ id="filterInput"
+ placeholder="Filter"
+ .bindValue=${this.filterText}
+ @bind-value-changed=${(e: BindValueChangeEvent) => {
+ this.filterText = e.detail.value;
+ }}
+ >
+ <input placeholder="Filter" />
+ </iron-input>
+ </header>
+ ${this.renderLoading()}
+ ${this.computeGroups().map(group => this.renderGroup(group))}
+ `;
+ }
+
+ private renderLoading() {
+ if (this.loaded) return;
+
+ return html`<div>Loading...</div>`;
+ }
+
+ private renderGroup(group: DisplayGroup) {
+ return html`
+ <div>
+ <span>${group.title}:</span>
+ <ul>
+ ${group.items.map(item => this.renderGroupItem(item))}
+ </ul>
+ </div>
+ `;
+ }
+
+ private renderGroupItem(item: string) {
+ return html`<li>${item}</li>`;
+ }
+
+ override willUpdate(changedProperties: PropertyValues) {
+ if (changedProperties.has('changeNum')) {
+ this.resetData();
+ }
+ }
+
loadData() {
if (!this.changeNum) {
return Promise.reject(new Error('missing required property changeNum'));
}
- this._filterText = '';
+ this.filterText = '';
return this.restApiService
.getChangeIncludedIn(this.changeNum)
.then(configs => {
if (!configs) {
return;
}
- this._includedIn = configs;
- this._loaded = true;
+ this.includedIn = configs;
+ this.loaded = true;
});
}
- _resetData() {
- this._includedIn = undefined;
- this._loaded = false;
+ private resetData() {
+ this.includedIn = undefined;
+ this.loaded = false;
}
- _computeGroups(includedIn: IncludedInInfo | undefined, filterText: string) {
- if (!includedIn || filterText === undefined) {
+ // private but used in test
+ computeGroups() {
+ if (!this.includedIn || this.filterText === undefined) {
return [];
}
const filter = (item: string) =>
- !filterText.length ||
- item.toLowerCase().indexOf(filterText.toLowerCase()) !== -1;
+ !this.filterText.length ||
+ item.toLowerCase().indexOf(this.filterText.toLowerCase()) !== -1;
const groups: DisplayGroup[] = [
- {title: 'Branches', items: includedIn.branches.filter(filter)},
- {title: 'Tags', items: includedIn.tags.filter(filter)},
+ {title: 'Branches', items: this.includedIn.branches.filter(filter)},
+ {title: 'Tags', items: this.includedIn.tags.filter(filter)},
];
- if (includedIn.external) {
- for (const externalKey of Object.keys(includedIn.external)) {
+ if (this.includedIn.external) {
+ for (const externalKey of Object.keys(this.includedIn.external)) {
groups.push({
title: externalKey,
- items: includedIn.external[externalKey].filter(filter),
+ items: this.includedIn.external[externalKey].filter(filter),
});
}
}
return groups.filter(g => g.items.length);
}
- _handleCloseTap(e: Event) {
+ private handleCloseTap(e: Event) {
e.preventDefault();
e.stopPropagation();
this.dispatchEvent(
@@ -110,10 +216,6 @@
})
);
}
-
- _computeLoadingClass(loaded: boolean) {
- return loaded ? 'loading loaded' : 'loading';
- }
}
declare global {
diff --git a/polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog_html.ts b/polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog_html.ts
deleted file mode 100644
index 674b7e7..0000000
--- a/polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog_html.ts
+++ /dev/null
@@ -1,106 +0,0 @@
-/**
- * @license
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import {html} from '@polymer/polymer/lib/utils/html-tag';
-
-export const htmlTemplate = html`
- <style include="gr-font-styles">
- /* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
- </style>
- <style include="shared-styles">
- :host {
- background-color: var(--dialog-background-color);
- display: block;
- max-height: 80vh;
- overflow-y: auto;
- padding: 4.5em var(--spacing-l) var(--spacing-l) var(--spacing-l);
- }
- header {
- background-color: var(--dialog-background-color);
- border-bottom: 1px solid var(--border-color);
- left: 0;
- padding: var(--spacing-l);
- position: absolute;
- right: 0;
- top: 0;
- }
- #title {
- display: inline-block;
- font-family: var(--header-font-family);
- font-size: var(--font-size-h3);
- font-weight: var(--font-weight-h3);
- line-height: var(--line-height-h3);
- margin-top: var(--spacing-xs);
- }
- #filterInput {
- display: inline-block;
- float: right;
- margin: 0 var(--spacing-l);
- padding: var(--spacing-xs);
- }
- .closeButtonContainer {
- float: right;
- }
- ul {
- margin-bottom: var(--spacing-l);
- }
- ul li {
- border: 1px solid var(--border-color);
- border-radius: var(--border-radius);
- background: var(--chip-background-color);
- display: inline-block;
- margin: 0 var(--spacing-xs) var(--spacing-s) var(--spacing-xs);
- padding: var(--spacing-xs) var(--spacing-s);
- }
- .loading.loaded {
- display: none;
- }
- </style>
- <header>
- <h1 id="title" class="heading-1">Included In:</h1>
- <span class="closeButtonContainer">
- <gr-button id="closeButton" link="" on-click="_handleCloseTap"
- >Close</gr-button
- >
- </span>
- <iron-input
- id="filterInput"
- placeholder="Filter"
- bind-value="{{_filterText}}"
- >
- <input
- is="iron-input"
- placeholder="Filter"
- bind-value="{{_filterText}}"
- />
- </iron-input>
- </header>
- <div class$="[[_computeLoadingClass(_loaded)]]">Loading...</div>
- <template
- is="dom-repeat"
- items="[[_computeGroups(_includedIn, _filterText)]]"
- as="group"
- >
- <div>
- <span>[[group.title]]:</span>
- <ul>
- <template is="dom-repeat" items="[[group.items]]">
- <li>[[item]]</li>
- </template>
- </ul>
- </div>
- </template>
-`;
diff --git a/polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog_test.ts b/polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog_test.ts
index 4e02155..e3d1e02 100644
--- a/polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog_test.ts
@@ -27,66 +27,66 @@
suite('gr-included-in-dialog', () => {
let element: GrIncludedInDialog;
- setup(() => {
+ setup(async () => {
element = basicFixture.instantiate();
+ await element.updateComplete;
});
- test('_computeGroups', () => {
- const includedIn = {branches: [], tags: []} as IncludedInInfo;
- let filterText = '';
- assert.deepEqual(element._computeGroups(includedIn, filterText), []);
+ test('computeGroups', () => {
+ element.includedIn = {branches: [], tags: []} as IncludedInInfo;
+ element.filterText = '';
+ assert.deepEqual(element.computeGroups(), []);
- includedIn.branches.push(
+ element.includedIn.branches.push(
'master' as BranchName,
'development' as BranchName,
'stable-2.0' as BranchName
);
- includedIn.tags.push(
+ element.includedIn.tags.push(
'v1.9' as TagName,
'v2.0' as TagName,
'v2.1' as TagName
);
- assert.deepEqual(element._computeGroups(includedIn, filterText), [
+ assert.deepEqual(element.computeGroups(), [
{title: 'Branches', items: ['master', 'development', 'stable-2.0']},
{title: 'Tags', items: ['v1.9', 'v2.0', 'v2.1']},
]);
- includedIn.external = {};
- assert.deepEqual(element._computeGroups(includedIn, filterText), [
+ element.includedIn.external = {};
+ assert.deepEqual(element.computeGroups(), [
{title: 'Branches', items: ['master', 'development', 'stable-2.0']},
{title: 'Tags', items: ['v1.9', 'v2.0', 'v2.1']},
]);
- includedIn.external.foo = ['abc', 'def', 'ghi'];
- assert.deepEqual(element._computeGroups(includedIn, filterText), [
+ element.includedIn.external.foo = ['abc', 'def', 'ghi'];
+ assert.deepEqual(element.computeGroups(), [
{title: 'Branches', items: ['master', 'development', 'stable-2.0']},
{title: 'Tags', items: ['v1.9', 'v2.0', 'v2.1']},
{title: 'foo', items: ['abc', 'def', 'ghi']},
]);
- filterText = 'v2';
- assert.deepEqual(element._computeGroups(includedIn, filterText), [
+ element.filterText = 'v2';
+ assert.deepEqual(element.computeGroups(), [
{title: 'Tags', items: ['v2.0', 'v2.1']},
]);
// Filtering is case-insensitive.
- filterText = 'V2';
- assert.deepEqual(element._computeGroups(includedIn, filterText), [
+ element.filterText = 'V2';
+ assert.deepEqual(element.computeGroups(), [
{title: 'Tags', items: ['v2.0', 'v2.1']},
]);
});
- test('_computeGroups with .bindValue', async () => {
+ test('computeGroups with .bindValue', async () => {
queryAndAssert<IronInputElement>(element, '#filterInput')!.bindValue =
'stable-3.2';
- const includedIn = {branches: [], tags: []} as IncludedInInfo;
- includedIn.branches.push(
+ element.includedIn = {branches: [], tags: []} as IncludedInInfo;
+ element.includedIn.branches.push(
'master' as BranchName,
'stable-3.2' as BranchName
);
- await flush();
- const filterText = element._filterText;
- assert.deepEqual(element._computeGroups(includedIn, filterText), [
+ await element.updateComplete;
+ assert.deepEqual(element.computeGroups(), [
{title: 'Branches', items: ['stable-3.2']},
]);
});
diff --git a/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row.ts b/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row.ts
index 9a38095..5da6fa8 100644
--- a/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row.ts
+++ b/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row.ts
@@ -16,10 +16,10 @@
*/
import '@polymer/iron-selector/iron-selector';
import '../../shared/gr-button/gr-button';
-import '../../../styles/shared-styles';
-import {PolymerElement} from '@polymer/polymer/polymer-element';
-import {htmlTemplate} from './gr-label-score-row_html';
-import {customElement, property} from '@polymer/decorators';
+import {sharedStyles} from '../../../styles/shared-styles';
+import {css, html, nothing, LitElement} from 'lit';
+import {customElement, property, query, state} from 'lit/decorators';
+import {ifDefined} from 'lit/directives/if-defined';
import {IronSelectorElement} from '@polymer/iron-selector/iron-selector';
import {
LabelNameToValueMap,
@@ -27,7 +27,7 @@
QuickLabelInfo,
DetailedLabelInfo,
} from '../../../types/common';
-import {hasOwnProperty} from '../../../utils/common-util';
+import {assertIsDefined, hasOwnProperty} from '../../../utils/common-util';
export interface Label {
name: string;
@@ -40,12 +40,6 @@
[key: number]: number;
}
-export interface GrLabelScoreRow {
- $: {
- labelSelector: IronSelectorElement;
- };
-}
-
declare global {
interface HTMLElementTagNameMap {
'gr-label-score-row': GrLabelScoreRow;
@@ -53,24 +47,23 @@
}
@customElement('gr-label-score-row')
-export class GrLabelScoreRow extends PolymerElement {
- static get template() {
- return htmlTemplate;
- }
-
+export class GrLabelScoreRow extends LitElement {
/**
* Fired when any label is changed.
*
* @event labels-changed
*/
+ @query('#labelSelector')
+ labelSelector?: IronSelectorElement;
+
@property({type: Object})
label: Label | undefined | null;
@property({type: Object})
labels?: LabelNameToInfoMap;
- @property({type: String, reflectToAttribute: true})
+ @property({type: String, reflect: true})
name?: string;
@property({type: Object})
@@ -79,87 +72,255 @@
@property({type: Object})
labelValues?: LabelValuesMap;
- @property({type: String})
- _selectedValueText = 'No value selected';
+ @state()
+ private selectedValueText = 'No value selected';
- @property({
- computed: '_computePermittedLabelValues(permittedLabels, label.name)',
- type: Array,
- })
- _items!: string[];
+ static override get styles() {
+ return [
+ sharedStyles,
+ css`
+ .labelNameCell,
+ .buttonsCell,
+ .selectedValueCell {
+ padding: var(--spacing-s) var(--spacing-m);
+ display: table-cell;
+ }
+ /* We want the :hover highlight to extend to the border of the dialog. */
+ .labelNameCell {
+ padding-left: var(--spacing-xl);
+ }
+ .selectedValueCell {
+ padding-right: var(--spacing-xl);
+ }
+ /* This is a trick to let the selectedValueCell take the remaining width. */
+ .labelNameCell,
+ .buttonsCell {
+ white-space: nowrap;
+ }
+ .selectedValueCell {
+ width: 75%;
+ }
+ .labelMessage {
+ color: var(--deemphasized-text-color);
+ }
+ gr-button {
+ min-width: 42px;
+ box-sizing: border-box;
+ --vote-text-color: var(--vote-chip-unselected-text-color);
+ }
+ gr-button.iron-selected {
+ --vote-text-color: var(--vote-chip-selected-text-color);
+ }
+ gr-button::part(paper-button) {
+ padding: 0 var(--spacing-m);
+ background-color: var(
+ --button-background-color,
+ var(--table-header-background-color)
+ );
+ border-color: var(--vote-chip-unselected-outline-color);
+ }
+ gr-button.iron-selected::part(paper-button) {
+ border-color: transparent;
+ }
+ gr-button {
+ --button-background-color: var(--vote-chip-unselected-color);
+ }
+ gr-button[vote='max'].iron-selected {
+ --button-background-color: var(--vote-chip-selected-positive-color);
+ }
+ gr-button[vote='positive'].iron-selected {
+ --button-background-color: var(--vote-chip-selected-positive-color);
+ }
+ gr-button[vote='neutral'].iron-selected {
+ --button-background-color: var(--vote-chip-selected-neutral-color);
+ }
+ gr-button[vote='negative'].iron-selected {
+ --button-background-color: var(--vote-chip-selected-negative-color);
+ }
+ gr-button[vote='min'].iron-selected {
+ --button-background-color: var(--vote-chip-selected-negative-color);
+ }
+ gr-button > gr-tooltip-content {
+ margin: 0px -10px;
+ padding: 0px 10px;
+ }
+ .placeholder {
+ display: inline-block;
+ width: 42px;
+ height: 1px;
+ }
+ .placeholder::before {
+ content: ' ';
+ }
+ .selectedValueCell {
+ color: var(--deemphasized-text-color);
+ font-style: italic;
+ }
+ .selectedValueCell.hidden {
+ display: none;
+ }
+ @media only screen and (max-width: 50em) {
+ .selectedValueCell {
+ display: none;
+ }
+ }
+ `,
+ ];
+ }
- get selectedItem() {
- if (!this._ironSelector) {
+ override render() {
+ return html`
+ <span class="labelNameCell" id="labelName" aria-hidden="true"
+ >${this.label?.name ?? ''}</span
+ >
+ ${this.renderButtonsCell()} ${this.renderSelectedValue()}
+ `;
+ }
+
+ private renderButtonsCell() {
+ return html`
+ <div class="buttonsCell">
+ ${this.renderBlankItems('start')} ${this.renderLabelSelector()}
+ ${this.renderBlankItems('end')}
+ ${!this._computeAnyPermittedLabelValues()
+ ? html` <span class="labelMessage">
+ You don't have permission to edit this label.
+ </span>`
+ : nothing}
+ </div>
+ `;
+ }
+
+ private renderBlankItems(position: string) {
+ const blankItems = this._computeBlankItems(position);
+ return blankItems.map(
+ _value => html`
+ <span class="placeholder" data-label="${this.label?.name ?? ''}">
+ </span>
+ `
+ );
+ }
+
+ private renderLabelSelector() {
+ return html`
+ <iron-selector
+ id="labelSelector"
+ attr-for-selected="data-value"
+ ?hidden="${!this._computeAnyPermittedLabelValues()}"
+ selected="${ifDefined(this._computeLabelValue())}"
+ @selected-item-changed=${this.setSelectedValueText}
+ role="radiogroup"
+ aria-labelledby="labelName"
+ >
+ ${this.renderPermittedLabels()}
+ </iron-selector>
+ `;
+ }
+
+ private renderPermittedLabels() {
+ const items = this.computePermittedLabelValues();
+ return items.map(
+ (value, index) => html`
+ <gr-button
+ role="radio"
+ vote="${this._computeVoteAttribute(
+ Number(value),
+ index,
+ items.length
+ )}"
+ title="${ifDefined(this.computeLabelValueTitle(value))}"
+ data-name="${ifDefined(this.label?.name)}"
+ data-value="${value}"
+ aria-label="${value}"
+ voteChip
+ flatten
+ >
+ <gr-tooltip-content
+ has-tooltip
+ light-tooltip
+ title="${ifDefined(this.computeLabelValueTitle(value))}"
+ >
+ ${value}
+ </gr-tooltip-content>
+ </gr-button>
+ `
+ );
+ }
+
+ private renderSelectedValue() {
+ return html`
+ <div class="selectedValueCell ${this.computeHiddenClass()}">
+ <span id="selectedValueLabel">${this.selectedValueText}</span>
+ </div>
+ `;
+ }
+
+ get selectedItem(): IronSelectorElement | undefined {
+ if (!this.labelSelector) {
return undefined;
}
- return this._ironSelector.selectedItem;
+ return this.labelSelector.selectedItem as IronSelectorElement;
}
get selectedValue() {
- if (!this._ironSelector) {
+ if (!this.labelSelector) {
return undefined;
}
- return this._ironSelector.selected;
+ return this.labelSelector.selected;
}
setSelectedValue(value: string) {
// The selector may not be present if it’s not at the latest patch set.
- if (!this._ironSelector) {
+ if (!this.labelSelector) {
return;
}
- this._ironSelector.select(value);
+ this.labelSelector.select(value);
}
- get _ironSelector() {
- return this.$ && this.$.labelSelector;
- }
-
- _computeBlankItems(
- permittedLabels: LabelNameToValueMap,
- label: string,
- side: string
- ) {
+ // Private but used in tests.
+ _computeBlankItems(side: string) {
if (
- !permittedLabels ||
- !permittedLabels[label] ||
- !permittedLabels[label].length ||
+ !this.label ||
+ !this.permittedLabels?.[this.label.name] ||
+ !this.permittedLabels[this.label.name].length ||
!this.labelValues ||
!Object.keys(this.labelValues).length
) {
return [];
}
- const startPosition = this.labelValues[Number(permittedLabels[label][0])];
+ const permittedLabel = this.permittedLabels[this.label.name];
+ const startPosition = this.labelValues[Number(permittedLabel[0])];
if (side === 'start') {
- return new Array(startPosition);
+ return new Array(startPosition).fill('');
}
const endPosition =
- this.labelValues[
- Number(permittedLabels[label][permittedLabels[label].length - 1])
- ];
- return new Array(Object.keys(this.labelValues).length - endPosition - 1);
+ this.labelValues[Number(permittedLabel[permittedLabel.length - 1])];
+ const length = Object.keys(this.labelValues).length - endPosition - 1;
+ return new Array(length).fill('');
}
- _getLabelValue(
- labels: LabelNameToInfoMap,
- permittedLabels: LabelNameToValueMap,
- label: Label
- ) {
- if (label.value) {
- return label.value;
+ private getLabelValue() {
+ assertIsDefined(this.labels);
+ assertIsDefined(this.label);
+ assertIsDefined(this.permittedLabels);
+ if (this.label.value) {
+ return this.label.value;
} else if (
- hasOwnProperty(labels[label.name], 'default_value') &&
- hasOwnProperty(permittedLabels, label.name)
+ hasOwnProperty(this.labels[this.label.name], 'default_value') &&
+ hasOwnProperty(this.permittedLabels, this.label.name)
) {
// default_value is an int, convert it to string label, e.g. "+1".
- return permittedLabels[label.name].find(
+ return this.permittedLabels[this.label.name].find(
value =>
- Number(value) === (labels[label.name] as QuickLabelInfo).default_value
+ Number(value) ===
+ (this.labels![this.label!.name] as QuickLabelInfo).default_value
);
}
return;
}
/**
+ * Private but used in tests.
* Maps the label value to exactly one of: min, max, positive, negative,
* neutral. Used for the 'vote' attribute, because we don't want to
* interfere with <iron-selector> using the 'class' attribute for setting
@@ -179,37 +340,29 @@
}
}
- _computeLabelValue(
- labels?: LabelNameToInfoMap,
- permittedLabels?: LabelNameToValueMap,
- label?: Label
- ) {
+ // Private but used in tests.
+ _computeLabelValue() {
// Polymer 2+ undefined check
- if (
- labels === undefined ||
- permittedLabels === undefined ||
- label === undefined
- ) {
- return null;
+ if (!this.labels || !this.permittedLabels || !this.label) {
+ return undefined;
}
- if (!labels[label.name]) {
- return null;
+ if (!this.labels[this.label.name]) {
+ return undefined;
}
- const labelValue = this._getLabelValue(labels, permittedLabels, label);
- const len = permittedLabels[label.name]
- ? permittedLabels[label.name].length
- : 0;
+ const labelValue = this.getLabelValue();
+ const permittedLabel = this.permittedLabels[this.label.name];
+ const len = permittedLabel ? permittedLabel.length : 0;
for (let i = 0; i < len; i++) {
- const val = permittedLabels[label.name][i];
+ const val = permittedLabel[i];
if (val === labelValue) {
return val;
}
}
- return null;
+ return undefined;
}
- _setSelectedValueText(e: Event) {
+ private setSelectedValueText = (e: Event) => {
// Needed because when the selected item changes, it first changes to
// nothing and then to the new item.
const selectedItem = (e.target as IronSelectorElement)
@@ -217,19 +370,17 @@
if (!selectedItem) {
return;
}
- if (!this.$.labelSelector.items) {
+ if (!this.labelSelector?.items) {
return;
}
- for (const item of this.$.labelSelector.items) {
+ for (const item of this.labelSelector.items) {
if (selectedItem === item) {
item.setAttribute('aria-checked', 'true');
} else {
item.removeAttribute('aria-checked');
}
}
- this._selectedValueText = selectedItem.getAttribute('title') || '';
- // Needed to update the style of the selected button.
- this.updateStyles();
+ this.selectedValueText = selectedItem.getAttribute('title') || '';
const name = selectedItem.dataset['name'];
const value = selectedItem.dataset['value'];
this.dispatchEvent(
@@ -239,47 +390,38 @@
composed: true,
})
);
- }
+ };
- _computeAnyPermittedLabelValues(
- permittedLabels: LabelNameToValueMap,
- labelName: string
- ) {
+ _computeAnyPermittedLabelValues() {
return (
- permittedLabels &&
- hasOwnProperty(permittedLabels, labelName) &&
- permittedLabels[labelName].length
+ this.permittedLabels &&
+ this.label &&
+ hasOwnProperty(this.permittedLabels, this.label.name) &&
+ this.permittedLabels[this.label.name].length
);
}
- _computeHiddenClass(permittedLabels: LabelNameToValueMap, labelName: string) {
- return !this._computeAnyPermittedLabelValues(permittedLabels, labelName)
- ? 'hidden'
- : '';
+ private computeHiddenClass() {
+ return !this._computeAnyPermittedLabelValues() ? 'hidden' : '';
}
- _computePermittedLabelValues(
- permittedLabels?: LabelNameToValueMap,
- labelName?: string
- ) {
- // Polymer 2: check for undefined
- if (permittedLabels === undefined || labelName === undefined) {
+ private computePermittedLabelValues() {
+ if (!this.permittedLabels || !this.label) {
return [];
}
- return permittedLabels[labelName] || [];
+ return this.permittedLabels[this.label.name] || [];
}
- _computeLabelValueTitle(
- labels: LabelNameToInfoMap,
- label: string,
- value: string
- ) {
- // TODO(TS): maybe add a type guard for DetailedLabelInfo and QuickLabelInfo
- return (
- labels[label] &&
- (labels[label] as DetailedLabelInfo).values &&
- (labels[label] as DetailedLabelInfo).values![value]
- );
+ private computeLabelValueTitle(value: string) {
+ if (!this.labels || !this.label) return '';
+ const label = this.labels[this.label.name];
+ if (label && (label as DetailedLabelInfo).values) {
+ // TODO(TS): maybe add a type guard for DetailedLabelInfo and
+ // QuickLabelInfo
+ return (label as DetailedLabelInfo).values![value];
+ } else {
+ return '';
+ }
}
}
diff --git a/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row_html.ts b/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row_html.ts
deleted file mode 100644
index e57a216..0000000
--- a/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row_html.ts
+++ /dev/null
@@ -1,160 +0,0 @@
-/**
- * @license
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import {html} from '@polymer/polymer/lib/utils/html-tag';
-
-export const htmlTemplate = html`
- <style include="shared-styles">
- .labelNameCell,
- .buttonsCell,
- .selectedValueCell {
- padding: var(--spacing-s) var(--spacing-m);
- display: table-cell;
- }
- /* We want the :hover highlight to extend to the border of the dialog. */
- .labelNameCell {
- padding-left: var(--spacing-xl);
- }
- .selectedValueCell {
- padding-right: var(--spacing-xl);
- }
- /* This is a trick to let the selectedValueCell take the remaining width. */
- .labelNameCell,
- .buttonsCell {
- white-space: nowrap;
- }
- .selectedValueCell {
- width: 75%;
- }
- .labelMessage {
- color: var(--deemphasized-text-color);
- }
- gr-button {
- min-width: 42px;
- box-sizing: border-box;
- }
- gr-button::part(paper-button) {
- background-color: var(
- --button-background-color,
- var(--table-header-background-color)
- );
- padding: 0 var(--spacing-m);
- }
- gr-button[vote='max'].iron-selected {
- --button-background-color: var(--vote-color-approved);
- }
- gr-button[vote='positive'].iron-selected {
- --button-background-color: var(--vote-color-recommended);
- }
- gr-button[vote='min'].iron-selected {
- --button-background-color: var(--vote-color-rejected);
- }
- gr-button[vote='negative'].iron-selected {
- --button-background-color: var(--vote-color-disliked);
- }
- gr-button[vote='neutral'].iron-selected {
- --button-background-color: var(--vote-color-neutral);
- }
- gr-button[vote='positive'].iron-selected::part(paper-button) {
- border-color: var(--vote-outline-recommended);
- }
- gr-button[vote='negative'].iron-selected::part(paper-button) {
- border-color: var(--vote-outline-disliked);
- }
- gr-button > gr-tooltip-content {
- margin: 0px -10px;
- padding: 0px 10px;
- }
- .placeholder {
- display: inline-block;
- width: 42px;
- height: 1px;
- }
- .placeholder::before {
- content: ' ';
- }
- .selectedValueCell {
- color: var(--deemphasized-text-color);
- font-style: italic;
- }
- .selectedValueCell.hidden {
- display: none;
- }
- @media only screen and (max-width: 50em) {
- .selectedValueCell {
- display: none;
- }
- }
- </style>
- <span class="labelNameCell" id="labelName" aria-hidden="true"
- >[[label.name]]</span
- >
- <div class="buttonsCell">
- <template
- is="dom-repeat"
- items="[[_computeBlankItems(permittedLabels, label.name, 'start')]]"
- as="value"
- >
- <span class="placeholder" data-label$="[[label.name]]"></span>
- </template>
- <iron-selector
- id="labelSelector"
- attr-for-selected="data-value"
- selected="[[_computeLabelValue(labels, permittedLabels, label)]]"
- hidden$="[[!_computeAnyPermittedLabelValues(permittedLabels, label.name)]]"
- on-selected-item-changed="_setSelectedValueText"
- role="radiogroup"
- aria-labelledby="labelName"
- >
- <template is="dom-repeat" items="[[_items]]" as="value">
- <gr-button
- role="radio"
- vote$="[[_computeVoteAttribute(value, index, _items.length)]]"
- title$="[[_computeLabelValueTitle(labels, label.name, value)]]"
- data-name$="[[label.name]]"
- data-value$="[[value]]"
- aria-label$="[[value]]"
- voteChip
- >
- <gr-tooltip-content
- has-tooltip
- title$="[[_computeLabelValueTitle(labels, label.name, value)]]"
- >
- [[value]]
- </gr-tooltip-content>
- </gr-button>
- </template>
- </iron-selector>
- <template
- is="dom-repeat"
- items="[[_computeBlankItems(permittedLabels, label.name, 'end')]]"
- as="value"
- >
- <span class="placeholder" data-label$="[[label.name]]"></span>
- </template>
- <span
- class="labelMessage"
- hidden$="[[_computeAnyPermittedLabelValues(permittedLabels, label.name)]]"
- >
- You don't have permission to edit this label.
- </span>
- </div>
- <div
- class$="selectedValueCell [[_computeHiddenClass(permittedLabels, label.name)]]"
- >
- <span id="selectedValueLabel">[[_selectedValueText]]</span>
- </div>
-`;
diff --git a/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row_test.js b/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row_test.js
deleted file mode 100644
index 51d76b2..0000000
--- a/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row_test.js
+++ /dev/null
@@ -1,339 +0,0 @@
-/**
- * @license
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import '../../../test/common-test-setup-karma.js';
-import './gr-label-score-row.js';
-
-const basicFixture = fixtureFromElement('gr-label-score-row');
-
-suite('gr-label-row-score tests', () => {
- let element;
-
- setup(async () => {
- element = basicFixture.instantiate();
- element.labels = {
- 'Code-Review': {
- values: {
- '0': 'No score',
- '+1': 'good',
- '+2': 'excellent',
- '-1': 'bad',
- '-2': 'terrible',
- },
- default_value: 0,
- value: 1,
- all: [{
- _account_id: 123,
- value: 1,
- }],
- },
- 'Verified': {
- values: {
- '0': 'No score',
- '+1': 'good',
- '+2': 'excellent',
- '-1': 'bad',
- '-2': 'terrible',
- },
- default_value: 0,
- value: 1,
- all: [{
- _account_id: 123,
- value: 1,
- }],
- },
- };
-
- element.permittedLabels = {
- 'Code-Review': [
- '-2',
- '-1',
- ' 0',
- '+1',
- '+2',
- ],
- 'Verified': [
- '-1',
- ' 0',
- '+1',
- ],
- };
-
- element.labelValues = {'0': 2, '1': 3, '2': 4, '-2': 0, '-1': 1};
-
- element.label = {
- name: 'Verified',
- value: '+1',
- };
-
- await flush();
- });
-
- function checkAriaCheckedValid() {
- const items = element.$.labelSelector.items;
- const selectedItem = element.selectedItem;
- for (let i = 0; i < items.length; i++) {
- const item = items[i];
- if (items[i] === selectedItem) {
- assert.isTrue(item.hasAttribute('aria-checked'), `item ${i}`);
- assert.equal(item.getAttribute('aria-checked'), 'true', `item ${i}`);
- } else {
- assert.isFalse(item.hasAttribute('aria-checked'), `item ${i}`);
- }
- }
- }
-
- test('label picker', async () => {
- const labelsChangedHandler = sinon.stub();
- element.addEventListener('labels-changed', labelsChangedHandler);
- assert.ok(element.$.labelSelector);
- MockInteractions.tap(
- element.shadowRoot.querySelector('gr-button[data-value="-1"]'));
- await flush();
- assert.strictEqual(element.selectedValue, '-1');
- assert.strictEqual(element.selectedItem.textContent.trim(), '-1');
- assert.strictEqual(element.$.selectedValueLabel.textContent.trim(), 'bad');
- const detail = labelsChangedHandler.args[0][0].detail;
- assert.equal(detail.name, 'Verified');
- assert.equal(detail.value, '-1');
- checkAriaCheckedValid();
- });
-
- test('_computeVoteAttribute', () => {
- let value = 1;
- let index = 0;
- const totalItems = 5;
- // positive and first position
- assert.equal(
- element._computeVoteAttribute(value, index, totalItems), 'positive');
- // negative and first position
- value = -1;
- assert.equal(
- element._computeVoteAttribute(value, index, totalItems), 'min');
- // negative but not first position
- index = 1;
- assert.equal(
- element._computeVoteAttribute(value, index, totalItems), 'negative');
- // neutral
- value = 0;
- assert.equal(
- element._computeVoteAttribute(value, index, totalItems), 'neutral');
- // positive but not last position
- value = 1;
- assert.equal(
- element._computeVoteAttribute(value, index, totalItems), 'positive');
- // positive and last position
- index = 4;
- assert.equal(
- element._computeVoteAttribute(value, index, totalItems), 'max');
- // negative and last position
- value = -1;
- assert.equal(
- element._computeVoteAttribute(value, index, totalItems), 'negative');
- });
-
- test('correct item is selected', () => {
- // 1 should be the value of the selected item
- assert.strictEqual(element.$.labelSelector.selected, '+1');
- assert.strictEqual(
- element.$.labelSelector.selectedItem.textContent.trim(), '+1');
- assert.strictEqual(element.$.selectedValueLabel.textContent.trim(), 'good');
- checkAriaCheckedValid();
- });
-
- test('_computeLabelValue', () => {
- assert.strictEqual(
- element._computeLabelValue(
- element.labels, element.permittedLabels, element.label),
- '+1');
- });
-
- test('_computeBlankItems', () => {
- element.labelValues = {
- '-2': 0,
- '-1': 1,
- '0': 2,
- '1': 3,
- '2': 4,
- };
-
- assert.strictEqual(
- element._computeBlankItems(element.permittedLabels, 'Code-Review')
- .length,
- 0);
-
- assert.strictEqual(
- element._computeBlankItems(element.permittedLabels, 'Verified').length,
- 1);
- });
-
- test('labelValues returns no keys', () => {
- element.labelValues = {};
-
- assert.deepEqual(
- element._computeBlankItems(element.permittedLabels, 'Code-Review'), []);
- });
-
- test('changes in label score are reflected in the DOM', async () => {
- element.labels = {
- 'Code-Review': {
- values: {
- '0': 'No score',
- '+1': 'good',
- '+2': 'excellent',
- '-1': 'bad',
- '-2': 'terrible',
- },
- default_value: 0,
- },
- 'Verified': {
- values: {
- ' 0': 'No score',
- '+1': 'good',
- '+2': 'excellent',
- '-1': 'bad',
- '-2': 'terrible',
- },
- default_value: 0,
- },
- };
- await flush();
- const selector = element.$.labelSelector;
- element.set('label', {name: 'Verified', value: ' 0'});
- await flush();
- assert.strictEqual(selector.selected, ' 0');
- assert.strictEqual(
- element.$.selectedValueLabel.textContent.trim(), 'No score');
- checkAriaCheckedValid();
- });
-
- test('without permitted labels', async () => {
- element.permittedLabels = {
- Verified: [
- '-1',
- ' 0',
- '+1',
- ],
- };
- await flush();
- assert.isOk(element.$.labelSelector);
- assert.isFalse(element.$.labelSelector.hidden);
-
- element.permittedLabels = {};
- await flush();
- assert.isOk(element.$.labelSelector);
- assert.isTrue(element.$.labelSelector.hidden);
-
- element.permittedLabels = {Verified: []};
- await flush();
- assert.isOk(element.$.labelSelector);
- assert.isTrue(element.$.labelSelector.hidden);
- });
-
- test('asymmetrical labels', async () => {
- element.permittedLabels = {
- 'Code-Review': [
- '-2',
- '-1',
- ' 0',
- '+1',
- '+2',
- ],
- 'Verified': [
- ' 0',
- '+1',
- ],
- };
- await flush();
- assert.strictEqual(element.$.labelSelector.items.length, 2);
- assert.strictEqual(element.root.querySelectorAll('.placeholder').length, 3);
-
- element.permittedLabels = {
- 'Code-Review': [
- ' 0',
- '+1',
- ],
- 'Verified': [
- '-2',
- '-1',
- ' 0',
- '+1',
- '+2',
- ],
- };
- await flush();
- assert.strictEqual(element.$.labelSelector.items.length, 5);
- assert.strictEqual(element.root.querySelectorAll('.placeholder').length, 0);
- });
-
- test('default_value', () => {
- element.permittedLabels = {
- Verified: [
- '-1',
- ' 0',
- '+1',
- ],
- };
- element.labels = {
- Verified: {
- values: {
- '0': 'No score',
- '+1': 'good',
- '+2': 'excellent',
- '-1': 'bad',
- '-2': 'terrible',
- },
- default_value: -1,
- },
- };
- element.label = {
- name: 'Verified',
- value: null,
- };
- flush();
- assert.strictEqual(element.selectedValue, '-1');
- checkAriaCheckedValid();
- });
-
- test('default_value is null if not permitted', () => {
- element.permittedLabels = {
- Verified: [
- '-1',
- ' 0',
- '+1',
- ],
- };
- element.labels = {
- 'Code-Review': {
- values: {
- '0': 'No score',
- '+1': 'good',
- '+2': 'excellent',
- '-1': 'bad',
- '-2': 'terrible',
- },
- default_value: -1,
- },
- };
- element.label = {
- name: 'Code-Review',
- value: null,
- };
- flush();
- assert.isNull(element.selectedValue);
- });
-});
diff --git a/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row_test.ts b/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row_test.ts
new file mode 100644
index 0000000..958b11a
--- /dev/null
+++ b/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row_test.ts
@@ -0,0 +1,408 @@
+/**
+ * @license
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import '../../../test/common-test-setup-karma';
+import './gr-label-score-row';
+import {GrLabelScoreRow} from './gr-label-score-row';
+import {AccountId} from '../../../api/rest-api';
+import {GrButton} from '../../shared/gr-button/gr-button';
+
+const basicFixture = fixtureFromElement('gr-label-score-row');
+
+suite('gr-label-row-score tests', () => {
+ let element: GrLabelScoreRow;
+
+ setup(async () => {
+ element = basicFixture.instantiate();
+ element.labels = {
+ 'Code-Review': {
+ values: {
+ '0': 'No score',
+ '+1': 'good',
+ '+2': 'excellent',
+ '-1': 'bad',
+ '-2': 'terrible',
+ },
+ default_value: 0,
+ value: 1,
+ all: [
+ {
+ _account_id: 123 as unknown as AccountId,
+ value: 1,
+ },
+ ],
+ },
+ Verified: {
+ values: {
+ '0': 'No score',
+ '+1': 'good',
+ '+2': 'excellent',
+ '-1': 'bad',
+ '-2': 'terrible',
+ },
+ default_value: 0,
+ value: 1,
+ all: [
+ {
+ _account_id: 123 as unknown as AccountId,
+ value: 1,
+ },
+ ],
+ },
+ };
+
+ element.permittedLabels = {
+ 'Code-Review': ['-2', '-1', ' 0', '+1', '+2'],
+ Verified: ['-1', ' 0', '+1'],
+ };
+
+ element.labelValues = {'0': 2, '1': 3, '2': 4, '-2': 0, '-1': 1};
+
+ element.label = {
+ name: 'Verified',
+ value: '+1',
+ };
+
+ await element.updateComplete;
+ await flush();
+ });
+
+ function checkAriaCheckedValid() {
+ const items = element.labelSelector!.items;
+ assert.ok(items);
+ const selectedItem = element.selectedItem;
+ for (let i = 0; i < items!.length; i++) {
+ const item = items![i];
+ if (items![i] === selectedItem) {
+ assert.isTrue(item.hasAttribute('aria-checked'), `item ${i}`);
+ assert.equal(item.getAttribute('aria-checked'), 'true', `item ${i}`);
+ } else {
+ assert.isFalse(item.hasAttribute('aria-checked'), `item ${i}`);
+ }
+ }
+ }
+
+ test('label picker', async () => {
+ const labelsChangedHandler = sinon.stub();
+ element.addEventListener('labels-changed', labelsChangedHandler);
+ assert.ok(element.labelSelector);
+ const button = element.shadowRoot!.querySelector(
+ 'gr-button[data-value="-1"]'
+ ) as GrButton;
+ button.click();
+ await element.updateComplete;
+
+ assert.strictEqual(element.selectedValue, '-1');
+ assert.strictEqual(element.selectedItem!.textContent!.trim(), '-1');
+ const selectedValueLabel = element.shadowRoot!.querySelector(
+ '#selectedValueLabel'
+ );
+ assert.strictEqual(selectedValueLabel!.textContent!.trim(), 'bad');
+ const detail = labelsChangedHandler.args[0][0].detail;
+ assert.equal(detail.name, 'Verified');
+ assert.equal(detail.value, '-1');
+ checkAriaCheckedValid();
+ });
+
+ test('_computeVoteAttribute', () => {
+ let value = 1;
+ let index = 0;
+ const totalItems = 5;
+ // positive and first position
+ assert.equal(
+ element._computeVoteAttribute(value, index, totalItems),
+ 'positive'
+ );
+ // negative and first position
+ value = -1;
+ assert.equal(
+ element._computeVoteAttribute(value, index, totalItems),
+ 'min'
+ );
+ // negative but not first position
+ index = 1;
+ assert.equal(
+ element._computeVoteAttribute(value, index, totalItems),
+ 'negative'
+ );
+ // neutral
+ value = 0;
+ assert.equal(
+ element._computeVoteAttribute(value, index, totalItems),
+ 'neutral'
+ );
+ // positive but not last position
+ value = 1;
+ assert.equal(
+ element._computeVoteAttribute(value, index, totalItems),
+ 'positive'
+ );
+ // positive and last position
+ index = 4;
+ assert.equal(
+ element._computeVoteAttribute(value, index, totalItems),
+ 'max'
+ );
+ // negative and last position
+ value = -1;
+ assert.equal(
+ element._computeVoteAttribute(value, index, totalItems),
+ 'negative'
+ );
+ });
+
+ test('correct item is selected', () => {
+ // 1 should be the value of the selected item
+ assert.strictEqual(element.labelSelector!.selected, '+1');
+ assert.strictEqual(element.selectedItem!.textContent!.trim(), '+1');
+ const selectedValueLabel = element.shadowRoot!.querySelector(
+ '#selectedValueLabel'
+ );
+ assert.strictEqual(selectedValueLabel!.textContent!.trim(), 'good');
+ checkAriaCheckedValid();
+ });
+
+ test('_computeLabelValue', () => {
+ assert.strictEqual(element._computeLabelValue(), '+1');
+ });
+
+ test('_computeBlankItems', () => {
+ element.labelValues = {
+ '-2': 0,
+ '-1': 1,
+ '0': 2,
+ '1': 3,
+ '2': 4,
+ };
+ element.label = {name: 'Code-Review', value: ' 0'};
+ assert.strictEqual(element._computeBlankItems('start').length, 0);
+
+ element.label = {name: 'Verified', value: ' 0'};
+ assert.strictEqual(element._computeBlankItems('start').length, 1);
+ });
+
+ test('labelValues returns no keys', () => {
+ element.labelValues = {};
+ element.label = {name: 'Code-Review', value: ' 0'};
+
+ assert.deepEqual(element._computeBlankItems('start'), []);
+ });
+
+ test('changes in label score are reflected in the DOM', async () => {
+ element.labels = {
+ 'Code-Review': {
+ values: {
+ '0': 'No score',
+ '+1': 'good',
+ '+2': 'excellent',
+ '-1': 'bad',
+ '-2': 'terrible',
+ },
+ default_value: 0,
+ },
+ Verified: {
+ values: {
+ ' 0': 'No score',
+ '+1': 'good',
+ '+2': 'excellent',
+ '-1': 'bad',
+ '-2': 'terrible',
+ },
+ default_value: 0,
+ },
+ };
+ // For some reason we need the element.labels to flush first before we
+ // change the element.label
+ await element.updateComplete;
+
+ element.label = {name: 'Verified', value: ' 0'};
+ await element.updateComplete;
+ // Wait for @selected-item-changed to fire
+ await flush();
+
+ const selector = element.labelSelector;
+ assert.strictEqual(selector!.selected, ' 0');
+ const selectedValueLabel = element.shadowRoot!.querySelector(
+ '#selectedValueLabel'
+ );
+ assert.strictEqual(selectedValueLabel!.textContent!.trim(), 'No score');
+ checkAriaCheckedValid();
+ });
+
+ test('without permitted labels', async () => {
+ element.permittedLabels = {
+ Verified: ['-1', ' 0', '+1'],
+ };
+ await element.updateComplete;
+ assert.isOk(element.labelSelector);
+ assert.isFalse(element.labelSelector!.hidden);
+
+ element.permittedLabels = {};
+ await element.updateComplete;
+ assert.isOk(element.labelSelector);
+ assert.isTrue(element.labelSelector!.hidden);
+
+ element.permittedLabels = {Verified: []};
+ await element.updateComplete;
+ assert.isOk(element.labelSelector);
+ assert.isTrue(element.labelSelector!.hidden);
+ });
+
+ test('asymmetrical labels', async () => {
+ element.permittedLabels = {
+ 'Code-Review': ['-2', '-1', ' 0', '+1', '+2'],
+ Verified: [' 0', '+1'],
+ };
+ await element.updateComplete;
+ assert.strictEqual(element.labelSelector!.items!.length, 2);
+ assert.strictEqual(
+ element.shadowRoot!.querySelectorAll('.placeholder').length,
+ 3
+ );
+
+ element.permittedLabels = {
+ 'Code-Review': [' 0', '+1'],
+ Verified: ['-2', '-1', ' 0', '+1', '+2'],
+ };
+ await element.updateComplete;
+ assert.strictEqual(element.labelSelector!.items!.length, 5);
+ assert.strictEqual(
+ element.shadowRoot!.querySelectorAll('.placeholder').length,
+ 0
+ );
+ });
+
+ test('default_value', async () => {
+ element.permittedLabels = {
+ Verified: ['-1', ' 0', '+1'],
+ };
+ element.labels = {
+ Verified: {
+ values: {
+ '0': 'No score',
+ '+1': 'good',
+ '+2': 'excellent',
+ '-1': 'bad',
+ '-2': 'terrible',
+ },
+ default_value: -1,
+ },
+ };
+ element.label = {
+ name: 'Verified',
+ value: null,
+ };
+ await element.updateComplete;
+ assert.strictEqual(element.selectedValue, '-1');
+ checkAriaCheckedValid();
+ });
+
+ test('default_value is null if not permitted', async () => {
+ element.permittedLabels = {
+ Verified: ['-1', ' 0', '+1'],
+ };
+ element.labels = {
+ 'Code-Review': {
+ values: {
+ '0': 'No score',
+ '+1': 'good',
+ '+2': 'excellent',
+ '-1': 'bad',
+ '-2': 'terrible',
+ },
+ default_value: -1,
+ },
+ };
+ element.label = {
+ name: 'Code-Review',
+ value: null,
+ };
+ await element.updateComplete;
+ assert.isNull(element.selectedValue);
+ });
+
+ test('shadowDom test', () => {
+ expect(element).shadowDom.to.equal(`
+ <span class="labelNameCell" id="labelName" aria-hidden="true"
+ >Verified</span>
+ <div class="buttonsCell">
+ <span class="placeholder" data-label="Verified"></span>
+ <iron-selector
+ aria-labelledby="labelName"
+ attr-for-selected="data-value"
+ id="labelSelector"
+ role="radiogroup"
+ selected="+1"
+ >
+ <gr-button
+ aria-disabled="false"
+ aria-label="-1"
+ data-name="Verified"
+ data-value="-1"
+ role="radio"
+ tabindex="0"
+ title="bad"
+ vote="min"
+ votechip=""
+ flatten=""
+ >
+ <gr-tooltip-content light-tooltip="" has-tooltip="" title="bad">
+ -1
+ </gr-tooltip-content>
+ </gr-button>
+ <gr-button
+ aria-disabled="false"
+ aria-label=" 0"
+ data-name="Verified"
+ data-value=" 0"
+ role="radio"
+ tabindex="0"
+ vote="neutral"
+ votechip=""
+ flatten=""
+ >
+ <gr-tooltip-content light-tooltip="" has-tooltip="">
+ 0
+ </gr-tooltip-content>
+ </gr-button>
+ <gr-button
+ aria-checked="true"
+ aria-disabled="false"
+ aria-label="+1"
+ class="iron-selected"
+ data-name="Verified"
+ data-value="+1"
+ role="radio"
+ tabindex="0"
+ title="good"
+ vote="max"
+ votechip=""
+ flatten=""
+ >
+ <gr-tooltip-content light-tooltip="" has-tooltip="" title="good">
+ +1
+ </gr-tooltip-content>
+ </gr-button>
+ </iron-selector>
+ <span class="placeholder" data-label="Verified"></span>
+ </div>
+ <div class="selectedValueCell ">
+ <span id="selectedValueLabel">good</span>
+ </div>
+ `);
+ });
+});
diff --git a/polygerrit-ui/app/elements/change/gr-label-scores/gr-label-scores_test.ts b/polygerrit-ui/app/elements/change/gr-label-scores/gr-label-scores_test.ts
index f529464..0310424 100644
--- a/polygerrit-ui/app/elements/change/gr-label-scores/gr-label-scores_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-label-scores/gr-label-scores_test.ts
@@ -82,7 +82,7 @@
'Code-Review': ['-2', '-1', ' 0', '+1', '+2'],
Verified: ['-1', ' 0', '+1'],
};
- await flush();
+ await element.updateComplete;
});
test('get and set label scores', async () => {
@@ -93,7 +93,7 @@
);
row.setSelectedValue('-1');
}
- await flush();
+ await element.updateComplete;
assert.deepEqual(element.getLabelValues(), {
'Code-Review': -1,
Verified: -1,
@@ -110,7 +110,7 @@
},
},
};
- await flush();
+ await element.updateComplete;
assert.deepEqual(element.getLabelValues(true), {'Code-Review': 0});
assert.deepEqual(element.getLabelValues(false), {});
diff --git a/polygerrit-ui/app/elements/change/gr-message-scores/gr-message-scores.ts b/polygerrit-ui/app/elements/change/gr-message-scores/gr-message-scores.ts
new file mode 100644
index 0000000..f3abe13
--- /dev/null
+++ b/polygerrit-ui/app/elements/change/gr-message-scores/gr-message-scores.ts
@@ -0,0 +1,199 @@
+/**
+ * @license
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import '../gr-trigger-vote/gr-trigger-vote';
+import {LitElement, css, html} from 'lit';
+import {customElement, property} from 'lit/decorators';
+import {ChangeInfo} from '../../../api/rest-api';
+import {
+ ChangeMessage,
+ LabelExtreme,
+ PATCH_SET_PREFIX_PATTERN,
+} from '../../../utils/comment-util';
+import {hasOwnProperty} from '../../../utils/common-util';
+import {getTriggerVotes} from '../../../utils/label-util';
+import {getAppContext} from '../../../services/app-context';
+import {KnownExperimentId} from '../../../services/flags/flags';
+
+const VOTE_RESET_TEXT = '0 (vote reset)';
+
+interface Score {
+ label?: string;
+ value?: string;
+}
+
+export const LABEL_TITLE_SCORE_PATTERN =
+ /^(-?)([A-Za-z0-9-]+?)([+-]\d+)?[.:]?$/;
+
+@customElement('gr-message-scores')
+export class GrMessageScores extends LitElement {
+ @property()
+ labelExtremes?: LabelExtreme;
+
+ @property({type: Object})
+ message?: ChangeMessage;
+
+ @property({type: Object})
+ change?: ChangeInfo;
+
+ static override get styles() {
+ return css`
+ .score,
+ gr-trigger-vote {
+ padding: 0 var(--spacing-s);
+ margin-right: var(--spacing-s);
+ display: inline-block;
+ }
+ .score {
+ box-sizing: border-box;
+ border-radius: var(--border-radius);
+ color: var(--vote-text-color);
+ text-align: center;
+ min-width: 115px;
+ }
+ .score.removed {
+ background-color: var(--vote-color-neutral);
+ }
+ .score.negative {
+ background-color: var(--vote-color-disliked);
+ border: 1px solid var(--vote-outline-disliked);
+ line-height: calc(var(--line-height-normal) - 2px);
+ color: var(--chip-color);
+ }
+ .score.negative.min {
+ background-color: var(--vote-color-rejected);
+ border: none;
+ padding-top: 1px;
+ padding-bottom: 1px;
+ color: var(--vote-text-color);
+ }
+ .score.positive {
+ background-color: var(--vote-color-recommended);
+ border: 1px solid var(--vote-outline-recommended);
+ line-height: calc(var(--line-height-normal) - 2px);
+ color: var(--chip-color);
+ }
+ .score.positive.max {
+ background-color: var(--vote-color-approved);
+ border: none;
+ padding-top: 1px;
+ padding-bottom: 1px;
+ color: var(--vote-text-color);
+ }
+
+ @media screen and (max-width: 50em) {
+ .score {
+ min-width: 0px;
+ }
+ }
+ `;
+ }
+
+ private readonly flagsService = getAppContext().flagsService;
+
+ override render() {
+ const scores = this._getScores(this.message, this.labelExtremes);
+ const triggerVotes = getTriggerVotes(this.change);
+ return scores.map(score => this.renderScore(score, triggerVotes));
+ }
+
+ private renderScore(score: Score, triggerVotes: string[]) {
+ if (
+ this.flagsService.isEnabled(KnownExperimentId.SUBMIT_REQUIREMENTS_UI) &&
+ score.label &&
+ triggerVotes.includes(score.label) &&
+ !score.value?.includes(VOTE_RESET_TEXT)
+ ) {
+ const labels = this.change?.labels ?? {};
+ return html`<gr-trigger-vote
+ .label="${score.label}"
+ .labelInfo="${labels[score.label]}"
+ .change="${this.change}"
+ .mutable="${false}"
+ disable-hovercards
+ >
+ </gr-trigger-vote>`;
+ }
+ return html`<span
+ class="score ${this._computeScoreClass(score, this.labelExtremes)}"
+ >
+ ${score.label} ${score.value}
+ </span>`;
+ }
+
+ _computeScoreClass(score?: Score, labelExtremes?: LabelExtreme) {
+ // Polymer 2: check for undefined
+ if (score === undefined || labelExtremes === undefined) {
+ return '';
+ }
+ if (!score.value) {
+ return '';
+ }
+ if (score.value.includes(VOTE_RESET_TEXT)) {
+ return 'removed';
+ }
+ const classes = [];
+ if (Number(score.value) > 0) {
+ classes.push('positive');
+ } else if (Number(score.value) < 0) {
+ classes.push('negative');
+ }
+ if (score.label) {
+ const extremes = labelExtremes[score.label];
+ if (extremes) {
+ const intScore = Number(score.value);
+ if (intScore === extremes.max) {
+ classes.push('max');
+ } else if (intScore === extremes.min) {
+ classes.push('min');
+ }
+ }
+ }
+ return classes.join(' ');
+ }
+
+ _getScores(message?: ChangeMessage, labelExtremes?: LabelExtreme): Score[] {
+ if (!message || !message.message || !labelExtremes) {
+ return [];
+ }
+ const line = message.message.split('\n', 1)[0];
+ const patchSetPrefix = PATCH_SET_PREFIX_PATTERN;
+ if (!line.match(patchSetPrefix)) {
+ return [];
+ }
+ const scoresRaw = line.split(patchSetPrefix)[1];
+ if (!scoresRaw) {
+ return [];
+ }
+ return scoresRaw
+ .split(' ')
+ .map(s => s.match(LABEL_TITLE_SCORE_PATTERN))
+ .filter(
+ ms => ms && ms.length === 4 && hasOwnProperty(labelExtremes, ms[2])
+ )
+ .map(ms => {
+ const label = ms?.[2];
+ const value = ms?.[1] === '-' ? VOTE_RESET_TEXT : ms?.[3];
+ return {label, value};
+ });
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ 'gr-message-scores': GrMessageScores;
+ }
+}
diff --git a/polygerrit-ui/app/elements/change/gr-message-scores/gr-message-scores_test.ts b/polygerrit-ui/app/elements/change/gr-message-scores/gr-message-scores_test.ts
new file mode 100644
index 0000000..d9c1db1
--- /dev/null
+++ b/polygerrit-ui/app/elements/change/gr-message-scores/gr-message-scores_test.ts
@@ -0,0 +1,174 @@
+/**
+ * @license
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import '../../../test/common-test-setup-karma';
+import './gr-message-scores';
+import {
+ createChange,
+ createChangeMessage,
+ createDetailedLabelInfo,
+} from '../../../test/test-data-generators';
+import {queryAll, stubFlags} from '../../../test/test-utils';
+import {GrMessageScores} from './gr-message-scores';
+
+const basicFixture = fixtureFromElement('gr-message-scores');
+
+suite('gr-message-score tests', () => {
+ let element: GrMessageScores;
+
+ setup(async () => {
+ element = basicFixture.instantiate();
+ await element.updateComplete;
+ });
+
+ test('votes', async () => {
+ element.message = {
+ ...createChangeMessage(),
+ author: {},
+ expanded: false,
+ message: 'Patch Set 1: Verified+1 Code-Review-2 Trybot-Label3+1 Blub+1',
+ };
+ element.labelExtremes = {
+ Verified: {max: 1, min: -1},
+ 'Code-Review': {max: 2, min: -2},
+ 'Trybot-Label3': {max: 3, min: 0},
+ };
+ await element.updateComplete;
+ const scoreChips = queryAll(element, '.score');
+ assert.equal(scoreChips.length, 3);
+
+ assert.isTrue(scoreChips[0].classList.contains('positive'));
+ assert.isTrue(scoreChips[0].classList.contains('max'));
+
+ assert.isTrue(scoreChips[1].classList.contains('negative'));
+ assert.isTrue(scoreChips[1].classList.contains('min'));
+
+ assert.isTrue(scoreChips[2].classList.contains('positive'));
+ assert.isFalse(scoreChips[2].classList.contains('min'));
+ });
+
+ test('Uploaded patch set X', async () => {
+ element.message = {
+ ...createChangeMessage(),
+ author: {},
+ expanded: false,
+ message:
+ 'Uploaded patch set 1:' +
+ 'Verified+1 Code-Review-2 Trybot-Label3+1 Blub+1',
+ };
+ element.labelExtremes = {
+ Verified: {max: 1, min: -1},
+ 'Code-Review': {max: 2, min: -2},
+ 'Trybot-Label3': {max: 3, min: 0},
+ };
+ await element.updateComplete;
+ const scoreChips = queryAll(element, '.score');
+ assert.equal(scoreChips.length, 3);
+
+ assert.isTrue(scoreChips[0].classList.contains('positive'));
+ assert.isTrue(scoreChips[0].classList.contains('max'));
+
+ assert.isTrue(scoreChips[1].classList.contains('negative'));
+ assert.isTrue(scoreChips[1].classList.contains('min'));
+
+ assert.isTrue(scoreChips[2].classList.contains('positive'));
+ assert.isFalse(scoreChips[2].classList.contains('min'));
+ });
+
+ test('Uploaded and rebased', async () => {
+ element.message = {
+ ...createChangeMessage(),
+ author: {},
+ expanded: false,
+ message: 'Uploaded patch set 4: Commit-Queue+1: Patch Set 3 was rebased.',
+ };
+ element.labelExtremes = {
+ 'Commit-Queue': {max: 2, min: -2},
+ };
+ await element.updateComplete;
+ const scoreChips = queryAll(element, '.score');
+ assert.equal(scoreChips.length, 1);
+ assert.isTrue(scoreChips[0].classList.contains('positive'));
+ });
+
+ test('removed votes', async () => {
+ element.message = {
+ ...createChangeMessage(),
+ author: {},
+ expanded: false,
+ message: 'Patch Set 1: Verified+1 -Code-Review -Commit-Queue',
+ };
+ element.labelExtremes = {
+ Verified: {max: 1, min: -1},
+ 'Code-Review': {max: 2, min: -2},
+ 'Commit-Queue': {max: 3, min: 0},
+ };
+ await element.updateComplete;
+ const scoreChips = queryAll(element, '.score');
+ assert.equal(scoreChips.length, 3);
+
+ assert.isTrue(scoreChips[1].classList.contains('removed'));
+ assert.isTrue(scoreChips[2].classList.contains('removed'));
+ });
+
+ test('false negative vote', async () => {
+ element.message = {
+ ...createChangeMessage(),
+ author: {},
+ expanded: false,
+ message: 'Patch Set 1: Cherry Picked from branch stable-2.14.',
+ };
+ element.labelExtremes = {};
+ await element.updateComplete;
+ const scoreChips = element.shadowRoot?.querySelectorAll('.score');
+ assert.equal(scoreChips?.length, 0);
+ });
+
+ test('reset vote', async () => {
+ stubFlags('isEnabled').returns(true);
+ element = basicFixture.instantiate();
+ element.change = {
+ ...createChange(),
+ labels: {
+ 'Commit-Queue': createDetailedLabelInfo(),
+ 'Auto-Submit': createDetailedLabelInfo(),
+ },
+ };
+ element.message = {
+ ...createChangeMessage(),
+ author: {},
+ expanded: false,
+ message: 'Patch Set 10: Auto-Submit+1 -Commit-Queue',
+ };
+ element.labelExtremes = {
+ 'Commit-Queue': {max: 2, min: 0},
+ 'Auto-Submit': {max: 1, min: 0},
+ };
+ await element.updateComplete;
+ const triggerChips =
+ element.shadowRoot?.querySelectorAll('gr-trigger-vote');
+ assert.equal(triggerChips?.length, 1);
+ expect(triggerChips?.[0]).shadowDom.equal(`<div class="container">
+ <span class="label">Auto-Submit</span>
+ </div>`);
+ const scoreChips = element.shadowRoot?.querySelectorAll('.score');
+ assert.equal(scoreChips?.length, 1);
+ expect(scoreChips?.[0]).dom.equal(`<span class="removed score">
+ Commit-Queue 0 (vote reset)
+ </span>`);
+ });
+});
diff --git a/polygerrit-ui/app/elements/change/gr-message/gr-message.ts b/polygerrit-ui/app/elements/change/gr-message/gr-message.ts
index cc7cf88..8664de3 100644
--- a/polygerrit-ui/app/elements/change/gr-message/gr-message.ts
+++ b/polygerrit-ui/app/elements/change/gr-message/gr-message.ts
@@ -22,26 +22,29 @@
import '../../shared/gr-date-formatter/gr-date-formatter';
import '../../shared/gr-formatted-text/gr-formatted-text';
import '../../../styles/shared-styles';
+import '../gr-message-scores/gr-message-scores';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-message_html';
import {MessageTag, SpecialFilePath} from '../../../constants/constants';
import {customElement, property, computed, observe} from '@polymer/decorators';
import {
ChangeInfo,
- ChangeMessageInfo,
ServerInfo,
ConfigInfo,
RepoName,
ReviewInputTag,
- VotingRangeInfo,
NumericChangeId,
ChangeMessageId,
PatchSetNum,
AccountInfo,
BasePatchSetNum,
} from '../../../types/common';
-import {CommentThread} from '../../../utils/comment-util';
-import {hasOwnProperty} from '../../../utils/common-util';
+import {
+ ChangeMessage,
+ CommentThread,
+ LabelExtreme,
+ PATCH_SET_PREFIX_PATTERN,
+} from '../../../utils/comment-util';
import {getAppContext} from '../../../services/app-context';
import {pluralize} from '../../../utils/string-util';
import {GerritNav} from '../../core/gr-navigation/gr-navigation';
@@ -52,12 +55,8 @@
} from '../../../utils/patch-set-util';
import {isServiceUser, replaceTemplates} from '../../../utils/account-util';
-const PATCH_SET_PREFIX_PATTERN = /^(?:Uploaded\s*)?[Pp]atch [Ss]et \d+:\s*(.*)/;
-const LABEL_TITLE_SCORE_PATTERN = /^(-?)([A-Za-z0-9-]+?)([+-]\d+)?[.:]?$/;
const UPLOADED_NEW_PATCHSET_PATTERN = /Uploaded patch set (\d+)./;
const MERGED_PATCHSET_PATTERN = /(\d+) is the latest approved patch-set/;
-const VOTE_RESET_TEXT = '0 (vote reset)';
-
declare global {
interface HTMLElementTagNameMap {
'gr-message': GrMessage;
@@ -68,20 +67,6 @@
id: ChangeMessageId;
}
-export interface ChangeMessage extends ChangeMessageInfo {
- // TODO(TS): maybe should be an enum instead
- type: string;
- expanded: boolean;
- commentThreads: CommentThread[];
-}
-
-export type LabelExtreme = {[labelName: string]: VotingRangeInfo};
-
-interface Score {
- label?: string;
- value?: string;
-}
-
@customElement('gr-message')
export class GrMessage extends PolymerElement {
static get template() {
@@ -444,63 +429,6 @@
return message.type === 'REVIEWER_UPDATE';
}
- _getScores(message?: ChangeMessage, labelExtremes?: LabelExtreme): Score[] {
- if (!message || !message.message || !labelExtremes) {
- return [];
- }
- const line = message.message.split('\n', 1)[0];
- const patchSetPrefix = PATCH_SET_PREFIX_PATTERN;
- if (!line.match(patchSetPrefix)) {
- return [];
- }
- const scoresRaw = line.split(patchSetPrefix)[1];
- if (!scoresRaw) {
- return [];
- }
- return scoresRaw
- .split(' ')
- .map(s => s.match(LABEL_TITLE_SCORE_PATTERN))
- .filter(
- ms => ms && ms.length === 4 && hasOwnProperty(labelExtremes, ms[2])
- )
- .map(ms => {
- const label = ms?.[2];
- const value = ms?.[1] === '-' ? VOTE_RESET_TEXT : ms?.[3];
- return {label, value};
- });
- }
-
- _computeScoreClass(score?: Score, labelExtremes?: LabelExtreme) {
- // Polymer 2: check for undefined
- if (score === undefined || labelExtremes === undefined) {
- return '';
- }
- if (!score.value) {
- return '';
- }
- if (score.value.includes(VOTE_RESET_TEXT)) {
- return 'removed';
- }
- const classes = [];
- if (Number(score.value) > 0) {
- classes.push('positive');
- } else if (Number(score.value) < 0) {
- classes.push('negative');
- }
- if (score.label) {
- const extremes = labelExtremes[score.label];
- if (extremes) {
- const intScore = Number(score.value);
- if (intScore === extremes.max) {
- classes.push('max');
- } else if (intScore === extremes.min) {
- classes.push('min');
- }
- }
- }
- return classes.join(' ');
- }
-
_computeClass(expanded?: boolean, author?: AccountInfo) {
const classes = [];
classes.push(expanded ? 'expanded' : 'collapsed');
diff --git a/polygerrit-ui/app/elements/change/gr-message/gr-message_html.ts b/polygerrit-ui/app/elements/change/gr-message/gr-message_html.ts
index 8def279..70e6381 100644
--- a/polygerrit-ui/app/elements/change/gr-message/gr-message_html.ts
+++ b/polygerrit-ui/app/elements/change/gr-message/gr-message_html.ts
@@ -144,15 +144,6 @@
cursor: pointer;
vertical-align: top;
}
- .score {
- box-sizing: border-box;
- border-radius: var(--border-radius);
- color: var(--vote-text-color);
- display: inline-block;
- padding: 0 var(--spacing-s);
- text-align: center;
- }
- .score,
.commentsSummary {
margin-right: var(--spacing-s);
min-width: 115px;
@@ -163,35 +154,6 @@
.commentsIcon {
vertical-align: top;
}
- .score.removed {
- background-color: var(--vote-color-neutral);
- }
- .score.negative {
- background-color: var(--vote-color-disliked);
- border: 1px solid var(--vote-outline-disliked);
- line-height: calc(var(--line-height-normal) - 2px);
- color: var(--chip-color);
- }
- .score.negative.min {
- background-color: var(--vote-color-rejected);
- border: none;
- padding-top: 1px;
- padding-bottom: 1px;
- color: var(--vote-text-color);
- }
- .score.positive {
- background-color: var(--vote-color-recommended);
- border: 1px solid var(--vote-outline-recommended);
- line-height: calc(var(--line-height-normal) - 2px);
- color: var(--chip-color);
- }
- .score.positive.max {
- background-color: var(--vote-color-approved);
- border: none;
- padding-top: 1px;
- padding-bottom: 1px;
- color: var(--vote-text-color);
- }
gr-account-label::part(gr-account-label-text) {
font-weight: var(--font-weight-bold);
}
@@ -203,7 +165,6 @@
.expanded .content {
padding-left: 0;
}
- .score,
.commentsSummary {
min-width: 0px;
}
@@ -226,15 +187,11 @@
account="[[author]]"
class="authorLabel"
></gr-account-label>
- <template
- is="dom-repeat"
- items="[[_getScores(message, labelExtremes)]]"
- as="score"
- >
- <span class$="score [[_computeScoreClass(score, labelExtremes)]]">
- [[score.label]] [[score.value]]
- </span>
- </template>
+ <gr-message-scores
+ label-extremes="[[labelExtremes]]"
+ message="[[message]]"
+ change="[[change]]"
+ ></gr-message-scores>
</div>
<template is="dom-if" if="[[_commentCountText]]">
<div class="commentsSummary">
diff --git a/polygerrit-ui/app/elements/change/gr-message/gr-message_test.ts b/polygerrit-ui/app/elements/change/gr-message/gr-message_test.ts
index 0fd39d2..2acc2a8 100644
--- a/polygerrit-ui/app/elements/change/gr-message/gr-message_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-message/gr-message_test.ts
@@ -28,7 +28,6 @@
import {
mockPromise,
query,
- queryAll,
queryAndAssert,
stubRestApi,
} from '../../../test/test-utils';
@@ -463,109 +462,6 @@
assert.equal(actual, expected);
});
});
-
- test('votes', () => {
- element.message = {
- ...createChangeMessage(),
- author: {},
- expanded: false,
- message: 'Patch Set 1: Verified+1 Code-Review-2 Trybot-Label3+1 Blub+1',
- };
- element.labelExtremes = {
- Verified: {max: 1, min: -1},
- 'Code-Review': {max: 2, min: -2},
- 'Trybot-Label3': {max: 3, min: 0},
- };
- flush();
- const scoreChips = queryAll(element, '.score');
- assert.equal(scoreChips.length, 3);
-
- assert.isTrue(scoreChips[0].classList.contains('positive'));
- assert.isTrue(scoreChips[0].classList.contains('max'));
-
- assert.isTrue(scoreChips[1].classList.contains('negative'));
- assert.isTrue(scoreChips[1].classList.contains('min'));
-
- assert.isTrue(scoreChips[2].classList.contains('positive'));
- assert.isFalse(scoreChips[2].classList.contains('min'));
- });
-
- test('Uploaded patch set X', () => {
- element.message = {
- ...createChangeMessage(),
- author: {},
- expanded: false,
- message:
- 'Uploaded patch set 1:' +
- 'Verified+1 Code-Review-2 Trybot-Label3+1 Blub+1',
- };
- element.labelExtremes = {
- Verified: {max: 1, min: -1},
- 'Code-Review': {max: 2, min: -2},
- 'Trybot-Label3': {max: 3, min: 0},
- };
- flush();
- const scoreChips = queryAll(element, '.score');
- assert.equal(scoreChips.length, 3);
-
- assert.isTrue(scoreChips[0].classList.contains('positive'));
- assert.isTrue(scoreChips[0].classList.contains('max'));
-
- assert.isTrue(scoreChips[1].classList.contains('negative'));
- assert.isTrue(scoreChips[1].classList.contains('min'));
-
- assert.isTrue(scoreChips[2].classList.contains('positive'));
- assert.isFalse(scoreChips[2].classList.contains('min'));
- });
-
- test('Uploaded and rebased', () => {
- element.message = {
- ...createChangeMessage(),
- author: {},
- expanded: false,
- message:
- 'Uploaded patch set 4: Commit-Queue+1: Patch Set 3 was rebased.',
- };
- element.labelExtremes = {
- 'Commit-Queue': {max: 2, min: -2},
- };
- flush();
- const scoreChips = queryAll(element, '.score');
- assert.equal(scoreChips.length, 1);
- assert.isTrue(scoreChips[0].classList.contains('positive'));
- });
-
- test('removed votes', () => {
- element.message = {
- ...createChangeMessage(),
- author: {},
- expanded: false,
- message: 'Patch Set 1: Verified+1 -Code-Review -Commit-Queue',
- };
- element.labelExtremes = {
- Verified: {max: 1, min: -1},
- 'Code-Review': {max: 2, min: -2},
- 'Commit-Queue': {max: 3, min: 0},
- };
- flush();
- const scoreChips = queryAll(element, '.score');
- assert.equal(scoreChips.length, 3);
-
- assert.isTrue(scoreChips[1].classList.contains('removed'));
- assert.isTrue(scoreChips[2].classList.contains('removed'));
- });
-
- test('false negative vote', () => {
- element.message = {
- ...createChangeMessage(),
- author: {},
- expanded: false,
- message: 'Patch Set 1: Cherry Picked from branch stable-2.14.',
- };
- element.labelExtremes = {};
- const scoreChips = element.root!.querySelectorAll('.score');
- assert.equal(scoreChips.length, 0);
- });
});
suite('when not logged in', () => {
diff --git a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.ts b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.ts
index 3c1baa6..851cf9a 100644
--- a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.ts
+++ b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.ts
@@ -41,7 +41,11 @@
ReviewerUpdateInfo,
VotingRangeInfo,
} from '../../../types/common';
-import {CommentThread, isRobot} from '../../../utils/comment-util';
+import {
+ CommentThread,
+ isRobot,
+ LabelExtreme,
+} from '../../../utils/comment-util';
import {GrMessage, MessageAnchorTapDetail} from '../gr-message/gr-message';
import {PolymerDeepPropertyChange} from '@polymer/polymer/interfaces';
import {DomRepeat} from '@polymer/polymer/lib/elements/dom-repeat';
@@ -249,7 +253,7 @@
_combinedMessages: CombinedMessage[] = [];
@property({type: Object, computed: '_computeLabelExtremes(labels.*)'})
- _labelExtremes: {[labelName: string]: VotingRangeInfo} = {};
+ _labelExtremes: LabelExtreme = {};
private readonly userModel = getAppContext().userModel;
diff --git a/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list.ts b/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list.ts
index b236173..86754c0 100644
--- a/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list.ts
+++ b/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list.ts
@@ -47,6 +47,7 @@
showNewSubmitRequirements,
} from '../../../utils/label-util';
import {sortReviewers} from '../../../utils/attention-set-util';
+import {KnownExperimentId} from '../../../services/flags/flags';
@customElement('gr-reviewer-list')
export class GrReviewerList extends PolymerElement {
@@ -233,6 +234,9 @@
}
_computeCanRemoveReviewer(reviewer: AccountInfo, mutable: boolean) {
+ if (this.flagsService.isEnabled(KnownExperimentId.SUBMIT_REQUIREMENTS_UI)) {
+ return false;
+ }
return mutable && isRemovableReviewer(this.change, reviewer);
}
diff --git a/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list_html.ts b/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list_html.ts
index dfe69a6..b697cd5 100644
--- a/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list_html.ts
+++ b/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list_html.ts
@@ -58,7 +58,6 @@
gr-vote-chip {
--gr-vote-chip-width: 14px;
--gr-vote-chip-height: 14px;
- margin-right: var(--spacing-s);
}
</style>
<div class="container">
@@ -72,12 +71,15 @@
highlightAttention
voteable-text="[[_computeVoteableText(reviewer, change)]]"
removable="[[_computeCanRemoveReviewer(reviewer, mutable)]]"
+ vote="[[_computeVote(reviewer, change)]]"
+ label="[[_computeCodeReviewLabel(change)]]"
>
<template is="dom-if" if="[[showNewSubmitRequirements(change)]]">
<gr-vote-chip
slot="vote-chip"
vote="[[_computeVote(reviewer, change)]]"
label="[[_computeCodeReviewLabel(change)]]"
+ circle-shape
></gr-vote-chip>
</template>
</gr-account-chip>
diff --git a/polygerrit-ui/app/elements/change/gr-submit-requirements/gr-submit-requirements.ts b/polygerrit-ui/app/elements/change/gr-submit-requirements/gr-submit-requirements.ts
index 15a971a1..8216f7b 100644
--- a/polygerrit-ui/app/elements/change/gr-submit-requirements/gr-submit-requirements.ts
+++ b/polygerrit-ui/app/elements/change/gr-submit-requirements/gr-submit-requirements.ts
@@ -16,9 +16,10 @@
*/
import '../../shared/gr-label-info/gr-label-info';
import '../gr-submit-requirement-hovercard/gr-submit-requirement-hovercard';
-import '../gr-trigger-vote-hovercard/gr-trigger-vote-hovercard';
+import '../gr-trigger-vote/gr-trigger-vote';
import '../gr-change-summary/gr-change-summary';
import '../../shared/gr-limited-text/gr-limited-text';
+import '../../shared/gr-vote-chip/gr-vote-chip';
import {LitElement, css, html, TemplateResult} from 'lit';
import {customElement, property, state} from 'lit/decorators';
import {ParsedChangeInfo} from '../../../types/types';
@@ -26,7 +27,6 @@
AccountInfo,
isDetailedLabelInfo,
isQuickLabelInfo,
- LabelInfo,
LabelNameToInfoMap,
SubmitRequirementResultInfo,
SubmitRequirementStatus,
@@ -47,7 +47,6 @@
import {CheckRun} from '../../../models/checks/checks-model';
import {getResultsOf, hasResultsOf} from '../../../models/checks/checks-util';
import {Category} from '../../../api/checks';
-import '../../shared/gr-vote-chip/gr-vote-chip';
import {fireShowPrimaryTab} from '../../../utils/event-util';
import {PrimaryTab} from '../../../constants/constants';
import {submitRequirementsStyles} from '../../../styles/gr-submit-requirements-styles';
@@ -219,7 +218,8 @@
requirement: SubmitRequirementResultInfo,
slot: TemplateResult
) {
- if (this.disableEndpoints) return slot;
+ if (this.disableEndpoints)
+ return html`<div class="votes-cell">${slot}</div>`;
const endpointName = this.calculateEndpointName(requirement.name);
return html`<gr-endpoint-decorator
@@ -357,6 +357,7 @@
.change="${this.change}"
.account="${this.account}"
.mutable="${this.mutable ?? false}"
+ .disableHovercards=${this.disableHovercards}
></gr-trigger-vote>`
)}
</section>`;
@@ -371,103 +372,8 @@
}
}
-@customElement('gr-trigger-vote')
-export class GrTriggerVote extends LitElement {
- @property()
- label?: string;
-
- @property({type: Object})
- labelInfo?: LabelInfo;
-
- @property({type: Object})
- change?: ParsedChangeInfo;
-
- @property({type: Object})
- account?: AccountInfo;
-
- @property({type: Boolean})
- mutable?: boolean;
-
- static override get styles() {
- return css`
- :host {
- display: block;
- }
- .container {
- box-sizing: border-box;
- border: 1px solid var(--border-color);
- border-radius: calc(var(--border-radius) + 2px);
- background-color: var(--background-color-primary);
- display: flex;
- padding: 0;
- padding-left: var(--spacing-s);
- padding-right: var(--spacing-xxs);
- align-items: center;
- }
- .label {
- padding-right: var(--spacing-s);
- font-weight: var(--font-weight-bold);
- }
- gr-vote-chip {
- --gr-vote-chip-width: 14px;
- --gr-vote-chip-height: 14px;
- margin-right: 0px;
- margin-left: var(--spacing-xs);
- }
- gr-vote-chip:first-of-type {
- margin-left: 0px;
- }
- `;
- }
-
- override render() {
- if (!this.labelInfo) return;
- return html`
- <div class="container">
- <gr-trigger-vote-hovercard
- .labelName=${this.label}
- .labelInfo=${this.labelInfo}
- >
- <gr-label-info
- slot="label-info"
- .change=${this.change}
- .account=${this.account}
- .mutable=${this.mutable}
- .label=${this.label}
- .labelInfo=${this.labelInfo}
- .showAllReviewers=${false}
- ></gr-label-info>
- </gr-trigger-vote-hovercard>
- <span class="label">${this.label}</span>
- ${this.renderVotes()}
- </div>
- `;
- }
-
- private renderVotes() {
- const {labelInfo} = this;
- if (!labelInfo) return;
- if (isDetailedLabelInfo(labelInfo)) {
- const approvals = getAllUniqueApprovals(labelInfo).filter(
- approval => !hasNeutralStatus(labelInfo, approval)
- );
- return approvals.map(
- approvalInfo => html`<gr-vote-chip
- .vote="${approvalInfo}"
- .label="${labelInfo}"
- ></gr-vote-chip>`
- );
- } else if (isQuickLabelInfo(labelInfo)) {
- return [html`<gr-vote-chip .label="${this.labelInfo}"></gr-vote-chip>`];
- } else {
- return html``;
- }
- }
-}
-
declare global {
interface HTMLElementTagNameMap {
'gr-submit-requirements': GrSubmitRequirements;
- 'gr-trigger-vote': GrTriggerVote;
}
}
diff --git a/polygerrit-ui/app/elements/change/gr-submit-requirements/gr-submit-requirements_test.ts b/polygerrit-ui/app/elements/change/gr-submit-requirements/gr-submit-requirements_test.ts
index 323c70f..37fa767 100644
--- a/polygerrit-ui/app/elements/change/gr-submit-requirements/gr-submit-requirements_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-submit-requirements/gr-submit-requirements_test.ts
@@ -27,6 +27,7 @@
createParsedChange,
createSubmitRequirementExpressionInfo,
createSubmitRequirementResultInfo,
+ createNonApplicableSubmitRequirementResultInfo,
} from '../../../test/test-data-generators';
import {SubmitRequirementResultInfo} from '../../../api/rest-api';
import {ParsedChangeInfo} from '../../../types/types';
@@ -44,7 +45,10 @@
};
const change: ParsedChangeInfo = {
...createParsedChange(),
- submit_requirements: [submitRequirement],
+ submit_requirements: [
+ submitRequirement,
+ createNonApplicableSubmitRequirementResultInfo(),
+ ],
labels: {
Verified: {
...createDetailedLabelInfo(),
diff --git a/polygerrit-ui/app/elements/change/gr-trigger-vote/gr-trigger-vote.ts b/polygerrit-ui/app/elements/change/gr-trigger-vote/gr-trigger-vote.ts
new file mode 100644
index 0000000..a00de22
--- /dev/null
+++ b/polygerrit-ui/app/elements/change/gr-trigger-vote/gr-trigger-vote.ts
@@ -0,0 +1,140 @@
+/**
+ * @license
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import '../../shared/gr-label-info/gr-label-info';
+import '../../shared/gr-vote-chip/gr-vote-chip';
+import '../gr-trigger-vote-hovercard/gr-trigger-vote-hovercard';
+import {LitElement, css, html} from 'lit';
+import {customElement, property} from 'lit/decorators';
+import {ParsedChangeInfo} from '../../../types/types';
+import {
+ AccountInfo,
+ isDetailedLabelInfo,
+ isQuickLabelInfo,
+ LabelInfo,
+} from '../../../api/rest-api';
+import {
+ getAllUniqueApprovals,
+ hasNeutralStatus,
+} from '../../../utils/label-util';
+
+@customElement('gr-trigger-vote')
+export class GrTriggerVote extends LitElement {
+ @property()
+ label?: string;
+
+ @property({type: Object})
+ labelInfo?: LabelInfo;
+
+ @property({type: Object})
+ change?: ParsedChangeInfo;
+
+ @property({type: Object})
+ account?: AccountInfo;
+
+ @property({type: Boolean})
+ mutable?: boolean;
+
+ @property({type: Boolean, attribute: 'disable-hovercards'})
+ disableHovercards = false;
+
+ static override get styles() {
+ return css`
+ :host {
+ display: block;
+ }
+ .container {
+ box-sizing: border-box;
+ border: 1px solid var(--border-color);
+ border-radius: calc(var(--border-radius) + 2px);
+ background-color: var(--background-color-primary);
+ display: flex;
+ padding: 0;
+ padding-left: var(--spacing-s);
+ padding-right: var(--spacing-xxs);
+ align-items: center;
+ }
+ .label {
+ padding-right: var(--spacing-s);
+ font-weight: var(--font-weight-bold);
+ }
+ gr-vote-chip {
+ --gr-vote-chip-width: 14px;
+ --gr-vote-chip-height: 14px;
+ margin-right: 0px;
+ margin-left: var(--spacing-xs);
+ }
+ gr-vote-chip:first-of-type {
+ margin-left: 0px;
+ }
+ `;
+ }
+
+ override render() {
+ if (!this.labelInfo) return;
+ return html`
+ <div class="container">
+ ${this.renderHovercard()}
+ <span class="label">${this.label}</span>
+ ${this.renderVotes()}
+ </div>
+ `;
+ }
+
+ private renderHovercard() {
+ if (this.disableHovercards) return;
+ return html`<gr-trigger-vote-hovercard
+ .labelName=${this.label}
+ .labelInfo=${this.labelInfo}
+ >
+ <gr-label-info
+ slot="label-info"
+ .change=${this.change}
+ .account=${this.account}
+ .mutable=${this.mutable}
+ .label=${this.label}
+ .labelInfo=${this.labelInfo}
+ .showAllReviewers=${false}
+ ></gr-label-info>
+ </gr-trigger-vote-hovercard>`;
+ }
+
+ private renderVotes() {
+ const {labelInfo} = this;
+ if (!labelInfo) return;
+ if (isDetailedLabelInfo(labelInfo)) {
+ const approvals = getAllUniqueApprovals(labelInfo).filter(
+ approval => !hasNeutralStatus(labelInfo, approval)
+ );
+ return approvals.map(
+ approvalInfo => html`<gr-vote-chip
+ .vote="${approvalInfo}"
+ .label="${labelInfo}"
+ ></gr-vote-chip>`
+ );
+ } else if (isQuickLabelInfo(labelInfo)) {
+ return [html`<gr-vote-chip .label="${this.labelInfo}"></gr-vote-chip>`];
+ } else {
+ return html``;
+ }
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ 'gr-trigger-vote': GrTriggerVote;
+ }
+}
diff --git a/polygerrit-ui/app/elements/change/gr-trigger-vote/gr-trigger-vote_test.ts b/polygerrit-ui/app/elements/change/gr-trigger-vote/gr-trigger-vote_test.ts
new file mode 100644
index 0000000..cd25da0
--- /dev/null
+++ b/polygerrit-ui/app/elements/change/gr-trigger-vote/gr-trigger-vote_test.ts
@@ -0,0 +1,90 @@
+/**
+ * @license
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import '../../../test/common-test-setup-karma';
+import {fixture} from '@open-wc/testing-helpers';
+import {html} from 'lit';
+import './gr-trigger-vote';
+import {GrTriggerVote} from './gr-trigger-vote';
+import {
+ createAccountWithIdNameAndEmail,
+ createApproval,
+ createDetailedLabelInfo,
+ createParsedChange,
+ createSubmitRequirementExpressionInfo,
+ createSubmitRequirementResultInfo,
+ createNonApplicableSubmitRequirementResultInfo,
+} from '../../../test/test-data-generators';
+import {SubmitRequirementResultInfo} from '../../../api/rest-api';
+import {ParsedChangeInfo} from '../../../types/types';
+
+suite('gr-trigger-vote tests', () => {
+ let element: GrTriggerVote;
+ setup(async () => {
+ const submitRequirement: SubmitRequirementResultInfo = {
+ ...createSubmitRequirementResultInfo(),
+ description: 'Test Description',
+ submittability_expression_result: {
+ ...createSubmitRequirementExpressionInfo(),
+ expression: 'label:Verified=MAX -label:Verified=MIN',
+ },
+ };
+ const change: ParsedChangeInfo = {
+ ...createParsedChange(),
+ submit_requirements: [
+ submitRequirement,
+ createNonApplicableSubmitRequirementResultInfo(),
+ ],
+ labels: {
+ Verified: {
+ ...createDetailedLabelInfo(),
+ all: [
+ {
+ ...createApproval(),
+ value: 2,
+ },
+ ],
+ },
+ },
+ };
+ const account = createAccountWithIdNameAndEmail();
+ const label = 'Verified';
+ const labelInfo = change?.labels?.[label];
+ element = await fixture<GrTriggerVote>(
+ html`<gr-trigger-vote
+ .label="${label}"
+ .labelInfo="${labelInfo}"
+ .change="${change}"
+ .account="${account}"
+ .mutable="${false}"
+ ></gr-trigger-vote>`
+ );
+ });
+
+ test('renders', () => {
+ expect(element).shadowDom.to.equal(`<div class="container">
+ <gr-trigger-vote-hovercard>
+ <gr-label-info slot="label-info"></gr-label-info>
+ </gr-trigger-vote-hovercard>
+ <span class="label">
+ Verified
+ </span>
+ <gr-vote-chip>
+ </gr-vote-chip>
+ </div>`);
+ });
+});
diff --git a/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation.ts b/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation.ts
index f8f4787..8541d5d 100644
--- a/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation.ts
+++ b/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation.ts
@@ -249,17 +249,11 @@
commentLink?: boolean;
}
-export interface GenerateUrlTopicViewParams {
- view: GerritView.TOPIC;
- topic?: string;
-}
-
export type GenerateUrlParameters =
| GenerateUrlSearchViewParameters
| GenerateUrlChangeViewParameters
| GenerateUrlRepoViewParameters
| GenerateUrlDashboardViewParameters
- | GenerateUrlTopicViewParams
| GenerateUrlGroupViewParameters
| GenerateUrlEditViewParameters
| GenerateUrlRootViewParameters
@@ -617,16 +611,6 @@
);
},
- navigateToTopicPage(topic: string) {
- this._navigate(
- this._getUrlFor({
- view: GerritView.TOPIC,
- topic,
- }),
- true /* redirect */
- );
- },
-
/**
* @param basePatchNum The string 'PARENT' can be used for none.
*/
diff --git a/polygerrit-ui/app/elements/core/gr-router/gr-router.ts b/polygerrit-ui/app/elements/core/gr-router/gr-router.ts
index 5c0a2ea..2680a31 100644
--- a/polygerrit-ui/app/elements/core/gr-router/gr-router.ts
+++ b/polygerrit-ui/app/elements/core/gr-router/gr-router.ts
@@ -43,7 +43,6 @@
isGenerateUrlDiffViewParameters,
RepoDetailView,
WeblinkType,
- GenerateUrlTopicViewParams,
} from '../gr-navigation/gr-navigation';
import {getAppContext} from '../../../services/app-context';
import {convertToPatchSetNum} from '../../../utils/patch-set-util';
@@ -78,14 +77,11 @@
toSearchParams,
} from '../../../utils/url-util';
import {Execution, LifeCycle, Timing} from '../../../constants/reporting';
-import {KnownExperimentId} from '../../../services/flags/flags';
const RoutePattern = {
ROOT: '/',
DASHBOARD: /^\/dashboard\/(.+)$/,
- // TODO(dhruvsri): remove /c once Change 322894 lands
- TOPIC: /^\/c\/topic\/([^/]*)\/?$/,
CUSTOM_DASHBOARD: /^\/dashboard\/?$/,
PROJECT_DASHBOARD: /^\/p\/(.+)\/\+\/dashboard\/(.+)/,
LEGACY_PROJECT_DASHBOARD: /^\/projects\/(.+),dashboards\/(.+)/,
@@ -269,7 +265,7 @@
// custom element having the id "app", but it is made explicit here.
// If you move this code to other place, please update comment about
// gr-router and gr-app in the PolyGerritIndexHtml.soy file if needed
-const app = document.querySelector('#app');
+const app = document.querySelector('gr-app');
if (!app) {
console.info('No gr-app found (running tests)');
}
@@ -315,8 +311,6 @@
private readonly restApiService = getAppContext().restApiService;
- private readonly flagsService = getAppContext().flagsService;
-
start() {
if (!this._app) {
return;
@@ -363,8 +357,6 @@
url = this._generateChangeUrl(params);
} else if (params.view === GerritView.DASHBOARD) {
url = this._generateDashboardUrl(params);
- } else if (params.view === GerritView.TOPIC) {
- url = this._generateTopicPageUrl(params);
} else if (
params.view === GerritView.DIFF ||
params.view === GerritView.EDIT
@@ -587,10 +579,6 @@
}
}
- _generateTopicPageUrl(params: GenerateUrlTopicViewParams) {
- return `/c/topic/${params.topic ?? ''}`;
- }
-
_sectionsToEncodedParams(sections: DashboardSection[], repoName?: RepoName) {
return sections.map(section => {
// If there is a repo name provided, make sure to substitute it into the
@@ -907,8 +895,6 @@
this._mapRoute(RoutePattern.DASHBOARD, '_handleDashboardRoute');
- this._mapRoute(RoutePattern.TOPIC, '_handleTopicRoute');
-
this._mapRoute(
RoutePattern.CUSTOM_DASHBOARD,
'_handleCustomDashboardRoute'
@@ -1233,13 +1219,6 @@
});
}
- _handleTopicRoute(data: PageContextWithQueryMap) {
- this._setParams({
- view: GerritView.TOPIC,
- topic: data.params[0],
- });
- }
-
/**
* Handle custom dashboard routes.
*
@@ -1557,16 +1536,6 @@
}
_handleQueryRoute(data: PageContextWithQueryMap) {
- if (this.flagsService.isEnabled(KnownExperimentId.TOPICS_PAGE)) {
- const query = data.params[0];
- const terms = query.split(' ');
- if (terms.length === 1) {
- const tokens = terms[0].split(':');
- if (tokens[0] === 'topic') {
- return GerritNav.navigateToTopicPage(tokens[1]);
- }
- }
- }
this._setParams({
view: GerritView.SEARCH,
query: data.params[0],
diff --git a/polygerrit-ui/app/elements/core/gr-router/gr-router_test.js b/polygerrit-ui/app/elements/core/gr-router/gr-router_test.js
index 148286e..31028d5 100644
--- a/polygerrit-ui/app/elements/core/gr-router/gr-router_test.js
+++ b/polygerrit-ui/app/elements/core/gr-router/gr-router_test.js
@@ -19,11 +19,10 @@
import './gr-router.js';
import {page} from '../../../utils/page-wrapper-utils.js';
import {GerritNav} from '../gr-navigation/gr-navigation.js';
-import {stubBaseUrl, stubRestApi, addListenerForTest, stubFlags} from '../../../test/test-utils.js';
+import {stubBaseUrl, stubRestApi, addListenerForTest} from '../../../test/test-utils.js';
import {_testOnly_RoutePattern} from './gr-router.js';
import {GerritView} from '../../../services/router/router-model.js';
import {ParentPatchSetNum} from '../../../types/common.js';
-import {KnownExperimentId} from '../../../services/flags/flags.js';
const basicFixture = fixtureFromElement('gr-router');
@@ -215,7 +214,6 @@
'_handleTagListFilterOffsetRoute',
'_handleTagListFilterRoute',
'_handleTagListOffsetRoute',
- '_handleTopicRoute',
'_handlePluginScreen',
];
@@ -261,15 +259,6 @@
});
suite('generateUrl', () => {
- test('topic page', () => {
- const params = {
- view: GerritView.TOPIC,
- topic: 'ggh',
- };
- assert.equal(element._generateUrl(params),
- '/c/topic/ggh');
- });
-
test('search', () => {
let params = {
view: GerritNav.View.SEARCH,
@@ -675,21 +664,6 @@
});
});
- test('_handleQueryRoute to topic page', () => {
- stubFlags('isEnabled').withArgs(KnownExperimentId.TOPICS_PAGE)
- .returns(true);
- const navStub = sinon.stub(GerritNav, 'navigateToTopicPage');
- let data = {params: ['topic:abcd']};
- element._handleQueryRoute(data);
-
- assert.isTrue(navStub.called);
-
- // multiple terms so topic page is not loaded
- data = {params: ['topic:abcd owner:self']};
- element._handleQueryRoute(data);
- assert.isTrue(navStub.calledOnce);
- });
-
test('_handleQueryLegacySuffixRoute', () => {
element._handleQueryLegacySuffixRoute({path: '/q/foo+bar,n,z'});
assert.isTrue(redirectStub.calledOnce);
@@ -1240,19 +1214,6 @@
});
});
- suite('topic routes', () => {
- test('_handleTopicRoute', () => {
- const url = '/c/topic/super complex-topic name with spaces/';
- const groups = url.match(_testOnly_RoutePattern.TOPIC);
-
- const data = {params: groups.slice(1)};
- assertDataToParams(data, '_handleTopicRoute', {
- view: GerritView.TOPIC,
- topic: 'super complex-topic name with spaces',
- });
- });
- });
-
suite('plugin routes', () => {
test('_handlePluginListOffsetRoute', () => {
const data = {params: {}};
diff --git a/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog.ts b/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog.ts
index fb05b02..567e1bb 100644
--- a/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog.ts
+++ b/polygerrit-ui/app/elements/diff/gr-apply-fix-dialog/gr-apply-fix-dialog.ts
@@ -18,7 +18,7 @@
import '../../../styles/shared-styles';
import '../../shared/gr-dialog/gr-dialog';
import '../../shared/gr-overlay/gr-overlay';
-import '../gr-diff/gr-diff';
+import '../../../embed/diff/gr-diff/gr-diff';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-apply-fix-dialog_html';
import {GerritNav} from '../../core/gr-navigation/gr-navigation';
@@ -40,7 +40,7 @@
import {fireCloseFixPreview, fireEvent} from '../../../utils/event-util';
import {DiffLayer, ParsedChangeInfo} from '../../../types/types';
import {GrButton} from '../../shared/gr-button/gr-button';
-import {TokenHighlightLayer} from '../gr-diff-builder/token-highlight-layer';
+import {TokenHighlightLayer} from '../../../embed/diff/gr-diff-builder/token-highlight-layer';
export interface GrApplyFixDialog {
$: {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.ts b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.ts
index ca69ef7..c5b509c 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.ts
@@ -16,7 +16,7 @@
*/
import '../../shared/gr-comment-thread/gr-comment-thread';
import '../../checks/gr-diff-check-result';
-import '../gr-diff/gr-diff';
+import '../../../embed/diff/gr-diff/gr-diff';
import {htmlTemplate} from './gr-diff-host_html';
import {
GerritNav,
@@ -27,7 +27,7 @@
getLine,
getSide,
SYNTAX_MAX_LINE_LENGTH,
-} from '../gr-diff/gr-diff-utils';
+} from '../../../embed/diff/gr-diff/gr-diff-utils';
import {getAppContext} from '../../../services/app-context';
import {
getParentIndex,
@@ -66,12 +66,15 @@
DiffPreferencesInfo,
IgnoreWhitespaceType,
} from '../../../types/diff';
-import {CreateCommentEventDetail, GrDiff} from '../gr-diff/gr-diff';
-import {GrSyntaxLayer} from '../gr-syntax-layer/gr-syntax-layer';
+import {
+ CreateCommentEventDetail,
+ GrDiff,
+} from '../../../embed/diff/gr-diff/gr-diff';
+import {GrSyntaxLayer} from '../../../embed/diff/gr-syntax-layer/gr-syntax-layer';
import {DiffViewMode, Side, CommentSide} from '../../../constants/constants';
import {PolymerDeepPropertyChange} from '@polymer/polymer/interfaces';
import {FilesWebLinks} from '../gr-patch-range-select/gr-patch-range-select';
-import {LineNumber, FILE} from '../gr-diff/gr-diff-line';
+import {LineNumber, FILE} from '../../../embed/diff/gr-diff/gr-diff-line';
import {GrCommentThread} from '../../shared/gr-comment-thread/gr-comment-thread';
import {KnownExperimentId} from '../../../services/flags/flags';
import {
@@ -83,8 +86,8 @@
} from '../../../utils/event-util';
import {getPluginLoader} from '../../shared/gr-js-api-interface/gr-plugin-loader';
import {assertIsDefined} from '../../../utils/common-util';
-import {DiffContextExpandedEventDetail} from '../gr-diff-builder/gr-diff-builder';
-import {TokenHighlightLayer} from '../gr-diff-builder/token-highlight-layer';
+import {DiffContextExpandedEventDetail} from '../../../embed/diff/gr-diff-builder/gr-diff-builder';
+import {TokenHighlightLayer} from '../../../embed/diff/gr-diff-builder/token-highlight-layer';
import {Timing} from '../../../constants/reporting';
import {ChangeComments} from '../gr-comment-api/gr-comment-api';
import {Subscription} from 'rxjs';
@@ -314,8 +317,6 @@
'create-comment',
e => this._handleCreateThread(e)
);
- this.addEventListener('render-start', () => this._handleRenderStart());
- this.addEventListener('render-content', () => this._handleRenderContent());
this.addEventListener('normalize-range', event =>
this._handleNormalizeRange(event)
);
@@ -386,6 +387,8 @@
* signal to report metrics event that started on location change.
*/
async reload(shouldReportMetric?: boolean) {
+ this.reporting.time(Timing.DIFF_TOTAL);
+ this.reporting.time(Timing.DIFF_LOAD);
this.clear();
assertIsDefined(this.path, 'path');
assertIsDefined(this.changeNum, 'changeNum');
@@ -421,7 +424,10 @@
this.editWeblinks = this._getEditWeblinks(diff);
this.filesWeblinks = this._getFilesWeblinks(diff);
this.diff = diff;
+ this.reporting.timeEnd(Timing.DIFF_LOAD, this.timingDetails());
+ this.reporting.time(Timing.DIFF_CONTENT);
const event = (await waitForEventOnce(this, 'render')) as CustomEvent;
+ this.reporting.timeEnd(Timing.DIFF_CONTENT, this.timingDetails());
if (shouldReportMetric) {
// We report diffViewContentDisplayed only on reload caused
// by params changed - expected only on Diff Page.
@@ -433,7 +439,7 @@
try {
await this.syntaxLayer.process();
} finally {
- this.reporting.timeEnd(Timing.DIFF_SYNTAX);
+ this.reporting.timeEnd(Timing.DIFF_SYNTAX, this.timingDetails());
}
}
} catch (e: unknown) {
@@ -445,10 +451,39 @@
this.reporting.error(new Error('reload error'), undefined, e);
}
} finally {
- this.reporting.timeEnd(Timing.DIFF_TOTAL);
+ this.reporting.timeEnd(Timing.DIFF_TOTAL, this.timingDetails());
}
}
+ /**
+ * Produces an event detail object for reporting.
+ */
+ private timingDetails() {
+ if (!this.diff) return {};
+ const metaLines =
+ (this.diff.meta_a?.lines ?? 0) + (this.diff.meta_b?.lines ?? 0);
+
+ let contentLines = 0;
+ let contentChanged = 0;
+ let contentUnchanged = 0;
+ for (const chunk of this.diff.content) {
+ const ab = chunk.ab?.length ?? 0;
+ const a = chunk.a?.length ?? 0;
+ const b = chunk.b?.length ?? 0;
+ contentLines += ab + ab + a + b;
+ contentChanged += a + b;
+ contentUnchanged += ab + ab;
+ }
+ return {
+ metaLines,
+ contentLines,
+ contentUnchanged,
+ contentChanged,
+ height:
+ this.$.diff?.shadowRoot?.querySelector('.diffContainer')?.clientHeight,
+ };
+ }
+
private getLayers(path: string, enableTokenHighlight: boolean): DiffLayer[] {
const layers = [];
if (enableTokenHighlight) {
@@ -485,7 +520,7 @@
const patchNum = this.patchRange?.patchNum;
if (!path || !patchNum || patchNum === EditPatchSetNum) return;
this.checksSubscription = this.getChecksModel()
- .allResultsLatest$.pipe(
+ .allResults$.pipe(
map(results =>
results.filter(result => {
if (result.patchset !== patchNum) return false;
@@ -540,6 +575,7 @@
private createCheckEl(check: RunResult) {
const pointer = check.codePointers?.[0];
assertIsDefined(pointer, 'code pointer of check result in diff');
+ const line = pointer.range.end_line || pointer.range.start_line || 'LOST';
const el = document.createElement('gr-diff-check-result');
// This is what gr-diff expects, even though this is a check, not a comment.
el.className = 'comment-thread';
@@ -547,9 +583,9 @@
el.result = check;
// These attributes are the "interface" between comments/checks and gr-diff.
// <gr-comment-thread> does not care about them and is not affected by them.
- el.setAttribute('slot', `${Side.RIGHT}-${pointer.range.end_line}`);
+ el.setAttribute('slot', `${Side.RIGHT}-${line}`);
el.setAttribute('diff-side', `${Side.RIGHT}`);
- el.setAttribute('line-num', `${pointer.range.end_line || 'LOST'}`);
+ el.setAttribute('line-num', `${line}`);
el.setAttribute('range', `${JSON.stringify(pointer.range)}`);
this.$.diff.appendChild(el);
return el;
@@ -1191,15 +1227,6 @@
this.syntaxLayer.addListener(renderUpdateListener);
}
- _handleRenderStart() {
- this.reporting.time(Timing.DIFF_TOTAL);
- this.reporting.time(Timing.DIFF_CONTENT);
- }
-
- _handleRenderContent() {
- this.reporting.timeEnd(Timing.DIFF_CONTENT);
- }
-
_handleNormalizeRange(event: CustomEvent) {
this.reporting.reportInteraction('normalize-range', {
side: event.detail.side,
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host_test.js b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host_test.js
index 4c04cbb..25edbda 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host_test.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host_test.js
@@ -25,7 +25,7 @@
import {EditPatchSetNum, ParentPatchSetNum} from '../../../types/common.js';
import {CoverageType} from '../../../types/types.js';
import {GerritNav} from '../../core/gr-navigation/gr-navigation.js';
-import {GrDiffBuilderImage} from '../gr-diff-builder/gr-diff-builder-image.js';
+import {GrDiffBuilderImage} from '../../../embed/diff/gr-diff-builder/gr-diff-builder-image.js';
const basicFixture = fixtureFromElement('gr-diff-host');
@@ -63,22 +63,6 @@
});
suite('render reporting', () => {
- test('starts total and content timer on render-start', () => {
- element.dispatchEvent(
- new CustomEvent('render-start', {bubbles: true, composed: true}));
- assert.isTrue(element.reporting.time.calledWithExactly(
- 'Diff Total Render'));
- assert.isTrue(element.reporting.time.calledWithExactly(
- 'Diff Content Render'));
- });
-
- test('ends content timer on render-content', () => {
- element.dispatchEvent(
- new CustomEvent('render-content', {bubbles: true, composed: true}));
- assert.isTrue(element.reporting.timeEnd.calledWithExactly(
- 'Diff Content Render'));
- });
-
test('ends total and syntax timer after syntax layer', async () => {
sinon.stub(element.reporting, 'diffViewContentDisplayed');
let notifySyntaxProcessed;
@@ -97,10 +81,12 @@
notifySyntaxProcessed();
// Multiple cascading microtasks are scheduled.
await flush();
- assert.isTrue(element.reporting.timeEnd.calledWithExactly(
- 'Diff Total Render'));
- assert.isTrue(element.reporting.timeEnd.calledWithExactly(
- 'Diff Syntax Render'));
+ const calls = element.reporting.timeEnd.getCalls();
+ assert.equal(calls.length, 4);
+ assert.equal(calls[0].args[0], 'Diff Load Render');
+ assert.equal(calls[1].args[0], 'Diff Content Render');
+ assert.equal(calls[2].args[0], 'Diff Syntax Render');
+ assert.equal(calls[3].args[0], 'Diff Total Render');
assert.isTrue(element.reporting.diffViewContentDisplayed.called);
});
@@ -112,13 +98,11 @@
// Multiple cascading microtasks are scheduled.
await flush();
await flush();
- // Reporting can be called with other parameters (ex. PluginsLoaded),
- // but only 'Diff Total Render' is important in this test.
- assert.equal(
- element.reporting.timeEnd.getCalls()
- .filter(call => call.calledWithExactly('Diff Total Render'))
- .length,
- 1);
+ const calls = element.reporting.timeEnd.getCalls();
+ assert.equal(calls.length, 3);
+ assert.equal(calls[0].args[0], 'Diff Load Render');
+ assert.equal(calls[1].args[0], 'Diff Content Render');
+ assert.equal(calls[2].args[0], 'Diff Total Render');
});
test('completes reload promise after syntax layer processing', async () => {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.ts b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.ts
index e6a2cd7..434788f 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.ts
@@ -25,10 +25,10 @@
import '../../shared/gr-select/gr-select';
import '../../shared/revision-info/revision-info';
import '../gr-comment-api/gr-comment-api';
-import '../gr-diff-cursor/gr-diff-cursor';
+import '../../../embed/diff/gr-diff-cursor/gr-diff-cursor';
import '../gr-apply-fix-dialog/gr-apply-fix-dialog';
import '../gr-diff-host/gr-diff-host';
-import '../gr-diff-mode-selector/gr-diff-mode-selector';
+import '../../../embed/diff/gr-diff-mode-selector/gr-diff-mode-selector';
import '../gr-diff-preferences-dialog/gr-diff-preferences-dialog';
import '../gr-patch-range-select/gr-patch-range-select';
import '../../change/gr-download-dialog/gr-download-dialog';
@@ -60,14 +60,14 @@
} from '../../../utils/path-list-util';
import {changeBaseURL, changeIsOpen} from '../../../utils/change-util';
import {customElement, observe, property} from '@polymer/decorators';
-import {GrDiffHost} from '../gr-diff-host/gr-diff-host';
+import {GrDiffHost} from '../../diff/gr-diff-host/gr-diff-host';
import {
DropdownItem,
GrDropdownList,
} from '../../shared/gr-dropdown-list/gr-dropdown-list';
import {GrOverlay} from '../../shared/gr-overlay/gr-overlay';
-import {ChangeComments} from '../gr-comment-api/gr-comment-api';
-import {GrDiffModeSelector} from '../gr-diff-mode-selector/gr-diff-mode-selector';
+import {ChangeComments} from '../../diff/gr-comment-api/gr-comment-api';
+import {GrDiffModeSelector} from '../../../embed/diff/gr-diff-mode-selector/gr-diff-mode-selector';
import {
BasePatchSetNum,
ChangeInfo,
@@ -95,7 +95,7 @@
} from '../../../types/types';
import {FilesWebLinks} from '../gr-patch-range-select/gr-patch-range-select';
import {PolymerDeepPropertyChange} from '@polymer/polymer/interfaces';
-import {GrDiffCursor} from '../gr-diff-cursor/gr-diff-cursor';
+import {GrDiffCursor} from '../../../embed/diff/gr-diff-cursor/gr-diff-cursor';
import {CommentSide, DiffViewMode, Side} from '../../../constants/constants';
import {GrApplyFixDialog} from '../gr-apply-fix-dialog/gr-apply-fix-dialog';
import {RevisionInfo as RevisionInfoObj} from '../../shared/revision-info/revision-info';
diff --git a/polygerrit-ui/app/elements/gr-app-element.ts b/polygerrit-ui/app/elements/gr-app-element.ts
index b25417c..0037f36 100644
--- a/polygerrit-ui/app/elements/gr-app-element.ts
+++ b/polygerrit-ui/app/elements/gr-app-element.ts
@@ -21,7 +21,6 @@
import './documentation/gr-documentation-search/gr-documentation-search';
import './change-list/gr-change-list-view/gr-change-list-view';
import './change-list/gr-dashboard-view/gr-dashboard-view';
-import './topic/gr-topic-view';
import './change/gr-change-view/gr-change-view';
import './core/gr-error-manager/gr-error-manager';
import './core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog';
@@ -144,9 +143,6 @@
_showDashboardView?: boolean;
@property({type: Boolean})
- _showTopicView?: boolean;
-
- @property({type: Boolean})
_showChangeView?: boolean;
@property({type: Boolean})
@@ -354,7 +350,6 @@
this.$.errorView.classList.remove('show');
this._showChangeListView = view === GerritView.SEARCH;
this._showDashboardView = view === GerritView.DASHBOARD;
- this._showTopicView = view === GerritView.TOPIC;
this._showChangeView = view === GerritView.CHANGE;
this._showDiffView = view === GerritView.DIFF;
this._showSettingsView = view === GerritView.SETTINGS;
diff --git a/polygerrit-ui/app/elements/gr-app-element_html.ts b/polygerrit-ui/app/elements/gr-app-element_html.ts
index 5bee20c..5b96720 100644
--- a/polygerrit-ui/app/elements/gr-app-element_html.ts
+++ b/polygerrit-ui/app/elements/gr-app-element_html.ts
@@ -130,9 +130,6 @@
view-state="{{_viewState.dashboardView}}"
></gr-dashboard-view>
</template>
- <template is="dom-if" if="[[_showTopicView]]">
- <gr-topic-view params="[[params]]"></gr-topic-view>
- </template>
<!-- Note that the change view does not have restamp="true" set, because we
want to re-use it as long as the change number does not change. -->
<template id="dom-if-change-view" is="dom-if" if="[[_showChangeView]]">
diff --git a/polygerrit-ui/app/elements/gr-app-global-var-init.ts b/polygerrit-ui/app/elements/gr-app-global-var-init.ts
index d0525ea..243e3d5 100644
--- a/polygerrit-ui/app/elements/gr-app-global-var-init.ts
+++ b/polygerrit-ui/app/elements/gr-app-global-var-init.ts
@@ -22,7 +22,7 @@
* expose these variables until plugins switch to direct import from polygerrit.
*/
-import {GrAnnotation} from './diff/gr-diff-highlight/gr-annotation';
+import {GrAnnotation} from '../embed/diff/gr-diff-highlight/gr-annotation';
import {page} from '../utils/page-wrapper-utils';
import {GrPluginActionContext} from './shared/gr-js-api-interface/gr-plugin-action-context';
import {initGerritPluginApi} from './shared/gr-js-api-interface/gr-gerrit';
diff --git a/polygerrit-ui/app/elements/gr-app-types.ts b/polygerrit-ui/app/elements/gr-app-types.ts
index 9c0ce9e..e5748fc 100644
--- a/polygerrit-ui/app/elements/gr-app-types.ts
+++ b/polygerrit-ui/app/elements/gr-app-types.ts
@@ -46,11 +46,6 @@
title?: string;
}
-export interface AppElementTopicParams {
- view: GerritView.TOPIC;
- topic?: string;
-}
-
export interface AppElementGroupParams {
view: GerritView.GROUP;
detail?: GroupDetailView;
@@ -152,7 +147,6 @@
export type AppElementParams =
| AppElementDashboardParams
- | AppElementTopicParams
| AppElementGroupParams
| AppElementAdminParams
| AppElementChangeViewParams
diff --git a/polygerrit-ui/app/elements/settings/gr-agreements-list/gr-agreements-list.ts b/polygerrit-ui/app/elements/settings/gr-agreements-list/gr-agreements-list.ts
index 43aefdc..2db7c76 100644
--- a/polygerrit-ui/app/elements/settings/gr-agreements-list/gr-agreements-list.ts
+++ b/polygerrit-ui/app/elements/settings/gr-agreements-list/gr-agreements-list.ts
@@ -62,7 +62,7 @@
return html`
<tr>
<td class="nameColumn">
- <a href="${this.getUrlBase(agreement.url)}" rel="external">
+ <a href="${this.getUrlBase(agreement?.url)}" rel="external">
${agreement.name}
</a>
</td>
@@ -94,8 +94,8 @@
return `${getBaseUrl()}/settings/new-agreement`;
}
- getUrlBase(item: string) {
- return `${getBaseUrl()}/${item}`;
+ getUrlBase(item?: string) {
+ return item ? `${getBaseUrl()}/${item}` : '';
}
}
diff --git a/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view.ts b/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view.ts
index 92d984d..677e0c1 100644
--- a/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view.ts
+++ b/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view.ts
@@ -196,4 +196,10 @@
? 'hideAgreementsTextBox'
: '';
}
+
+ _computeAgreements(serverConfig?: ServerInfo) {
+ return (serverConfig?.auth.contributor_agreements ?? []).filter(
+ agreement => agreement.url
+ );
+ }
}
diff --git a/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view_html.ts b/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view_html.ts
index ce95ccb..564297fa 100644
--- a/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view_html.ts
+++ b/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view_html.ts
@@ -66,10 +66,7 @@
<main>
<h1 class="heading-1">New Contributor Agreement</h1>
<h3 class="heading-3">Select an agreement type:</h3>
- <template
- is="dom-repeat"
- items="[[_serverConfig.auth.contributor_agreements]]"
- >
+ <template is="dom-repeat" items="[[_computeAgreements(_serverConfig)]]">
<span class="contributorAgreementButton">
<input
id$="claNewAgreementsInput[[item.name]]"
diff --git a/polygerrit-ui/app/elements/shared/gr-account-chip/gr-account-chip.ts b/polygerrit-ui/app/elements/shared/gr-account-chip/gr-account-chip.ts
index 9ce62c1..1d032da3 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-chip/gr-account-chip.ts
+++ b/polygerrit-ui/app/elements/shared/gr-account-chip/gr-account-chip.ts
@@ -17,11 +17,18 @@
import '../gr-account-link/gr-account-link';
import '../gr-button/gr-button';
import '../gr-icons/gr-icons';
-import {AccountInfo, ChangeInfo} from '../../../types/common';
+import {
+ AccountInfo,
+ ApprovalInfo,
+ ChangeInfo,
+ LabelInfo,
+} from '../../../types/common';
import {getAppContext} from '../../../services/app-context';
import {LitElement, css, html} from 'lit';
import {customElement, property} from 'lit/decorators';
-import {classMap} from 'lit/directives/class-map';
+import {ClassInfo, classMap} from 'lit/directives/class-map';
+import {KnownExperimentId} from '../../../services/flags/flags';
+import {getLabelStatus, hasVoted, LabelStatus} from '../../../utils/label-util';
@customElement('gr-account-chip')
export class GrAccountChip extends LitElement {
@@ -79,8 +86,16 @@
@property({type: Boolean})
transparentBackground = false;
+ @property({type: Object})
+ vote?: ApprovalInfo;
+
+ @property({type: Object})
+ label?: LabelInfo;
+
private readonly restApiService = getAppContext().restApiService;
+ private readonly flagsService = getAppContext().flagsService;
+
static override get styles() {
return [
css`
@@ -122,6 +137,16 @@
.container gr-account-link::part(gr-account-link-text) {
color: var(--deemphasized-text-color);
}
+ .container.disliked {
+ border: 1px solid var(--vote-outline-disliked);
+ }
+ .container.recommended {
+ border: 1px solid var(--vote-outline-recommended);
+ }
+ .container.disliked,
+ .container.recommended {
+ --account-label-padding-horizontal: 2px;
+ }
`,
];
}
@@ -153,6 +178,7 @@
return html`${customStyle}
<div
class="${classMap({
+ ...this.computeVoteClasses(),
container: true,
transparentBackground: this.transparentBackground,
})}"
@@ -207,6 +233,25 @@
Promise.resolve(!!(cfg && cfg.plugin && cfg.plugin.has_avatars))
);
}
+
+ private computeVoteClasses(): ClassInfo {
+ if (
+ !this.flagsService.isEnabled(KnownExperimentId.SUBMIT_REQUIREMENTS_UI) ||
+ !this.label ||
+ !this.account ||
+ !hasVoted(this.label, this.account)
+ ) {
+ return {};
+ }
+ const status = getLabelStatus(this.label, this.vote?.value);
+ if ([LabelStatus.APPROVED, LabelStatus.RECOMMENDED].includes(status)) {
+ return {recommended: true};
+ } else if ([LabelStatus.REJECTED, LabelStatus.DISLIKED].includes(status)) {
+ return {disliked: true};
+ } else {
+ return {};
+ }
+ }
}
declare global {
diff --git a/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label.ts b/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label.ts
index 99e10ae..274601f 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label.ts
+++ b/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label.ts
@@ -85,9 +85,6 @@
})
cancelLeftPadding = false;
- @property({type: Boolean})
- hideStatus = false;
-
@state()
_config?: ServerInfo;
@@ -163,14 +160,6 @@
height: 12px;
vertical-align: top;
}
- iron-icon.status {
- color: var(--deemphasized-text-color);
- width: 14px;
- height: 14px;
- vertical-align: top;
- position: relative;
- top: 2px;
- }
.name {
display: inline-block;
text-decoration: inherit;
@@ -259,12 +248,6 @@
<span class="name">
${this._computeName(account, this.firstName, this._config)}
</span>
- ${!this.hideStatus && account.status
- ? html`<iron-icon
- class="status"
- icon="gr-icons:unavailable"
- ></iron-icon>`
- : ''}
${this.renderAccountStatusPlugins()}
</span>
</span>`;
@@ -284,8 +267,6 @@
});
}
- // Note: account statuses from plugins are shown regardless of
- // hideStatus setting
private renderAccountStatusPlugins() {
if (!this.account?._account_id) {
return;
diff --git a/polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link.ts b/polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link.ts
index f0c9106..34e5043 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link.ts
+++ b/polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link.ts
@@ -56,9 +56,6 @@
@property({type: Boolean})
hideAvatar = false;
- @property({type: Boolean})
- hideStatus = false;
-
/**
* Only show the first name in the account label.
*/
@@ -93,7 +90,6 @@
?forceAttention=${this.forceAttention}
?highlightAttention=${this.highlightAttention}
?hideAvatar=${this.hideAvatar}
- ?hideStatus=${this.hideStatus}
?firstName=${this.firstName}
.voteableText=${this.voteableText}
exportparts="gr-account-label-text: gr-account-link-text"
diff --git a/polygerrit-ui/app/elements/shared/gr-button/gr-button.ts b/polygerrit-ui/app/elements/shared/gr-button/gr-button.ts
index 2da1b19..c28e83b 100644
--- a/polygerrit-ui/app/elements/shared/gr-button/gr-button.ts
+++ b/polygerrit-ui/app/elements/shared/gr-button/gr-button.ts
@@ -54,6 +54,10 @@
@property({type: Boolean, reflect: true})
link = false;
+ // If flattened then the button will not be shown as raised.
+ @property({type: Boolean, reflect: true})
+ flatten = false;
+
@property({type: Boolean, reflect: true})
loading = false;
@@ -190,7 +194,7 @@
override render() {
return html`<paper-button
- ?raised="${!this.link}"
+ ?raised="${!this.link && !this.flatten}"
?disabled="${this.disabled || this.loading}"
role="button"
tabindex="-1"
diff --git a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.ts b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.ts
index 7ed49de..ecb5761 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.ts
+++ b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.ts
@@ -17,7 +17,7 @@
import '../../../styles/gr-a11y-styles';
import '../../../styles/shared-styles';
import '../gr-comment/gr-comment';
-import '../../diff/gr-diff/gr-diff';
+import '../../../embed/diff/gr-diff/gr-diff';
import '../gr-copy-clipboard/gr-copy-clipboard';
import {css, html, LitElement, PropertyValues} from 'lit';
import {customElement, property, query, queryAll, state} from 'lit/decorators';
@@ -51,15 +51,15 @@
UrlEncodedCommentId,
} from '../../../types/common';
import {GrComment} from '../gr-comment/gr-comment';
-import {FILE} from '../../diff/gr-diff/gr-diff-line';
+import {FILE} from '../../../embed/diff/gr-diff/gr-diff-line';
import {GrButton} from '../gr-button/gr-button';
import {DiffInfo, DiffPreferencesInfo} from '../../../types/diff';
import {DiffLayer, RenderPreferences} from '../../../api/diff';
import {assertIsDefined} from '../../../utils/common-util';
import {fire, fireAlert, waitForEventOnce} from '../../../utils/event-util';
-import {GrSyntaxLayer} from '../../diff/gr-syntax-layer/gr-syntax-layer';
-import {TokenHighlightLayer} from '../../diff/gr-diff-builder/token-highlight-layer';
-import {anyLineTooLong} from '../../diff/gr-diff/gr-diff-utils';
+import {GrSyntaxLayer} from '../../../embed/diff/gr-syntax-layer/gr-syntax-layer';
+import {TokenHighlightLayer} from '../../../embed/diff/gr-diff-builder/token-highlight-layer';
+import {anyLineTooLong} from '../../../embed/diff/gr-diff/gr-diff-utils';
import {getUserName} from '../../../utils/display-name-util';
import {generateAbsoluteUrl} from '../../../utils/url-util';
import {sharedStyles} from '../../../styles/shared-styles';
diff --git a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.ts b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.ts
index 0cf0a04..b6571ec 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.ts
+++ b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.ts
@@ -488,7 +488,6 @@
<gr-account-label
.account="${this.comment?.author ?? this.account}"
class="${classMap(classes)}"
- hideStatus
>
</gr-account-label>
`;
diff --git a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_test.ts b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_test.ts
index 1db643a..2bf4ed2 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_test.ts
+++ b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_test.ts
@@ -89,7 +89,7 @@
<div class="container" id="container">
<div class="header" id="header">
<div class="headerLeft">
- <gr-account-label deselected="" hidestatus=""></gr-account-label>
+ <gr-account-label deselected=""></gr-account-label>
</div>
<div class="headerMiddle">
<span class="collapsedContent">
@@ -116,7 +116,7 @@
<div class="container" id="container">
<div class="header" id="header">
<div class="headerLeft">
- <gr-account-label deselected="" hidestatus=""></gr-account-label>
+ <gr-account-label deselected=""></gr-account-label>
</div>
<div class="headerMiddle"></div>
<span class="patchset-text">Patchset 1</span>
@@ -210,7 +210,7 @@
<div class="container draft" id="container">
<div class="header" id="header">
<div class="headerLeft">
- <gr-account-label class="draft" deselected="" hidestatus=""></gr-account-label>
+ <gr-account-label class="draft" deselected=""></gr-account-label>
<gr-tooltip-content
class="draftTooltip" has-tooltip="" max-width="20em" show-icon=""
title="This draft is only visible to you. To publish drafts, click the 'Reply' or 'Start review' button at the top of the change or press the 'a' key."
@@ -263,7 +263,7 @@
<div class="container draft" id="container">
<div class="header" id="header">
<div class="headerLeft">
- <gr-account-label class="draft" deselected="" hidestatus=""></gr-account-label>
+ <gr-account-label class="draft" deselected=""></gr-account-label>
<gr-tooltip-content
class="draftTooltip" has-tooltip="" max-width="20em" show-icon=""
title="This draft is only visible to you. To publish drafts, click the 'Reply' or 'Start review' button at the top of the change or press the 'a' key."
diff --git a/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands.ts b/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands.ts
index 6eb19da..64f97ca 100644
--- a/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands.ts
+++ b/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands.ts
@@ -18,53 +18,44 @@
import '@polymer/paper-tabs/paper-tab';
import '@polymer/paper-tabs/paper-tabs';
import '../gr-shell-command/gr-shell-command';
-import '../../../styles/gr-paper-styles';
-import '../../../styles/shared-styles';
-import {PolymerElement} from '@polymer/polymer/polymer-element';
-import {htmlTemplate} from './gr-download-commands_html';
-import {customElement, property} from '@polymer/decorators';
-import {PaperTabsElement} from '@polymer/paper-tabs/paper-tabs';
import {getAppContext} from '../../../services/app-context';
import {queryAndAssert} from '../../../utils/common-util';
import {GrShellCommand} from '../gr-shell-command/gr-shell-command';
+import {paperStyles} from '../../../styles/gr-paper-styles';
+import {sharedStyles} from '../../../styles/shared-styles';
+import {LitElement, html, css} from 'lit';
+import {customElement, property, state} from 'lit/decorators';
+import {fire} from '../../../utils/event-util';
+import {BindValueChangeEvent} from '../../../types/events';
declare global {
interface HTMLElementEventMap {
'selected-changed': CustomEvent<{value: number}>;
+ 'selected-scheme-changed': BindValueChangeEvent;
}
interface HTMLElementTagNameMap {
'gr-download-commands': GrDownloadCommands;
}
}
-export interface GrDownloadCommands {
- $: {
- downloadTabs: PaperTabsElement;
- };
-}
-
export interface Command {
title: string;
command: string;
}
@customElement('gr-download-commands')
-export class GrDownloadCommands extends PolymerElement {
- static get template() {
- return htmlTemplate;
- }
-
+export class GrDownloadCommands extends LitElement {
// TODO(TS): maybe default to [] as only used in dom-repeat
@property({type: Array})
commands?: Command[];
- @property({type: Boolean})
- _loggedIn = false;
+ // private but used in test
+ @state() loggedIn = false;
@property({type: Array})
schemes: string[] = [];
- @property({type: String, notify: true})
+ @property({type: String})
selectedScheme?: string;
@property({type: Boolean})
@@ -79,14 +70,15 @@
override connectedCallback() {
super.connectedCallback();
- this._getLoggedIn().then(loggedIn => {
- this._loggedIn = loggedIn;
+ this.restApiService.getLoggedIn().then(loggedIn => {
+ this.loggedIn = loggedIn;
});
this.subscriptions.push(
this.userModel.preferences$.subscribe(prefs => {
if (prefs?.download_scheme) {
// Note (issue 5180): normalize the download scheme with lower-case.
this.selectedScheme = prefs.download_scheme.toLowerCase();
+ fire(this, 'selected-scheme-changed', {value: this.selectedScheme});
}
})
);
@@ -100,42 +92,121 @@
super.disconnectedCallback();
}
+ static override get styles() {
+ return [
+ paperStyles,
+ sharedStyles,
+ css`
+ paper-tabs {
+ height: 3rem;
+ margin-bottom: var(--spacing-m);
+ --paper-tabs-selection-bar-color: var(--link-color);
+ }
+ paper-tab {
+ max-width: 15rem;
+ text-transform: uppercase;
+ --paper-tab-ink: var(--link-color);
+ }
+ label,
+ input {
+ display: block;
+ }
+ label {
+ font-weight: var(--font-weight-bold);
+ }
+ .schemes {
+ display: flex;
+ justify-content: space-between;
+ }
+ .commands {
+ display: flex;
+ flex-direction: column;
+ }
+ gr-shell-command {
+ margin-bottom: var(--spacing-m);
+ }
+ .hidden {
+ display: none;
+ }
+ `,
+ ];
+ }
+
+ override render() {
+ return html`
+ <div class="schemes">${this.renderDownloadTabs()}</div>
+ ${this.renderCommands()}
+ `;
+ }
+
+ private renderDownloadTabs() {
+ if (this.schemes.length <= 1) return;
+
+ const selectedIndex =
+ this.schemes.findIndex(scheme => scheme === this.selectedScheme) || 0;
+ return html`
+ <paper-tabs
+ id="downloadTabs"
+ .selected=${selectedIndex}
+ @selected-changed=${this.handleTabChange}
+ >
+ ${this.schemes.map(scheme => this.renderPaperTab(scheme))}
+ </paper-tabs>
+ `;
+ }
+
+ private renderPaperTab(scheme: string) {
+ return html` <paper-tab data-scheme=${scheme}>${scheme}</paper-tab> `;
+ }
+
+ private renderCommands() {
+ if (!this.schemes.length) return;
+
+ return html`
+ <div class="commands">
+ ${this.commands?.map((command, index) =>
+ this.renderShellCommand(command, index)
+ )}
+ </div>
+ `;
+ }
+
+ private renderShellCommand(command: Command, index: number) {
+ return html`
+ <gr-shell-command
+ class="${this.computeClass(command.title)}"
+ .label=${command.title}
+ .command=${command.command}
+ .tooltip=${this.computeTooltip(index)}
+ ></gr-shell-command>
+ `;
+ }
+
focusOnCopy() {
queryAndAssert<GrShellCommand>(this, 'gr-shell-command').focusOnCopy();
}
- _getLoggedIn() {
- return this.restApiService.getLoggedIn();
- }
-
- _handleTabChange(e: CustomEvent<{value: number}>) {
+ private handleTabChange = (e: CustomEvent<{value: number}>) => {
const scheme = this.schemes[e.detail.value];
if (scheme && scheme !== this.selectedScheme) {
- this.set('selectedScheme', scheme);
- if (this._loggedIn) {
+ this.selectedScheme = scheme;
+ fire(this, 'selected-scheme-changed', {value: scheme});
+ if (this.loggedIn) {
this.userModel.updatePreferences({
download_scheme: this.selectedScheme,
});
}
}
- }
+ };
- _computeSelected(schemes: string[], selectedScheme?: string) {
- return `${schemes.findIndex(scheme => scheme === selectedScheme) || 0}`;
- }
-
- _computeShowTabs(schemes: string[]) {
- return schemes.length > 1 ? '' : 'hidden';
- }
-
- _computeTooltip(showKeyboardShortcutTooltips: boolean, index: number) {
- return index <= 4 && showKeyboardShortcutTooltips
+ private computeTooltip(index: number) {
+ return index <= 4 && this.showKeyboardShortcutTooltips
? `Keyboard shortcut: ${index + 1}`
: '';
}
// TODO: maybe unify with strToClassName from dom-util
- _computeClass(title: string) {
+ private computeClass(title: string) {
// Only retain [a-z] chars, so "Cherry Pick" becomes "cherrypick".
return '_label_' + title.replace(/[^a-z]+/gi, '').toLowerCase();
}
diff --git a/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands_html.ts b/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands_html.ts
deleted file mode 100644
index f9c08ba..0000000
--- a/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands_html.ts
+++ /dev/null
@@ -1,78 +0,0 @@
-/**
- * @license
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import {html} from '@polymer/polymer/lib/utils/html-tag';
-
-export const htmlTemplate = html`
- <style include="gr-paper-styles">
- /* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
- </style>
- <style include="shared-styles">
- paper-tabs {
- height: 3rem;
- margin-bottom: var(--spacing-m);
- --paper-tabs-selection-bar-color: var(--link-color);
- }
- paper-tab {
- max-width: 15rem;
- text-transform: uppercase;
- --paper-tab-ink: var(--link-color);
- }
- label,
- input {
- display: block;
- }
- label {
- font-weight: var(--font-weight-bold);
- }
- .schemes {
- display: flex;
- justify-content: space-between;
- }
- .commands {
- display: flex;
- flex-direction: column;
- }
- gr-shell-command {
- margin-bottom: var(--spacing-m);
- }
- .hidden {
- display: none;
- }
- </style>
- <div class="schemes">
- <paper-tabs
- id="downloadTabs"
- class$="[[_computeShowTabs(schemes)]]"
- selected="[[_computeSelected(schemes, selectedScheme)]]"
- on-selected-changed="_handleTabChange"
- >
- <template is="dom-repeat" items="[[schemes]]" as="scheme">
- <paper-tab data-scheme$="[[scheme]]">[[scheme]]</paper-tab>
- </template>
- </paper-tabs>
- </div>
- <div class="commands" hidden$="[[!schemes.length]]" hidden="">
- <template is="dom-repeat" items="[[commands]]" as="command" indexAs="index">
- <gr-shell-command
- class$="[[_computeClass(command.title)]]"
- label="[[command.title]]"
- command="[[command.command]]"
- tooltip="[[_computeTooltip(showKeyboardShortcutTooltips, index)]]"
- ></gr-shell-command>
- </template>
- </div>
-`;
diff --git a/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands_test.ts b/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands_test.ts
index bd0ca70..eb9490f 100644
--- a/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands_test.ts
+++ b/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands_test.ts
@@ -18,11 +18,12 @@
import '../../../test/common-test-setup-karma';
import './gr-download-commands';
import {GrDownloadCommands} from './gr-download-commands';
-import {isHidden, queryAndAssert, stubRestApi} from '../../../test/test-utils';
+import {query, queryAndAssert, stubRestApi} from '../../../test/test-utils';
import {createPreferences} from '../../../test/test-data-generators';
import * as MockInteractions from '@polymer/iron-test-helpers/mock-interactions';
import {GrShellCommand} from '../gr-shell-command/gr-shell-command';
import {createDefaultPreferences} from '../../../constants/constants';
+import {PaperTabsElement} from '@polymer/paper-tabs/paper-tabs';
const basicFixture = fixtureFromElement('gr-download-commands');
@@ -63,7 +64,7 @@
element.schemes = SCHEMES;
element.commands = COMMANDS;
element.selectedScheme = SELECTED_SCHEME;
- await flush();
+ await element.updateComplete;
});
test('focusOnCopy', () => {
@@ -75,30 +76,37 @@
assert.isTrue(focusStub.called);
});
- test('element visibility', () => {
- assert.isFalse(isHidden(queryAndAssert(element, 'paper-tabs')));
- assert.isFalse(isHidden(queryAndAssert(element, '.commands')));
+ test('element visibility', async () => {
+ assert.isTrue(Boolean(query(element, 'paper-tabs')));
+ assert.isTrue(Boolean(query(element, '.commands')));
element.schemes = [];
- assert.isTrue(isHidden(queryAndAssert(element, 'paper-tabs')));
- assert.isTrue(isHidden(queryAndAssert(element, '.commands')));
+ await element.updateComplete;
+ assert.isFalse(Boolean(query(element, 'paper-tabs')));
+ assert.isFalse(Boolean(query(element, '.commands')));
});
- test('tab selection', () => {
- assert.equal(element.$.downloadTabs.selected, '0');
+ test('tab selection', async () => {
+ assert.equal(
+ queryAndAssert<PaperTabsElement>(element, '#downloadTabs').selected,
+ '0'
+ );
MockInteractions.tap(queryAndAssert(element, '[data-scheme="ssh"]'));
- flush();
+ await element.updateComplete;
assert.equal(element.selectedScheme, 'ssh');
- assert.equal(element.$.downloadTabs.selected, '2');
+ assert.equal(
+ queryAndAssert<PaperTabsElement>(element, '#downloadTabs').selected,
+ '2'
+ );
});
- test('saves scheme to preferences', () => {
- element._loggedIn = true;
+ test('saves scheme to preferences', async () => {
+ element.loggedIn = true;
const savePrefsStub = stubRestApi('savePreferences').returns(
Promise.resolve(createDefaultPreferences())
);
- flush();
+ await element.updateComplete;
const repoTab = queryAndAssert(element, 'paper-tab[data-scheme="repo"]');
@@ -114,7 +122,7 @@
suite('authenticated', () => {
test('loads scheme from preferences', async () => {
const element = basicFixture.instantiate();
- await flush();
+ await element.updateComplete;
element.userModel.setPreferences({
...createPreferences(),
download_scheme: 'repo',
@@ -124,7 +132,7 @@
test('normalize scheme from preferences', async () => {
const element = basicFixture.instantiate();
- await flush();
+ await element.updateComplete;
element.userModel.setPreferences({
...createPreferences(),
download_scheme: 'REPO',
diff --git a/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text.ts b/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text.ts
index 417d7fb..c6f44af 100644
--- a/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text.ts
+++ b/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text.ts
@@ -121,6 +121,7 @@
border-left-width: var(--spacing-s);
margin: var(--spacing-m) 0;
padding: var(--spacing-s) var(--spacing-m);
+ overflow-x: scroll;
}
li {
list-style-type: disc;
diff --git a/polygerrit-ui/app/elements/shared/gr-hovercard-account/gr-hovercard-account.ts b/polygerrit-ui/app/elements/shared/gr-hovercard-account/gr-hovercard-account.ts
index c79c7f3..4330451 100644
--- a/polygerrit-ui/app/elements/shared/gr-hovercard-account/gr-hovercard-account.ts
+++ b/polygerrit-ui/app/elements/shared/gr-hovercard-account/gr-hovercard-account.ts
@@ -18,6 +18,8 @@
import '@polymer/iron-icon/iron-icon';
import '../gr-avatar/gr-avatar';
import '../gr-button/gr-button';
+import '../../plugins/gr-endpoint-decorator/gr-endpoint-decorator';
+import '../../plugins/gr-endpoint-param/gr-endpoint-param';
import {getAppContext} from '../../../services/app-context';
import {accountKey, isSelf} from '../../../utils/account-util';
import {customElement, property} from 'lit/decorators';
@@ -105,6 +107,9 @@
.voteable {
padding: var(--spacing-s) var(--spacing-l);
}
+ .statusPlugin {
+ padding: var(--spacing-l) var(--spacing-l) var(--spacing-m);
+ }
.top {
display: flex;
padding-top: var(--spacing-xl);
@@ -172,7 +177,7 @@
<div class="email">${this.account.email}</div>
</div>
</div>
- ${this.renderAccountStatus()}
+ ${this.renderAccountStatusPlugins()} ${this.renderAccountStatus()}
${this.voteableText
? html`
<div class="voteable">
@@ -213,14 +218,24 @@
`;
}
+ private renderAccountStatusPlugins() {
+ return html`
+ <div class="statusPlugin">
+ <gr-endpoint-decorator name="hovercard-status">
+ <gr-endpoint-param
+ name="account"
+ .value="${this.account}"
+ ></gr-endpoint-param>
+ </gr-endpoint-decorator>
+ </div>
+ `;
+ }
+
private renderAccountStatus() {
if (!this.account.status) return;
return html`
<div class="status">
- <span class="title">
- <iron-icon icon="gr-icons:unavailable"></iron-icon>
- Status:
- </span>
+ <span class="title">Status:</span>
<span class="value">${this.account.status}</span>
</div>
`;
diff --git a/polygerrit-ui/app/elements/shared/gr-hovercard-account/gr-hovercard-account_test.ts b/polygerrit-ui/app/elements/shared/gr-hovercard-account/gr-hovercard-account_test.ts
index 66a4e1b..b8622e0 100644
--- a/polygerrit-ui/app/elements/shared/gr-hovercard-account/gr-hovercard-account_test.ts
+++ b/polygerrit-ui/app/elements/shared/gr-hovercard-account/gr-hovercard-account_test.ts
@@ -93,6 +93,11 @@
</div>
</div>
</div>
+ <div class="statusPlugin">
+ <gr-endpoint-decorator name="hovercard-status">
+ <gr-endpoint-param name="account"></gr-endpoint-param>
+ </gr-endpoint-decorator>
+ </div>
</div>
`);
});
diff --git a/polygerrit-ui/app/elements/shared/gr-labeled-autocomplete/gr-labeled-autocomplete.ts b/polygerrit-ui/app/elements/shared/gr-labeled-autocomplete/gr-labeled-autocomplete.ts
index 85f29cb..753835d 100644
--- a/polygerrit-ui/app/elements/shared/gr-labeled-autocomplete/gr-labeled-autocomplete.ts
+++ b/polygerrit-ui/app/elements/shared/gr-labeled-autocomplete/gr-labeled-autocomplete.ts
@@ -16,24 +16,19 @@
*/
import '../gr-autocomplete/gr-autocomplete';
import '../../../styles/shared-styles';
-import {PolymerElement} from '@polymer/polymer/polymer-element';
-import {htmlTemplate} from './gr-labeled-autocomplete_html';
-import {customElement, property} from '@polymer/decorators';
+import {LitElement, css, html, PropertyValues} from 'lit';
+import {customElement, property, query} from 'lit/decorators';
import {
GrAutocomplete,
AutocompleteQuery,
} from '../gr-autocomplete/gr-autocomplete';
+import {assertIsDefined} from '../../../utils/common-util';
+import {fire} from '../../../utils/event-util';
-export interface GrLabeledAutocomplete {
- $: {
- autocomplete: GrAutocomplete;
- };
-}
@customElement('gr-labeled-autocomplete')
-export class GrLabeledAutocomplete extends PolymerElement {
- static get template() {
- return htmlTemplate;
- }
+export class GrLabeledAutocomplete extends LitElement {
+ @query('#autocomplete')
+ autocomplete?: GrAutocomplete;
/**
* Fired when a value is chosen.
@@ -44,7 +39,7 @@
@property({type: Object})
query: AutocompleteQuery = () => Promise.resolve([]);
- @property({type: String, notify: true})
+ @property({type: String})
text = '';
@property({type: String})
@@ -56,15 +51,73 @@
@property({type: Boolean})
disabled = false;
- _handleTriggerClick(e: Event) {
+ static override get styles() {
+ return css`
+ :host {
+ display: block;
+ width: 12em;
+ }
+ #container {
+ background: var(--chip-background-color);
+ border-radius: 1em;
+ padding: var(--spacing-m);
+ }
+ #header {
+ color: var(--deemphasized-text-color);
+ font-weight: var(--font-weight-bold);
+ font-size: var(--font-size-small);
+ }
+ #body {
+ display: flex;
+ }
+ #trigger {
+ color: var(--deemphasized-text-color);
+ cursor: pointer;
+ padding-left: var(--spacing-s);
+ }
+ #trigger:hover {
+ color: var(--primary-text-color);
+ }
+ `;
+ }
+
+ override render() {
+ return html`
+ <div id="container">
+ <div id="header">${this.label}</div>
+ <div id="body">
+ <gr-autocomplete
+ id="autocomplete"
+ threshold="0"
+ .query="${this.query}"
+ ?disabled="${this.disabled}"
+ .placeholder="${this.placeholder}"
+ borderless=""
+ ></gr-autocomplete>
+ <div id="trigger" @click="${this._handleTriggerClick}">â–¼</div>
+ </div>
+ </div>
+ `;
+ }
+
+ override willUpdate(changedProperties: PropertyValues) {
+ if (changedProperties.has('text')) {
+ fire(this, 'text-changed', this.text);
+ }
+ }
+
+ // Private but used in tests.
+ _handleTriggerClick = (e: Event) => {
// Stop propagation here so we don't confuse gr-autocomplete, which
// listens for taps on body to try to determine when it's blurred.
e.stopPropagation();
- this.$.autocomplete.focus();
- }
+ assertIsDefined(this.autocomplete);
+ this.autocomplete.focus();
+ };
setText(text: string) {
- this.$.autocomplete.setText(text);
+ assertIsDefined(this.autocomplete);
+ this.autocomplete.setText(text);
}
clear() {
@@ -73,6 +126,9 @@
}
declare global {
+ interface HTMLElementEventMap {
+ 'text-changed': CustomEvent<string>;
+ }
interface HTMLElementTagNameMap {
'gr-labeled-autocomplete': GrLabeledAutocomplete;
}
diff --git a/polygerrit-ui/app/elements/shared/gr-labeled-autocomplete/gr-labeled-autocomplete_html.ts b/polygerrit-ui/app/elements/shared/gr-labeled-autocomplete/gr-labeled-autocomplete_html.ts
deleted file mode 100644
index 934ab84..0000000
--- a/polygerrit-ui/app/elements/shared/gr-labeled-autocomplete/gr-labeled-autocomplete_html.ts
+++ /dev/null
@@ -1,61 +0,0 @@
-/**
- * @license
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import {html} from '@polymer/polymer/lib/utils/html-tag';
-
-export const htmlTemplate = html`
- <style include="shared-styles">
- :host {
- display: block;
- width: 12em;
- }
- #container {
- background: var(--chip-background-color);
- border-radius: 1em;
- padding: var(--spacing-m);
- }
- #header {
- color: var(--deemphasized-text-color);
- font-weight: var(--font-weight-bold);
- font-size: var(--font-size-small);
- }
- #body {
- display: flex;
- }
- #trigger {
- color: var(--deemphasized-text-color);
- cursor: pointer;
- padding-left: var(--spacing-s);
- }
- #trigger:hover {
- color: var(--primary-text-color);
- }
- </style>
- <div id="container">
- <div id="header">[[label]]</div>
- <div id="body">
- <gr-autocomplete
- id="autocomplete"
- threshold="0"
- query="[[query]]"
- disabled="[[disabled]]"
- placeholder="[[placeholder]]"
- borderless=""
- ></gr-autocomplete>
- <div id="trigger" on-click="_handleTriggerClick">â–¼</div>
- </div>
- </div>
-`;
diff --git a/polygerrit-ui/app/elements/shared/gr-labeled-autocomplete/gr-labeled-autocomplete_test.ts b/polygerrit-ui/app/elements/shared/gr-labeled-autocomplete/gr-labeled-autocomplete_test.ts
index d6fc45f..49b14b4 100644
--- a/polygerrit-ui/app/elements/shared/gr-labeled-autocomplete/gr-labeled-autocomplete_test.ts
+++ b/polygerrit-ui/app/elements/shared/gr-labeled-autocomplete/gr-labeled-autocomplete_test.ts
@@ -18,28 +18,51 @@
import '../../../test/common-test-setup-karma';
import './gr-labeled-autocomplete';
import {GrLabeledAutocomplete} from './gr-labeled-autocomplete';
+import {assertIsDefined} from '../../../utils/common-util';
const basicFixture = fixtureFromElement('gr-labeled-autocomplete');
suite('gr-labeled-autocomplete tests', () => {
let element: GrLabeledAutocomplete;
- setup(() => {
+ setup(async () => {
element = basicFixture.instantiate();
+ await element.updateComplete;
});
test('tapping trigger focuses autocomplete', () => {
const e = {stopPropagation: () => undefined};
const stopPropagationStub = sinon.stub(e, 'stopPropagation');
- const autocompleteStub = sinon.stub(element.$.autocomplete, 'focus');
+ assertIsDefined(element.autocomplete);
+ const autocompleteStub = sinon.stub(element.autocomplete, 'focus');
element._handleTriggerClick(e as Event);
assert.isTrue(stopPropagationStub.calledOnce);
assert.isTrue(autocompleteStub.calledOnce);
});
test('setText', () => {
- const setTextStub = sinon.stub(element.$.autocomplete, 'setText');
+ assertIsDefined(element.autocomplete);
+ const setTextStub = sinon.stub(element.autocomplete, 'setText');
element.setText('foo-bar');
assert.isTrue(setTextStub.calledWith('foo-bar'));
});
+
+ test('shadowDom', async () => {
+ element.label = 'Some label';
+ await element.updateComplete;
+
+ expect(element).shadowDom.to.equal(`
+ <div id="container">
+ <div id="header">Some label</div>
+ <div id="body">
+ <gr-autocomplete
+ id="autocomplete"
+ threshold="0"
+ borderless=""
+ ></gr-autocomplete>
+ <div id="trigger">â–¼</div>
+ </div>
+ </div>
+ `);
+ });
});
diff --git a/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text.ts b/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text.ts
index 9adb8ae..c283876 100644
--- a/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text.ts
+++ b/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text.ts
@@ -61,14 +61,6 @@
white-space: var(--linked-text-white-space, pre-wrap);
word-wrap: var(--linked-text-word-wrap, break-word);
}
- :host([disabled]) ::slotted(a) {
- color: inherit;
- text-decoration: none;
- pointer-events: none;
- }
- ::slotted(a) {
- color: var(--link-color);
- }
`;
}
@@ -97,6 +89,8 @@
override updated(changedProperties: PropertyValues): void {
if (changedProperties.has('content') || changedProperties.has('config')) {
this._contentOrConfigChanged();
+ } else if (changedProperties.has('disabled')) {
+ this.styleLinks();
}
}
@@ -131,7 +125,7 @@
// Ensure links to the same host originating from commentlink configs
// open in the same tab. When target is not set - default is _self
// @see Issue 4616
- this.outputElement!.querySelectorAll('a').forEach(anchor => {
+ this.outputElement.querySelectorAll('a').forEach(anchor => {
if (anchor.hostname === window.location.hostname) {
anchor.removeAttribute('target');
} else {
@@ -139,6 +133,30 @@
}
anchor.setAttribute('rel', 'noopener');
});
+
+ this.styleLinks();
+ }
+
+ /**
+ * Styles the links based on whether gr-linked-text is disabled or not
+ */
+ private styleLinks() {
+ assertIsDefined(this.outputElement);
+ this.outputElement.querySelectorAll('a').forEach(anchor => {
+ anchor.setAttribute('style', this.computeLinkStyle());
+ });
+ }
+
+ private computeLinkStyle() {
+ if (this.disabled) {
+ return `
+ color: inherit;
+ text-decoration: none;
+ pointer-events: none;
+ `;
+ } else {
+ return 'color: var(--link-color)';
+ }
}
/**
diff --git a/polygerrit-ui/app/elements/shared/gr-repo-branch-picker/gr-repo-branch-picker.ts b/polygerrit-ui/app/elements/shared/gr-repo-branch-picker/gr-repo-branch-picker.ts
index 85dbbc6..9c88c5c 100644
--- a/polygerrit-ui/app/elements/shared/gr-repo-branch-picker/gr-repo-branch-picker.ts
+++ b/polygerrit-ui/app/elements/shared/gr-repo-branch-picker/gr-repo-branch-picker.ts
@@ -15,13 +15,9 @@
* limitations under the License.
*/
import '@polymer/iron-icon/iron-icon';
-import '../../../styles/shared-styles';
import '../gr-icons/gr-icons';
import '../gr-labeled-autocomplete/gr-labeled-autocomplete';
-import {PolymerElement} from '@polymer/polymer/polymer-element';
-import {htmlTemplate} from './gr-repo-branch-picker_html';
import {singleDecodeURL} from '../../../utils/url-util';
-import {customElement, property} from '@polymer/decorators';
import {AutocompleteQuery} from '../gr-autocomplete/gr-autocomplete';
import {
BranchName,
@@ -31,58 +27,106 @@
} from '../../../types/common';
import {GrLabeledAutocomplete} from '../gr-labeled-autocomplete/gr-labeled-autocomplete';
import {getAppContext} from '../../../services/app-context';
+import {sharedStyles} from '../../../styles/shared-styles';
+import {LitElement, PropertyValues, html, css} from 'lit';
+import {customElement, property, state, query} from 'lit/decorators';
+import {assertIsDefined} from '../../../utils/common-util';
+import {fire} from '../../../utils/event-util';
+import {BindValueChangeEvent} from '../../../types/events';
const SUGGESTIONS_LIMIT = 15;
const REF_PREFIX = 'refs/heads/';
-export interface GrRepoBranchPicker {
- $: {
- repoInput: GrLabeledAutocomplete;
- branchInput: GrLabeledAutocomplete;
- };
-}
@customElement('gr-repo-branch-picker')
-export class GrRepoBranchPicker extends PolymerElement {
- static get template() {
- return htmlTemplate;
- }
+export class GrRepoBranchPicker extends LitElement {
+ @query('#repoInput') protected repoInput?: GrLabeledAutocomplete;
- @property({type: String, notify: true, observer: '_repoChanged'})
+ @query('#branchInput') protected branchInput?: GrLabeledAutocomplete;
+
+ @property({type: String})
repo?: RepoName;
- @property({type: String, notify: true})
+ @property({type: String})
branch?: BranchName;
- @property({type: Boolean})
- _branchDisabled = false;
+ @state() private branchDisabled = false;
- @property({type: Object})
- _query: AutocompleteQuery = () => Promise.resolve([]);
+ private readonly query: AutocompleteQuery = () => Promise.resolve([]);
- @property({type: Object})
- _repoQuery: AutocompleteQuery = () => Promise.resolve([]);
+ private readonly repoQuery: AutocompleteQuery = () => Promise.resolve([]);
private readonly restApiService = getAppContext().restApiService;
constructor() {
super();
- this._query = input => this._getRepoBranchesSuggestions(input);
- this._repoQuery = input => this._getRepoSuggestions(input);
+ this.query = input => this.getRepoBranchesSuggestions(input);
+ this.repoQuery = input => this.getRepoSuggestions(input);
}
override connectedCallback() {
super.connectedCallback();
if (this.repo) {
- this.$.repoInput.setText(this.repo);
+ assertIsDefined(this.repoInput, 'repoInput');
+ this.repoInput.setText(this.repo);
+ }
+ this.branchDisabled = !this.repo;
+ }
+
+ static override get styles() {
+ return [
+ sharedStyles,
+ css`
+ :host {
+ display: block;
+ }
+ gr-labeled-autocomplete,
+ iron-icon {
+ display: inline-block;
+ }
+ iron-icon {
+ margin-bottom: var(--spacing-l);
+ }
+ `,
+ ];
+ }
+
+ override render() {
+ return html`
+ <div>
+ <gr-labeled-autocomplete
+ id="repoInput"
+ label="Repository"
+ placeholder="Select repo"
+ .query=${this.repoQuery}
+ @commit=${(e: CustomEvent<{value: string}>) => {
+ this.repoCommitted(e);
+ }}
+ >
+ </gr-labeled-autocomplete>
+ <iron-icon icon="gr-icons:chevron-right"></iron-icon>
+ <gr-labeled-autocomplete
+ id="branchInput"
+ label="Branch"
+ placeholder="Select branch"
+ ?disabled=${this.branchDisabled}
+ .query=${this.query}
+ @commit=${(e: CustomEvent<{value: string}>) => {
+ this.branchCommitted(e);
+ }}
+ >
+ </gr-labeled-autocomplete>
+ </div>
+ `;
+ }
+
+ override willUpdate(changedProperties: PropertyValues) {
+ if (changedProperties.has('repo')) {
+ this.repoChanged();
}
}
- override ready() {
- super.ready();
- this._branchDisabled = !this.repo;
- }
-
- _getRepoBranchesSuggestions(input: string) {
+ // private but used in test
+ getRepoBranchesSuggestions(input: string) {
if (!this.repo) {
return Promise.resolve([]);
}
@@ -91,26 +135,10 @@
}
return this.restApiService
.getRepoBranches(input, this.repo, SUGGESTIONS_LIMIT)
- .then(res => this._branchResponseToSuggestions(res));
+ .then(res => this.branchResponseToSuggestions(res));
}
- _getRepoSuggestions(input: string) {
- return this.restApiService
- .getRepos(input, SUGGESTIONS_LIMIT)
- .then(res => this._repoResponseToSuggestions(res));
- }
-
- _repoResponseToSuggestions(res: ProjectInfoWithName[] | undefined) {
- if (!res) return [];
- return res.map(repo => {
- return {
- name: repo.name,
- value: singleDecodeURL(repo.id),
- };
- });
- }
-
- _branchResponseToSuggestions(res: BranchInfo[] | undefined) {
+ private branchResponseToSuggestions(res: BranchInfo[] | undefined) {
if (!res) return [];
return res.map(branchInfo => {
let branch;
@@ -123,21 +151,45 @@
});
}
- _repoCommitted(e: CustomEvent<{value: string}>) {
+ // private but used in test
+ getRepoSuggestions(input: string) {
+ return this.restApiService
+ .getRepos(input, SUGGESTIONS_LIMIT)
+ .then(res => this.repoResponseToSuggestions(res));
+ }
+
+ private repoResponseToSuggestions(res: ProjectInfoWithName[] | undefined) {
+ if (!res) return [];
+ return res.map(repo => {
+ return {
+ name: repo.name,
+ value: singleDecodeURL(repo.id),
+ };
+ });
+ }
+
+ private repoCommitted(e: CustomEvent<{value: string}>) {
this.repo = e.detail.value as RepoName;
+ fire(this, 'repo-changed', {value: e.detail.value});
}
- _branchCommitted(e: CustomEvent<{value: string}>) {
+ private branchCommitted(e: CustomEvent<{value: string}>) {
this.branch = e.detail.value as BranchName;
+ fire(this, 'branch-changed', {value: e.detail.value});
}
- _repoChanged() {
- this.$.branchInput.clear();
- this._branchDisabled = !this.repo;
+ private repoChanged() {
+ assertIsDefined(this.branchInput, 'branchInput');
+ this.branchInput.clear();
+ this.branchDisabled = !this.repo;
}
}
declare global {
+ interface HTMLElementEventMap {
+ 'branch-changed': BindValueChangeEvent;
+ 'repo-changed': BindValueChangeEvent;
+ }
interface HTMLElementTagNameMap {
'gr-repo-branch-picker': GrRepoBranchPicker;
}
diff --git a/polygerrit-ui/app/elements/shared/gr-repo-branch-picker/gr-repo-branch-picker_html.ts b/polygerrit-ui/app/elements/shared/gr-repo-branch-picker/gr-repo-branch-picker_html.ts
deleted file mode 100644
index 3e551b6..0000000
--- a/polygerrit-ui/app/elements/shared/gr-repo-branch-picker/gr-repo-branch-picker_html.ts
+++ /dev/null
@@ -1,52 +0,0 @@
-/**
- * @license
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import {html} from '@polymer/polymer/lib/utils/html-tag';
-
-export const htmlTemplate = html`
- <style include="shared-styles">
- :host {
- display: block;
- }
- gr-labeled-autocomplete,
- iron-icon {
- display: inline-block;
- }
- iron-icon {
- margin-bottom: var(--spacing-l);
- }
- </style>
- <div>
- <gr-labeled-autocomplete
- id="repoInput"
- label="Repository"
- placeholder="Select repo"
- on-commit="_repoCommitted"
- query="[[_repoQuery]]"
- >
- </gr-labeled-autocomplete>
- <iron-icon icon="gr-icons:chevron-right"></iron-icon>
- <gr-labeled-autocomplete
- id="branchInput"
- label="Branch"
- placeholder="Select branch"
- disabled="[[_branchDisabled]]"
- on-commit="_branchCommitted"
- query="[[_query]]"
- >
- </gr-labeled-autocomplete>
- </div>
-`;
diff --git a/polygerrit-ui/app/elements/shared/gr-repo-branch-picker/gr-repo-branch-picker_test.ts b/polygerrit-ui/app/elements/shared/gr-repo-branch-picker/gr-repo-branch-picker_test.ts
index 31efa73..f8b1343 100644
--- a/polygerrit-ui/app/elements/shared/gr-repo-branch-picker/gr-repo-branch-picker_test.ts
+++ b/polygerrit-ui/app/elements/shared/gr-repo-branch-picker/gr-repo-branch-picker_test.ts
@@ -26,11 +26,12 @@
suite('gr-repo-branch-picker tests', () => {
let element: GrRepoBranchPicker;
- setup(() => {
+ setup(async () => {
element = basicFixture.instantiate();
+ await element.updateComplete;
});
- suite('_getRepoSuggestions', () => {
+ suite('getRepoSuggestions', () => {
let getReposStub: sinon.SinonStub;
setup(() => {
getReposStub = stubRestApi('getRepos').returns(
@@ -57,7 +58,7 @@
test('converts to suggestion objects', async () => {
const input = 'plugins/avatars';
- const suggestions = await element._getRepoSuggestions(input);
+ const suggestions = await element.getRepoSuggestions(input);
assert.isTrue(getReposStub.calledWith(input));
const unencodedNames = [
'plugins/avatars-external',
@@ -76,7 +77,7 @@
});
});
- suite('_getRepoBranchesSuggestions', () => {
+ suite('getRepoBranchesSuggestions', () => {
let getRepoBranchesStub: sinon.SinonStub;
setup(() => {
getRepoBranchesStub = stubRestApi('getRepoBranches').returns(
@@ -95,9 +96,7 @@
const repo = 'gerrit';
const branchInput = 'stable-2.1';
element.repo = repo as RepoName;
- const suggestions = await element._getRepoBranchesSuggestions(
- branchInput
- );
+ const suggestions = await element.getRepoBranchesSuggestions(branchInput);
assert.isTrue(getRepoBranchesStub.calledWith(branchInput, repo, 15));
const refNames = [
'stable-2.10',
@@ -121,16 +120,16 @@
const repo = 'gerrit' as RepoName;
const branchInput = 'refs/heads/stable-2.1';
element.repo = repo;
- return element._getRepoBranchesSuggestions(branchInput).then(() => {
+ return element.getRepoBranchesSuggestions(branchInput).then(() => {
assert.isTrue(getRepoBranchesStub.calledWith('stable-2.1', repo, 15));
});
});
test('does not query when repo is unset', async () => {
- await element._getRepoBranchesSuggestions('');
+ await element.getRepoBranchesSuggestions('');
assert.isFalse(getRepoBranchesStub.called);
element.repo = 'gerrit' as RepoName;
- await element._getRepoBranchesSuggestions('');
+ await element.getRepoBranchesSuggestions('');
assert.isTrue(getRepoBranchesStub.called);
});
});
diff --git a/polygerrit-ui/app/elements/shared/gr-tooltip-content/gr-tooltip-content.ts b/polygerrit-ui/app/elements/shared/gr-tooltip-content/gr-tooltip-content.ts
index e387a3d..6ba69a5 100644
--- a/polygerrit-ui/app/elements/shared/gr-tooltip-content/gr-tooltip-content.ts
+++ b/polygerrit-ui/app/elements/shared/gr-tooltip-content/gr-tooltip-content.ts
@@ -34,6 +34,11 @@
@property({type: Boolean, attribute: 'has-tooltip', reflect: true})
hasTooltip = false;
+ // A light tooltip will disappear immediately when the original hovered
+ // over content is no longer hovered over.
+ @property({type: Boolean, attribute: 'light-tooltip', reflect: true})
+ lightTooltip = false;
+
@property({type: Boolean, attribute: 'position-below', reflect: true})
positionBelow = false;
@@ -158,7 +163,9 @@
window.addEventListener('scroll', this.windowScrollHandler);
this.addEventListener('mouseleave', this.hideHandler);
this.addEventListener('click', this.hideHandler);
- tooltip.addEventListener('mouseleave', this.hideHandler);
+ if (!this.lightTooltip) {
+ tooltip.addEventListener('mouseleave', this.hideHandler);
+ }
}
_handleHideTooltip(e: Event | undefined) {
@@ -171,7 +178,8 @@
// Do not hide if mouse left this or this.tooltip and came to this or
// this.tooltip
if (
- (e as MouseEvent)?.relatedTarget === this.tooltip ||
+ (!this.lightTooltip &&
+ (e as MouseEvent)?.relatedTarget === this.tooltip) ||
(e as MouseEvent)?.relatedTarget === this
) {
return;
@@ -181,7 +189,9 @@
this.removeEventListener('mouseleave', this.hideHandler);
this.removeEventListener('click', this.hideHandler);
this.setAttribute('title', this.originalTitle);
- this.tooltip?.removeEventListener('mouseleave', this.hideHandler);
+ if (!this.lightTooltip) {
+ this.tooltip?.removeEventListener('mouseleave', this.hideHandler);
+ }
if (this.tooltip?.parentNode) {
this.tooltip.parentNode.removeChild(this.tooltip);
diff --git a/polygerrit-ui/app/elements/shared/gr-vote-chip/gr-vote-chip.ts b/polygerrit-ui/app/elements/shared/gr-vote-chip/gr-vote-chip.ts
index ba7dc36..b011d23 100644
--- a/polygerrit-ui/app/elements/shared/gr-vote-chip/gr-vote-chip.ts
+++ b/polygerrit-ui/app/elements/shared/gr-vote-chip/gr-vote-chip.ts
@@ -35,7 +35,9 @@
'gr-vote-chip': GrVoteChip;
}
}
-
+/**
+ * @attr {Boolean} circle-shape - element has shape as circle
+ */
@customElement('gr-vote-chip')
export class GrVoteChip extends LitElement {
@property({type: Object})
@@ -53,6 +55,10 @@
static override get styles() {
return [
css`
+ :host([circle-shape]) .vote-chip {
+ border-radius: 50%;
+ border: none;
+ }
.vote-chip.max {
background-color: var(--vote-color-approved);
padding: 2px;
@@ -151,15 +157,9 @@
private computeClass() {
if (!this.label) {
return '';
- } else if (isDetailedLabelInfo(this.label)) {
- if (this.vote?.value) {
- const status = getLabelStatus(this.label, this.vote.value);
- return classForLabelStatus(status);
- }
- } else if (isQuickLabelInfo(this.label)) {
- const status = getLabelStatus(this.label);
+ } else {
+ const status = getLabelStatus(this.label, this.vote?.value);
return classForLabelStatus(status);
}
- return '';
}
}
diff --git a/polygerrit-ui/app/elements/topic/gr-topic-summary.ts b/polygerrit-ui/app/elements/topic/gr-topic-summary.ts
deleted file mode 100644
index 81e5686..0000000
--- a/polygerrit-ui/app/elements/topic/gr-topic-summary.ts
+++ /dev/null
@@ -1,64 +0,0 @@
-/**
- * @license
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import {customElement, property, state} from 'lit/decorators';
-import {LitElement, html, PropertyValues} from 'lit-element/lit-element';
-import {getAppContext} from '../../services/app-context';
-import '../shared/gr-button/gr-button';
-
-/**
- * A summary of a topic with buttons for performing topic-level operations.
- */
-@customElement('gr-topic-summary')
-export class GrTopicSummary extends LitElement {
- @property({type: String})
- topicName?: string;
-
- @state()
- private changeCount?: number;
-
- private restApiService = getAppContext().restApiService;
-
- override willUpdate(changedProperties: PropertyValues) {
- // TODO: receive data from the model once it is added.
- if (changedProperties.has('topicName')) {
- this.restApiService
- .getChanges(undefined /* changesPerPage */, `topic:${this.topicName}`)
- .then(response => {
- this.changeCount = response?.length ?? 0;
- });
- }
- }
-
- override render() {
- if (this.topicName === undefined) {
- return;
- }
- return html`
- <span>Topic: ${this.topicName}</span>
- <span>${this.changeCount} changes</span>
- <gr-button>Reply</gr-button>
- <gr-button>Select Changes</gr-button>
- `;
- }
-}
-
-declare global {
- interface HTMLElementTagNameMap {
- 'gr-topic-summary': GrTopicSummary;
- }
-}
diff --git a/polygerrit-ui/app/elements/topic/gr-topic-summary_test.ts b/polygerrit-ui/app/elements/topic/gr-topic-summary_test.ts
deleted file mode 100644
index 33a9029..0000000
--- a/polygerrit-ui/app/elements/topic/gr-topic-summary_test.ts
+++ /dev/null
@@ -1,44 +0,0 @@
-/**
- * @license
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import '../../test/common-test-setup-karma';
-import {createChange} from '../../test/test-data-generators';
-import {queryAll, stubRestApi} from '../../test/test-utils';
-import './gr-topic-summary';
-import {GrTopicSummary} from './gr-topic-summary';
-
-const basicFixture = fixtureFromElement('gr-topic-summary');
-const topicName = 'myTopic';
-
-suite('gr-topic-summary tests', () => {
- let element: GrTopicSummary;
-
- setup(async () => {
- stubRestApi('getChanges')
- .withArgs(undefined, `topic:${topicName}`)
- .resolves([createChange(), createChange(), createChange()]);
- element = basicFixture.instantiate();
- element.topicName = topicName;
- await element.updateComplete;
- });
-
- test('shows topic information', () => {
- const labels = queryAll<HTMLSpanElement>(element, 'span');
- assert.equal(labels[0].textContent, `Topic: ${topicName}`);
- assert.equal(labels[1].textContent, '3 changes');
- });
-});
diff --git a/polygerrit-ui/app/elements/topic/gr-topic-tree-repo.ts b/polygerrit-ui/app/elements/topic/gr-topic-tree-repo.ts
deleted file mode 100644
index 234f058..0000000
--- a/polygerrit-ui/app/elements/topic/gr-topic-tree-repo.ts
+++ /dev/null
@@ -1,61 +0,0 @@
-/**
- * @license
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import './gr-topic-tree-row';
-import {customElement, property} from 'lit/decorators';
-import {LitElement, html, css} from 'lit-element/lit-element';
-import '../shared/gr-button/gr-button';
-import {ChangeInfo, RepoName} from '../../api/rest-api';
-
-/**
- * A view of changes that all belong to the same repository.
- */
-@customElement('gr-topic-tree-repo')
-export class GrTopicTreeRepo extends LitElement {
- @property({type: String})
- repoName?: RepoName;
-
- @property({type: Array})
- changes?: ChangeInfo[];
-
- static override styles = css`
- :host {
- display: contents;
- }
- `;
-
- override render() {
- if (this.repoName === undefined || this.changes === undefined) {
- return;
- }
- // TODO: Groups of related changes should be separated within the repository.
- return html`
- <h2>Repo ${this.repoName}</h2>
- ${this.changes.map(change => this.renderTreeRow(change))}
- `;
- }
-
- private renderTreeRow(change: ChangeInfo) {
- return html`<gr-topic-tree-row .change=${change}></gr-topic-tree-row>`;
- }
-}
-
-declare global {
- interface HTMLElementTagNameMap {
- 'gr-topic-tree-repo': GrTopicTreeRepo;
- }
-}
diff --git a/polygerrit-ui/app/elements/topic/gr-topic-tree-repo_test.ts b/polygerrit-ui/app/elements/topic/gr-topic-tree-repo_test.ts
deleted file mode 100644
index 2e903b5..0000000
--- a/polygerrit-ui/app/elements/topic/gr-topic-tree-repo_test.ts
+++ /dev/null
@@ -1,42 +0,0 @@
-/**
- * @license
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import {RepoName} from '../../api/rest-api';
-import '../../test/common-test-setup-karma';
-import {createChange} from '../../test/test-data-generators';
-import {queryAndAssert} from '../../test/test-utils';
-import './gr-topic-tree-repo';
-import {GrTopicTreeRepo} from './gr-topic-tree-repo';
-
-const basicFixture = fixtureFromElement('gr-topic-tree-repo');
-const repoName = 'myRepo' as RepoName;
-
-suite('gr-topic-tree-repo tests', () => {
- let element: GrTopicTreeRepo;
-
- setup(async () => {
- element = basicFixture.instantiate();
- element.repoName = repoName;
- element.changes = [createChange()];
- await element.updateComplete;
- });
-
- test('shows repository name', () => {
- const heading = queryAndAssert<HTMLHeadingElement>(element, 'h2');
- assert.equal(heading.textContent, `Repo ${repoName}`);
- });
-});
diff --git a/polygerrit-ui/app/elements/topic/gr-topic-tree-row.ts b/polygerrit-ui/app/elements/topic/gr-topic-tree-row.ts
deleted file mode 100644
index 0355bee..0000000
--- a/polygerrit-ui/app/elements/topic/gr-topic-tree-row.ts
+++ /dev/null
@@ -1,88 +0,0 @@
-/**
- * @license
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import {customElement, property} from 'lit/decorators';
-import {LitElement, html, css} from 'lit-element/lit-element';
-import '../shared/gr-button/gr-button';
-import {ChangeInfo} from '../../api/rest-api';
-
-// TODO: copied from gr-change-list-item. Extract both places to a util.
-enum ChangeSize {
- XS = 10,
- SMALL = 50,
- MEDIUM = 250,
- LARGE = 1000,
-}
-
-/**
- * A single change shown as part of the topic tree.
- */
-@customElement('gr-topic-tree-row')
-export class GrTopicTreeRow extends LitElement {
- @property({type: Object})
- change?: ChangeInfo;
-
- static override styles = css`
- :host {
- display: contents;
- }
- `;
-
- override render() {
- if (this.change === undefined) {
- return;
- }
- const authorName =
- this.change.revisions?.[this.change.current_revision!].commit?.author
- .name;
- return html`
- <tr>
- <td>${this.computeSize(this.change)}</td>
- <td>${this.change.subject}</td>
- <td>${this.change.topic}</td>
- <td>${this.change.branch}</td>
- <td>${authorName}</td>
- <td>${this.change.status}</td>
- </tr>
- `;
- }
-
- // TODO: copied from gr-change-list-item. Extract both places to a util.
- private computeSize(change: ChangeInfo) {
- const delta = change.insertions + change.deletions;
- if (isNaN(delta) || delta === 0) {
- return;
- }
- if (delta < ChangeSize.XS) {
- return 'XS';
- } else if (delta < ChangeSize.SMALL) {
- return 'S';
- } else if (delta < ChangeSize.MEDIUM) {
- return 'M';
- } else if (delta < ChangeSize.LARGE) {
- return 'L';
- } else {
- return 'XL';
- }
- }
-}
-
-declare global {
- interface HTMLElementTagNameMap {
- 'gr-topic-tree-row': GrTopicTreeRow;
- }
-}
diff --git a/polygerrit-ui/app/elements/topic/gr-topic-tree-row_test.ts b/polygerrit-ui/app/elements/topic/gr-topic-tree-row_test.ts
deleted file mode 100644
index e73cf13..0000000
--- a/polygerrit-ui/app/elements/topic/gr-topic-tree-row_test.ts
+++ /dev/null
@@ -1,102 +0,0 @@
-/**
- * @license
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import {ChangeInfo, ChangeStatus, TopicName} from '../../api/rest-api';
-import '../../test/common-test-setup-karma';
-import {
- createChangeViewChange,
- TEST_BRANCH_ID,
- TEST_SUBJECT,
-} from '../../test/test-data-generators';
-import {queryAll} from '../../test/test-utils';
-import './gr-topic-tree-row';
-import {GrTopicTreeRow} from './gr-topic-tree-row';
-
-const basicFixture = fixtureFromElement('gr-topic-tree-row');
-
-suite('gr-topic-tree-row tests', () => {
- let element: GrTopicTreeRow;
- const change: ChangeInfo = {
- ...createChangeViewChange(),
- insertions: 50,
- topic: 'myTopic' as TopicName,
- };
-
- setup(async () => {
- element = basicFixture.instantiate();
- element.change = change;
- await element.updateComplete;
- });
-
- test('shows columns of change information', () => {
- const columns = queryAll<HTMLTableCellElement>(element, 'td');
- assert.equal(columns[0].textContent, 'M');
- assert.equal(columns[1].textContent, TEST_SUBJECT);
- assert.equal(columns[2].textContent, 'myTopic');
- assert.equal(columns[3].textContent, TEST_BRANCH_ID);
- assert.equal(columns[4].textContent, 'Test name');
- assert.equal(columns[5].textContent, ChangeStatus.NEW);
- });
-
- test('shows unknown size', async () => {
- element.change = {...change, insertions: 0, deletions: 0};
- await element.updateComplete;
-
- const columns = queryAll<HTMLTableCellElement>(element, 'td');
- assert.equal(columns[0].textContent, '');
- });
-
- test('shows XS size', async () => {
- element.change = {...change, insertions: 3, deletions: 6};
- await element.updateComplete;
-
- const columns = queryAll<HTMLTableCellElement>(element, 'td');
- assert.equal(columns[0].textContent, 'XS');
- });
-
- test('shows S size', async () => {
- element.change = {...change, insertions: 9, deletions: 40};
- await element.updateComplete;
-
- const columns = queryAll<HTMLTableCellElement>(element, 'td');
- assert.equal(columns[0].textContent, 'S');
- });
-
- test('shows M size', async () => {
- element.change = {...change, insertions: 249, deletions: 0};
- await element.updateComplete;
-
- const columns = queryAll<HTMLSpanElement>(element, 'td');
- assert.equal(columns[0].textContent, 'M');
- });
-
- test('shows L size', async () => {
- element.change = {...change, insertions: 499, deletions: 500};
- await element.updateComplete;
-
- const columns = queryAll<HTMLTableCellElement>(element, 'td');
- assert.equal(columns[0].textContent, 'L');
- });
-
- test('shows XL size', async () => {
- element.change = {...change, insertions: 1000, deletions: 1};
- await element.updateComplete;
-
- const columns = queryAll<HTMLTableCellElement>(element, 'td');
- assert.equal(columns[0].textContent, 'XL');
- });
-});
diff --git a/polygerrit-ui/app/elements/topic/gr-topic-tree.ts b/polygerrit-ui/app/elements/topic/gr-topic-tree.ts
deleted file mode 100644
index a993d90..0000000
--- a/polygerrit-ui/app/elements/topic/gr-topic-tree.ts
+++ /dev/null
@@ -1,116 +0,0 @@
-/**
- * @license
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import './gr-topic-tree-repo';
-import {customElement, property, state} from 'lit/decorators';
-import {LitElement, html, PropertyValues} from 'lit-element/lit-element';
-import {getAppContext} from '../../services/app-context';
-import '../shared/gr-button/gr-button';
-import {ChangeInfo, RepoName} from '../../api/rest-api';
-
-/**
- * A tree-like dashboard showing changes related to a topic, organized by
- * repository.
- */
-@customElement('gr-topic-tree')
-export class GrTopicTree extends LitElement {
- @property({type: String})
- topicName?: string;
-
- @state()
- private changesByRepo = new Map<RepoName, ChangeInfo[]>();
-
- private restApiService = getAppContext().restApiService;
-
- override willUpdate(changedProperties: PropertyValues) {
- // TODO: Receive data from the model once it is added.
- if (changedProperties.has('topicName')) {
- this.loadAndSortChangesFromTopic();
- }
- }
-
- override render() {
- return html`
- <table>
- <thead>
- <tr>
- <td>Size</td>
- <td>Subject</td>
- <td>Topic</td>
- <td>Branch</td>
- <td>Owner</td>
- <td>Status</td>
- </tr>
- </thead>
- <tbody>
- ${Array.from(this.changesByRepo).map(([repoName, changes]) =>
- this.renderRepoSection(repoName, changes)
- )}
- </tbody>
- </table>
- `;
- }
-
- private renderRepoSection(repoName: RepoName, changes: ChangeInfo[]) {
- return html`
- <gr-topic-tree-repo
- .repoName=${repoName}
- .changes=${changes}
- ></gr-topic-tree-repo>
- `;
- }
-
- private async loadAndSortChangesFromTopic(): Promise<void> {
- const changesInTopic = this.topicName
- ? await this.restApiService.getChangesWithSameTopic(this.topicName)
- : [];
- const changesSubmittedTogether = await this.loadChangesSubmittedTogether(
- changesInTopic
- );
- this.changesByRepo.clear();
- for (const change of changesSubmittedTogether) {
- if (this.changesByRepo.has(change.project)) {
- this.changesByRepo.get(change.project)!.push(change);
- } else {
- this.changesByRepo.set(change.project, [change]);
- }
- }
- this.requestUpdate();
- }
-
- private async loadChangesSubmittedTogether(
- changesInTopic?: ChangeInfo[]
- ): Promise<ChangeInfo[]> {
- // All changes in the topic will be submitted together, so we can use any of
- // them for the request to getChangesSubmittedTogether as long as the topic
- // is not empty.
- if (!changesInTopic || changesInTopic.length === 0) {
- return [];
- }
- const response = await this.restApiService.getChangesSubmittedTogether(
- changesInTopic[0]._number,
- ['NON_VISIBLE_CHANGES', 'CURRENT_REVISION', 'CURRENT_COMMIT']
- );
- return response?.changes ?? [];
- }
-}
-
-declare global {
- interface HTMLElementTagNameMap {
- 'gr-topic-tree': GrTopicTree;
- }
-}
diff --git a/polygerrit-ui/app/elements/topic/gr-topic-tree_test.ts b/polygerrit-ui/app/elements/topic/gr-topic-tree_test.ts
deleted file mode 100644
index 72e14b8..0000000
--- a/polygerrit-ui/app/elements/topic/gr-topic-tree_test.ts
+++ /dev/null
@@ -1,101 +0,0 @@
-/**
- * @license
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import {ChangeInfo, RepoName} from '../../api/rest-api';
-import '../../test/common-test-setup-karma';
-import {createChange} from '../../test/test-data-generators';
-import {mockPromise, queryAll, stubRestApi} from '../../test/test-utils';
-import {SubmittedTogetherInfo} from '../../types/common';
-import './gr-topic-tree';
-import {GrTopicTree} from './gr-topic-tree';
-import {GrTopicTreeRepo} from './gr-topic-tree-repo';
-
-const basicFixture = fixtureFromElement('gr-topic-tree');
-
-const repo1Name = 'repo1' as RepoName;
-const repo2Name = 'repo2' as RepoName;
-const repo3Name = 'repo3' as RepoName;
-
-function createChangeForRepo(repoName: string): ChangeInfo {
- return {...createChange(), project: repoName as RepoName};
-}
-
-suite('gr-topic-tree tests', () => {
- let element: GrTopicTree;
- const repo1ChangeOutsideTopic = createChangeForRepo(repo1Name);
- const repo1ChangesInTopic = [
- createChangeForRepo(repo1Name),
- createChangeForRepo(repo1Name),
- ];
- const repo2ChangesInTopic = [
- createChangeForRepo(repo2Name),
- createChangeForRepo(repo2Name),
- ];
- const repo3ChangesInTopic = [
- createChangeForRepo(repo3Name),
- createChangeForRepo(repo3Name),
- ];
-
- setup(async () => {
- stubRestApi('getChangesWithSameTopic')
- .withArgs('myTopic')
- .resolves([
- ...repo1ChangesInTopic,
- ...repo2ChangesInTopic,
- ...repo3ChangesInTopic,
- ]);
- const changesSubmittedTogetherPromise =
- mockPromise<SubmittedTogetherInfo>();
- stubRestApi('getChangesSubmittedTogether').returns(
- changesSubmittedTogetherPromise
- );
- element = basicFixture.instantiate();
- element.topicName = 'myTopic';
-
- // The first update will trigger the data to be loaded. The second update
- // will be rendering the loaded data.
- await element.updateComplete;
- changesSubmittedTogetherPromise.resolve({
- changes: [
- ...repo1ChangesInTopic,
- repo1ChangeOutsideTopic,
- ...repo2ChangesInTopic,
- ...repo3ChangesInTopic,
- ],
- non_visible_changes: 0,
- });
- await changesSubmittedTogetherPromise;
- await element.updateComplete;
- });
-
- test('groups changes by repo', () => {
- const repoSections = queryAll<GrTopicTreeRepo>(
- element,
- 'gr-topic-tree-repo'
- );
- assert.lengthOf(repoSections, 3);
- assert.equal(repoSections[0].repoName, repo1Name);
- assert.sameMembers(repoSections[0].changes!, [
- ...repo1ChangesInTopic,
- repo1ChangeOutsideTopic,
- ]);
- assert.equal(repoSections[1].repoName, repo2Name);
- assert.sameMembers(repoSections[1].changes!, repo2ChangesInTopic);
- assert.equal(repoSections[2].repoName, repo3Name);
- assert.sameMembers(repoSections[2].changes!, repo3ChangesInTopic);
- });
-});
diff --git a/polygerrit-ui/app/elements/topic/gr-topic-view.ts b/polygerrit-ui/app/elements/topic/gr-topic-view.ts
deleted file mode 100644
index 86099b5..0000000
--- a/polygerrit-ui/app/elements/topic/gr-topic-view.ts
+++ /dev/null
@@ -1,75 +0,0 @@
-/**
- * @license
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import {customElement, property, state} from 'lit/decorators';
-import {LitElement, html, PropertyValues} from 'lit';
-import {AppElementTopicParams} from '../gr-app-types';
-import {getAppContext} from '../../services/app-context';
-import {KnownExperimentId} from '../../services/flags/flags';
-import {GerritNav} from '../core/gr-navigation/gr-navigation';
-import {GerritView} from '../../services/router/router-model';
-import './gr-topic-summary';
-import './gr-topic-tree';
-
-/**
- * A page showing all information about a topic and changes that are related
- * to that topic.
- */
-@customElement('gr-topic-view')
-export class GrTopicView extends LitElement {
- @property({type: Object})
- params?: AppElementTopicParams;
-
- @state()
- topicName?: string;
-
- private readonly flagsService = getAppContext().flagsService;
-
- override willUpdate(changedProperties: PropertyValues) {
- if (changedProperties.has('params')) {
- this.paramsChanged();
- }
- }
-
- override render() {
- if (this.topicName === undefined) {
- return;
- }
- // TODO: Add topic selector
- return html`
- <gr-topic-summary .topicName=${this.topicName}></gr-topic-summary>
- <gr-topic-tree .topicName=${this.topicName}></gr-topic-tree>
- `;
- }
-
- paramsChanged() {
- if (this.params?.view !== GerritView.TOPIC) return;
- this.topicName = this.params?.topic;
- if (
- !this.flagsService.isEnabled(KnownExperimentId.TOPICS_PAGE) &&
- this.topicName
- ) {
- GerritNav.navigateToSearchQuery(`topic:${this.topicName}`);
- }
- }
-}
-
-declare global {
- interface HTMLElementTagNameMap {
- 'gr-topic-view': GrTopicView;
- }
-}
diff --git a/polygerrit-ui/app/elements/topic/gr-topic-view_test.ts b/polygerrit-ui/app/elements/topic/gr-topic-view_test.ts
deleted file mode 100644
index 726fca2..0000000
--- a/polygerrit-ui/app/elements/topic/gr-topic-view_test.ts
+++ /dev/null
@@ -1,59 +0,0 @@
-/**
- * @license
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import {KnownExperimentId} from '../../services/flags/flags';
-import '../../test/common-test-setup-karma';
-import {createGenerateUrlTopicViewParams} from '../../test/test-data-generators';
-import {stubFlags} from '../../test/test-utils';
-import {GerritNav} from '../core/gr-navigation/gr-navigation';
-import './gr-topic-view';
-import {GrTopicView} from './gr-topic-view';
-
-const basicFixture = fixtureFromElement('gr-topic-view');
-
-suite('gr-topic-view tests', () => {
- let element: GrTopicView;
- let redirectStub: sinon.SinonStub;
-
- async function commonSetup(experimentEnabled: boolean) {
- redirectStub = sinon.stub(GerritNav, 'navigateToSearchQuery');
- stubFlags('isEnabled')
- .withArgs(KnownExperimentId.TOPICS_PAGE)
- .returns(experimentEnabled);
- element = basicFixture.instantiate();
- element.params = createGenerateUrlTopicViewParams();
- await element.updateComplete;
- }
-
- suite('experiment enabled', () => {
- setup(async () => {
- await commonSetup(true);
- });
- test('does not redirect to search results page if experiment is enabled', () => {
- assert.isTrue(redirectStub.notCalled);
- });
- });
-
- suite('experiment disabled', () => {
- setup(async () => {
- await commonSetup(false);
- });
- test('redirects to search results page if experiment is disabled', () => {
- assert.isTrue(redirectStub.calledWith('topic:myTopic'));
- });
- });
-});
diff --git a/polygerrit-ui/app/elements/diff/gr-context-controls/gr-context-controls.ts b/polygerrit-ui/app/embed/diff/gr-context-controls/gr-context-controls.ts
similarity index 98%
rename from polygerrit-ui/app/elements/diff/gr-context-controls/gr-context-controls.ts
rename to polygerrit-ui/app/embed/diff/gr-context-controls/gr-context-controls.ts
index 6e43fdc..d9f5a4d 100644
--- a/polygerrit-ui/app/elements/diff/gr-context-controls/gr-context-controls.ts
+++ b/polygerrit-ui/app/embed/diff/gr-context-controls/gr-context-controls.ts
@@ -26,14 +26,14 @@
import {of, EMPTY, Subject} from 'rxjs';
import {switchMap, delay} from 'rxjs/operators';
-import '../../shared/gr-button/gr-button';
+import '../../../elements/shared/gr-button/gr-button';
import {pluralize} from '../../../utils/string-util';
import {fire} from '../../../utils/event-util';
import {DiffInfo} from '../../../types/diff';
import {assertIsDefined} from '../../../utils/common-util';
import {css, html, LitElement, TemplateResult} from 'lit';
import {customElement, property} from 'lit/decorators';
-import {subscribe} from '../../lit/subscription-controller';
+import {subscribe} from '../../../elements/lit/subscription-controller';
import {
ContextButtonType,
diff --git a/polygerrit-ui/app/elements/diff/gr-context-controls/gr-context-controls_test.ts b/polygerrit-ui/app/embed/diff/gr-context-controls/gr-context-controls_test.ts
similarity index 100%
rename from polygerrit-ui/app/elements/diff/gr-context-controls/gr-context-controls_test.ts
rename to polygerrit-ui/app/embed/diff/gr-context-controls/gr-context-controls_test.ts
diff --git a/polygerrit-ui/app/elements/diff/gr-coverage-layer/gr-coverage-layer.ts b/polygerrit-ui/app/embed/diff/gr-coverage-layer/gr-coverage-layer.ts
similarity index 100%
rename from polygerrit-ui/app/elements/diff/gr-coverage-layer/gr-coverage-layer.ts
rename to polygerrit-ui/app/embed/diff/gr-coverage-layer/gr-coverage-layer.ts
diff --git a/polygerrit-ui/app/elements/diff/gr-coverage-layer/gr-coverage-layer_html.ts b/polygerrit-ui/app/embed/diff/gr-coverage-layer/gr-coverage-layer_html.ts
similarity index 100%
rename from polygerrit-ui/app/elements/diff/gr-coverage-layer/gr-coverage-layer_html.ts
rename to polygerrit-ui/app/embed/diff/gr-coverage-layer/gr-coverage-layer_html.ts
diff --git a/polygerrit-ui/app/elements/diff/gr-coverage-layer/gr-coverage-layer_test.js b/polygerrit-ui/app/embed/diff/gr-coverage-layer/gr-coverage-layer_test.js
similarity index 100%
rename from polygerrit-ui/app/elements/diff/gr-coverage-layer/gr-coverage-layer_test.js
rename to polygerrit-ui/app/embed/diff/gr-coverage-layer/gr-coverage-layer_test.js
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-binary.ts b/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-builder-binary.ts
similarity index 100%
rename from polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-binary.ts
rename to polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-builder-binary.ts
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element.ts b/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-builder-element.ts
similarity index 99%
rename from polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element.ts
rename to polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-builder-element.ts
index b4a0e9d..701f8c5 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-builder-element.ts
@@ -16,7 +16,7 @@
*/
import '../gr-coverage-layer/gr-coverage-layer';
import '../gr-diff-processor/gr-diff-processor';
-import '../../shared/gr-hovercard/gr-hovercard';
+import '../../../elements/shared/gr-hovercard/gr-hovercard';
import '../gr-ranged-comment-layer/gr-ranged-comment-layer';
import './gr-diff-builder-side-by-side';
import {PolymerElement} from '@polymer/polymer/polymer-element';
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element_html.ts b/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-builder-element_html.ts
similarity index 100%
rename from polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element_html.ts
rename to polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-builder-element_html.ts
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element_test.js b/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-builder-element_test.js
similarity index 100%
rename from polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element_test.js
rename to polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-builder-element_test.js
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-image.ts b/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-builder-image.ts
similarity index 98%
rename from polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-image.ts
rename to polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-builder-image.ts
index 5629aa4..52f1bbc 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-image.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-builder-image.ts
@@ -18,7 +18,7 @@
import {GrDiffBuilderSideBySide} from './gr-diff-builder-side-by-side';
import {ImageInfo} from '../../../types/common';
import {DiffInfo, DiffPreferencesInfo} from '../../../types/diff';
-import {GrEndpointParam} from '../../plugins/gr-endpoint-param/gr-endpoint-param';
+import {GrEndpointParam} from '../../../elements/plugins/gr-endpoint-param/gr-endpoint-param';
import {RenderPreferences} from '../../../api/diff';
import '../gr-diff-image-viewer/gr-image-viewer';
import {GrImageViewer} from '../gr-diff-image-viewer/gr-image-viewer';
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-side-by-side.ts b/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-builder-side-by-side.ts
similarity index 100%
rename from polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-side-by-side.ts
rename to polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-builder-side-by-side.ts
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-unified.ts b/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-builder-unified.ts
similarity index 100%
rename from polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-unified.ts
rename to polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-builder-unified.ts
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-unified_test.js b/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-builder-unified_test.js
similarity index 100%
rename from polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-unified_test.js
rename to polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-builder-unified_test.js
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.ts b/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-builder.ts
similarity index 100%
rename from polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.ts
rename to polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-builder.ts
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/token-highlight-layer.ts b/polygerrit-ui/app/embed/diff/gr-diff-builder/token-highlight-layer.ts
similarity index 87%
rename from polygerrit-ui/app/elements/diff/gr-diff-builder/token-highlight-layer.ts
rename to polygerrit-ui/app/embed/diff/gr-diff-builder/token-highlight-layer.ts
index 54b2450f..3ab02e1 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/token-highlight-layer.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff-builder/token-highlight-layer.ts
@@ -20,11 +20,7 @@
import {GrAnnotation} from '../gr-diff-highlight/gr-annotation';
import {debounce, DelayedTask} from '../../../utils/async-util';
-import {
- getLineElByChild,
- getSideByLineEl,
- getPreviousContentNodes,
-} from '../gr-diff/gr-diff-utils';
+import {getLineElByChild, getSideByLineEl} from '../gr-diff/gr-diff-utils';
import {
getLineNumberByChild,
@@ -39,6 +35,15 @@
/** CSS class for the currently hovered token. */
const CSS_HIGHLIGHT = 'token-highlight';
+/** CSS class marking which text value each token corresponds */
+const TOKEN_TEXT_PREFIX = 'tk-text-';
+
+/**
+ * CSS class marking which index (column) where token starts within a line of code.
+ * The assumption is that we can only have a single token per column start per line.
+ */
+const TOKEN_INDEX_PREFIX = 'tk-index-';
+
export const HOVER_DELAY_MS = 200;
const LINE_LENGTH_LIMIT = 500;
@@ -137,10 +142,19 @@
// with super long tokens. Let's guard against this scenario.
if (length > TOKEN_LENGTH_LIMIT) continue;
atLeastOneTokenMatched = true;
- const css = token === this.currentHighlight ? CSS_HIGHLIGHT : CSS_TOKEN;
- // We add the tk-* class so that we can look up the token later easily
+ const highlightTypeClass =
+ token === this.currentHighlight ? CSS_HIGHLIGHT : CSS_TOKEN;
+ const textClass = `${TOKEN_TEXT_PREFIX}${token}`;
+ const indexClass = `${TOKEN_INDEX_PREFIX}${index}`;
+ // We add the TOKEN_TEXT_PREFIX class so that we can look up the token later easily
// even if the token element was split up into multiple smaller nodes.
- GrAnnotation.annotateElement(el, index, length, `tk-${token} ${css}`);
+ // All parts of a single token will share a common TOKEN_INDEX_PREFIX class within the line of code.
+ GrAnnotation.annotateElement(
+ el,
+ index,
+ length,
+ `${textClass} ${indexClass} ${highlightTypeClass}`
+ );
// We could try to detect whether we are re-rendering instead of initially
// rendering the line. Then we would not have to call storeLineForToken()
// again. But since the Set swallows the duplicates we don't care.
@@ -235,11 +249,17 @@
el.classList.contains(CSS_TOKEN) ||
el.classList.contains(CSS_HIGHLIGHT)
) {
- const tkClass = [...el.classList].find(c => c.startsWith('tk-'));
+ const tkTextClass = [...el.classList].find(c =>
+ c.startsWith(TOKEN_TEXT_PREFIX)
+ );
const line = lineNumberToNumber(getLineNumberByChild(el));
- if (!line || !tkClass)
+ if (!line || !tkTextClass)
return {line: 0, token: undefined, element: undefined};
- return {line, token: tkClass.substring(3), element: el};
+ return {
+ line,
+ token: tkTextClass.substring(TOKEN_TEXT_PREFIX.length),
+ element: el,
+ };
}
if (el.tagName === 'TD')
return {line: 0, token: undefined, element: undefined};
@@ -281,17 +301,19 @@
this.tokenHighlightListener(undefined);
return;
}
- const previousTextLength = getPreviousContentNodes(element)
- .map(sib => sib.textContent!.length)
- .reduce((partial_sum, a) => partial_sum + a, 0);
const lineEl = getLineElByChild(element);
assertIsDefined(lineEl, 'Line element should be found!');
+ const tokenIndexStr = [...element.classList]
+ .find(c => c.startsWith(TOKEN_INDEX_PREFIX))
+ ?.substring(TOKEN_INDEX_PREFIX.length);
+ assertIsDefined(tokenIndexStr, 'Index class should be found!');
+ const index = Number(tokenIndexStr);
const side = getSideByLineEl(lineEl);
const range = {
start_line: line,
- start_column: previousTextLength + 1, // 1-based inclusive
+ start_column: index + 1, // 1-based inclusive
end_line: line,
- end_column: previousTextLength + token.length, // 1-based inclusive
+ end_column: index + token.length, // 1-based inclusive
};
this.tokenHighlightListener({token, element, side, range});
}
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/token-highlight-layer_test.ts b/polygerrit-ui/app/embed/diff/gr-diff-builder/token-highlight-layer_test.ts
similarity index 93%
rename from polygerrit-ui/app/elements/diff/gr-diff-builder/token-highlight-layer_test.ts
rename to polygerrit-ui/app/embed/diff/gr-diff-builder/token-highlight-layer_test.ts
index a0670b8..384f173 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/token-highlight-layer_test.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff-builder/token-highlight-layer_test.ts
@@ -138,14 +138,26 @@
const el = createLine('these are words');
annotate(el);
assert.isTrue(annotateElementStub.calledThrice);
- assertAnnotation(annotateElementStub.args[0], el, 0, 5, 'tk-these token');
- assertAnnotation(annotateElementStub.args[1], el, 6, 3, 'tk-are token');
+ assertAnnotation(
+ annotateElementStub.args[0],
+ el,
+ 0,
+ 5,
+ 'tk-text-these tk-index-0 token'
+ );
+ assertAnnotation(
+ annotateElementStub.args[1],
+ el,
+ 6,
+ 3,
+ 'tk-text-are tk-index-6 token'
+ );
assertAnnotation(
annotateElementStub.args[2],
el,
10,
5,
- 'tk-words token'
+ 'tk-text-words tk-index-10 token'
);
});
@@ -187,7 +199,7 @@
annotate(line1);
const line2 = createLine('three words');
annotate(line2, Side.RIGHT, 2);
- const words1 = queryAndAssert(line1, '.tk-words');
+ const words1 = queryAndAssert(line1, '.tk-text-words');
assert.isTrue(words1.classList.contains('token'));
dispatchMouseEvent(
'mouseover',
@@ -217,7 +229,7 @@
annotate(line1);
const line2 = createLine('three words');
annotate(line2, Side.RIGHT, 1000);
- const words1 = queryAndAssert(line1, '.tk-words');
+ const words1 = queryAndAssert(line1, '.tk-text-words');
assert.isTrue(words1.classList.contains('token'));
dispatchMouseEvent(
'mouseover',
@@ -241,7 +253,7 @@
annotate(line1);
const line2 = createLine('three words', 2);
annotate(line2, Side.RIGHT, 2);
- const words1 = queryAndAssert(line1, '.tk-words');
+ const words1 = queryAndAssert(line1, '.tk-text-words');
assert.isTrue(words1.classList.contains('token'));
dispatchMouseEvent(
'mouseover',
@@ -268,7 +280,7 @@
annotate(line1);
const line2 = createLine('three words', 2);
annotate(line2, Side.RIGHT, 2);
- const words1 = queryAndAssert(line1, '.tk-words');
+ const words1 = queryAndAssert(line1, '.tk-text-words');
assert.isTrue(words1.classList.contains('token'));
dispatchMouseEvent(
'mouseover',
@@ -296,7 +308,10 @@
const line2 = createLine('can be highlighted', 2);
annotate(line1);
annotate(line2, Side.RIGHT, 2);
- const tokenNode = queryAndAssert(line1, '.tk-tokenWithSingleOccurence');
+ const tokenNode = queryAndAssert(
+ line1,
+ '.tk-text-tokenWithSingleOccurence'
+ );
assert.isTrue(tokenNode.classList.contains('token'));
dispatchMouseEvent(
'mouseover',
@@ -324,7 +339,7 @@
annotate(line1);
const line2 = createLine('three words', 2);
annotate(line2, Side.RIGHT, 2);
- const words1 = queryAndAssert(line1, '.tk-words');
+ const words1 = queryAndAssert(line1, '.tk-text-words');
assert.isTrue(words1.classList.contains('token'));
dispatchMouseEvent(
'mouseover',
@@ -348,7 +363,7 @@
annotate(line1);
const line2 = createLine('three words', 2);
annotate(line2, Side.RIGHT, 2);
- const words1 = queryAndAssert(line1, '.tk-words');
+ const words1 = queryAndAssert(line1, '.tk-text-words');
assert.isTrue(words1.classList.contains('token'));
dispatchMouseEvent(
'mouseover',
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor.ts b/polygerrit-ui/app/embed/diff/gr-diff-cursor/gr-diff-cursor.ts
similarity index 99%
rename from polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor.ts
rename to polygerrit-ui/app/embed/diff/gr-diff-cursor/gr-diff-cursor.ts
index 89ab885..35b89ec 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff-cursor/gr-diff-cursor.ts
@@ -29,7 +29,7 @@
import {
GrCursorManager,
isTargetable,
-} from '../../shared/gr-cursor-manager/gr-cursor-manager';
+} from '../../../elements/shared/gr-cursor-manager/gr-cursor-manager';
import {GrDiffLineType} from '../gr-diff/gr-diff-line';
import {GrDiffGroupType} from '../gr-diff/gr-diff-group';
import {GrDiff} from '../gr-diff/gr-diff';
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor_test.js b/polygerrit-ui/app/embed/diff/gr-diff-cursor/gr-diff-cursor_test.js
similarity index 100%
rename from polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor_test.js
rename to polygerrit-ui/app/embed/diff/gr-diff-cursor/gr-diff-cursor_test.js
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-annotation.ts b/polygerrit-ui/app/embed/diff/gr-diff-highlight/gr-annotation.ts
similarity index 100%
rename from polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-annotation.ts
rename to polygerrit-ui/app/embed/diff/gr-diff-highlight/gr-annotation.ts
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-annotation_test.js b/polygerrit-ui/app/embed/diff/gr-diff-highlight/gr-annotation_test.js
similarity index 100%
rename from polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-annotation_test.js
rename to polygerrit-ui/app/embed/diff/gr-diff-highlight/gr-annotation_test.js
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight.ts b/polygerrit-ui/app/embed/diff/gr-diff-highlight/gr-diff-highlight.ts
similarity index 100%
rename from polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight.ts
rename to polygerrit-ui/app/embed/diff/gr-diff-highlight/gr-diff-highlight.ts
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight_html.ts b/polygerrit-ui/app/embed/diff/gr-diff-highlight/gr-diff-highlight_html.ts
similarity index 100%
rename from polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight_html.ts
rename to polygerrit-ui/app/embed/diff/gr-diff-highlight/gr-diff-highlight_html.ts
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight_test.js b/polygerrit-ui/app/embed/diff/gr-diff-highlight/gr-diff-highlight_test.js
similarity index 100%
rename from polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight_test.js
rename to polygerrit-ui/app/embed/diff/gr-diff-highlight/gr-diff-highlight_test.js
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-range-normalizer.ts b/polygerrit-ui/app/embed/diff/gr-diff-highlight/gr-range-normalizer.ts
similarity index 100%
rename from polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-range-normalizer.ts
rename to polygerrit-ui/app/embed/diff/gr-diff-highlight/gr-range-normalizer.ts
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-image-viewer/gr-image-viewer.ts b/polygerrit-ui/app/embed/diff/gr-diff-image-viewer/gr-image-viewer.ts
similarity index 99%
rename from polygerrit-ui/app/elements/diff/gr-diff-image-viewer/gr-image-viewer.ts
rename to polygerrit-ui/app/embed/diff/gr-diff-image-viewer/gr-image-viewer.ts
index 290773b..32a3282 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-image-viewer/gr-image-viewer.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff-image-viewer/gr-image-viewer.ts
@@ -25,8 +25,8 @@
import './gr-overview-image';
import './gr-zoomed-image';
-import {GrLibLoader} from '../../shared/gr-lib-loader/gr-lib-loader';
-import {RESEMBLEJS_LIBRARY_CONFIG} from '../../shared/gr-lib-loader/resemblejs_config';
+import {GrLibLoader} from '../../../elements/shared/gr-lib-loader/gr-lib-loader';
+import {RESEMBLEJS_LIBRARY_CONFIG} from '../../../elements/shared/gr-lib-loader/resemblejs_config';
import {css, html, LitElement, PropertyValues} from 'lit';
import {customElement, property, query, state} from 'lit/decorators';
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-image-viewer/gr-overview-image.ts b/polygerrit-ui/app/embed/diff/gr-diff-image-viewer/gr-overview-image.ts
similarity index 100%
rename from polygerrit-ui/app/elements/diff/gr-diff-image-viewer/gr-overview-image.ts
rename to polygerrit-ui/app/embed/diff/gr-diff-image-viewer/gr-overview-image.ts
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-image-viewer/gr-zoomed-image.ts b/polygerrit-ui/app/embed/diff/gr-diff-image-viewer/gr-zoomed-image.ts
similarity index 100%
rename from polygerrit-ui/app/elements/diff/gr-diff-image-viewer/gr-zoomed-image.ts
rename to polygerrit-ui/app/embed/diff/gr-diff-image-viewer/gr-zoomed-image.ts
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-image-viewer/util.ts b/polygerrit-ui/app/embed/diff/gr-diff-image-viewer/util.ts
similarity index 100%
rename from polygerrit-ui/app/elements/diff/gr-diff-image-viewer/util.ts
rename to polygerrit-ui/app/embed/diff/gr-diff-image-viewer/util.ts
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-image-viewer/util_test.js b/polygerrit-ui/app/embed/diff/gr-diff-image-viewer/util_test.js
similarity index 100%
rename from polygerrit-ui/app/elements/diff/gr-diff-image-viewer/util_test.js
rename to polygerrit-ui/app/embed/diff/gr-diff-image-viewer/util_test.js
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-mode-selector/gr-diff-mode-selector.ts b/polygerrit-ui/app/embed/diff/gr-diff-mode-selector/gr-diff-mode-selector.ts
similarity index 98%
rename from polygerrit-ui/app/elements/diff/gr-diff-mode-selector/gr-diff-mode-selector.ts
rename to polygerrit-ui/app/embed/diff/gr-diff-mode-selector/gr-diff-mode-selector.ts
index bfb2bce..022dbb9 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-mode-selector/gr-diff-mode-selector.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff-mode-selector/gr-diff-mode-selector.ts
@@ -18,7 +18,7 @@
import '@polymer/iron-icon/iron-icon';
import '@polymer/iron-a11y-announcer/iron-a11y-announcer';
import '../../../styles/shared-styles';
-import '../../shared/gr-button/gr-button';
+import '../../../elements/shared/gr-button/gr-button';
import {DiffViewMode} from '../../../constants/constants';
import {htmlTemplate} from './gr-diff-mode-selector_html';
import {customElement, property} from '@polymer/decorators';
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-mode-selector/gr-diff-mode-selector_html.ts b/polygerrit-ui/app/embed/diff/gr-diff-mode-selector/gr-diff-mode-selector_html.ts
similarity index 100%
rename from polygerrit-ui/app/elements/diff/gr-diff-mode-selector/gr-diff-mode-selector_html.ts
rename to polygerrit-ui/app/embed/diff/gr-diff-mode-selector/gr-diff-mode-selector_html.ts
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-mode-selector/gr-diff-mode-selector_test.ts b/polygerrit-ui/app/embed/diff/gr-diff-mode-selector/gr-diff-mode-selector_test.ts
similarity index 100%
rename from polygerrit-ui/app/elements/diff/gr-diff-mode-selector/gr-diff-mode-selector_test.ts
rename to polygerrit-ui/app/embed/diff/gr-diff-mode-selector/gr-diff-mode-selector_test.ts
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor.ts b/polygerrit-ui/app/embed/diff/gr-diff-processor/gr-diff-processor.ts
similarity index 100%
rename from polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor.ts
rename to polygerrit-ui/app/embed/diff/gr-diff-processor/gr-diff-processor.ts
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor_test.js b/polygerrit-ui/app/embed/diff/gr-diff-processor/gr-diff-processor_test.js
similarity index 100%
rename from polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor_test.js
rename to polygerrit-ui/app/embed/diff/gr-diff-processor/gr-diff-processor_test.js
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection.ts b/polygerrit-ui/app/embed/diff/gr-diff-selection/gr-diff-selection.ts
similarity index 100%
rename from polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection.ts
rename to polygerrit-ui/app/embed/diff/gr-diff-selection/gr-diff-selection.ts
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection_html.ts b/polygerrit-ui/app/embed/diff/gr-diff-selection/gr-diff-selection_html.ts
similarity index 100%
rename from polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection_html.ts
rename to polygerrit-ui/app/embed/diff/gr-diff-selection/gr-diff-selection_html.ts
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection_test.js b/polygerrit-ui/app/embed/diff/gr-diff-selection/gr-diff-selection_test.js
similarity index 100%
rename from polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection_test.js
rename to polygerrit-ui/app/embed/diff/gr-diff-selection/gr-diff-selection_test.js
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff-group.ts b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-group.ts
similarity index 100%
rename from polygerrit-ui/app/elements/diff/gr-diff/gr-diff-group.ts
rename to polygerrit-ui/app/embed/diff/gr-diff/gr-diff-group.ts
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff-group_test.js b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-group_test.js
similarity index 100%
rename from polygerrit-ui/app/elements/diff/gr-diff/gr-diff-group_test.js
rename to polygerrit-ui/app/embed/diff/gr-diff/gr-diff-group_test.js
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff-line.ts b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-line.ts
similarity index 100%
rename from polygerrit-ui/app/elements/diff/gr-diff/gr-diff-line.ts
rename to polygerrit-ui/app/embed/diff/gr-diff/gr-diff-line.ts
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff-utils.ts b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-utils.ts
similarity index 87%
rename from polygerrit-ui/app/elements/diff/gr-diff/gr-diff-utils.ts
rename to polygerrit-ui/app/embed/diff/gr-diff/gr-diff-utils.ts
index 63db013..81c296f 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff-utils.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-utils.ts
@@ -129,29 +129,6 @@
rootId: string;
}
-const VISIBLE_TEXT_NODE_TYPES = [Node.TEXT_NODE, Node.ELEMENT_NODE];
-
-export function getPreviousContentNodes(node?: Node | null) {
- const sibs = [];
- while (node) {
- const {parentNode, previousSibling} = node;
- const topContentLevel =
- parentNode &&
- (parentNode as HTMLElement).classList.contains('contentText');
- let previousEl: Node | undefined | null;
- if (previousSibling) {
- previousEl = previousSibling;
- } else if (!topContentLevel) {
- previousEl = parentNode?.previousSibling;
- }
- if (previousEl && VISIBLE_TEXT_NODE_TYPES.includes(previousEl.nodeType)) {
- sibs.push(previousEl);
- }
- node = previousEl;
- }
- return sibs;
-}
-
export function isThreadEl(node: Node): node is GrDiffThreadElement {
return (
node.nodeType === Node.ELEMENT_NODE &&
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.ts b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff.ts
similarity index 99%
rename from polygerrit-ui/app/elements/diff/gr-diff/gr-diff.ts
rename to polygerrit-ui/app/embed/diff/gr-diff/gr-diff.ts
index 030275f..ab7d0ab 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff.ts
@@ -15,8 +15,8 @@
* limitations under the License.
*/
import '../../../styles/shared-styles';
-import '../../shared/gr-button/gr-button';
-import '../../shared/gr-icons/gr-icons';
+import '../../../elements/shared/gr-button/gr-button';
+import '../../../elements/shared/gr-icons/gr-icons';
import '../gr-diff-builder/gr-diff-builder-element';
import '../gr-diff-highlight/gr-diff-highlight';
import '../gr-diff-selection/gr-diff-selection';
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_html.ts b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff_html.ts
similarity index 100%
rename from polygerrit-ui/app/elements/diff/gr-diff/gr-diff_html.ts
rename to polygerrit-ui/app/embed/diff/gr-diff/gr-diff_html.ts
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.js b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff_test.js
similarity index 100%
rename from polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.js
rename to polygerrit-ui/app/embed/diff/gr-diff/gr-diff_test.js
diff --git a/polygerrit-ui/app/elements/diff/gr-range-header/gr-range-header.ts b/polygerrit-ui/app/embed/diff/gr-range-header/gr-range-header.ts
similarity index 100%
rename from polygerrit-ui/app/elements/diff/gr-range-header/gr-range-header.ts
rename to polygerrit-ui/app/embed/diff/gr-range-header/gr-range-header.ts
diff --git a/polygerrit-ui/app/elements/diff/gr-ranged-comment-hint/gr-ranged-comment-hint.ts b/polygerrit-ui/app/embed/diff/gr-ranged-comment-hint/gr-ranged-comment-hint.ts
similarity index 100%
rename from polygerrit-ui/app/elements/diff/gr-ranged-comment-hint/gr-ranged-comment-hint.ts
rename to polygerrit-ui/app/embed/diff/gr-ranged-comment-hint/gr-ranged-comment-hint.ts
diff --git a/polygerrit-ui/app/elements/diff/gr-ranged-comment-hint/gr-ranged-comment-hint_test.ts b/polygerrit-ui/app/embed/diff/gr-ranged-comment-hint/gr-ranged-comment-hint_test.ts
similarity index 100%
rename from polygerrit-ui/app/elements/diff/gr-ranged-comment-hint/gr-ranged-comment-hint_test.ts
rename to polygerrit-ui/app/embed/diff/gr-ranged-comment-hint/gr-ranged-comment-hint_test.ts
diff --git a/polygerrit-ui/app/elements/diff/gr-ranged-comment-layer/gr-ranged-comment-layer.ts b/polygerrit-ui/app/embed/diff/gr-ranged-comment-layer/gr-ranged-comment-layer.ts
similarity index 100%
rename from polygerrit-ui/app/elements/diff/gr-ranged-comment-layer/gr-ranged-comment-layer.ts
rename to polygerrit-ui/app/embed/diff/gr-ranged-comment-layer/gr-ranged-comment-layer.ts
diff --git a/polygerrit-ui/app/elements/diff/gr-ranged-comment-layer/gr-ranged-comment-layer_html.ts b/polygerrit-ui/app/embed/diff/gr-ranged-comment-layer/gr-ranged-comment-layer_html.ts
similarity index 100%
rename from polygerrit-ui/app/elements/diff/gr-ranged-comment-layer/gr-ranged-comment-layer_html.ts
rename to polygerrit-ui/app/embed/diff/gr-ranged-comment-layer/gr-ranged-comment-layer_html.ts
diff --git a/polygerrit-ui/app/elements/diff/gr-ranged-comment-layer/gr-ranged-comment-layer_test.js b/polygerrit-ui/app/embed/diff/gr-ranged-comment-layer/gr-ranged-comment-layer_test.js
similarity index 100%
rename from polygerrit-ui/app/elements/diff/gr-ranged-comment-layer/gr-ranged-comment-layer_test.js
rename to polygerrit-ui/app/embed/diff/gr-ranged-comment-layer/gr-ranged-comment-layer_test.js
diff --git a/polygerrit-ui/app/elements/diff/gr-ranged-comment-themes/gr-ranged-comment-theme.ts b/polygerrit-ui/app/embed/diff/gr-ranged-comment-themes/gr-ranged-comment-theme.ts
similarity index 100%
rename from polygerrit-ui/app/elements/diff/gr-ranged-comment-themes/gr-ranged-comment-theme.ts
rename to polygerrit-ui/app/embed/diff/gr-ranged-comment-themes/gr-ranged-comment-theme.ts
diff --git a/polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box.ts b/polygerrit-ui/app/embed/diff/gr-selection-action-box/gr-selection-action-box.ts
similarity index 96%
rename from polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box.ts
rename to polygerrit-ui/app/embed/diff/gr-selection-action-box/gr-selection-action-box.ts
index 0f64d9e..a2fb79f 100644
--- a/polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box.ts
+++ b/polygerrit-ui/app/embed/diff/gr-selection-action-box/gr-selection-action-box.ts
@@ -15,8 +15,8 @@
* limitations under the License.
*/
import '../../../styles/shared-styles';
-import '../../shared/gr-tooltip/gr-tooltip';
-import {GrTooltip} from '../../shared/gr-tooltip/gr-tooltip';
+import '../../../elements/shared/gr-tooltip/gr-tooltip';
+import {GrTooltip} from '../../../elements/shared/gr-tooltip/gr-tooltip';
import {customElement, property} from '@polymer/decorators';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-selection-action-box_html';
diff --git a/polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box_html.ts b/polygerrit-ui/app/embed/diff/gr-selection-action-box/gr-selection-action-box_html.ts
similarity index 100%
rename from polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box_html.ts
rename to polygerrit-ui/app/embed/diff/gr-selection-action-box/gr-selection-action-box_html.ts
diff --git a/polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box_test.js b/polygerrit-ui/app/embed/diff/gr-selection-action-box/gr-selection-action-box_test.js
similarity index 100%
rename from polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box_test.js
rename to polygerrit-ui/app/embed/diff/gr-selection-action-box/gr-selection-action-box_test.js
diff --git a/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer.ts b/polygerrit-ui/app/embed/diff/gr-syntax-layer/gr-syntax-layer.ts
similarity index 98%
rename from polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer.ts
rename to polygerrit-ui/app/embed/diff/gr-syntax-layer/gr-syntax-layer.ts
index f892410..b3d6605 100644
--- a/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer.ts
+++ b/polygerrit-ui/app/embed/diff/gr-syntax-layer/gr-syntax-layer.ts
@@ -19,8 +19,8 @@
import {CancelablePromise, util} from '../../../scripts/util';
import {DiffFileMetaInfo, DiffInfo} from '../../../types/diff';
import {DiffLayer, DiffLayerListener, HighlightJS} from '../../../types/types';
-import {GrLibLoader} from '../../shared/gr-lib-loader/gr-lib-loader';
-import {HLJS_LIBRARY_CONFIG} from '../../shared/gr-lib-loader/highlightjs_config';
+import {GrLibLoader} from '../../../elements/shared/gr-lib-loader/gr-lib-loader';
+import {HLJS_LIBRARY_CONFIG} from '../../../elements/shared/gr-lib-loader/highlightjs_config';
import {Side} from '../../../constants/constants';
const LANGUAGE_MAP = new Map<string, string>([
diff --git a/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer_test.js b/polygerrit-ui/app/embed/diff/gr-syntax-layer/gr-syntax-layer_test.js
similarity index 100%
rename from polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer_test.js
rename to polygerrit-ui/app/embed/diff/gr-syntax-layer/gr-syntax-layer_test.js
diff --git a/polygerrit-ui/app/elements/diff/gr-syntax-themes/gr-syntax-theme.ts b/polygerrit-ui/app/embed/diff/gr-syntax-themes/gr-syntax-theme.ts
similarity index 100%
rename from polygerrit-ui/app/elements/diff/gr-syntax-themes/gr-syntax-theme.ts
rename to polygerrit-ui/app/embed/diff/gr-syntax-themes/gr-syntax-theme.ts
diff --git a/polygerrit-ui/app/embed/gr-diff.ts b/polygerrit-ui/app/embed/gr-diff.ts
index 64ef214..1b32c55 100644
--- a/polygerrit-ui/app/embed/gr-diff.ts
+++ b/polygerrit-ui/app/embed/gr-diff.ts
@@ -23,11 +23,11 @@
// exposed by shared gr-diff component.
import '../api/embed';
import '../scripts/bundled-polymer';
-import '../elements/diff/gr-diff/gr-diff';
-import '../elements/diff/gr-diff-cursor/gr-diff-cursor';
-import {TokenHighlightLayer} from '../elements/diff/gr-diff-builder/token-highlight-layer';
-import {GrDiffCursor} from '../elements/diff/gr-diff-cursor/gr-diff-cursor';
-import {GrAnnotation} from '../elements/diff/gr-diff-highlight/gr-annotation';
+import './diff/gr-diff/gr-diff';
+import './diff/gr-diff-cursor/gr-diff-cursor';
+import {TokenHighlightLayer} from './diff/gr-diff-builder/token-highlight-layer';
+import {GrDiffCursor} from './diff/gr-diff-cursor/gr-diff-cursor';
+import {GrAnnotation} from './diff/gr-diff-highlight/gr-annotation';
import {createDiffAppContext} from './gr-diff-app-context-init';
import {injectAppContext} from '../services/app-context';
diff --git a/polygerrit-ui/app/gr-diff/gr-diff-root.ts b/polygerrit-ui/app/gr-diff/gr-diff-root.ts
index fbe81fb..7111d80 100644
--- a/polygerrit-ui/app/gr-diff/gr-diff-root.ts
+++ b/polygerrit-ui/app/gr-diff/gr-diff-root.ts
@@ -15,4 +15,4 @@
* limitations under the License.
*/
-import '../elements/diff/gr-diff/gr-diff';
+import '../embed/diff/gr-diff/gr-diff';
diff --git a/polygerrit-ui/app/models/bulk-actions/bulk-actions-model.ts b/polygerrit-ui/app/models/bulk-actions/bulk-actions-model.ts
new file mode 100644
index 0000000..2f10b3a
--- /dev/null
+++ b/polygerrit-ui/app/models/bulk-actions/bulk-actions-model.ts
@@ -0,0 +1,55 @@
+/**
+ * @license
+ * Copyright 2022 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import {ChangeInfo} from '../../api/rest-api';
+import {Model} from '../model';
+import {Finalizable} from '../../services/registry';
+import {RestApiService} from '../../services/gr-rest-api/gr-rest-api';
+
+// TODO: consider keeping only changeId's as the object might become stale
+export interface BulkActionsState {
+ selectedChanges: ChangeInfo[];
+}
+
+const initialState: BulkActionsState = {
+ selectedChanges: [],
+};
+
+export class BulkActionsModel
+ extends Model<BulkActionsState>
+ implements Finalizable
+{
+ constructor(_restApiService: RestApiService) {
+ super(initialState);
+ }
+
+ addSelectedChange(change: ChangeInfo) {
+ const current = this.subject$.getValue();
+ const selectedChanges = [...current.selectedChanges];
+ selectedChanges.push(change);
+ this.setState({...current, selectedChanges});
+ }
+
+ removeSelectedChange(change: ChangeInfo) {
+ const current = this.subject$.getValue();
+ const selectedChanges = [...current.selectedChanges];
+ const index = selectedChanges.findIndex(item => item.id === change.id);
+ if (index === -1) return;
+ selectedChanges.splice(index, 1);
+ this.setState({...current, selectedChanges});
+ }
+
+ /** Required for testing */
+ getState() {
+ return this.subject$.getValue();
+ }
+
+ setState(state: BulkActionsState) {
+ this.subject$.next(state);
+ }
+
+ finalize() {}
+}
diff --git a/polygerrit-ui/app/models/bulk-actions/bulk-actions-model_test.ts b/polygerrit-ui/app/models/bulk-actions/bulk-actions-model_test.ts
new file mode 100644
index 0000000..6c1bbef
--- /dev/null
+++ b/polygerrit-ui/app/models/bulk-actions/bulk-actions-model_test.ts
@@ -0,0 +1,43 @@
+/**
+ * @license
+ * Copyright 2022 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import {createChange} from '../../test/test-data-generators';
+import {ChangeId} from '../../api/rest-api';
+import {BulkActionsModel} from './bulk-actions-model';
+import {getAppContext} from '../../services/app-context';
+import '../../test/common-test-setup-karma';
+
+suite('bulk actions model test', () => {
+ test('add and remove selected changes', () => {
+ const c1 = createChange();
+ c1.change_id = '1' as ChangeId;
+ const c2 = createChange();
+ c2.change_id = '2' as ChangeId;
+
+ const bulkActionsModel = new BulkActionsModel(
+ getAppContext().restApiService
+ );
+
+ assert.deepEqual(bulkActionsModel.getState().selectedChanges, []);
+
+ bulkActionsModel.addSelectedChange(c1);
+ assert.deepEqual(bulkActionsModel.getState().selectedChanges, [{...c1}]);
+
+ bulkActionsModel.addSelectedChange(c2);
+ assert.deepEqual(bulkActionsModel.getState().selectedChanges, [
+ {
+ ...c1,
+ },
+ {...c2},
+ ]);
+
+ bulkActionsModel.removeSelectedChange(c1);
+ assert.deepEqual(bulkActionsModel.getState().selectedChanges, [{...c2}]);
+
+ bulkActionsModel.removeSelectedChange(c2);
+ assert.deepEqual(bulkActionsModel.getState().selectedChanges, []);
+ });
+});
diff --git a/polygerrit-ui/app/models/checks/checks-model.ts b/polygerrit-ui/app/models/checks/checks-model.ts
index d23b7dc..7b3b95f 100644
--- a/polygerrit-ui/app/models/checks/checks-model.ts
+++ b/polygerrit-ui/app/models/checks/checks-model.ts
@@ -353,6 +353,16 @@
.filter(r => r !== undefined)
);
+ public allResults$ = select(
+ combineLatest([
+ this.checksSelectedPatchsetNumber$,
+ this.allResultsSelected$,
+ this.allResultsLatest$,
+ ]),
+ ([selectedPs, selected, latest]) =>
+ selectedPs ? [...selected, ...latest] : latest
+ );
+
constructor(
readonly routerModel: RouterModel,
readonly changeModel: ChangeModel,
diff --git a/polygerrit-ui/app/package.json b/polygerrit-ui/app/package.json
index 281a1eb..7be3d4a 100644
--- a/polygerrit-ui/app/package.json
+++ b/polygerrit-ui/app/package.json
@@ -37,7 +37,7 @@
"ba-linkify": "^1.0.1",
"codemirror-minified": "^5.62.2",
"immer": "^9.0.5",
- "lit": "2.0.2",
+ "lit": "^2.1.1",
"page": "^1.11.6",
"polymer-bridges": "file:../../polymer-bridges/",
"polymer-resin": "^2.0.1",
diff --git a/polygerrit-ui/app/services/flags/flags.ts b/polygerrit-ui/app/services/flags/flags.ts
index 44e3ec7..515dab8 100644
--- a/polygerrit-ui/app/services/flags/flags.ts
+++ b/polygerrit-ui/app/services/flags/flags.ts
@@ -29,7 +29,7 @@
NEW_IMAGE_DIFF_UI = 'UiFeature__new_image_diff_ui',
CHECKS_DEVELOPER = 'UiFeature__checks_developer',
SUBMIT_REQUIREMENTS_UI = 'UiFeature__submit_requirements_ui',
- TOPICS_PAGE = 'UiFeature__topics_page',
+ BULK_ACTIONS = 'UiFeature__bulk_actions_dashboard',
CHECK_RESULTS_IN_DIFFS = 'UiFeature__check_results_in_diffs',
DIFF_RENDERING_LIT = 'UiFeature__diff_rendering_lit',
}
diff --git a/polygerrit-ui/app/services/gr-reporting/gr-reporting.ts b/polygerrit-ui/app/services/gr-reporting/gr-reporting.ts
index 0da8b4c..b035bb1 100644
--- a/polygerrit-ui/app/services/gr-reporting/gr-reporting.ts
+++ b/polygerrit-ui/app/services/gr-reporting/gr-reporting.ts
@@ -68,20 +68,6 @@
*/
timeEnd(name: Timing, eventDetails?: EventDetails): void;
/**
- * Reports just line timeEnd, but additionally reports an average given a
- * denominator and a separate reporting name for the average.
- *
- * @param name Timing name.
- * @param averageName Average timing name.
- * @param denominator Number by which to divide the total to
- * compute the average.
- */
- timeEndWithAverage(
- name: Timing,
- averageName: Timing,
- denominator: number
- ): void;
- /**
* Get a timer object for reporting a user timing. The start time will be
* the time that the object has been created, and the end time will be the
* time that the "end" method is called on the object.
diff --git a/polygerrit-ui/app/services/gr-reporting/gr-reporting_impl.ts b/polygerrit-ui/app/services/gr-reporting/gr-reporting_impl.ts
index bf10da9..0d29d8c 100644
--- a/polygerrit-ui/app/services/gr-reporting/gr-reporting_impl.ts
+++ b/polygerrit-ui/app/services/gr-reporting/gr-reporting_impl.ts
@@ -671,30 +671,6 @@
}
/**
- * Reports just line timeEnd, but additionally reports an average given a
- * denominator and a separate reporting name for the average.
- *
- * @param name Timing name.
- * @param averageName Average timing name.
- * @param denominator Number by which to divide the total to
- * compute the average.
- */
- timeEndWithAverage(name: Timing, averageName: Timing, denominator: number) {
- if (!hasOwnProperty(this._baselines, name)) {
- return;
- }
- const baseTime = this._baselines[name];
- this.timeEnd(name);
-
- // Guard against division by zero.
- if (!denominator) {
- return;
- }
- const time = now() - baseTime;
- this._reportTiming(averageName, time / denominator);
- }
-
- /**
* Send a timing report with an arbitrary time value.
*
* @param name Timing name.
diff --git a/polygerrit-ui/app/services/gr-reporting/gr-reporting_mock.ts b/polygerrit-ui/app/services/gr-reporting/gr-reporting_mock.ts
index 2a5c532..01454c6 100644
--- a/polygerrit-ui/app/services/gr-reporting/gr-reporting_mock.ts
+++ b/polygerrit-ui/app/services/gr-reporting/gr-reporting_mock.ts
@@ -81,5 +81,4 @@
setChangeId: () => {},
time: () => {},
timeEnd: () => {},
- timeEndWithAverage: () => {},
};
diff --git a/polygerrit-ui/app/services/gr-reporting/gr-reporting_test.js b/polygerrit-ui/app/services/gr-reporting/gr-reporting_test.js
index 990a5c8..f7612cd 100644
--- a/polygerrit-ui/app/services/gr-reporting/gr-reporting_test.js
+++ b/polygerrit-ui/app/services/gr-reporting/gr-reporting_test.js
@@ -283,19 +283,6 @@
assert.isTrue(service.reporter.calledOnce);
});
- test('timeEndWithAverage', () => {
- const nowStub = sinon.stub(window.performance, 'now').returns(0);
- nowStub.returns(1000);
- service.time('foo');
- nowStub.returns(1100);
- service.timeEndWithAverage('foo', 'bar', 10);
- assert.isTrue(service.reporter.calledTwice);
- assert.isTrue(service.reporter.calledWithMatch(
- 'timing-report', 'UI Latency', 'foo', 100));
- assert.isTrue(service.reporter.calledWithMatch(
- 'timing-report', 'UI Latency', 'bar', 10));
- });
-
test('reportExtension', () => {
service.reportExtension('foo');
assert.isTrue(service.reporter.calledWithExactly(
diff --git a/polygerrit-ui/app/services/router/router-model.ts b/polygerrit-ui/app/services/router/router-model.ts
index 5bd228b..221e55b 100644
--- a/polygerrit-ui/app/services/router/router-model.ts
+++ b/polygerrit-ui/app/services/router/router-model.ts
@@ -35,7 +35,6 @@
ROOT = 'root',
SEARCH = 'search',
SETTINGS = 'settings',
- TOPIC = 'topic',
}
export interface RouterState {
diff --git a/polygerrit-ui/app/styles/themes/app-theme.ts b/polygerrit-ui/app/styles/themes/app-theme.ts
index 7c922f6..4746d6d 100644
--- a/polygerrit-ui/app/styles/themes/app-theme.ts
+++ b/polygerrit-ui/app/styles/themes/app-theme.ts
@@ -177,6 +177,8 @@
--not-working-hours-icon-background-color: var(--purple-50);
--not-working-hours-icon-color: var(--purple-700);
--unavailability-icon-color: var(--gray-700);
+ --unavailability-chip-icon-color: var(--orange-900);
+ --unavailability-chip-background-color: var(--yellow-50);
/* text colors */
--primary-text-color: var(--gray-900);
@@ -250,6 +252,15 @@
--vote-outline-recommended: var(--green-700);
--vote-color-rejected: var(--red-300);
+ /* vote chip background colors */
+ --vote-chip-unselected-outline-color: var(--gray-500);
+ --vote-chip-unselected-color: white;
+ --vote-chip-selected-positive-color: var(--green-300);
+ --vote-chip-selected-neutral-color: var(--gray-300);
+ --vote-chip-selected-negative-color: var(--red-300);
+ --vote-chip-unselected-text-color: black;
+ --vote-chip-selected-text-color: black;
+
--outline-color-focus: var(--gray-900);
/* misc colors */
diff --git a/polygerrit-ui/app/styles/themes/dark-theme.ts b/polygerrit-ui/app/styles/themes/dark-theme.ts
index 79dec45..e1365a4 100644
--- a/polygerrit-ui/app/styles/themes/dark-theme.ts
+++ b/polygerrit-ui/app/styles/themes/dark-theme.ts
@@ -136,6 +136,15 @@
--vote-outline-recommended: var(--green-200);
--vote-color-rejected: var(--red-200);
+ /* vote chip background colors */
+ --vote-chip-unselected-outline-color: var(--gray-500);
+ --vote-chip-unselected-color: var(--grey-800);
+ --vote-chip-selected-positive-color: var(--green-200);
+ --vote-chip-selected-neutral-color: var(--gray-300);
+ --vote-chip-selected-negative-color: var(--red-200);
+ --vote-chip-unselected-text-color: white;
+ --vote-chip-selected-text-color: black;
+
--outline-color-focus: var(--gray-100);
/* misc colors */
diff --git a/polygerrit-ui/app/test/test-data-generators.ts b/polygerrit-ui/app/test/test-data-generators.ts
index d0179ef..626a650 100644
--- a/polygerrit-ui/app/test/test-data-generators.ts
+++ b/polygerrit-ui/app/test/test-data-generators.ts
@@ -103,6 +103,7 @@
import {CommitInfoWithRequiredCommit} from '../elements/change/gr-change-metadata/gr-change-metadata';
import {WebLinkInfo} from '../types/diff';
import {
+ ChangeMessage,
CommentThread,
createCommentThreads,
DraftInfo,
@@ -111,11 +112,7 @@
import {GerritView} from '../services/router/router-model';
import {ChangeComments} from '../elements/diff/gr-comment-api/gr-comment-api';
import {EditRevisionInfo, ParsedChangeInfo} from '../types/types';
-import {ChangeMessage} from '../elements/change/gr-message/gr-message';
-import {
- GenerateUrlEditViewParameters,
- GenerateUrlTopicViewParams,
-} from '../elements/core/gr-navigation/gr-navigation';
+import {GenerateUrlEditViewParameters} from '../elements/core/gr-navigation/gr-navigation';
import {
DetailedLabelInfo,
QuickLabelInfo,
@@ -510,13 +507,6 @@
};
}
-export function createGenerateUrlTopicViewParams(): GenerateUrlTopicViewParams {
- return {
- view: GerritView.TOPIC,
- topic: 'myTopic',
- };
-}
-
export function createRequirement(): Requirement {
return {
status: RequirementStatus.OK,
@@ -782,6 +772,15 @@
};
}
+export function createNonApplicableSubmitRequirementResultInfo(): SubmitRequirementResultInfo {
+ return {
+ name: 'Verified',
+ status: SubmitRequirementStatus.NOT_APPLICABLE,
+ applicability_expression_result: createSubmitRequirementExpressionInfo(),
+ is_legacy: false,
+ };
+}
+
export function createRunResult(): RunResult {
return {
attemptDetails: [],
diff --git a/polygerrit-ui/app/types/events.ts b/polygerrit-ui/app/types/events.ts
index 3a46e60..6ad9e71 100644
--- a/polygerrit-ui/app/types/events.ts
+++ b/polygerrit-ui/app/types/events.ts
@@ -15,11 +15,10 @@
* limitations under the License.
*/
import {PatchSetNum} from './common';
-import {Comment} from '../utils/comment-util';
+import {ChangeMessage, Comment} from '../utils/comment-util';
import {FetchRequest} from './types';
import {LineNumberEventDetail, MovedLinkClickedEventDetail} from '../api/diff';
import {Category, RunStatus} from '../api/checks';
-import {ChangeMessage} from '../elements/change/gr-message/gr-message';
export enum EventType {
BIND_VALUE_CHANGED = 'bind-value-changed',
diff --git a/polygerrit-ui/app/utils/comment-util.ts b/polygerrit-ui/app/utils/comment-util.ts
index ee26915..501da7d 100644
--- a/polygerrit-ui/app/utils/comment-util.ts
+++ b/polygerrit-ui/app/utils/comment-util.ts
@@ -29,6 +29,8 @@
RevisionPatchSetNum,
AccountInfo,
AccountDetailInfo,
+ ChangeMessageInfo,
+ VotingRangeInfo,
} from '../types/common';
import {CommentSide, SpecialFilePath} from '../constants/constants';
import {parseDate} from './date-util';
@@ -89,6 +91,18 @@
id: UrlEncodedCommentId;
}
+export interface ChangeMessage extends ChangeMessageInfo {
+ // TODO(TS): maybe should be an enum instead
+ type: string;
+ expanded: boolean;
+ commentThreads: CommentThread[];
+}
+
+export type LabelExtreme = {[labelName: string]: VotingRangeInfo};
+
+export const PATCH_SET_PREFIX_PATTERN =
+ /^(?:Uploaded\s*)?[Pp]atch [Ss]et \d+:\s*(.*)/;
+
export function sortComments<T extends SortableComment>(comments: T[]): T[] {
return comments.slice(0).sort((c1, c2) => {
const d1 = isDraft(c1);
diff --git a/polygerrit-ui/app/utils/hljs-util.ts b/polygerrit-ui/app/utils/hljs-util.ts
new file mode 100644
index 0000000..1bd2072
--- /dev/null
+++ b/polygerrit-ui/app/utils/hljs-util.ts
@@ -0,0 +1,145 @@
+/**
+ * @license
+ * Copyright 2022 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+/**
+ * Utilities related to working with the HighlightJS syntax highlighting lib.
+ *
+ * Note that this utility is mostly used by the hljs-worker, which is a Web
+ * Worker and can thus not depend on document, the DOM or any related
+ * functionality.
+ */
+
+/**
+ * With these expressions you can match exactly what HighlightJS produces. It
+ * is really that simple:
+ * https://github.com/highlightjs/highlight.js/blob/main/src/lib/html_renderer.js
+ */
+const openingSpan = new RegExp('<span class="(.*?)">');
+const closingSpan = new RegExp('</span>');
+
+/** Can be used for `length` in SyntaxLayerRange. */
+const UNCLOSED = -1;
+
+/** Range of characters in a line to be syntax highlighted. */
+export interface SyntaxLayerRange {
+ /** 1-based inclusive. */
+ start: number;
+ /** Can only be UNCLOSED during processing. */
+ length: number;
+ /** HighlightJS specific names, e.g. 'literal'. */
+ className: string;
+}
+
+/**
+ * HighlightJS produces one long HTML string with HTML elements spanning
+ * multiple lines. <gr-diff> is line based, needs all elements closed at the end
+ * of the line, and is not interested in the HTML that HighlightJS produces.
+ *
+ * So we are splitting the HTML string up into lines and process them one by
+ * one. Each <span> is detected, converted into a SyntaxLayerRange and removed.
+ * Unclosed spans will be carried over to the next line.
+ */
+export function highlightedStringToRanges(
+ highlightedCode: string
+): SyntaxLayerRange[][] {
+ // What the function eventually returns.
+ const rangesPerLine: SyntaxLayerRange[][] = [];
+ // The unclosed ranges that are carried over from one line to the next.
+ let carryOverRanges: SyntaxLayerRange[] = [];
+
+ for (let line of highlightedCode.split('\n')) {
+ const ranges: SyntaxLayerRange[] = [...carryOverRanges];
+ carryOverRanges = [];
+ rangesPerLine.push(ranges);
+
+ // Remove all span tags one after another from left to right.
+ // For each opening <span ...> push a new (unclosed) range.
+ // For each closing </span> close the latest unclosed range.
+ let removal: SpanRemoval | undefined;
+ while ((removal = removeFirstSpan(line)) !== undefined) {
+ if (removal.type === SpanType.OPENING) {
+ ranges.push({
+ start: removal.offset,
+ length: UNCLOSED,
+ className: removal.class ?? '',
+ });
+ } else {
+ const unclosed = lastUnclosed(ranges);
+ unclosed.length = removal.offset - unclosed.start;
+ }
+ line = removal.lineAfter;
+ }
+
+ // All unclosed spans need to have the length set such that they extend to
+ // the end of the line. And they have to be carried over to the next line
+ // as cloned objects with start:0.
+ const lineLength = line.length;
+ for (const range of ranges) {
+ if (isUnclosed(range)) {
+ carryOverRanges.push({...range, start: 0});
+ range.length = lineLength - range.start;
+ }
+ }
+ }
+ if (carryOverRanges.length > 0) {
+ throw new Error('unclosed <span>s in highlighted code');
+ }
+ return rangesPerLine;
+}
+
+function isUnclosed(range: SyntaxLayerRange) {
+ return range.length === UNCLOSED;
+}
+
+function lastUnclosed(ranges: SyntaxLayerRange[]) {
+ const unclosed = [...ranges].reverse().find(isUnclosed);
+ if (!unclosed) throw new Error('no unclosed range found');
+ return unclosed;
+}
+
+/** Used for `type` in SpanRemoval. */
+export enum SpanType {
+ OPENING,
+ CLOSING,
+}
+
+/** Return type for removeFirstSpan(). */
+export interface SpanRemoval {
+ type: SpanType;
+ /** The line string after removing the matched span tag. */
+ lineAfter: string;
+ /** The matched css class for OPENING spans. undefined for CLOSING. */
+ class?: string;
+ /** At which char in the line did the removed span tag start? */
+ offset: number;
+}
+
+/**
+ * Finds the first <span ...> or </span>, removes it from the line and returns
+ * details about the removal. Returns `undefined`, if neither is found.
+ */
+export function removeFirstSpan(line: string): SpanRemoval | undefined {
+ const openingMatch = openingSpan.exec(line);
+ const openingIndex = openingMatch?.index ?? Number.MAX_VALUE;
+ const closingMatch = closingSpan.exec(line);
+ const closingIndex = closingMatch?.index ?? Number.MAX_VALUE;
+ if (openingIndex === Number.MAX_VALUE && closingIndex === Number.MAX_VALUE) {
+ return undefined;
+ }
+ const type =
+ openingIndex < closingIndex ? SpanType.OPENING : SpanType.CLOSING;
+ const offset = type === SpanType.OPENING ? openingIndex : closingIndex;
+ const match = type === SpanType.OPENING ? openingMatch : closingMatch;
+ if (match === null) return undefined;
+ const length = match[0].length;
+ const removal: SpanRemoval = {
+ type,
+ lineAfter: line.slice(0, offset) + line.slice(offset + length),
+ offset,
+ class: type === SpanType.OPENING ? match[1] : undefined,
+ };
+ return removal;
+}
diff --git a/polygerrit-ui/app/utils/hljs-util_test.ts b/polygerrit-ui/app/utils/hljs-util_test.ts
new file mode 100644
index 0000000..3c577ca
--- /dev/null
+++ b/polygerrit-ui/app/utils/hljs-util_test.ts
@@ -0,0 +1,162 @@
+/**
+ * @license
+ * Copyright 2022 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+import '../test/common-test-setup-karma';
+import './hljs-util';
+import {
+ highlightedStringToRanges,
+ removeFirstSpan,
+ SpanType,
+} from './hljs-util';
+
+suite('file hljs-util', () => {
+ suite('function removeFirstSpan()', () => {
+ test('no matches', async () => {
+ assert.isUndefined(removeFirstSpan(''));
+ assert.isUndefined(removeFirstSpan('span'));
+ assert.isUndefined(removeFirstSpan('<span>'));
+ assert.isUndefined(removeFirstSpan('</span'));
+ assert.isUndefined(removeFirstSpan('asdf'));
+ });
+
+ test('simple opening match', async () => {
+ const removal = removeFirstSpan('asdf<span class="c">asdf');
+ assert.deepEqual(removal, {
+ type: SpanType.OPENING,
+ lineAfter: 'asdfasdf',
+ class: 'c',
+ offset: 4,
+ });
+ });
+
+ test('simple closing match', async () => {
+ const removal = removeFirstSpan('asdf</span>asdf');
+ assert.deepEqual(removal, {
+ type: SpanType.CLOSING,
+ lineAfter: 'asdfasdf',
+ class: undefined,
+ offset: 4,
+ });
+ });
+ });
+
+ suite('function highlightedStringToRanges()', () => {
+ test('no ranges', async () => {
+ assert.deepEqual(highlightedStringToRanges(''), [[]]);
+ assert.deepEqual(highlightedStringToRanges('\n'), [[], []]);
+ assert.deepEqual(highlightedStringToRanges('asdf\nasdf\nasdf'), [
+ [],
+ [],
+ [],
+ ]);
+ });
+
+ test('one line, one span', async () => {
+ assert.deepEqual(
+ highlightedStringToRanges('asdf<span class="c">qwer</span>asdf'),
+ [[{start: 4, length: 4, className: 'c'}]]
+ );
+ assert.deepEqual(
+ highlightedStringToRanges('<span class="d">asdfqwer</span>'),
+ [[{start: 0, length: 8, className: 'd'}]]
+ );
+ });
+
+ test('one line, two spans one after another', async () => {
+ assert.deepEqual(
+ highlightedStringToRanges(
+ 'asdf<span class="c">qwer</span>zxcv<span class="d">qwer</span>asdf'
+ ),
+ [
+ [
+ {start: 4, length: 4, className: 'c'},
+ {start: 12, length: 4, className: 'd'},
+ ],
+ ]
+ );
+ });
+
+ test('one line, two nested spans', async () => {
+ assert.deepEqual(
+ highlightedStringToRanges(
+ 'asdf<span class="c">qwer<span class="d">zxcv</span>qwer</span>asdf'
+ ),
+ [
+ [
+ {start: 4, length: 12, className: 'c'},
+ {start: 8, length: 4, className: 'd'},
+ ],
+ ]
+ );
+ });
+
+ test('two lines, one span each', async () => {
+ assert.deepEqual(
+ highlightedStringToRanges(
+ 'asdf<span class="c">qwer</span>asdf\n' +
+ 'asd<span class="d">qwe</span>asd'
+ ),
+ [
+ [{start: 4, length: 4, className: 'c'}],
+ [{start: 3, length: 3, className: 'd'}],
+ ]
+ );
+ });
+
+ test('one span over two lines', async () => {
+ assert.deepEqual(
+ highlightedStringToRanges(
+ 'asdf<span class="c">qwer\n' + 'asdf</span>qwer'
+ ),
+ [
+ [{start: 4, length: 4, className: 'c'}],
+ [{start: 0, length: 4, className: 'c'}],
+ ]
+ );
+ });
+
+ test('two spans over two lines', async () => {
+ assert.deepEqual(
+ highlightedStringToRanges(
+ 'asdf<span class="c">qwer<span class="d">zxcv\n' +
+ 'asdf</span>qwer</span>zxcv'
+ ),
+ [
+ [
+ {start: 4, length: 8, className: 'c'},
+ {start: 8, length: 4, className: 'd'},
+ ],
+ [
+ {start: 0, length: 8, className: 'c'},
+ {start: 0, length: 4, className: 'd'},
+ ],
+ ]
+ );
+ });
+
+ test('two spans over four lines', async () => {
+ assert.deepEqual(
+ highlightedStringToRanges(
+ 'asdf<span class="c">qwer\n' +
+ 'asdf<span class="d">qwer\n' +
+ 'asdf</span>qwer\n' +
+ 'asdf</span>qwer'
+ ),
+ [
+ [{start: 4, length: 4, className: 'c'}],
+ [
+ {start: 0, length: 8, className: 'c'},
+ {start: 4, length: 4, className: 'd'},
+ ],
+ [
+ {start: 0, length: 8, className: 'c'},
+ {start: 0, length: 4, className: 'd'},
+ ],
+ [{start: 0, length: 4, className: 'c'}],
+ ]
+ );
+ });
+ });
+});
diff --git a/polygerrit-ui/app/utils/label-util.ts b/polygerrit-ui/app/utils/label-util.ts
index 82380849..49790e6 100644
--- a/polygerrit-ui/app/utils/label-util.ts
+++ b/polygerrit-ui/app/utils/label-util.ts
@@ -232,7 +232,7 @@
type: 'all' | 'onlyOverride' | 'onlySubmittability' = 'all'
): string[] {
let labels: string[] = [];
- if (type !== 'onlyOverride') {
+ if (requirement.submittability_expression_result && type !== 'onlyOverride') {
labels = labels.concat(
extractLabelsFrom(requirement.submittability_expression_result.expression)
);
diff --git a/polygerrit-ui/app/utils/label-util_test.ts b/polygerrit-ui/app/utils/label-util_test.ts
index fbd0aa1..4e6d2f6 100644
--- a/polygerrit-ui/app/utils/label-util_test.ts
+++ b/polygerrit-ui/app/utils/label-util_test.ts
@@ -43,12 +43,10 @@
createChange,
createSubmitRequirementExpressionInfo,
createSubmitRequirementResultInfo,
+ createNonApplicableSubmitRequirementResultInfo,
createDetailedLabelInfo,
} from '../test/test-data-generators';
-import {
- SubmitRequirementResultInfo,
- SubmitRequirementStatus,
-} from '../api/rest-api';
+import {SubmitRequirementResultInfo} from '../api/rest-api';
const VALUES_0 = {
'0': 'neutral',
@@ -281,6 +279,12 @@
const labels = extractAssociatedLabels(submitRequirement);
assert.deepEqual(labels, ['Verified', 'Build-cop-override']);
});
+ test('non-applicable that has no labels', () => {
+ const submitRequirement =
+ createNonApplicableSubmitRequirementResultInfo();
+ const labels = extractAssociatedLabels(submitRequirement);
+ assert.deepEqual(labels, []);
+ });
});
suite('getRequirements()', () => {
@@ -314,10 +318,7 @@
});
test('filter not applicable', () => {
const requirement = createSubmitRequirementResultInfo();
- const requirement2 = {
- ...createSubmitRequirementResultInfo(),
- status: SubmitRequirementStatus.NOT_APPLICABLE,
- };
+ const requirement2 = createNonApplicableSubmitRequirementResultInfo();
const change = createChangeInfoWith([requirement, requirement2]);
assert.deepEqual(getRequirements(change), [requirement]);
});
diff --git a/polygerrit-ui/app/yarn.lock b/polygerrit-ui/app/yarn.lock
index d3b22eb..9729aba 100644
--- a/polygerrit-ui/app/yarn.lock
+++ b/polygerrit-ui/app/yarn.lock
@@ -2,10 +2,10 @@
# yarn lockfile v1
-"@lit/reactive-element@^1.0.0":
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/@lit/reactive-element/-/reactive-element-1.0.1.tgz#853cacd4d78d79059f33f66f8e7b0e5c34bee294"
- integrity sha512-nSD5AA2AZkKuXuvGs8IK7K5ZczLAogfDd26zT9l6S7WzvqALdVWcW5vMUiTnZyj5SPcNwNNANj0koeV1ieqTFQ==
+"@lit/reactive-element@^1.1.0":
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/@lit/reactive-element/-/reactive-element-1.2.0.tgz#c62444a0e3d3f8d3a6875ad56f867279aa89fa88"
+ integrity sha512-7i/Fz8enAQ2AN5DyJ2i2AFERufjP6x1NjuHoNgDyJkjjHxEoo8kVyyHxu1A9YyeShlksjt5FvpvENBDuivQHLA==
"@mapbox/node-pre-gyp@^1.0.0":
version "1.0.5"
@@ -652,29 +652,29 @@
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=
-lit-element@^3.0.0:
- version "3.0.1"
- resolved "https://registry.yarnpkg.com/lit-element/-/lit-element-3.0.1.tgz#3c545af17d8a46268bc1dd5623a47486e6ff76f4"
- integrity sha512-vs9uybH9ORyK49CFjoNGN85HM9h5bmisU4TQ63phe/+GYlwvY/3SIFYKdjV6xNvzz8v2MnVC+9+QOkPqh+Q3Ew==
+lit-element@^3.1.0:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/lit-element/-/lit-element-3.1.1.tgz#562d5ccbc8ba0c01d8ba4a0ac3576263167d2ccb"
+ integrity sha512-14ClnMAU8EXnzC+M2/KDd3SFmNUn1QUw1+GxWkEMwGV3iaH8ObunMlO5svzvaWlkSV0WlxJCi40NGnDVJ2XZKQ==
dependencies:
- "@lit/reactive-element" "^1.0.0"
- lit-html "^2.0.0"
+ "@lit/reactive-element" "^1.1.0"
+ lit-html "^2.1.0"
-lit-html@^2.0.0:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/lit-html/-/lit-html-2.0.1.tgz#63241015efa07bc9259b6f96f04abd052d2a1f95"
- integrity sha512-KF5znvFdXbxTYM/GjpdOOnMsjgRcFGusTnB54ixnCTya5zUR0XqrDRj29ybuLS+jLXv1jji6Y8+g4W7WP8uL4w==
+lit-html@^2.1.0:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/lit-html/-/lit-html-2.1.1.tgz#f4da485798a0d967514d31730d387350fafb79f7"
+ integrity sha512-E4BImK6lopAYanJpvcGaAG8kQFF1ccIulPu2BRNZI7acFB6i4ujjjsnaPVFT1j/4lD9r8GKih0Y8d7/LH8SeyQ==
dependencies:
"@types/trusted-types" "^2.0.2"
-lit@2.0.2:
- version "2.0.2"
- resolved "https://registry.yarnpkg.com/lit/-/lit-2.0.2.tgz#5e6f422924e0732258629fb379556b6d23f7179c"
- integrity sha512-hKA/1YaSB+P+DvKWuR2q1Xzy/iayhNrJ3aveD0OQ9CKn6wUjsdnF/7LavDOJsKP/K5jzW/kXsuduPgRvTFrFJw==
+lit@^2.1.1:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/lit/-/lit-2.1.1.tgz#65f43abca945988f696391f762c645ba51966b0b"
+ integrity sha512-yqDqf36IhXwOxIQSFqCMgpfvDCRdxLCLZl7m/+tO5C9W/OBHUj17qZpiMBT35v97QMVKcKEi1KZ3hZRyTwBNsQ==
dependencies:
- "@lit/reactive-element" "^1.0.0"
- lit-element "^3.0.0"
- lit-html "^2.0.0"
+ "@lit/reactive-element" "^1.1.0"
+ lit-element "^3.1.0"
+ lit-html "^2.1.0"
lru-cache@^6.0.0:
version "6.0.0"
diff --git a/tools/deps.bzl b/tools/deps.bzl
index 3dff9ce9..86eb872 100644
--- a/tools/deps.bzl
+++ b/tools/deps.bzl
@@ -17,7 +17,7 @@
GITILES_REPO = GERRIT
# When updating Bouncy Castle, also update it in bazlets.
-BC_VERS = "1.61"
+BC_VERS = "1.64"
HTTPCOMP_VERS = "4.5.2"
JETTY_VERS = "9.4.36.v20210114"
BYTE_BUDDY_VERSION = "1.10.7"
@@ -575,19 +575,19 @@
maven_jar(
name = "bcprov",
artifact = "org.bouncycastle:bcprov-jdk15on:" + BC_VERS,
- sha1 = "00df4b474e71be02c1349c3292d98886f888d1f7",
+ sha1 = "1467dac1b787b5ad2a18201c0c281df69882259e",
)
maven_jar(
name = "bcpg",
artifact = "org.bouncycastle:bcpg-jdk15on:" + BC_VERS,
- sha1 = "422656435514ab8a28752b117d5d2646660a0ace",
+ sha1 = "56956a8c63ccadf62e7c678571cf86f30bd84441",
)
maven_jar(
name = "bcpkix",
artifact = "org.bouncycastle:bcpkix-jdk15on:" + BC_VERS,
- sha1 = "89bb3aa5b98b48e584eee2a7401b7682a46779b4",
+ sha1 = "3dac163e20110817d850d17e0444852a6d7d0bd7",
)
maven_jar(
diff --git a/tools/nongoogle.bzl b/tools/nongoogle.bzl
index 3432eb9..272f822 100644
--- a/tools/nongoogle.bzl
+++ b/tools/nongoogle.bzl
@@ -1,11 +1,10 @@
load("//tools/bzl:maven_jar.bzl", "maven_jar")
-load("@bazel_tools//tools/build_defs/repo:java.bzl", "java_import_external")
-GUAVA_VERSION = "31.0.1-jre"
+GUAVA_VERSION = "30.1-jre"
-GUAVA_BIN_SHA1 = "119ea2b2bc205b138974d351777b20f02b92704b"
+GUAVA_BIN_SHA1 = "00d0c3ce2311c9e36e73228da25a6e99b2ab826f"
-GUAVA_TESTLIB_BIN_SHA1 = "6f9d0fb58913ce77de477280b21f8801fb617151"
+GUAVA_TESTLIB_BIN_SHA1 = "798c3827308605cd69697d8f1596a1735d3ef6e2"
GUAVA_DOC_URL = "https://google.github.io/guava/releases/" + GUAVA_VERSION + "/api/docs/"
@@ -36,18 +35,18 @@
sha1 = "cb2f351bf4463751201f43bb99865235d5ba07ca",
)
- SSHD_VERS = "2.7.0"
+ SSHD_VERS = "2.8.0"
maven_jar(
name = "sshd-osgi",
artifact = "org.apache.sshd:sshd-osgi:" + SSHD_VERS,
- sha1 = "a101aad0f79ad424498098f7e91c39d3d92177c1",
+ sha1 = "b2a59b73c045f40d5722b9160d4f909a646d86c9",
)
maven_jar(
name = "sshd-sftp",
artifact = "org.apache.sshd:sshd-sftp:" + SSHD_VERS,
- sha1 = "0c9eff7145e20b338c1dd6aca36ba93ed7c0147c",
+ sha1 = "d3cd9bc8d335b3ed1a86d2965deb4d202de27442",
)
maven_jar(
@@ -65,7 +64,7 @@
maven_jar(
name = "sshd-mina",
artifact = "org.apache.sshd:sshd-mina:" + SSHD_VERS,
- sha1 = "22799941ec7bd5170ea890363cb968e400a69c41",
+ sha1 = "02f78100cce376198be798a37c84aaf945e8a0f7",
)
maven_jar(
@@ -123,12 +122,6 @@
)
maven_jar(
- name = "flogger-google-extensions",
- artifact = "com.google.flogger:google-extensions:" + FLOGGER_VERS,
- sha1 = "c49493bd815e3842b8406e21117119d560399977",
- )
-
- maven_jar(
name = "flogger-system-backend",
artifact = "com.google.flogger:flogger-system-backend:" + FLOGGER_VERS,
sha1 = "4bee7ebbd97c63ca7fb17529aeb49a57b670d061",
@@ -167,13 +160,10 @@
)
# Keep this version of Soy synchronized with the version used in Gitiles.
- java_import_external(
+ maven_jar(
name = "soy",
- jar_sha256 = "428bb756a7e554383c349697ab527c8507f3f961203152f8df7e937fd5a14130",
- jar_urls = [
- "https://github.com/davido/closure-templates/releases/download/2022-01-18/soy-2022-01-18.jar",
- ],
- licenses = ["unencumbered"], # public domain
+ artifact = "com.google.template:soy:2021-02-01",
+ sha1 = "8e833744832ba88059205a1e30e0898f925d8cb5",
)
# Test-only dependencies below.