Skip to content
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

Deploy recent changes to GAP #1022

Open
wants to merge 52 commits into
base: deployments/gap
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
ec6e4e5
Fix grouping and ordering updates
milesmcc Aug 1, 2024
dbe8bc0
Fix package mode
milesmcc Aug 19, 2024
838e339
deploy staging to 1.2.4
milesmcc Aug 19, 2024
b1c0b29
Update dependencies
milesmcc Aug 19, 2024
4b11e7e
Update base deps
milesmcc Aug 19, 2024
1a03fd9
Back to older Debian
milesmcc Aug 22, 2024
bb34382
Fix Selenium archival
milesmcc Aug 22, 2024
4663e65
Don't apply empty tags to user projects
milesmcc Aug 26, 2024
5489ed6
Add explicit chromedriver
milesmcc Aug 26, 2024
981e431
Fix chromium-driver install
milesmcc Aug 26, 2024
4fcafc9
Possibly fix chromedriver
milesmcc Aug 26, 2024
bd40ee0
Update docs and remove unused workflow
milesmcc Aug 26, 2024
38bcc56
Don't collapse updates with attachments
noah-schechter Aug 30, 2024
4223f03
256 options -> 512 options
noah-schechter Aug 30, 2024
e0ca097
Disaggregate improvements
noah-schechter Aug 30, 2024
7069777
Merge pull request #1039 from atlosdotorg/options-expansion
milesmcc Aug 31, 2024
ee6a63b
update base img
milesmcc Aug 31, 2024
146220f
Update archiver
milesmcc Aug 31, 2024
d8d9ade
--disable-dev-shm-usage
milesmcc Aug 31, 2024
70f7d8e
Add the ability to turn off Atlos archival (closes #1034)
milesmcc Aug 31, 2024
6acc135
Update versions when archival is disabled
milesmcc Aug 31, 2024
772e948
Update empty source material copy
milesmcc Sep 1, 2024
e8264be
Update api_tokens_component.ex
noah-schechter Sep 2, 2024
51a765a
Add in-platform API docs
noah-schechter Sep 2, 2024
6ecb3db
Cleanup
noah-schechter Sep 2, 2024
504e3aa
Update docs site
noah-schechter Sep 2, 2024
249ade3
Don't show project's ID
noah-schechter Sep 2, 2024
ba8f9e7
Fix plurals
noah-schechter Sep 2, 2024
369d8e6
More granular geolocation filter
milesmcc Sep 5, 2024
d22d563
Update Python packages and correctly clean up Chromium
milesmcc Oct 3, 2024
b63a8b6
Add basic incident sort functionality
noah-schechter Oct 23, 2024
aa70131
Update archival documentation
noah-schechter Oct 23, 2024
969ed89
Add attribute groups to documentation
noah-schechter Oct 23, 2024
f713d4b
Enable docs development in devcontainer
noah-schechter Oct 23, 2024
24b2822
Fix new incidents
milesmcc Nov 4, 2024
23d7a95
Add support for searching attribute values
milesmcc Nov 4, 2024
57b1d2e
Add the ability to jump directly to a page
milesmcc Nov 4, 2024
2fb1ad8
Fix pagination logic
milesmcc Nov 4, 2024
57c36b5
Fix pagination total count
milesmcc Nov 4, 2024
451a681
Fix page links
milesmcc Nov 4, 2024
532b18b
Fix direct pagination
milesmcc Nov 4, 2024
f3c195e
Don't apply tags when empty
milesmcc Nov 4, 2024
b1d0b24
Fix tag check
milesmcc Nov 4, 2024
ba7697c
Merge pull request #1051 from atlosdotorg/devcontainer-updates
milesmcc Nov 4, 2024
da3c722
Merge pull request #1050 from atlosdotorg/docs-improvements
milesmcc Nov 4, 2024
f80db18
Merge pull request #1049 from atlosdotorg/sort-by-date
milesmcc Nov 4, 2024
9d2cf46
Merge pull request #1042 from atlosdotorg/api-docs
milesmcc Nov 4, 2024
397cf45
Merge pull request #1041 from atlosdotorg/noah-schechter-patch-4
milesmcc Nov 4, 2024
b23182a
Make it possible to remove the final tag from an incident
milesmcc Nov 7, 2024
695f41d
Limit displayed incidents to the first 50 in bulk upload preview
milesmcc Nov 28, 2024
ada8d9f
Correctly handle invalid dates
milesmcc Dec 19, 2024
2120ee5
fix decorators
milesmcc Dec 19, 2024
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
2 changes: 1 addition & 1 deletion .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,4 @@ RUN su ${USERNAME} -c "mix local.hex --force \
# Add Atlos-specific dependencies
RUN apt-get update \
&& export DEBIAN_FRONTEND=noninteractive \
&& apt-get -y install --no-install-recommends ffmpeg imagemagick curl python3 python3-dev python3-pip python3-poetry chromium
&& apt-get -y install --no-install-recommends ffmpeg imagemagick curl python3 python3-dev python3-pip python3-poetry chromium chromium-driver
9 changes: 8 additions & 1 deletion .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
// Use 'forwardPorts' to make a list of ports inside the container available locally.
// This can be used to network with other containers or with the host.
"forwardPorts": [
1313,
4000,
4001,
5432
Expand All @@ -33,6 +34,12 @@
"ghcr.io/devcontainers/features/docker-in-docker:2": {},
"ghcr.io/devcontainers/features/azure-cli:1": {},
"ghcr.io/devcontainers/features/terraform:1": {},
"ghcr.io/devcontainers/features/github-cli:1": {}
"ghcr.io/devcontainers/features/github-cli:1": {},
"ghcr.io/devcontainers/features/hugo:1": {
"version": "0.121.2"
},
"ghcr.io/devcontainers/features/go:1": {
"version": "1.19.4"
}
}
}
20 changes: 0 additions & 20 deletions .github/workflows/deploy-gap.yml

