Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions app_dart/docs/cicd_flowchart.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# CICD Label Flowchart

This flowchart describes the logic for managing the `CICD` label in Cocoon for running presubmit CI.

```mermaid
Comment thread
eyebrowsoffire marked this conversation as resolved.
flowchart TD
PR_Opened([Event: PR Opened]) --> Is_Draft{Is Draft?}

Is_Draft -- No --> Is_Privileged_Open{Is Privileged?}
Is_Draft -- Yes --> Create_Awaiting[Create Awaiting Check Run]

Is_Privileged_Open -- Yes --> Add_Label[Add CICD Label]
Add_Label --> Start_Pre[Start Presubmits]

Is_Privileged_Open -- No --> Create_Awaiting

Create_Awaiting --> State_Awaiting((State: Awaiting))
Start_Pre --> State_Running((State: Running))

State_Awaiting --> Label_Added([Event: CICD Label Added])
Label_Added --> Resolve_Awaiting[Resolve Awaiting Check Run]
Resolve_Awaiting --> Start_Pre

State_Running --> Changes_Pushed([Event: Changes Pushed])
Changes_Pushed --> Is_Privileged_Push{Is Privileged?}
Remove_Label --> Create_Awaiting

Is_Privileged_Push -- No --> Remove_Label[Remove CICD Label]

Is_Privileged_Push -- Yes --> Label_Present{Has CICD Label?}
Label_Present -- Yes --> Start_Pre
Label_Present -- No --> Create_Awaiting
```

## Alternative State Machine Diagram (PlantUML)

```plantuml
@startuml
state PR {
state Waiting
Waiting : entry / Create Awaiting Check Run
Waiting : exit / resolve(awaiting check)

state Running
Running: entry/ Start Presubmits

state if_priv2 <<choice>>
Running --> if_priv2: onPushed
if_priv2 --> Running: isPrivileged && labeled(CICD)
if_priv2 --> Waiting : default: remove(CICD)

state if_draft <<choice>>
PR --> if_draft: openned

if_draft --> Waiting : isDraft
if_draft --> Running: isPrivileged
if_draft --> Waiting : default

Waiting --> Running: labeled(CICD)
}
@enduml
```
1 change: 1 addition & 0 deletions app_dart/lib/cocoon_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,5 @@ export 'src/service/flags/dynamic_config.dart';
export 'src/service/gerrit_service.dart';
export 'src/service/github_checks_service.dart';
export 'src/service/luci_build_service.dart';
export 'src/service/pull_request_manager.dart';
export 'src/service/scheduler.dart';
85 changes: 85 additions & 0 deletions app_dart/lib/src/model/firestore/pull_request_state.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// Copyright 2026 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:convert';

import 'package:github/github.dart';
import 'package:googleapis/firestore/v1.dart';

import '../../service/firestore.dart';
import 'base.dart';

/// Represents the state of a Pull Request that we want to persist across events.
final class PullRequestState extends AppDocument<PullRequestState> {
static const kCollectionId = 'pullRequestStates';
static const kIsPrivilegedField = 'is_privileged';
static const kLatestShaField = 'latest_sha';
static const kScheduledShaField = 'scheduled_sha';
static const kSlugField = 'slug';
static const kNumberField = 'number';

@override
AppDocumentMetadata<PullRequestState> get runtimeMetadata => metadata;

/// Description of the document in Firestore.
static final metadata = AppDocumentMetadata<PullRequestState>(
collectionId: kCollectionId,
fromDocument: PullRequestState.fromDocument,
);

/// Create [PullRequestState] from a Document.
static PullRequestState fromDocument(Document doc) {
return PullRequestState()
..fields = doc.fields!
..name = doc.name!;
}

/// Whether the PR author is a privileged user (roller or flutter-hacker).
bool? get isPrivileged => fields[kIsPrivilegedField]?.booleanValue;

set isPrivileged(bool? value) {
if (value != null) {
fields[kIsPrivilegedField] = value.toValue();
}
}

/// The latest SHA that we have processed for this PR.
String? get latestSha => fields[kLatestShaField]?.stringValue;

set latestSha(String? value) {
if (value != null) {
fields[kLatestShaField] = value.toValue();
}
}

/// The SHA for which we have scheduled presubmits.
String? get scheduledSha => fields[kScheduledShaField]?.stringValue;

set scheduledSha(String? value) {
if (value != null) {
fields[kScheduledShaField] = value.toValue();
}
}

/// The repository slug associated with the pull request.
RepositorySlug get slug => RepositorySlug.fromJson(
json.decode(fields[kSlugField]!.stringValue!) as Map<String, Object?>,
);

set slug(RepositorySlug value) {
fields[kSlugField] = json.encode(value.toJson()).toValue();
Comment thread
eyebrowsoffire marked this conversation as resolved.
}

/// The PR number.
int get number => int.parse(fields[kNumberField]!.integerValue!);

set number(int value) {
fields[kNumberField] = value.toValue();
}

/// Generates the document ID for a PR.
static String getDocumentId(RepositorySlug slug, int number) {
return '${slug.owner}\u001F${slug.name}\u001F$number';
}
}
Loading
Loading