Merge "Lit Migration of GrLabeledAutocomplete"
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/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/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/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/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/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/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/polygerrit-ui/app/api/rest-api.ts b/polygerrit-ui/app/api/rest-api.ts
index 314ea8b..5cbae90 100644
--- a/polygerrit-ui/app/api/rest-api.ts
+++ b/polygerrit-ui/app/api/rest-api.ts
@@ -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/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_test.ts b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_test.ts
index 8932d52..ab8d2d7 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,6 +25,7 @@
createChange,
createSubmitRequirementExpressionInfo,
createSubmitRequirementResultInfo,
+ createNonApplicableSubmitRequirementResultInfo,
} from '../../../test/test-data-generators';
import {query, queryAndAssert, stubRestApi} from '../../../test/test-utils';
import {
@@ -483,7 +484,10 @@
};
const change: ChangeInfo = {
...createChange(),
- submit_requirements: [submitRequirement],
+ submit_requirements: [
+ submitRequirement,
+ createNonApplicableSubmitRequirementResultInfo(),
+ ],
unresolved_comment_count: 1,
};
const element = await fixture<GrChangeListItem>(
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..9492fa9
--- /dev/null
+++ b/polygerrit-ui/app/elements/change/gr-message-scores/gr-message-scores.ts
@@ -0,0 +1,169 @@
+/**
+ * @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 {LitElement, css, html} from 'lit';
+import {customElement, property} from 'lit/decorators';
+import {
+ ChangeMessage,
+ LabelExtreme,
+ PATCH_SET_PREFIX_PATTERN,
+} from '../../../utils/comment-util';
+import {hasOwnProperty} from '../../../utils/common-util';
+
+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;
+
+ static override get styles() {
+ return css`
+ .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;
+ margin-right: var(--spacing-s);
+ 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;
+ }
+ }
+ `;
+ }
+
+ override render() {
+ const scores = this._getScores(this.message, this.labelExtremes);
+ return scores.map(score => this.renderScore(score));
+ }
+
+ private renderScore(score: Score) {
+ 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..7398909
--- /dev/null
+++ b/polygerrit-ui/app/elements/change/gr-message-scores/gr-message-scores_test.ts
@@ -0,0 +1,136 @@
+/**
+ * @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 {createChangeMessage} from '../../../test/test-data-generators';
+import {queryAll} 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);
+ });
+});
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..628af83 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,10 @@
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]]"
+ ></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-submit-requirements/gr-submit-requirements.ts b/polygerrit-ui/app/elements/change/gr-submit-requirements/gr-submit-requirements.ts
index 15a971a1..8fa2fa4 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';
@@ -371,103 +370,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..d920b46
--- /dev/null
+++ b/polygerrit-ui/app/elements/change/gr-trigger-vote/gr-trigger-vote.ts
@@ -0,0 +1,132 @@
+/**
+ * @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;
+
+ 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-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/test/test-data-generators.ts b/polygerrit-ui/app/test/test-data-generators.ts
index d0179ef..1abb0b9 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,7 +112,6 @@
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,
@@ -782,6 +782,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/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/tools/nongoogle.bzl b/tools/nongoogle.bzl
index 3432eb9..3c5ba88 100644
--- a/tools/nongoogle.bzl
+++ b/tools/nongoogle.bzl
@@ -36,18 +36,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 +65,7 @@
maven_jar(
name = "sshd-mina",
artifact = "org.apache.sshd:sshd-mina:" + SSHD_VERS,
- sha1 = "22799941ec7bd5170ea890363cb968e400a69c41",
+ sha1 = "02f78100cce376198be798a37c84aaf945e8a0f7",
)
maven_jar(