This file was deleted.

35 changes: 34 additions & 1 deletion docs/content/docs/Incidents/attributes.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,37 @@ To customize a project’s data model:
{{< callout type="warning" >}}
**Removing attributes during an investigation risks data integrity.**
If you remove an attribute from your project's data model, that attribute—and its values—will be removed from all incidents in the project.
{{< /callout >}}
{{< /callout >}}

## Attribute groups
In investigations with many attributes, we recommend creating attribute groups to keep your data model easily understandable for your team.

Attribute groups have several components:
- **Name**—Use a group's name to describe the group. A location-related attributes might fit into a "Location" group.
- **Description**—A longer text field that provides more context on the group's purpose.
- **Color**—Use a group's color to visually distinguish it from other attribute groups.

### Create an attribute group
To create an attribute group:
1. Navigate to the **Projects** page.
2. Select the relevant project.
3. Click on the **Manage** page and scroll down to the **Attributes** pane.
4. Click **Add Group**.
5. Add a name, description, and color for the group.
6. Click **Save**.

### Add an attribute to an attribute group
To add an existing attribute to a group:
1. Navigate to the **Projects** page.
2. Select the relevant project.
3. Click on the **Manage** page and scroll down to the **Attributes** pane.
4. Drag the attribute from its current location to its new attribute group.

### Exclude attribute groups from incident creation
In investigations with many attributes, you may want to exclude some attribute groups from the new incident form so that investigators can more quickly create incidents. To exclude attribute groups from the form:
1. Navigate to the **Projects** page.
2. Select the relevant project.
3. Click on the **Manage** page and scroll down to the **Attributes** pane.
4. On the relevant attribute group, click **Edit Group**.
5. Uncheck the **Include this group in the incident creation window** box.
6. Click **Save**.
18 changes: 18 additions & 0 deletions docs/content/docs/Incidents/source-material.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,24 @@ Atlos’ archival system will attempt to archive the following artifacts:
Note that Atlos’ automatic archival system is best-effort; Atlos will not always archive every piece of media on a webpage. It is also subject to the whims of social media platforms’ changing software and policies. We think Atlos’ automatic archival tool is a convenient system for quickly preserving sources during a journalistic investigation, but it’s not suitable for forensic or legal evidentiary purposes.
{{< /callout >}}

### Disable automatic archival
In rare cases—for example, if your investigation has a third-party archival setup—you may want to disable Atlos' automatic archival. To do so:
1. Navigate to the **Projects** page.
2. Select the relevant project.
3. Click on the **Manage** page.
4. Under **General**, expand **Advanced Options**.
5. Disable automatic Atlos archival.
6. Click **Save**.

