I have been working through the Aikau tutorials on Github but can't work out how to create a list that I can pass on to a view. The requirement is to select all workflow tasks for all users and display the results.
I have added the following widgets which displays the details of one users(hard coded), but I need to cycle through all the users and display all workflows.
model.jsonModel = {
services: [
"alfresco/services/CrudService"
],
widgets:[
{
name: "alfresco/lists/AlfSortablePaginatedList",
config: {
loadDataPublishTopic: "ALF_CRUD_GET_ALL",
loadDataPublishPayload: {
url: "api/task-instances?authority=abeecher"
},
itemsProperty: "data",
widgets: [
{
name: "alfresco/lists/views/AlfListView",
config: {
additionalCssClasses: "bordered",
widgetsForHeader: [
{
name: "alfresco/lists/views/layouts/HeaderCell",
config: {
label: "Workflow ID",
sortable: true,
sortValue: "id"
}
},
{
name: "alfresco/lists/views/layouts/HeaderCell",
config: {
label: "Description"
}
},
{
name: "alfresco/lists/views/layouts/HeaderCell",
config: {
label: "Status"
}
},
{
name: "alfresco/lists/views/layouts/HeaderCell",
config: {
label: "Due Date",
sortable: true,
sortValue: "properties.bpm_dueDate"
}
},
{
name: "alfresco/lists/views/layouts/HeaderCell",
config: {
label: "Created By"
}
},
{
name: "alfresco/lists/views/layouts/HeaderCell",
config: {
label: "Assigned To",
sortable: true,
sortValue: "owner.firstName"
}
},
{
name: "alfresco/lists/views/layouts/HeaderCell",
config: {
label: "Tag1"
}
},
{
name: "alfresco/lists/views/layouts/HeaderCell",
config: {
label: "Tag2"
}
},
{
name: "alfresco/lists/views/layouts/HeaderCell",
config: {
label: "Tag3"
}
}
],
widgets: [
{
name: "alfresco/lists/views/layouts/Row",
config: {
widgets: [
{
name: "alfresco/lists/views/layouts/Cell",
config: {
additionalCssClasses: "mediumpad",
widgets: [
{
name: "alfresco/renderers/Property",
config: {
propertyToRender: "id",
}
}
]
}
},
{
name: "alfresco/lists/views/layouts/Cell",
config: {
widgets: [
{
name: "alfresco/renderers/Property",
config: {
propertyToRender: "workflowInstance.message",
}
}
]
}
},
{
name: "alfresco/lists/views/layouts/Cell",
config: {
widgets: [
{
name: "alfresco/renderers/Property",
config: {
propertyToRender: "state",
}
}
]
}
},
{
name: "alfresco/lists/views/layouts/Cell",
config: {
widgets: [
{
name: "alfresco/renderers/Property",
config: {
propertyToRender: "properties.bpm_dueDate",
}
}
]
}
},
{
name: "alfresco/lists/views/layouts/Cell",
config: {
widgets: [
{
name: "alfresco/renderers/Property",
config: {
propertyToRender: "workflowInstance.initiator.firstName" ,
}
}
]
}
},
{
name: "alfresco/lists/views/layouts/Cell",
config: {
widgets: [
{
name: "alfresco/renderers/Property",
config: {
propertyToRender: "owner.firstName",
}
}
]
}
},
]
}
}
]
}
}
]
}
}
]
};
The final solution will require the ability to sort the columns and be able to click on a task to see the underlying workflow. What would be the best way to create the initial list based on these requirements?
If I was to write a widget that did the building of the list, how do I couple the widget to the form? Is this a pub/sub solution since the user is not clicking on anything - just loading the page?
I assume that I would need to write custom a webscript if I use the "url" keyword under the loadDataPublishPayload option? If I did write a webscript, what would be the final statement to return the json data to the form?
I just need some guidance on the best way forward.
At the time of writing, using the latest released version of Aikau (1.0.83) it is not possible to do this using out-of-the-box widgets and services.
The main problem is that there is no full mapping between Aikau and the Share XML based forms runtime. This blog post explains the issue at hand. It is however something that we're working on.
Once the "alfresco/services/FormsRuntimeService" is complete this will be an easier exercise to complete. There would be no need to write additional widgets for the lists because the existing list widgets handle all the requirements for sorting/pagination (if the underlying REST API supports sorting and pagination!).
I guess your best way forward is very dependent upon how quickly require this solution. We're making progress with the FormsRuntimeService, but I can't say when it will be fully ready.
The existing pages in Share that show tasks and workflow that do use the Share Forms Runtime rely on APIs that return HTML that is hard-coded to work with the Share YUI2 based widgets - it might be possible to achieve what you want to using the older Surf Component / YUI2 widget approach. That might be one other area to explore.
The main thing to do is to establish whether or not there are existing REST APIs that will meet you core requirements
Related
URL:https://firestore.googleapis.com/v1/{parent=projects//databases//documents}:runQuery
Request body:
{ structuredQuery:
{ from: [
{ collectionId: 'questions'
}
],
orderBy: [
{ field:
{ fieldPath: 'created'
}, direction: 'DESCENDING' }
], select: { fields:
[
{ fieldPath: 'id' },
{ fieldPath: 'details' },
{ fieldPath: 'question' },
{ fieldPath: 'votes' }
]
},
where: {
compositeFilter: {
filters: [
{ fieldFilter: {
field: {
fieldPath: 'author'
},
op: 'EQUAL',
value: {
stringValue: "henry"
}
}
}
], op: 'AND'
}
},
limit: 4
}
}
What error do I get?
[{
"readTime": "2022-12-26T12:46:55.107240Z"
}]
the response isn't returning the expected results
What am I trying to do?
Fetch the given field from the collection: "questions" where the "author" matches "henry"
The documentation is available and it looks pretty straighforward.
Making REST calls
All REST API endpoints exist under the base URL https://firestore.googleapis.com/v1/.
To create a path to a document with the ID LA in the collection
cities under the project YOUR_PROJECT_ID you would use the
following structure.
/projects/YOUR_PROJECT_ID/databases/(default)/documents/cities/LA
To interact with this path, combine it with the base API URL.
https://firestore.googleapis.com/v1/projects/YOUR_PROJECT_ID/databases/(default)/documents/cities/LA
The best way to begin experimenting with the REST API is to use
the API Explorer, which automatically generates Google Identity OAuth
2.0 tokens and allows you to examine the API.
A user should be allowed to access a resource if they are in a team that is allowed to access the resource.
How can I do this in security rules ?
I have collections:
teams, with a .members field in each
resources, with a teamsThatCanAccess for each
If i wrote this in js, itd be something like:
canUserAccess = (userId, resource) => {
teams = resource.teamsThatCanAccess
hasAccess = false
teams.forEach((team) => {
if (userId in team.members) {
hasAccess = true
}
}
return hasAccess
}
However, as I understand it, security rules dont like loops.
--EDIT--
To illustrate further, the database I'm building will look like something like this:
teams = [
{ name: "teamA", org: "org1", members: ["uid1", "uid2", "uid3"] },
{ name: "teamB", org: "org1", members: ["uid1", "uid2"] },
{ name: "teamC", org: "org1", members: ["uid3", "uid4", "uid5"] },
{ name: "teamD", org: "org2", members: ["uid201", "uid202"] },
]
resources = [
{
id: "projectId1",
name: "project 1",
org: "org1",
teamsThatCanAccess: ["teamA", "teamB"],
},
{
id: "projectId2",
name: "project 2",
org: "org1",
teamsThatCanAccess: ["teamA", "teamB", "teamC"],
},
{
id: "projectId3",
name: "project 3",
org: "org1",
teamsThatCanAccess: ["teamC"],
},
{
id: "projectId4",
name: "project 201",
org: "org2",
teamsThatCanAccess: ["teamD"],
},
]
projectFiles = [
{ content: "document text", project: "projectId1" },
{ content: "document text 2", project: "projectId1" },
{ content: "document text 3", project: "projectId2" },
]
Based on what you described, you have a structure that looks like this:
// document at /teams/someTeamId
{
"members": [
"uid1",
"uid2",
"uid3"
],
/* ... */
}
// document at /resources/someResourceId
{
"teamsThatCanAccess": [
"someTeamId",
"otherTeamId"
],
/* ... */
}
To secure the data, you will need to introduce a new collection of documents, called something like teamsByUser:
// document at /teamsByUser/uid1
{
"memberOf": [
"someTeamId",
"otherTeamId"
]
}
By introducing this array, you can now use the rules.List#hasAny method to find if there is any overlap between the memberOf array in /teamsByUser/{userId} and the teamsThatCanAccess array in /resources/{resourceId}.
This will then allow you to configure your rules as
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /resources/{resourceId} {
allow read: if resource.data.size() == 0 // is empty?
|| get(/databases/$(database)/documents/teamsByUser/$(request.auth.uid)).data.memberOf.hasAny(resource.data.teamsThatCanAccess); // accessing user is a member of an allowed team
}
// don't forget to add rules to prevent users joining arbitrary teams from clients
}
}
I have this issue were I'm trying to request images I've uploaded on my WordPress instance with graphql from Gatsby but I only get the most recent item I uploaded on wordpress.
When i query using the Graphiql IDE on WordPress I the desired results. See this image ➡️
Image of the query and response in get from Graphiql IDE on wordpress
Below is the query and response in get from Graphiql IDE on wordpress
// Graphiql IDE query
{
mediaItems {
edges {
node {
databaseId
}
}
}
}
// The response
{
"data": {
"mediaItems": {
"edges": [
{
"node": {
"databaseId": 30
}
},
{
"node": {
"databaseId": 28
}
},
{
"node": {
"databaseId": 26
}
},
{
"node": {
"databaseId": 20
}
}
]
}
},
"extensions": {
"debug": []
}
}
but when I query from the http://localhost:8000/___graphql endpoint on my local development as I said earlier I only get the most recent upload.
Image of local development query and response
the code is below
// The query to http://localhost:8000/___graphql endpoint
{
wpMediaItem {
altText
localFile {
childImageSharp {
gatsbyImageData(placeholder: TRACED_SVG, width: 100)
id
}
}
}
}
// The response
{
"data": {
"wpMediaItem": {
"altText": "background lines",
"localFile": {
"childImageSharp": null
}
}
},
"extensions": {}
}
The ednpoints I can query
the enpoints img 1
the enpoints img 2
Below is my gatsby-config.js file
const path = require("path");
// Get paths of Gatsby's required rules, which as of writing is located at:
// https://github.com/gatsbyjs/gatsby/tree/fbfe3f63dec23d279a27b54b4057dd611dce74bb/packages/
// gatsby/src/utils/eslint-rules
const gatsbyRequiredRules = path.join(
process.cwd(),
"node_modules",
"gatsby",
"dist",
"utils",
"eslint-rules"
);
module.exports = {
siteMetadata: {
title: `########`,
description: `################`,
author: `###################`,
siteUrl: `###############`,
},
plugins: [
`gatsby-plugin-image`,
{
resolve: `gatsby-source-filesystem`,
options: {
name: `images`,
path: `${__dirname}/src/images`,
},
},
`gatsby-transformer-sharp`,
`gatsby-plugin-sharp`,
{
resolve: `gatsby-plugin-manifest`,
options: {
name: `gatsby-starter-default`,
short_name: `starter`,
start_url: `/`,
background_color: `#663399`,
// This will impact how browsers show your PWA/website
// https://css-tricks.com/meta-theme-color-and-trickery/
// theme_color: `#663399`,
display: `minimal-ui`,
icon: `src/images/gatsby-icon.png`, // This path is relative to the root of the site.
},
},
`gatsby-plugin-react-helmet`,
{
resolve: `gatsby-source-wordpress`,
options: {
url: `http://34.133.115.37/graphql`,
},
},
{
resolve: `gatsby-plugin-google-fonts`,
options: {
fonts: [`Poppins`],
display: `swap`,
},
},
{
resolve: `gatsby-plugin-styled-components`,
options: {
// Add any options here
},
},
{
resolve: "gatsby-plugin-eslint",
options: {
// Gatsby required rules directory
rulePaths: [gatsbyRequiredRules],
// Default settings that may be ommitted or customized
stages: ["develop"],
extensions: ["js", "jsx", "ts", "tsx"],
exclude: ["node_modules", "bower_components", ".cache", "public"],
// Any additional eslint-webpack-plugin options below
// ...
},
},
// this (optional) plugin enables Progressive Web App + Offline functionality
// To learn more, visit: https://gatsby.dev/offline
// `gatsby-plugin-offline`,
],
}
I don't know but probably my query is request is wrong, I have tried every can some help
wpMediaItem is not the same node as wpMediaItems (spot the trailing S). The first one is querying a single node, which by default would be sorted by upload date, while wpMediaItems fetch for all images, an array of images (edges in this case), that's why you can get edges in one query and an isolated node in the other, directly accessing for that node data (altText, etc.).
Take a deeper look at the GraphiQL playground (http://localhost:8000/___graphql) to get the correct node, but it should be there.
We want to customize the faceted search by passing extra argument in the faceted search URL and read it in org\alfresco\slingshot\search\search.get.js---->search.lib.js.
http://localhost:8080/share/page/dp/ws/faceted-search#searchTerm=Koala.jpg&scope=repo&nodeRef=test
In searchDocLib json ,we have nodeRef value assigned it to selectedContainer but that argument is not coming in search.get.js. Basically how to pass extra argument in searchDocLib?How to enable logs for faceted-search.get.js so that logger statements should be printed in share.log?
var noderef = (page.url.args["nodeRef"] != null) ? page.url.args["nodeRef"] : "";
logger.log(page.url.templateArgs.nodeRef+"....nodeRef = "+nodeRef);
// Build the searchDocLib model
var searchDocLib = {
id: "FCTSRCH_SEARCH_RESULTS_LIST",
name: "alfresco/documentlibrary/AlfSearchList",
config: {
viewPreferenceProperty: "org.alfresco.share.searchList.viewRendererName",
view: viewRendererName,
waitForPageWidgets: true,
useHash: true,
useLocalStorageHashFallback: true,
hashVarsForUpdate: [
"searchTerm",
"facetFilters",
"sortField",
"sortAscending",
"query",
"scope",
"selectedContainer"
],
selectedScope: "repo",
useInfiniteScroll: true,
siteId: null,
rootNode: repoRootNode,
repo: false,
selectedContainer: noderef,
additionalControlsTarget: "FCTSRCH_RESULTS_MENU_BAR",
additionalViewControlVisibilityConfig: hideOnZeroResultsConfig,
widgets: [
{
id: "FCTSRCH_SEARCH_ADVICE_NO_RESULTS",
name: "alfresco/documentlibrary/views/AlfSearchListView",
config: {
widgetsForNoDataDisplay: widgetsForNoDataDisplay,
a11yCaption: msg.get("faceted-search.results.caption"),
a11yCaptionClass: "hiddenAccessible",
widgetsForHeader: [
{
id: "FCTSRCH_THUMBNAIL_HEADER_CELL",
name: "alfresco/documentlibrary/views/layouts/HeaderCell",
config: {
label: msg.get("faceted-search.results.heading.thumbnail"),
class: "hiddenAccessible",
a11yScope: "col"
}
},
{
id: "FCTSRCH_DETAILS_HEADER_CELL",
name: "alfresco/documentlibrary/views/layouts/HeaderCell",
config: {
label: msg.get("faceted-search.results.heading.details"),
class: "hiddenAccessible",
a11yScope: "col"
}
},
{
id: "FCTSRCH_ACTIONS_HEADER_CELL",
name: "alfresco/documentlibrary/views/layouts/HeaderCell",
config: {
label: msg.get("faceted-search.results.heading.actions"),
class: "hiddenAccessible",
a11yScope: "col"
}
}
],
widgets: [
{
id: "FCTSRCH_SEARCH_RESULT",
name: "alfresco/search/AlfSearchResult",
config: {
enableContextMenu: false
}
}
]
}
},
{
id: "FCTSRCH_GALLERY_VIEW",
name: "alfresco/documentlibrary/views/AlfGalleryView",
config: {
showNextLink: true,
nextLinkLabel: msg.get("faceted-search.show-more-results.label"),
widgetsForNoDataDisplay: widgetsForNoDataDisplay,
widgets: [
{
id: "FCTSRCH_GALLERY_VIEW_THUMBNAIL_DOC_OR_FOLDER",
name: "alfresco/search/SearchGalleryThumbnail",
config: {
widgetsForSelectBar: [
{
id: "FCTSRCH_GALLERY_VIEW_MORE_INFO_OR_FOLDER",
name: "alfresco/renderers/MoreInfo",
align: "right",
config: {
filterActions: true,
xhrRequired: true
}
}
],
publishTopic: "ALF_NAVIGATE_TO_PAGE",
renderFilter: [
{
property: "type",
values: ["document","folder"],
negate: false
}
]
}
},
{
id: "FCTSRCH_GALLERY_VIEW_THUMBNAIL_OTHER",
name: "alfresco/search/SearchGalleryThumbnail",
config: {
widgetsForSelectBar: [
{
id: "FCTSRCH_GALLERY_VIEW_MORE_INFO_OTHER",
name: "alfresco/renderers/MoreInfo",
align: "right",
config: {
filterActions: true,
allowedActionsString: "[\"document-delete\"]",
xhrRequired: true
}
}
],
publishTopic: "ALF_NAVIGATE_TO_PAGE",
renderFilter: [
{
property: "type",
values: ["document","folder"],
negate: true
}
]
}
}
]
}
},
{
id: "FCTSRCH_INFINITE_SCROLL",
name: "alfresco/documentlibrary/AlfDocumentListInfiniteScroll"
}
]
}
};
I've written a blog post that covers customizing the search page. Although it isn't exactly the same use case, the principle remains the same - you're going to want to create your own SearchService (extending the default one) and then swap yours for the default one in the faceted-search page model. You'll want to extend the onSearchRequest function to include the extra request parameter.
I'm trying to write a small plugin and I want the user to be able to input an array into a nested object in grunt config, something like:
myTask: {
default: {
options: {
name: "someName"
deploy: {
envs: ["dev", "staging", "prod"]
}
}
}
}
In my task I'm trying to grab this.options.deploy.envs but it's undefined.
Not seen this style of plugin configuration before. Might be wiser to follow existing conventions for multi tasks:
myTask: {
deploy: {
options: {
name: "someName",
envs: ["dev", "staging", "prod"]
}
},
development: {
options: {
name: "othertarget",
envs: ["dev"]
}
}
}
In any case, you are missing a comma after "someName".