-
Notifications
You must be signed in to change notification settings - Fork 4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Sweep: Add a code field to each case and show it in the UI #118
Comments
🚀 Here's the PR! #120See Sweep's progress at the progress dashboard! ⚡ Sweep Basic Tier: I'm using GPT-4. You have 4 GPT-4 tickets left for the month and 2 for the day. (tracking ID:
f7e6019b19 )For more GPT-4 tickets, visit our payment portal. For a one week free trial, try Sweep Pro (unlimited GPT-4 tickets). Tip I can email you next time I complete a pull request if you set up your email here! Actions (click)
GitHub Actions✓Here are the GitHub Actions logs prior to making any changes: Sandbox logs for
|
class CreateCases < ActiveRecord::Migration[5.2] | |
def change | |
create_table :cases do |t| | |
t.string :name | |
t.references :status, foreign_key: true | |
t.references :priority, foreign_key: true | |
t.references :created_by, foreign_key: { to_table: :users } | |
t.timestamps | |
end |
true-positive/docs/work_with_cases/create_a_case.md
Lines 1 to 30 in 58358b0
--- | |
id: create_a_case | |
title: Create a Case | |
sidebar_label: Create a Case | |
--- | |
To create a case in the web console, click "Cases" in the top navigation menu. This will open a page listing all your cases. Then click "Create Case", which will open a modal like this: | |
 | |
Now choose if you'd like to create a case from scratch or from an existing template. | |
## From scratch | |
We suggest creating one-off cases from scratch. Simply provide a name, status, and priority for the case in the form above. | |
## From a template | |
For types of cases you encounter often, however, like phishing or commodity malware, you should [create case templates](/docs/administer/case_templates) for them. | |
Then, you can initialize a case from a template in one click: | |
 | |