### Enable Internet Archive archival
The Internet Archive is a third-party, public archive of the web. In some, less sensitive investigations, you may want to send any link added to your project to the Internet Archive for redundant archival. This archive will be visible to anyone on the internet, so do not enable this feature if your investigation is especially sensitive. To enable Internet Archive archival:
1. Navigate to the **Projects** page.
2. Select the relevant project.
3. Click on the **Manage** page.
4. Under **General**, expand **Advanced Options**.
5. Click **Share with the Internet Archive**
6. Click **Save**.

## Deduplication
Deduplication, or the process of finding and eliminating duplicates, can be extremely time-consuming. To accelerate the process, Atlos deduplicates source material in two phases:
- **Link-level deduplication—** When an investigator pastes a link into the automatic archival system or the manual upload option, Atlos will immediately notify the investigator if they’ve added a link that is already in another incident in their project.
Expand Down
4 changes: 2 additions & 2 deletions docs/content/docs/Technical/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,11 +168,11 @@ requests.post(
```

### Update an incident's attribute value
`POST /api/v2/update/:slug/:attribute_name` updates the attribute `:attribute_name` in the incident with slug `:slug`. It has two parameters:
`POST /api/v2/update/:slug/:attribute_identifier` updates the attribute `:attribute_identifier` in the incident with slug `:slug`. It has two parameters:
- `value`, the new value of the attribute (required). For text or single-select attributes, `value` should be a string. For multi-select attributes, `value` should be a list of strings.
- `message`, a string to be displayed as an explanation for the update (optional). If `message` is provided, it will be added as a comment to the incident (as part of the tracked change).

The `:attribute_name` may not be the name of the attribute displayed in the interface:
You can find the `:attribute_identifier` in the **Access** pane of your project. Attributes' names in the Atlos interface are different from their API identifiers:
- Core attributes have string names (such as `description` and `status`).
- Custom attributes are identified by a long ID.

Expand Down
8 changes: 4 additions & 4 deletions platform/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@
# - https://pkgs.org/ - resource for finding needed packages
# - Ex: hexpm/elixir:1.13.4-erlang-24.3.3-debian-bullseye-20210902-slim
#
ARG ELIXIR_VERSION=1.16.2
ARG OTP_VERSION=26.2.5
ARG DEBIAN_VERSION=bookworm-20240513
ARG ELIXIR_VERSION=1.17.2
ARG OTP_VERSION=27.0.1
ARG DEBIAN_VERSION=bookworm-20240812

ARG BUILDER_IMAGE="hexpm/elixir:${ELIXIR_VERSION}-erlang-${OTP_VERSION}-debian-${DEBIAN_VERSION}"
ARG RUNNER_IMAGE="debian:${DEBIAN_VERSION}"
Expand Down Expand Up @@ -99,7 +99,7 @@ COPY utils utils
ENV UTILS_DIR="/app/utils"

# Install packages needed for runtime environment
RUN apt-get update -y && apt-get install -y ffmpeg imagemagick curl python3-full dbus python3-pip python3-poetry chromium tmpreaper ca-certificates postgresql-client jq \
RUN apt-get update -y && apt-get install -y ffmpeg imagemagick curl python3-full dbus python3-pip python3-poetry chromium chromium-driver tmpreaper ca-certificates postgresql-client jq \
&& apt-get clean && rm -f /var/lib/apt/lists/*_*

USER atlos
Expand Down
14 changes: 7 additions & 7 deletions platform/assets/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion platform/deployments/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ To setup continuous deployment, you'll need to configure a GitHub environment wi

* `AZURE_CREDENTIALS`: The output of `az ad sp create-for-rbac --name "github-actions-<staging/prod>" --role contributor --scopes /subscriptions/<subscription_id>/resourceGroups/<resource_group_id> --json-auth`
* Note (+ TODO): This credential is _overprovisioned_. Azure shockingly doesn't have a built-in role for "deploy to Azure Container Apps", so we have to use the `contributor` role. This is a security risk, but it's the best we can do for now without getting Azure P2 or P3. They [say](https://github.com/microsoft/azure-container-apps/issues/35#issuecomment-1675072081) that they will introduce more roles in Q4 2023.
* `AZURE_CONTAINER_APP_NAME`: The name of the Azure Container App name
* `AZURE_CONTAINER_APP_NAME`: The name of the Azure Container App (e.g., `ca-main-platform-staging-eastus`)
* `AZURE_CONTAINER_APP_RESOURCE_GROUP`: The name of the Azure Container Apps resource group

You'll also need to be sure that a corresponding GitHub Actions workflow file exists for the given deployment.
Expand Down
2 changes: 1 addition & 1 deletion platform/deployments/container_app.tf
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ resource "azurerm_container_app" "platform" {

container {
name = "platform"
image = "ghcr.io/atlosdotorg/atlos:main"
image = "ghcr.io/atlosdotorg/atlos:v1.2.4"

cpu = 1.0
memory = "2Gi"
Expand Down
79 changes: 61 additions & 18 deletions platform/lib/platform/global_search.ex
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,20 @@ defmodule Platform.GlobalSearch do
defmemo perform_search(query, %User{} = user) when is_binary(query), expires_in: 10000 do
query_lower_raw = String.trim(query) |> String.downcase()

query =
query_cleaned =
String.trim(query)
|> String.downcase()

query_only_alphaneumeric = String.replace(query, ~r/[^a-zA-Z0-9\s\-]/, "")
query = query |> String.replace(~r/\s+/, " OR ") |> String.replace(~r/[^a-zA-Z0-9\s\-]/, "")

query_websearch =
query_cleaned |> String.replace(~r/\s+/, " OR ") |> String.replace(~r/[^a-zA-Z0-9\s\-]/, "")

media_version_query =
from(
mv in MediaVersion,
where:
fragment("? @@ websearch_to_tsquery('simple', ?)", mv.searchable, ^query) or
fragment("? @@ websearch_to_tsquery('simple', ?)", mv.searchable, ^query_websearch) or
ilike(mv.source_url, ^"%#{query_only_alphaneumeric}%") or
fragment("LOWER(?) = ?", mv.source_url, ^query_lower_raw),
join: m in assoc(mv, :media),
Expand All @@ -48,7 +50,11 @@ defmodule Platform.GlobalSearch do
fragment("LOWER(?) = ?", mv.source_url, ^query_lower_raw)
),
desc:
fragment("ts_rank_cd(?, websearch_to_tsquery('simple', ?))", mv.searchable, ^query),
fragment(
"ts_rank_cd(?, websearch_to_tsquery('simple', ?))",
mv.searchable,
^query_websearch
),
desc: mv.inserted_at
],
limit: 3,
Expand All @@ -59,16 +65,21 @@ defmodule Platform.GlobalSearch do
ilike(mv.source_url, ^"%#{query_only_alphaneumeric}%") or
fragment("LOWER(?) = ?", mv.source_url, ^query_lower_raw),
cd_rank:
fragment("ts_rank_cd(?, websearch_to_tsquery('simple', ?))", mv.searchable, ^query)
fragment(
"ts_rank_cd(?, websearch_to_tsquery('simple', ?))",
mv.searchable,
^query_websearch
)
}
)

media_query =
from(
m in Media,
where:
fragment("? @@ websearch_to_tsquery('simple', ?)", m.searchable, ^query) or
fragment("? @@ websearch_to_tsquery('simple', ?)", m.searchable, ^query_websearch) or
ilike(m.attr_description, ^"%#{query_only_alphaneumeric}%") or
ilike(m.searchable_text, ^"%#{query_cleaned}%") or
ilike(m.slug, ^"%#{query_only_alphaneumeric}%"),
join: p in assoc(m, :project),
join: pm in assoc(p, :memberships),
Expand All @@ -82,7 +93,11 @@ defmodule Platform.GlobalSearch do
ilike(m.slug, ^"%#{query_only_alphaneumeric}%")
),
desc:
fragment("ts_rank_cd(?, websearch_to_tsquery('simple', ?))", m.searchable, ^query),
fragment(
"ts_rank_cd(?, websearch_to_tsquery('simple', ?))",
m.searchable,
^query_websearch
),
desc: m.inserted_at
],
limit: 3,
Expand All @@ -93,7 +108,11 @@ defmodule Platform.GlobalSearch do
ilike(m.attr_description, ^"%#{query_only_alphaneumeric}%") or
ilike(m.slug, ^"%#{query_only_alphaneumeric}%"),
cd_rank:
fragment("ts_rank_cd(?, websearch_to_tsquery('simple', ?))", m.searchable, ^query)
fragment(
"ts_rank_cd(?, websearch_to_tsquery('simple', ?))",
m.searchable,
^query_websearch
)
}
)

Expand All @@ -102,7 +121,7 @@ defmodule Platform.GlobalSearch do
u in User,
where:
u.username != "atlos" and
(fragment("? @@ websearch_to_tsquery('simple', ?)", u.searchable, ^query) or
(fragment("? @@ websearch_to_tsquery('simple', ?)", u.searchable, ^query_websearch) or
ilike(u.username, ^"%#{query_only_alphaneumeric}%")),
order_by: [
asc:
Expand All @@ -111,23 +130,31 @@ defmodule Platform.GlobalSearch do
ilike(u.username, ^"%#{query_only_alphaneumeric}%")
),
desc:
fragment("ts_rank_cd(?, websearch_to_tsquery('simple', ?))", u.searchable, ^query),
fragment(
"ts_rank_cd(?, websearch_to_tsquery('simple', ?))",
u.searchable,
^query_websearch
),
desc: u.inserted_at
],
limit: 3,
select: %{
item: u,
exact_match: ilike(u.username, ^"%#{query_only_alphaneumeric}%"),
cd_rank:
fragment("ts_rank_cd(?, websearch_to_tsquery('simple', ?))", u.searchable, ^query)
fragment(
"ts_rank_cd(?, websearch_to_tsquery('simple', ?))",
u.searchable,
^query_websearch
)
}
)

projects_query =
from(
p in Project,
where:
fragment("? @@ websearch_to_tsquery('simple', ?)", p.searchable, ^query) or
fragment("? @@ websearch_to_tsquery('simple', ?)", p.searchable, ^query_websearch) or
ilike(p.name, ^"%#{query_only_alphaneumeric}%"),
join: pm in assoc(p, :memberships),
on: pm.user_id == ^user.id,
Expand All @@ -139,23 +166,31 @@ defmodule Platform.GlobalSearch do
ilike(p.name, ^"%#{query_only_alphaneumeric}%")
),
desc:
fragment("ts_rank_cd(?, websearch_to_tsquery('simple', ?))", p.searchable, ^query),
fragment(
"ts_rank_cd(?, websearch_to_tsquery('simple', ?))",
p.searchable,
^query_websearch
),
desc: p.inserted_at
],
limit: 3,
select: %{
item: p,
exact_match: ilike(p.name, ^"%#{query_only_alphaneumeric}%"),
cd_rank:
fragment("ts_rank_cd(?, websearch_to_tsquery('simple', ?))", p.searchable, ^query)
fragment(
"ts_rank_cd(?, websearch_to_tsquery('simple', ?))",
p.searchable,
^query_websearch
)
}
)

updates_query =
from(
u in Update,
where:
fragment("? @@ websearch_to_tsquery('simple', ?)", u.searchable, ^query) or
fragment("? @@ websearch_to_tsquery('simple', ?)", u.searchable, ^query_websearch) or
ilike(u.explanation, ^"%#{query_only_alphaneumeric}%") or
ilike(u.explanation, ^"%#{query_lower_raw}%"),
join: m in assoc(u, :media),
Expand All @@ -171,7 +206,11 @@ defmodule Platform.GlobalSearch do
ilike(u.explanation, ^"%#{query_lower_raw}%")
),
desc:
fragment("ts_rank_cd(?, websearch_to_tsquery('simple', ?))", u.searchable, ^query),
fragment(
"ts_rank_cd(?, websearch_to_tsquery('simple', ?))",
u.searchable,
^query_websearch
),
desc: u.inserted_at
],
limit: 3,
Expand All @@ -181,7 +220,11 @@ defmodule Platform.GlobalSearch do
ilike(u.explanation, ^"%#{query_only_alphaneumeric}%") or
ilike(u.explanation, ^"%#{query_lower_raw}%"),
cd_rank:
fragment("ts_rank_cd(?, websearch_to_tsquery('simple', ?))", u.searchable, ^query)
fragment(
"ts_rank_cd(?, websearch_to_tsquery('simple', ?))",
u.searchable,
^query_websearch
)
}
)
|> Platform.Updates.preload_fields()
Expand All @@ -198,7 +241,7 @@ defmodule Platform.GlobalSearch do
|> Enum.filter(fn item -> Permissions.can_view_media?(user, item.item) end)
end),
Task.async(fn ->
if String.length(query) < 3 do
if String.length(query_websearch) < 3 do
[]
else
Repo.all(users_query)
Expand Down
Loading
Loading