🌊 Add Data Flows to Directus 🌊 (#12522)

* Replace attachment with correct icon

* Use standardized options formatting

* Improve preview styling, fix names

* Format IDs of DB read operation as csv

* Remove flow active state from header

* Don't return null for unknown flows

* Fix webhook trigger not showing id

* Fix alignment of attachment

* Make heading secondary if it's the reject handler

* Use flow name in subtitle of operation drawer

* Rename "Create new Operation" -> "Create Operation"

* Make name/key required

* Give name autofocus

* Add "uncaught exception" log message

* Various improvements on operations

* default status to "active"

* Add status dropdown at the bottom of trigger

* put status dot to the right of flow name header

* add toggle status option in context menu

* fix trigger options staging

* fix flow deletion

* show configured operation key on name hover

* prevent block pushing status toggle down

* ensure key is unique between operations in a flow

* allow add new panel when previous one is deleted

* fix staged panels temporarily disappear

The deletion of newTree.id causes it to "disconnect" when saving, thus causing it seemingly disappear. Using a cloneDeep prevents it from mutating the current stagedPanels

* hide key input when in query mode

* add write operation

* undo previous route props change

* include staged panel keys in key validation

* fix key validation logic

* add color to flow & insights

* ensure trigger does not have reject button

* prevent operation key error showing up when saving

* change context menu to Delete Operation

* fix add operation when removed operation is staged

* Hide ID in read preview when in query mode

* fix reject button showing without edit mode

* fix status toggle in flow overview

* simplify request operation methods & allow other

* fix preview function type

* simplify slot syntax

* add manual trigger

* simplify manual trigger handler

* prevent drawer closing on esc

* allow filter config without selecting collection

* improve affordance of add operation button

* fix loner reject button color

* Added emitEvents option to write operation (#13121)

* added emitEvent option toggle to write operation

* Re-gen package-lock

Co-authored-by: rijkvanzanten <rijkvanzanten@me.com>

* Clean up active/inactive toggle

* Align arrow to grid

* Visually align border radius of glow

* Tweak padding of footer in panels

* Remove init event

* Combine event triggers into single "Hook" trigger

* Fix mail operation

* Cleanup imports, return undefined for webhook executino flows

* Add border to panel footer

* Upgrade preview of hook trigger

* Don't warn on uncaught flow executions

* Clean up types

* Fix typing of reload

* Default to correct icon

* Add migrations to remove webhooks table

* Remove webhooks

* Update icons for triggers

* Reorganize triggers

* Merge flows and webhooks migrations

* Add permissions option to database read operatoin

* Add permissions configuration to database write

* Remove flow logs in favor of using directus_activity

* Upgrade webhook configuration, fix create operatoin

* Rename validation->condition

* Subdue everything when inactive

* Tweak tests

* Fix the test for real

* Remove circular FK trigger, please MSSQL

* Make things worse to please MSFT

* Add input

* Drop input scope from condition operation

* naming and description changes

* Default flow overview icon color to primary

* add danger styling to delete flow button

* fix hint buttons subdued style

* Hide trigger unlinked resolve btn when not editing

Don't show "check mark icon" or "operation arrow" on empty trigger when NOT in edit mode

* show email "to" value as CSV

* remove unused webhook.preview translation

* Default sort order of overview table to `name`

* Track activity / revisions in flows

* Extract w/ the intent to reuse revisions fetching

* Move Action type to shared to facilitate app use

* [WIP] Start rendering logs drawer from sidebar

* Fix type error (sorry Eron)

* add update operation

* add delete operation

* use parseJSON util in operations

* Add missing fields to flows system data

* Await promise in sleep operation

* fix e2e test missing flows & operations tables

* Add fallback title to flow logs drawer

* Add default value to flow prop for flow-dialog

* Hydrate flows store before moving to details page when creating flow

* Rename CRUD operations to item-*

* Change trigger options subtitle to Trigger Options

* Remove trigger name option

* Fix typescript complaining

* Remove two lines

* Fix notification operation

* Log error when executing a schedule flow

* Fix schedule flow activity tracking

* Fix notification operation when there is no user

* Make permissions for notifications configurable

* Do not drop non null constraint from column that is nullable

* Remove invalid option from activity seed

* Show resolve/reject dot when operation has successor

* Improve flow arrow placement

* Prevent arrow color from flickering

* Fix arrow being stuck when hovered while saving

* Fix arrows not being subdued on lone leaf operations

* Add tooltips to operation handles

* Remove option to trigger flow on init

* Move operation handle tooltip to icon

* Disconnect duplicated operation

* Fix deleting connected operations

* Make delete action name generic in v-workspace-panel

* Use flow-specific wording in flow edit tooltip

* Simplify hint handle check

* Fix deleting first operation

* Use useEditsGuard composable in flow component

* Add asynchronous option to webhook trigger

* Add option to make preview elements copyable

* Add hover transition to panels

* Register operation preview components as operation-preview-*

* Remove selectability of panel header and operation body

* Add return option to filter and operation triggers

* Add missing key

* Remove unnecessary ampersand from URLs in filter tests (#13523)

* Remove unused prop

* New translations en-US.yaml (Polish) (#13524)

* My favorite

* v9.11.1

* v9.11.1

* New translations en-US.yaml (Polish) (#13528)

* New translations en-US.yaml (Czech) (#13541)

* New translations en-US.yaml (Czech) (#13545)

* fix metadata for directus_folders (#13527)

* add `meta` to list responses for OAS (#13531)

* remove existing pasting check on slug values (#13532)

* Add copy button to user id on user info page (#13540)

* New translations en-US.yaml (Czech) (#13547)

* New translations en-US.yaml (Czech) (#13548)

* Added missing "DB_SSL_*_FILE" to the "_FILE" allow list. (#13539)

* Remove workaround in release flow (#13455)

This forces the release workflow to use `node@16.15` which includes `npm@8.5`.

* Remove npmrc files which prevent lockfile creation in workspaces (#13444)

* Remove npmrc files which prevent lockfile creation in workspaces

Since `v8.5.0` npm will detect that it is running inside a workspace and issue commands at the root package.

* Require a minimum npm version of 8.5.0

* Package-lock 🖤

* Don't consider SIGN_OUT an SSO error (#13389)

* Don't consider SIGN_OUT an SSO error

* Add SESSION_EXPIRED as valid reason

* Improve translation for require_value_to_be_set (#13363)

English + Dutch

Co-authored-by: Rijk van Zanten <rijkvanzanten@me.com>

* Fix field conditions optionDefaults computed property (#13563)

* fix: remove .value from options

* additional ref fix & type/null errors

Co-authored-by: Azri Kahar <42867097+azrikahar@users.noreply.github.com>

* `last_page` type was `date` instead of `str` (#13577)

* `last_page` type was `date` instead of `str`

* Update docs/reference/system/users.md

Co-authored-by: Rijk van Zanten <rijkvanzanten@me.com>

* New Crowdin updates (#13557)

* New translations en-US.yaml (Romanian)

* New translations en-US.yaml (Indonesian)

* New translations en-US.yaml (Spanish, Chile)

* New translations en-US.yaml (Thai)

* New translations en-US.yaml (Spanish, Latin America)

* New translations en-US.yaml (Russian)

* New translations en-US.yaml (Polish)

* New translations en-US.yaml (Swedish)

* New translations en-US.yaml (Turkish)

* New translations en-US.yaml (Portuguese, Brazilian)

* New translations en-US.yaml (French)

* New translations en-US.yaml (Spanish)

* New translations en-US.yaml (Bulgarian)

* New translations en-US.yaml (Catalan)

* New translations en-US.yaml (Danish)

* New translations en-US.yaml (German)

* New translations en-US.yaml (Finnish)

* New translations en-US.yaml (Hungarian)

* New translations en-US.yaml (Chinese Simplified)

* New translations en-US.yaml (Italian)

* New translations en-US.yaml (Slovenian)

* New translations en-US.yaml (Ukrainian)

* New translations en-US.yaml (English, United Kingdom)

* New translations en-US.yaml (English, Canada)

* New translations en-US.yaml (French, Canada)

* New translations en-US.yaml (Croatian)

* New translations en-US.yaml (Czech)

* New translations en-US.yaml (Czech)

* New translations en-US.yaml (Czech)

* New translations en-US.yaml (Czech)

* New translations en-US.yaml (Czech)

* New translations en-US.yaml (Czech)

* New translations en-US.yaml (Czech)

* New translations en-US.yaml (Czech)

* New translations en-US.yaml (Czech)

* New translations en-US.yaml (Czech)

* New translations en-US.yaml (Czech)

* New translations en-US.yaml (Czech)

* New translations en-US.yaml (Czech)

* New translations en-US.yaml (Czech)

* New translations en-US.yaml (Czech)

* Fix validate query number comparison

Ref https://github.com/directus/directus/pull/13492#issuecomment-1138770254

* New translations en-US.yaml (Polish) (#13580)

* add to project (#13581)

* Allow authentication using MSSQL azure-active-directory-service-principal-secret (#11141)

* Extract ignored settings requires by azure authentication

* Change the way to extract initial database settings

* Fix invalid names after extracting from env util

* Replace missing var after solving conflicts

* Add default value to poolconfig

* This should unbreak it

Co-authored-by: Rijk van Zanten <rijkvanzanten@me.com>

* Create pull_request_template.md

* Update pull_request_template.md

* Add System token interface (#13549)

* Add system token interface

* use system token interface in users token field

* Update placeholder

* move notice below input

* fix clear value interaction

* update placeholder and notice text

* remove unused translation key

* rename class to match current naming

* fix bugs in disabled state and it's UX

Co-authored-by: Rijk van Zanten <rijkvanzanten@me.com>
Co-authored-by: jaycammarano <jay.cammarano@gmail.com>

* New Crowdin updates (#13586)

* New translations en-US.yaml (Romanian)

* New translations en-US.yaml (Indonesian)

* New translations en-US.yaml (Spanish, Chile)

* New translations en-US.yaml (Thai)

* New translations en-US.yaml (Serbian (Latin))

* New translations en-US.yaml (Spanish, Latin America)

* New translations en-US.yaml (Russian)

* New translations en-US.yaml (Polish)

* New translations en-US.yaml (Portuguese)

* New translations en-US.yaml (Swedish)

* New translations en-US.yaml (Turkish)

* New translations en-US.yaml (Estonian)

* New translations en-US.yaml (Portuguese, Brazilian)

* New translations en-US.yaml (French)

* New translations en-US.yaml (Spanish)

* New translations en-US.yaml (Bulgarian)

* New translations en-US.yaml (Catalan)

* New translations en-US.yaml (Czech)

* New translations en-US.yaml (Danish)

* New translations en-US.yaml (German)

* New translations en-US.yaml (Finnish)

* New translations en-US.yaml (Hungarian)

* New translations en-US.yaml (Chinese Simplified)

* New translations en-US.yaml (Italian)

* New translations en-US.yaml (Japanese)

* New translations en-US.yaml (Dutch)

* New translations en-US.yaml (Slovenian)

* New translations en-US.yaml (Ukrainian)

* New translations en-US.yaml (English, United Kingdom)

* New translations en-US.yaml (English, Canada)

* New translations en-US.yaml (French, Canada)

* New translations en-US.yaml (Croatian)

* Add project_url to defaultTemplateData (#12033)

Might be useful in template footers.

* Update items.md

* Rename panel to tile

* Rename preview->overview

* Style flow log detail

* Log all parsed options

* Show used options in revision

* Finish log detail drawer

* new create flow flow

* fix firstOpen for new create flow flow

* update field layout for create flow form

* Fix TS typing

* Fix missing import

* Append random hash to key when duplicating operations

* Revert "Remove webhooks"

This reverts commit 044d3d8b66.

* Don't delete webhooks

* Make option preview selectable

* Prevent invalid linking when duplicating operations after creating operations

* Prevent sending of malformed query filter when deleting flow

* implement new manual trigger

* simplify payload for manual trigger

* use buttons instead of dropdown + run button

* add async option & loading state

* add collection check to manual trigger

* emit refresh after running flow in sidebar

* Add cross-instance messenger for reloading

* Use flow drawer for both create and edit

* Add manual trigger flow permissions to app recommended

Ensures that non-admin users can actually see the flows sidebar detail

* Add basic logs redaction

* Remove endpoint to trigger an operation

* Allow configuring location for manual trigger

* Rename "hook" trigger to "event"

* Tweak icon size

* Fix create flow button in info notice

* Make activity tracking full width

* Tweak descriptions

* Too long for comfort

* Remove mode option from item-* operations

* fix manual trigger empty collections option

* Add no-logs-yet message in sidebar detail

* Reset trigger options on change of trigger

* Rename `data`->`payload`

* Remove mode from preview of item-* operations

* Return operation options with "{{key}}" as raw value

* Show flow name in delete confirmation

* Add default generated name/key to new operations

* shorten arrows WIP

still needs icons moved

* rename note to description

* fix hint button icons

* update event hook type labels

* Animate resolve/reject arrow hints

* reorder event types

* Use x+4 instead of x+6 for new operation panels

* compress options to fit 6 lines in operation

* update hook labels

* animate trigger box shadow

sorry, rijk!

* update (global) disabled button color 1 shade

* Format times nicer

* Add placeholder for query

* add a note

* Fix formatting for curly brackets in translations

* Add item Create/update payload placeholder

* Add placeholder to user uuid

* Accept either null or undefined for nullable operation options

* Allow any string as request body

* Add more placeholders

* Consolidate filterScope and actionScope, filterCollections and actionCollections

* Rename flow note to description in types

Co-authored-by: rijkvanzanten <rijkvanzanten@me.com>
Co-authored-by: Azri Kahar <42867097+azrikahar@users.noreply.github.com>
Co-authored-by: Brainslug <br41nslug@users.noreply.github.com>
Co-authored-by: Ben Haynes <ben@rngr.org>
Co-authored-by: Nicola Krumschmidt <nicola.krumschmidt@freenet.de>
Co-authored-by: Aiden Foxx <aiden.foxx.mail@gmail.com>
Co-authored-by: Jan-Willem <jan-willem@qdentity.nl>
Co-authored-by: Yasser Lahbibi <yasser.lahbibi@apenhet.com>
Co-authored-by: Louis <32719150+louisgavalda@users.noreply.github.com>
Co-authored-by: Jay Cammarano <67079013+jaycammarano@users.noreply.github.com>
Co-authored-by: Erick Torres <ericktorresh@gmail.com>
Co-authored-by: jaycammarano <jay.cammarano@gmail.com>
Co-authored-by: Yuriy Belenko <yura-bely@mail.ru>
Co-authored-by: ian <licitdev@gmail.com>
This commit is contained in:
Nitwel
2022-06-03 00:18:49 +02:00
committed by GitHub
parent d175290657
commit 857eae54de
133 changed files with 9498 additions and 2448 deletions

View File

@@ -0,0 +1,110 @@
<template>
<sidebar-detail v-if="manualFlows.length > 0" icon="bolt" :title="t('flows')">
<div class="fields">
<div v-for="manualFlow in manualFlows" :key="manualFlow.id" class="field full">
<v-button
v-tooltip="primaryKey ? t('run_flow_on_current') : t('run_flow_on_selected', selection.length)"
small
full-width
:loading="runningFlows.includes(manualFlow.id)"
:disabled="!primaryKey && selection.length === 0"
@click="runManualFlow(manualFlow.id)"
>
<v-icon :name="manualFlow.icon ?? 'bolt'" small left />
{{ manualFlow.name }}
</v-button>
</div>
</div>
</sidebar-detail>
</template>
<script lang="ts" setup>
import api from '@/api';
import { useFlowsStore } from '@/stores';
import { notify } from '@/utils/notify';
import { unexpectedError } from '@/utils/unexpected-error';
import { useCollection } from '@directus/shared/composables';
import { computed, ref, toRefs } from 'vue';
import { useI18n } from 'vue-i18n';
interface Props {
collection: string;
primaryKey?: string;
selection: (number | string)[];
location: 'collection' | 'item';
}
const props = withDefaults(defineProps<Props>(), {
primaryKey: undefined,
selection: () => [],
});
const emit = defineEmits(['refresh']);
const { t } = useI18n();
const { collection, primaryKey, selection } = toRefs(props);
const { primaryKeyField } = useCollection(collection);
const flowsStore = useFlowsStore();
const manualFlows = computed(() =>
flowsStore
.getManualFlowsForCollection(collection.value)
.filter(
(flow) =>
!flow.options?.location || flow.options?.location === 'both' || flow.options?.location === props.location
)
);
const runningFlows = ref<string[]>([]);
async function runManualFlow(flowId: string) {
const selectedFlow = manualFlows.value.find((flow) => flow.id === flowId);
if (!selectedFlow || !primaryKeyField.value) return;
runningFlows.value = [...runningFlows.value, flowId];
try {
const keys = primaryKey.value ? [primaryKey.value] : selection.value;
await api.post(`/flows/trigger/${flowId}`, { collection: collection.value, keys });
emit('refresh');
notify({
title: t('run_flow_success', { flow: selectedFlow.name }),
});
} catch (err: any) {
unexpectedError(err);
} finally {
runningFlows.value = runningFlows.value.filter((runningFlow) => runningFlow !== flowId);
}
}
</script>
<style lang="scss" scoped>
@import '@/styles/mixins/form-grid';
.fields {
@include form-grid;
}
.fields {
--form-vertical-gap: 24px;
.type-label {
font-size: 1rem;
}
}
:deep(.v-button) .button:disabled {
--v-button-background-color-disabled: var(--background-normal-alt);
}
.v-icon {
margin-right: 8px;
}
</style>

View File

@@ -12,25 +12,17 @@
</v-detail>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue';
<script lang="ts" setup>
import { ref } from 'vue';
import RevisionItem from './revision-item.vue';
export default defineComponent({
components: { RevisionItem },
props: {
group: {
type: Object,
required: true,
},
},
emits: ['click'],
setup() {
const expand = ref(true);
return { expand };
},
});
</script>
interface Props {
group: Record<string, any>;
}
<style scoped></style>
defineProps<Props>();
defineEmits(['click']);
const expand = ref(true);
</script>

View File

@@ -35,208 +35,47 @@
</sidebar-detail>
</template>
<script lang="ts">
<script lang="ts" setup>
import { useRevisions } from '@/composables/use-revisions';
import { abbreviateNumber } from '@directus/shared/utils';
import { ref, toRefs, watch } from 'vue';
import { useI18n } from 'vue-i18n';
import { defineComponent, ref, watch } from 'vue';
import { Revision, RevisionsByDate } from './types';
import api from '@/api';
import { groupBy, orderBy } from 'lodash';
import { isToday, isYesterday, isThisYear } from 'date-fns';
import formatLocalized from '@/utils/localized-format';
import RevisionsDateGroup from './revisions-date-group.vue';
import RevisionsDrawer from './revisions-drawer.vue';
import { unexpectedError } from '@/utils/unexpected-error';
import { abbreviateNumber } from '@directus/shared/utils';
export default defineComponent({
components: { RevisionsDrawer, RevisionsDateGroup },
props: {
collection: {
type: String,
required: true,
},
primaryKey: {
type: [String, Number],
required: true,
},
},
emits: ['revert'],
setup(props) {
const { t } = useI18n();
interface Props {
collection: string;
primaryKey: string | number;
}
const { revisions, revisionsByDate, loading, refresh, revisionsCount, pagesCount, created } = useRevisions(
props.collection,
props.primaryKey
);
const props = defineProps<Props>();
const modalActive = ref(false);
const modalCurrentRevision = ref<number | null>(null);
const page = ref<number>(1);
defineEmits(['revert']);
watch(
() => page.value,
(newPage) => {
refresh(newPage);
}
);
const { t } = useI18n();
return {
t,
revisions,
revisionsByDate,
loading,
refresh,
modalActive,
modalCurrentRevision,
openModal,
revisionsCount,
created,
page,
pagesCount,
abbreviateNumber,
};
const { collection, primaryKey } = toRefs(props);
function openModal(id: number) {
modalCurrentRevision.value = id;
modalActive.value = true;
}
const { revisions, revisionsByDate, loading, refresh, revisionsCount, pagesCount, created } = useRevisions(
collection,
primaryKey
);
function useRevisions(collection: string, primaryKey: number | string) {
const revisions = ref<Revision[] | null>(null);
const revisionsByDate = ref<RevisionsByDate[] | null>(null);
const loading = ref(false);
const revisionsCount = ref(0);
const created = ref<Revision>();
const pagesCount = ref(0);
const modalActive = ref(false);
const modalCurrentRevision = ref<number | null>(null);
const page = ref<number>(1);
getRevisions();
watch(
() => page.value,
(newPage) => {
refresh(newPage);
}
);
return { created, revisions, revisionsByDate, loading, refresh, revisionsCount, pagesCount };
async function getRevisions(page = 0) {
loading.value = true;
const pageSize = 100;
try {
const response = await api.get(`/revisions`, {
params: {
filter: {
collection: {
_eq: collection,
},
item: {
_eq: primaryKey,
},
},
sort: '-id',
limit: pageSize,
page,
fields: [
'id',
'data',
'delta',
'collection',
'item',
'activity.action',
'activity.timestamp',
'activity.user.id',
'activity.user.email',
'activity.user.first_name',
'activity.user.last_name',
'activity.ip',
'activity.user_agent',
],
meta: ['filter_count'],
},
});
const createdResponse = await api.get(`/revisions`, {
params: {
filter: {
collection: {
_eq: collection,
},
item: {
_eq: primaryKey,
},
activity: {
action: {
_eq: 'create',
},
},
},
sort: '-id',
limit: 1,
fields: [
'id',
'data',
'delta',
'collection',
'item',
'activity.action',
'activity.timestamp',
'activity.user.id',
'activity.user.email',
'activity.user.first_name',
'activity.user.last_name',
'activity.ip',
'activity.user_agent',
],
meta: ['filter_count'],
},
});
created.value = createdResponse.data.data?.[0];
const revisionsGroupedByDate = groupBy(
response.data.data.filter((revision: any) => !!revision.activity),
(revision: Revision) => {
// revision's timestamp date is in iso-8601
const date = new Date(new Date(revision.activity.timestamp).toDateString());
return date;
}
);
const revisionsGrouped: RevisionsByDate[] = [];
for (const [key, value] of Object.entries(revisionsGroupedByDate)) {
const date = new Date(key);
const today = isToday(date);
const yesterday = isYesterday(date);
const thisYear = isThisYear(date);
let dateFormatted: string;
if (today) dateFormatted = t('today');
else if (yesterday) dateFormatted = t('yesterday');
else if (thisYear) dateFormatted = await formatLocalized(date, String(t('date-fns_date_short_no_year')));
else dateFormatted = await formatLocalized(date, String(t('date-fns_date_short')));
revisionsGrouped.push({
date: date,
dateFormatted: String(dateFormatted),
revisions: orderBy(value, ['activity.timestamp'], ['desc']),
});
}
revisionsByDate.value = orderBy(revisionsGrouped, ['date'], ['desc']);
revisions.value = orderBy(response.data.data, ['activity.timestamp'], ['desc']);
revisionsCount.value = response.data.meta.filter_count;
pagesCount.value = Math.ceil(revisionsCount.value / pageSize);
} catch (err) {
unexpectedError(err);
} finally {
loading.value = false;
}
}
async function refresh(page = 0) {
await getRevisions(page);
}
}
},
});
function openModal(id: number) {
modalCurrentRevision.value = id;
modalActive.value = true;
}
</script>
<style lang="scss" scoped>

View File

@@ -58,8 +58,8 @@ export default defineComponent({
if (!name) return null;
const currentValue = props.revision.delta[fieldKey];
const previousValue = previousRevision.value.data[fieldKey];
const currentValue = props.revision.delta?.[fieldKey];
const previousValue = previousRevision.value.data?.[fieldKey];
if (isEqual(currentValue, previousValue)) return null;

View File

@@ -18,6 +18,7 @@ export type Revision = {
last_name: string;
};
};
timestampFormatted: string;
};
export type RevisionsByDate = {