## From emails | |
Finally, you can create cases from inbound emails. | |
For example, you might want to create a case in True Positive from your phishing case template for every email that's sent to [email protected] | |
true-positive/backend/app/models/case.rb
Lines 1 to 99 in 58358b0
require 'ancestry' | |
class Case < ApplicationRecord | |
has_ancestry :orphan_strategy => :rootify | |
validates :name, presence: true | |
validates :status, presence: true | |
validates :priority, presence: true | |
validates :created_by, presence: true | |
belongs_to :created_by, :class_name => 'User' | |
belongs_to :assigned_to, :class_name => 'User', optional: true | |
belongs_to :status | |
belongs_to :priority | |
has_many :comments, as: :commentable, dependent: :destroy | |
has_many :indicators, dependent: :destroy | |
has_many :case_members, as: :caseable, dependent: :destroy | |
has_many :case_groups, as: :caseable, dependent: :destroy | |
has_many :task_groups, -> { order(position: :asc) }, as: :caseable, dependent: :destroy | |
has_many :tasks, through: :task_groups | |
has_many :attachments, as: :attachable, dependent: :destroy | |
after_create :add_creator_to_members | |
acts_as_taggable_on :tags | |
def link_to_case | |
if Rails.env.development? | |
"http://localhost:3000/cases/#{self.id}" | |
elsif Rails.env.production? | |
"https://console.truepositive.app/cases/#{self.id}" | |
else | |
raise "Your environment must be either development or production to get a link to a case." | |
end | |
end | |
def has_member(user) | |
# Returns true iff the specified user is a member of this case, regardless of role. | |
self.case_members.where(user: user).exists? | |
end | |
def is_a_member_of_a_group_with_access(user) | |
# Returns true iff the specified user is a member of a group with access to this case | |
self.case_groups.select { |cgroup| cgroup.group.users.include? user }.count >= 1 | |
end | |
def to_s | |
self.name | |
end | |
def is_merged | |
# Whether or not this case has been merged into another case | |
not self.parent.nil? | |
end | |
def merged_cases | |
# Lists the other cases that have been merged into this one | |
self.children | |
end | |
def merged_into | |
# The case this case has been merged into | |
self.parent | |
end | |
def case_member_count | |
self.case_members.count | |
end | |
def case_group_count | |
self.case_groups.count | |
end | |
def completed_task_count | |
# Number of completed tasks in this case | |
self.tasks.where(done: true).count | |
end | |
def total_task_count | |
# Number of total tasks in this case | |
self.tasks.count | |
end | |
def task_group_count | |
# Number of task groups in this case | |
self.task_groups.count | |
end | |
def attachment_count | |
# Number of attachments in this case | |
self.attachments.count | |
end | |
private | |
def add_creator_to_members | |
# Add the user who created this case to its list of members, so he/she can access it. | |
self.case_members.create(user: self.created_by, role: "CAN_EDIT") | |
end |
true-positive/backend/db/schema.rb
Lines 1 to 351 in 58358b0
# This file is auto-generated from the current state of the database. Instead | |
# of editing this file, please use the migrations feature of Active Record to | |
# incrementally modify your database, and then regenerate this schema definition. | |
# | |
# This file is the source Rails uses to define your schema when running `rails | |
# db:schema:load`. When creating a new database, `rails db:schema:load` tends to | |
# be faster and is potentially less error prone than running all of your | |
# migrations from scratch. Old migrations may fail to apply correctly if those | |
# migrations use external dependencies or application code. | |
# | |
# It's strongly recommended that you check this file into your version control system. | |
ActiveRecord::Schema.define(version: 2020_01_28_075422) do | |
# These are extensions that must be enabled in order to support this database | |
enable_extension "plpgsql" | |
create_table "action_mailbox_inbound_emails", force: :cascade do |t| | |
t.integer "status", default: 0, null: false | |
t.string "message_id", null: false | |
t.string "message_checksum", null: false | |
t.datetime "created_at", precision: 6, null: false | |
t.datetime "updated_at", precision: 6, null: false | |
t.index ["message_id", "message_checksum"], name: "index_action_mailbox_inbound_emails_uniqueness", unique: true | |
end | |
create_table "active_storage_attachments", force: :cascade do |t| | |
t.string "name", null: false | |
t.string "record_type", null: false | |
t.bigint "record_id", null: false | |
t.bigint "blob_id", null: false | |
t.datetime "created_at", null: false | |
t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id" | |
t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true | |
end | |
create_table "active_storage_blobs", force: :cascade do |t| | |
t.string "key", null: false | |
t.string "filename", null: false | |
t.string "content_type" | |
t.text "metadata" | |
t.bigint "byte_size", null: false | |
t.string "checksum", null: false | |
t.datetime "created_at", null: false | |
t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true | |
end | |
create_table "api_tokens", force: :cascade do |t| | |
t.string "name" | |
t.text "api_token" | |
t.bigint "user_id" | |
t.datetime "created_at", null: false | |
t.datetime "updated_at", null: false | |
t.index ["user_id"], name: "index_api_tokens_on_user_id" | |
end | |
create_table "attachments", force: :cascade do |t| | |
t.bigint "created_by_id" | |
t.string "attachable_type" | |
t.bigint "attachable_id" | |
t.datetime "created_at", precision: 6, null: false | |
t.datetime "updated_at", precision: 6, null: false | |
t.index ["attachable_type", "attachable_id"], name: "index_attachments_on_attachable_type_and_attachable_id" | |
t.index ["created_by_id"], name: "index_attachments_on_created_by_id" | |
end | |
create_table "case_audits", force: :cascade do |t| | |
t.integer "action" | |
t.json "parameters" | |
t.bigint "created_by_id" | |
t.datetime "created_at", null: false | |
t.datetime "updated_at", null: false | |
t.string "auditable_type" | |
t.bigint "auditable_id" | |
t.index ["auditable_type", "auditable_id"], name: "index_case_audits_on_auditable_type_and_auditable_id" | |
t.index ["created_by_id"], name: "index_case_audits_on_created_by_id" | |
end | |
create_table "case_groups", force: :cascade do |t| | |
t.string "caseable_type" | |
t.bigint "caseable_id" | |
t.bigint "group_id" | |
t.datetime "created_at", null: false | |
t.datetime "updated_at", null: false | |
t.integer "role" | |
t.index ["caseable_type", "caseable_id"], name: "index_case_groups_on_caseable_type_and_caseable_id" | |
t.index ["group_id"], name: "index_case_groups_on_group_id" | |
end | |
create_table "case_members", force: :cascade do |t| | |
t.string "caseable_type" | |
t.bigint "caseable_id" | |
t.bigint "user_id" | |
t.integer "role" | |
t.datetime "created_at", null: false | |
t.datetime "updated_at", null: false | |
t.index ["caseable_type", "caseable_id"], name: "index_case_members_on_caseable_type_and_caseable_id" | |
t.index ["user_id"], name: "index_case_members_on_user_id" | |
end | |
create_table "case_template_users", force: :cascade do |t| | |
t.bigint "case_template_id" | |
t.bigint "user_id" | |
t.integer "role" | |
t.datetime "created_at", null: false | |
t.datetime "updated_at", null: false | |
t.index ["case_template_id"], name: "index_case_template_users_on_case_template_id" | |
t.index ["user_id"], name: "index_case_template_users_on_user_id" | |
end | |
create_table "case_templates", force: :cascade do |t| | |
t.string "name" | |
t.text "description" | |
t.bigint "status_id" | |
t.bigint "priority_id" | |
t.bigint "created_by_id" | |
t.datetime "created_at", null: false | |
t.datetime "updated_at", null: false | |
t.bigint "assigned_to_id" | |
t.index ["assigned_to_id"], name: "index_case_templates_on_assigned_to_id" | |
t.index ["created_by_id"], name: "index_case_templates_on_created_by_id" | |
t.index ["priority_id"], name: "index_case_templates_on_priority_id" | |
t.index ["status_id"], name: "index_case_templates_on_status_id" | |
end | |
create_table "cases", force: :cascade do |t| | |
t.string "name" | |
t.bigint "status_id" | |
t.bigint "priority_id" | |
t.bigint "created_by_id" | |
t.datetime "created_at", null: false | |
t.datetime "updated_at", null: false | |
t.text "description" | |
t.bigint "assigned_to_id" | |
t.string "ancestry" | |
t.text "reason_for_merging" | |
t.index ["ancestry"], name: "index_cases_on_ancestry" | |
t.index ["assigned_to_id"], name: "index_cases_on_assigned_to_id" | |
t.index ["created_by_id"], name: "index_cases_on_created_by_id" | |
t.index ["priority_id"], name: "index_cases_on_priority_id" | |
t.index ["status_id"], name: "index_cases_on_status_id" | |
end | |
create_table "comments", force: :cascade do |t| | |
t.text "comment" | |
t.bigint "created_by_id" | |
t.string "commentable_type" | |
t.bigint "commentable_id" | |
t.datetime "created_at", null: false | |
t.datetime "updated_at", null: false | |
t.index ["commentable_type", "commentable_id"], name: "index_comments_on_commentable_type_and_commentable_id" | |
t.index ["created_by_id"], name: "index_comments_on_created_by_id" | |
end | |
create_table "create_case_email_addresses", force: :cascade do |t| | |
t.string "email" | |
t.bigint "case_template_id" | |
t.datetime "created_at", precision: 6, null: false | |
t.datetime "updated_at", precision: 6, null: false | |
t.bigint "created_by_id" | |
t.bigint "default_creator_id" | |
t.index ["case_template_id"], name: "index_create_case_email_addresses_on_case_template_id" | |
t.index ["created_by_id"], name: "index_create_case_email_addresses_on_created_by_id" | |
t.index ["default_creator_id"], name: "index_create_case_email_addresses_on_default_creator_id" | |
end | |
create_table "forms", force: :cascade do |t| | |
t.string "name" | |
t.bigint "created_by_id" | |
t.datetime "created_at", null: false | |
t.datetime "updated_at", null: false | |
t.json "form_schema" | |
t.json "ui_schema" | |
t.index ["created_by_id"], name: "index_forms_on_created_by_id" | |
end | |
create_table "group_users", force: :cascade do |t| | |
t.bigint "user_id" | |
t.bigint "group_id" | |
t.datetime "created_at", null: false | |
t.datetime "updated_at", null: false | |
t.index ["group_id"], name: "index_group_users_on_group_id" | |
t.index ["user_id"], name: "index_group_users_on_user_id" | |
end | |
create_table "groups", force: :cascade do |t| | |
t.string "name" | |
t.datetime "created_at", null: false | |
t.datetime "updated_at", null: false | |
end | |
create_table "indicators", force: :cascade do |t| | |
t.string "name" | |
t.text "indicator" | |
t.text "description" | |
t.integer "indicator_type" | |
t.bigint "created_by_id" | |
t.bigint "case_id" | |
t.datetime "created_at", null: false | |
t.datetime "updated_at", null: false | |
t.index ["case_id"], name: "index_indicators_on_case_id" | |
t.index ["created_by_id"], name: "index_indicators_on_created_by_id" | |
end | |
create_table "pg_search_documents", force: :cascade do |t| | |
t.text "content" | |
t.string "searchable_type" | |
t.bigint "searchable_id" | |
t.datetime "created_at", null: false | |
t.datetime "updated_at", null: false | |
t.index ["searchable_type", "searchable_id"], name: "index_pg_search_documents_on_searchable_type_and_searchable_id" | |
end | |
create_table "priorities", force: :cascade do |t| | |
t.string "name" | |
t.text "description" | |
t.datetime "created_at", null: false | |
t.datetime "updated_at", null: false | |
end | |
create_table "statuses", force: :cascade do |t| | |
t.string "name" | |
t.text "description" | |
t.datetime "created_at", null: false | |
t.datetime "updated_at", null: false | |
t.boolean "default_status", default: false, null: false | |
end | |
create_table "taggings", id: :serial, force: :cascade do |t| | |
t.integer "tag_id" | |
t.string "taggable_type" | |
t.integer "taggable_id" | |
t.string "tagger_type" | |
t.integer "tagger_id" | |
t.string "context", limit: 128 | |
t.datetime "created_at" | |
t.index ["context"], name: "index_taggings_on_context" | |
t.index ["tag_id", "taggable_id", "taggable_type", "context", "tagger_id", "tagger_type"], name: "taggings_idx", unique: true | |
t.index ["tag_id"], name: "index_taggings_on_tag_id" | |
t.index ["taggable_id", "taggable_type", "context"], name: "index_taggings_on_taggable_id_and_taggable_type_and_context" | |
t.index ["taggable_id", "taggable_type", "tagger_id", "context"], name: "taggings_idy" | |
t.index ["taggable_id"], name: "index_taggings_on_taggable_id" | |
t.index ["taggable_type"], name: "index_taggings_on_taggable_type" | |
t.index ["tagger_id", "tagger_type"], name: "index_taggings_on_tagger_id_and_tagger_type" | |
t.index ["tagger_id"], name: "index_taggings_on_tagger_id" | |
end | |
create_table "tags", id: :serial, force: :cascade do |t| | |
t.string "name" | |
t.integer "taggings_count", default: 0 | |
t.index ["name"], name: "index_tags_on_name", unique: true | |
end | |
create_table "task_group_task_templates", force: :cascade do |t| | |
t.bigint "task_group_id", null: false | |
t.bigint "task_template_id", null: false | |
t.datetime "created_at", precision: 6, null: false | |
t.datetime "updated_at", precision: 6, null: false | |
t.integer "position" | |
t.index ["task_group_id"], name: "index_task_group_task_templates_on_task_group_id" | |
t.index ["task_template_id"], name: "index_task_group_task_templates_on_task_template_id" | |
end | |
create_table "task_groups", force: :cascade do |t| | |
t.string "caseable_type" | |
t.bigint "caseable_id" | |
t.string "name" | |
t.bigint "created_by_id" | |
t.integer "position" | |
t.index ["caseable_type", "caseable_id"], name: "index_task_groups_on_caseable_type_and_caseable_id" | |
t.index ["created_by_id"], name: "index_task_groups_on_created_by_id" | |
end | |
create_table "task_templates", force: :cascade do |t| | |
t.string "name" | |
t.text "description" | |
t.datetime "created_at", null: false | |
t.datetime "updated_at", null: false | |
t.bigint "assigned_to_id" | |
t.index ["assigned_to_id"], name: "index_task_templates_on_assigned_to_id" | |
end | |
create_table "tasks", force: :cascade do |t| | |
t.string "name" | |
t.text "description" | |
t.boolean "done", default: false, null: false | |
t.bigint "created_by_id" | |
t.bigint "assigned_to_id" | |
t.datetime "created_at", null: false | |
t.datetime "updated_at", null: false | |
t.integer "position" | |
t.bigint "task_group_id" | |
t.index ["assigned_to_id"], name: "index_tasks_on_assigned_to_id" | |
t.index ["created_by_id"], name: "index_tasks_on_created_by_id" | |
t.index ["task_group_id"], name: "index_tasks_on_task_group_id" | |
end | |
create_table "users", force: :cascade do |t| | |
t.string "username" | |
t.string "email" | |
t.string "password_digest" | |
t.string "auth_tokens" | |
t.string "unconfirmed_email" | |
t.string "confirmation_token" | |
t.datetime "confirmation_sent_at" | |
t.datetime "confirmed_at" | |
t.string "reset_password_token" | |
t.datetime "reset_password_sent_at" | |
t.string "last_sign_in_ip" | |
t.datetime "last_sign_in_at" | |
t.string "invitation_token" | |
t.datetime "invitation_sent_at" | |
t.datetime "invitation_accepted_at" | |
t.datetime "invitation_created_at" | |
t.datetime "created_at", null: false | |
t.datetime "updated_at", null: false | |
t.datetime "disabled_at" | |
end | |
add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id" | |
add_foreign_key "api_tokens", "users" | |
add_foreign_key "attachments", "users", column: "created_by_id" | |
add_foreign_key "case_audits", "users", column: "created_by_id" | |
add_foreign_key "case_groups", "groups" | |
add_foreign_key "case_members", "users" | |
add_foreign_key "case_template_users", "case_templates" | |
add_foreign_key "case_template_users", "users" | |
add_foreign_key "case_templates", "priorities" | |
add_foreign_key "case_templates", "statuses" | |
add_foreign_key "case_templates", "users", column: "assigned_to_id" | |
add_foreign_key "case_templates", "users", column: "created_by_id" | |
add_foreign_key "cases", "priorities" | |
add_foreign_key "cases", "statuses" | |
add_foreign_key "cases", "users", column: "assigned_to_id" | |
add_foreign_key "cases", "users", column: "created_by_id" | |
add_foreign_key "comments", "users", column: "created_by_id" | |
add_foreign_key "create_case_email_addresses", "case_templates" | |
add_foreign_key "create_case_email_addresses", "users", column: "created_by_id" | |
add_foreign_key "create_case_email_addresses", "users", column: "default_creator_id" | |
add_foreign_key "forms", "users", column: "created_by_id" | |
add_foreign_key "group_users", "groups" | |
add_foreign_key "group_users", "users" | |
add_foreign_key "indicators", "cases" | |
add_foreign_key "indicators", "users", column: "created_by_id" | |
add_foreign_key "task_group_task_templates", "task_groups" | |
add_foreign_key "task_group_task_templates", "task_templates" | |
add_foreign_key "task_groups", "users", column: "created_by_id" | |
add_foreign_key "task_templates", "users", column: "assigned_to_id" | |
add_foreign_key "tasks", "task_groups" | |
add_foreign_key "tasks", "users", column: "assigned_to_id" | |
add_foreign_key "tasks", "users", column: "created_by_id" |
Step 2: ⌨️ Coding
Create backend/db/migrate/[timestamp]_add_code_to_cases.rb with contents:
• Create a new database migration file to add a "code" column to the "cases" table.
• The column should be a string type and should not allow null values.
• The migration should also add an index to the "code" column to ensure quick lookups and enforce uniqueness.
- Running GitHub Actions for
backend/db/migrate/[timestamp]_add_code_to_cases.rb
✓ Edit
Check backend/db/migrate/[timestamp]_add_code_to_cases.rb with contents:Ran GitHub Actions for 2aff274f59b1e6a29e28239a3200a49c36605829:
Modify backend/app/models/case.rb with contents:
• Add a callback in the Case model to set the code before validation or before saving the record.
• The callback should calculate the next code value by finding the highest current code number, incrementing it by one, and prepending "CODE-".
• Ensure that the code is unique before saving by checking against existing codes in the database.
• Add a validation to ensure the presence of the code.--- +++ @@ -7,6 +7,7 @@ validates :status, presence: true validates :priority, presence: true validates :created_by, presence: true + validates :code, presence: true belongs_to :created_by, :class_name => 'User' belongs_to :assigned_to, :class_name => 'User', optional: true @@ -21,6 +22,7 @@ has_many :tasks, through: :task_groups has_many :attachments, as: :attachable, dependent: :destroy + before_validation :set_unique_code after_create :add_creator_to_members acts_as_taggable_on :tags @@ -93,6 +95,12 @@ end private + + def set_unique_code + highest_code = Case.maximum(:code).to_s.sub("CODE-", '').to_i + self.code = "CODE-#{highest_code + 1}" + end + def add_creator_to_members # Add the user who created this case to its list of members, so he/she can access it. self.case_members.create(user: self.created_by, role: "CAN_EDIT")
- Running GitHub Actions for
backend/app/models/case.rb
✓ Edit
Check backend/app/models/case.rb with contents:Ran GitHub Actions for 513b931ce691ae2b12ba334fa5852ecff86d4587:
Create backend/graphql/mutations/create_case.rb with contents:
• Modify the create_case mutation to ensure that the code is generated and saved when a new case is created.
• Since the code generation is handled in the Case model, no additional changes should be needed here other than ensuring the case is saved with the generated code.
- Running GitHub Actions for
backend/graphql/mutations/create_case.rb
✓ Edit
Check backend/graphql/mutations/create_case.rb with contents:Ran GitHub Actions for 84946ea39e968d0eae15fd59ee0d704a95c4bb79:
Create backend/graphql/types/case_type.rb with contents:
• Add a field to the GraphQL CaseType to expose the "code" attribute.
• This will allow the frontend to query for the case code.
- Running GitHub Actions for
backend/graphql/types/case_type.rb
✓ Edit
Check backend/graphql/types/case_type.rb with contents:Ran GitHub Actions for b951afb923b44f34c098cf1d60fd972da778d20c:
Create frontend/path_to_case_component with contents:
• Update the frontend component responsible for displaying case details to include the case code.
• Ensure that the case code is displayed alongside the status and priority in the UI.
• If necessary, update the GraphQL query used in the frontend to fetch the case details to include the "code" field.
- Running GitHub Actions for
frontend/path_to_case_component
✓ Edit
Check frontend/path_to_case_component with contents:Ran GitHub Actions for cdb1d6752e36c39098ba01d6c708295395adda4f:
Step 3: 🔁 Code Review
I have finished reviewing the code for completeness. I did not find errors for sweep/add_a_code_field_to_each_case_and_show_i
.
🎉 Latest improvements to Sweep:
- New dashboard launched for real-time tracking of Sweep issues, covering all stages from search to coding.
- Integration of OpenAI's latest Assistant API for more efficient and reliable code planning and editing, improving speed by 3x.
- Use the GitHub issues extension for creating Sweep issues directly from your editor.
💡 To recreate the pull request edit the issue title or description. To tweak the pull request, leave a comment on the pull request.Something wrong? Let us know.
This is an automated message generated by Sweep AI.
Details
Currently, just like each case has a status and a priority, I want each case to have a "code". A code must take the form of "CODE-X" where X is a number. This code value should be auto-incremented as a case is created - CODE-1, CODE-2, etc
Modify the frontend and backend code to make this change. You'll need to create a database script as well as modify all case related endpoints. On the frontend side, remember to show the code on the Case page alongside the status and priority.
Checklist
backend/db/migrate/[timestamp]_add_code_to_cases.rb
✓ 2aff274 Editbackend/db/migrate/[timestamp]_add_code_to_cases.rb
✓ Editbackend/app/models/case.rb
✓ 513b931 Editbackend/app/models/case.rb
✓ Editbackend/graphql/mutations/create_case.rb
✓ 84946ea Editbackend/graphql/mutations/create_case.rb
✓ Editbackend/graphql/types/case_type.rb
✓ b951afb Editbackend/graphql/types/case_type.rb
✓ Editfrontend/path_to_case_component
✓ cdb1d67 Editfrontend/path_to_case_component
✓ EditThe text was updated successfully, but these errors were encountered: