Merge "Add ChecksApi types and interface"
diff --git a/polygerrit-ui/app/elements/plugins/gr-checks-api/gr-checks-api-types.ts b/polygerrit-ui/app/elements/plugins/gr-checks-api/gr-checks-api-types.ts
new file mode 100644
index 0000000..f587da1
--- /dev/null
+++ b/polygerrit-ui/app/elements/plugins/gr-checks-api/gr-checks-api-types.ts
@@ -0,0 +1,370 @@
+/**
+ * @license
+ * Copyright (C) 2020 The Android Open Source Settings
+ *
+ * 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.
+ */
+
+// IMPORTANT !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+// The entire API is currently in DRAFT state.
+// Changes to all type and interfaces are expected.
+// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+export interface GrChecksApiInterface {
+ /**
+ * Must only be called once. You cannot register twice. You cannot unregister.
+ */
+ register(provider: ChecksProvider, config?: ChecksApiConfig): void;
+
+ /**
+ * Forces Gerrit to call fetch() on the registered provider. Can be called
+ * when the provider has gotten an update and does not want wait for the next
+ * polling interval to pass.
+ */
+ announceUpdate(): void;
+}
+
+export interface ChecksApiConfig {
+ /**
+ * How often should the provider be called for new CheckData while the user
+ * navigates change related pages and the browser tab remains visible?
+ * Set to 0 to disable polling. Default is 60 seconds.
+ */
+ fetchPollingIntervalSeconds: number;
+}
+
+export interface ChecksProvider {
+ /**
+ * Gerrit calls this method when ...
+ * - ... the change or diff page is loaded.
+ * - ... the user switches back to a Gerrit tab with a change or diff page.
+ * - ... while the tab is visible in a regular polling interval, see
+ * ChecksApiConfig.
+ */
+ fetch(change: number, patchset: number): Promise<FetchResponse>;
+}
+
+export interface FetchResponse {
+ responseCode: ResponseCode;
+
+ /** Only relevant when the responseCode is ERROR. */
+ errorMessage?: string;
+
+ /**
+ * Only relevant when the responseCode is NOT_LOGGED_IN.
+ * Gerrit displays a "Login" button and calls this callback when the user
+ * clicks on the button.
+ */
+ loginCallback?: () => void;
+
+ runs: CheckRun[];
+ results: CheckResult[];
+}
+
+export enum ResponseCode {
+ OK,
+ ERROR,
+ NOT_LOGGED_IN,
+}
+
+/**
+ * A CheckRun models an entity that has start/end timestamps and can be in
+ * either of the states RUNNABLE, RUNNING, COMPLETED. By itself it cannot model
+ * an error, neither can it be failed or successful by itself. A run can be
+ * associated with 0 to n results (see below). So until runs are completed the
+ * runs are more interesting for the user: What is going on at the moment? When
+ * runs are completed the users' interest shifts to results: What do I have to
+ * fix? The only actions that can be associated with runs are RUN and CANCEL.
+ */
+export interface CheckRun {
+ /**
+ * Gerrit requests check runs and results from the plugin by change number and
+ * patchset number. So these two properties can as well be left empty when
+ * returning results to the Gerrit UI and are thus optional.
+ */
+ change?: number;
+ /**
+ * Typically only runs for the latest patchset are requested and presented.
+ * Older runs and their results are only available on request, e.g. by
+ * switching to another patchset in a dropdown
+ *
+ * TBD: CI data providers may decide that runs and results are applicable to a
+ * newer patchset, even if they were produced for an older, e.g. because only
+ * the commit message was changed. Maybe that warrants the addition of another
+ * optional field, e.g. `original_patchset`.
+ */
+ patchset?: number;
+ /**
+ * The UI will focus on just the latest attempt per run. Former attempts are
+ * accessible, but initially collapsed/hidden. Lower number means older
+ * attempt. Every run has its own attempt numbering, so attempt 3 of run A is
+ * not directly related to attempt 3 of run B.
+ *
+ * RUNNABLE runs must use `undefined` as attempt.
+ * COMPLETED and RUNNING runs must use an attempt number >=0.
+ *
+ * TBD: Optionally providing aggregate information about former attempts will
+ * probably be a useful feature, but we are deferring the exact data modeling
+ * of that to later.
+ */
+ attempt?: number;
+
+ /**
+ * An optional opaque identifier not used by Gerrit directly, but might be
+ * used by plugin extensions and callbacks.
+ */
+ externalId?: string;
+
+ // The following 3 properties are independent of this run *instance*. They
+ // just describe what the check is about and will be identical for other
+ // attempts or patchsets or changes.
+
+ /**
+ * The unique name of the check. There can’t be two runs with the same
+ * change/patchset/attempt/checkName combination.
+ * Multiple attempts of the same run must have the same checkName.
+ * It should be expected that this string is cut off at ~30 chars in the UI.
+ * The full name will then be shown in a tooltip.
+ */
+ checkName: string;
+ /**
+ * Optional description of the check. Only shown as a tooltip or in a
+ * hovercard.
+ */
+ checkDescription?: string;
+ /**
+ * Optional http link to an external page with more detailed information about
+ * this run. Must begin with 'http'.
+ */
+ checkLink?: string;
+
+ /**
+ * RUNNABLE: Not run (yet). Mostly useful for runs that the user can trigger
+ * (see actions). Cannot contain results.
+ * RUNNING: Subsumes "scheduled".
+ * COMPLETED: The attempt of the run has finished. Does not indicate at all
+ * whether the run was successful or not. Outcomes can and should
+ * be modeled using the CheckResult entity.
+ */
+ status: RunStatus;
+ /**
+ * Optional short description of the run status. This is a plain string
+ * without styling or formatting options. It will only be shown as a tooltip
+ * or in a hovercard.
+ *
+ * Examples:
+ * "40 tests running, 30 completed: 0 failing so far",
+ * "Scheduled 5 minutes ago, not running yet".
+ */
+ statusDescription?: string;
+ /**
+ * Optional http link to an external page with more detailed information about
+ * the run status. Must begin with 'http'.
+ */
+ statusLink?: string;
+
+ /**
+ * Optional reference to a Gerrit label (e.g. "Verified") that this result
+ * influences. Allows the user to understand and navigate the relationship
+ * between CI results and submit requirements,
+ * see also https://gerrit-review.googlesource.com/c/homepage/+/279176.
+ */
+ labelName?: string;
+
+ /**
+ * Optional callbacks to the CI plugin. Must be implemented individually by
+ * each plugin. The most important actions (which get special UI treatment)
+ * are:
+ * "Run" for RUNNABLE and COMPLETED runs.
+ * "Cancel" for RUNNING runs.
+ */
+ actions: Action[];
+
+ scheduledTimestamp?: Date;
+ startedTimestamp?: Date;
+ finishedTimestamp?: Date;
+
+ /**
+ * List of results produced by this run.
+ * RUNNABLE runs must not have results.
+ * RUNNING runs can contain (intermediate) results.
+ * Nesting the results in runs enforces that:
+ * - A run can have 0-n results.
+ * - A result is associated with exactly one run.
+ */
+ results: CheckResult[];
+}
+
+export interface Action {
+ name: string;
+ tooltip?: string;
+ /**
+ * Primary actions will get a more prominent treatment in the UI. For example
+ * primary actions might be rendered as buttons versus just menu entries in
+ * an overflow menu.
+ */
+ primary: boolean;
+ callback: ActionCallback;
+}
+
+export type ActionCallback = (
+ change: number,
+ patchset: number,
+ attempt: number,
+ externalId: string,
+ /** Identical to 'checkName' property of CheckRun. */
+ checkName: string,
+ /** Identical to 'name' property of Action entity. */
+ actionName: string
+) => Promise<ActionResult>;
+
+export interface ActionResult {
+ /** An empty errorMessage means success. */
+ errorMessage?: string;
+}
+
+export enum RunStatus {
+ RUNNABLE,
+ RUNNING,
+ COMPLETED,
+}
+
+export interface CheckResult {
+ /**
+ * An optional opaque identifier not used by Gerrit directly, but might be
+ * used by plugin extensions and callbacks.
+ */
+ externalId?: string;
+
+ /**
+ * INFO: The user will typically not bother to look into this category,
+ * only for looking up something that they are searching for. Can be
+ * used for reporting secondary metrics and analysis, or a wider
+ * range of artifacts produced by the CI system.
+ * WARNING: A warning is something that should be read before submitting the
+ * change. The user should not ignore it, but it is also not blocking
+ * submit. It has a similar level of importance as an unresolved
+ * comment.
+ * ERROR: An error indicates that the change must not or cannot be submitted
+ * without fixing the problem. Errors will be visualized very
+ * prominently to the user.
+ *
+ * The ‘tags’ field below can be used for further categorization, e.g. for
+ * distinguishing FAILED vs TIMED_OUT.
+ */
+ category: Category;
+
+ /**
+ * Short description of the check result.
+ *
+ * It should be expected that this string might be cut off at ~80 chars in the
+ * UI. The full description will then be shown in a tooltip.
+ * This is a plain string without styling or formatting options.
+ *
+ * Examples:
+ * MessageConverterTest failed with: 'kermit' expected, but got 'ernie'.
+ * Binary size of javascript bundle has increased by 27%.
+ */
+ summary: string;
+
+ /**
+ * Exhaustive optional message describing the check result.
+ * Will be initially collapsed. Might potentially be very long, e.g. a log of
+ * MB size. The UI is not limiting this. CI data providers are responsible for
+ * not killing the browser. :-)
+ *
+ * For now this is just a plain unformatted string. The only formatting
+ * applied is the one that Gerrit also applies to human comments. TBD: Both
+ * human comments and check result messages should get richer formatting
+ * options.
+ */
+ message?: string;
+
+ /**
+ * Tags allow a CI System to further categorize a result, e.g. making a list
+ * of results filterable by the end-user.
+ * The name is free-form, but there is a predefined set of TagColors to
+ * choose from with a recommendation of color for common tags, see below.
+ *
+ * Examples:
+ * PASS, FAIL, SCHEDULED, OBSOLETE, SKIPPED, TIMED_OUT, INFRA_ERROR, FLAKY
+ * WIN, MAC, LINUX
+ * BUILD, TEST, LINT
+ * INTEGRATION, E2E, SCREENSHOT
+ */
+ tags: Tag[];
+
+ /**
+ * Links provide an opportunity for the end-user to easily access details and
+ * artifacts. Links are displayed by an icon+tooltip only. They don’t have a
+ * name, making them clearly distinguishable from tags and actions.
+ *
+ * There is a fixed set of LinkIcons to choose from, see below.
+ *
+ * Examples:
+ * Link to test log.
+ * Link to result artifacts such as images and screenshots.
+ * Link to downloadable artifacts such as ZIP or APK files.
+ */
+ links: Link[];
+
+ /**
+ * Callbacks to the CI plugin. Must be implemented individually by each
+ * plugin. Actions are rendered as buttons. If there are more than two actions
+ * per result, then further actions are put into an overflow menu. Sort order
+ * is defined by the data provider.
+ *
+ * Examples:
+ * Acknowledge/Dismiss, Delete, Report a bug, Report as not useful,
+ * Make blocking, Downgrade severity.
+ */
+ actions: Action[];
+}
+
+export enum Category {
+ INFO,
+ WARNING,
+ ERROR,
+}
+
+export interface Tag {
+ name: string;
+ tooltip?: string;
+ color?: TagColor;
+}
+
+// TBD: Add more ...
+// TBD: Clarify standard colors for common tags.
+export enum TagColor {
+ GRAY,
+ GREEN,
+}
+
+export interface Link {
+ /** Must begin with 'http'. */
+ url: string;
+ tooltip?: string;
+ /**
+ * Primary links will get a more prominent treatment in the UI, e.g. being
+ * always visible in the results table or also showing up in the change page
+ * summary of checks.
+ */
+ primary: boolean;
+ icon: LinkIcon;
+}
+
+// TBD: Add more ...
+export enum LinkIcon {
+ EXTERNAL,
+ DOWNLOAD,
+}
diff --git a/polygerrit-ui/app/elements/plugins/gr-checks-api/gr-checks-api.ts b/polygerrit-ui/app/elements/plugins/gr-checks-api/gr-checks-api.ts
index bfcff33..50b9222 100644
--- a/polygerrit-ui/app/elements/plugins/gr-checks-api/gr-checks-api.ts
+++ b/polygerrit-ui/app/elements/plugins/gr-checks-api/gr-checks-api.ts
@@ -15,7 +15,60 @@
* limitations under the License.
*/
import {PluginApi} from '../gr-plugin-types';
+import {
+ ChecksApiConfig,
+ ChecksProvider,
+ GrChecksApiInterface,
+ ResponseCode,
+} from './gr-checks-api-types';
-export class GrChecksApi {
+const DEFAULT_CONFIG: ChecksApiConfig = {
+ fetchPollingIntervalSeconds: 60,
+};
+
+enum State {
+ NOT_REGISTERED,
+ REGISTERED,
+ FETCHING,
+}
+
+/**
+ * Plugin API for checks.
+ *
+ * This object is created/returned to plugins that want to provide check data.
+ * Plugins normally just call register() once at startup and then wait for
+ * fetch() being called on the provider interface.
+ */
+export class GrChecksApi implements GrChecksApiInterface {
+ private provider?: ChecksProvider;
+
+ config?: ChecksApiConfig;
+
+ private state = State.NOT_REGISTERED;
+
constructor(readonly plugin: PluginApi) {}
+
+ announceUpdate() {
+ // TODO(brohlfs): Implement!
+ }
+
+ register(provider: ChecksProvider, config?: ChecksApiConfig): void {
+ if (this.state !== State.NOT_REGISTERED || this.provider)
+ throw new Error('Only one provider can be registered per plugin.');
+ this.state = State.REGISTERED;
+ this.provider = provider;
+ this.config = config ?? DEFAULT_CONFIG;
+ }
+
+ async fetch(change: number, patchset: number) {
+ if (this.state === State.NOT_REGISTERED || !this.provider)
+ throw new Error('Cannot fetch checks without a registered provider.');
+ if (this.state === State.FETCHING) return;
+ this.state = State.FETCHING;
+ const response = await this.provider.fetch(change, patchset);
+ this.state = State.REGISTERED;
+ if (response.responseCode === ResponseCode.OK) {
+ // TODO(brohlfs): Do something with the response.
+ }
+ }
}