I have multiple rows in a table, in one of which is a link I'd like to automatically click. For my starting point I have the last instance that a row contains the text "Test Question?":
ID:
1416
Edit/Approve
Ignore
Email
Name:
Submitter Name
Open
N/A
Location:
Submitter Location
Member:
No
Question Text:
Test Question?
Response 1 Text:
Response 2 Text:
ID:
1417
Edit/Approve
Ignore
Email
Name:
Submitter Name
Open
N/A
Location:
Submitter Location
Member:
No
Question Text:
Test Question?
Response 1 Text:
Response 2 Text:
So, //tr[td='Test Question?'][last()] would be something like "Question Text: Test Question?"
Anyway, three rows up from that there's a row with a bunch of links in it. I would like the xpath to the link in the first cell.
Now I tried to use position(), but stuff like //tr[position()=(//tr[td='Test Question?'][last()][position()])-3] just isn't the right way to do it and I can't find any good examples.
Thanx
Dave
That's going to be a pretty gnarly xpath to say the least, but you're looking at axes starting with ancestor::tr and then preceding-sibling. Tunnel up to <tr>, then across three (you'll need to ref position() here I think) and then down in a normal fashion from there to your goal.
Edit: easier than I thought, preceding-sibling counts backwards.
./ancestor::tr/preceding-sibling::tr[3]/td[1]/a
Related
I get a daily email that lists upcoming appointments, and their length. The number of appointments vary from day to day.
The emails go like this:
================
Today's Schedule
9:30 AM
3h
Brazilian Blowout
[Client #1 name]
12:30 PM
1h
Women's Cut
[Client 2 name]
6:00 PM
45m
Men's Cut
[Client #3 name]
Projected Revenue
===================
I want to create an event in a Google Calendar for each appointment, and it seems like zapier MIGHT be able to do this, but all the help resources I can find are very general in nature.
Is this do-able on Zapier? If so, any nudges in the right direction would be awesome.
Any thoughts greatly appreciated.
I had some time to kill and enjoy the odd challenge. So I have put together a solution that should do what you are looking for. I will break it down by steps.
TEMPLATE
Zapier Trigger - Step 1
Type: Trigger
Module: Gmail
Criteria: User Dependent
Comments: For the trigger zap you will want to use a Gmail specific trigger, something to the effect of "execute trigger on emails titled 'xyz'", or "emails labeled 'xyz'" if you setup a filter in your inbox.
Input screenshot:
Output Screenshot:
Zapier Action - Step 2
Type: Action
Module: Code (Python 3)
Comments: The Code offered by Zapier executes whatever (properly written) code you place in its container. It is especially handy as it allows you to incorporate data from previous steps in it through the use of a dictionary variable titled 'input_data'. Zapier offers the Code module in two languages: Javascript and Python. As I am most familiar with Python my solution for this step was written in Python. I will append the code to the end of this answer. Using the data held in the body of the email (retrieved in step 1) we can execute some string manipulations and datetime conversions to break apart the email into its component parts and pass those on to the following Action Step: Create Calendar Event.
Input Screenshot:
Output Screenshot:
Zapier Action - Step 3
Type: Action
Module: Google Calendar - Create Event
Comments: Using the data outputted from the previous code step we can fill out the required fields for creating a new appointment.
Input Screenshot:
Output Screenshot:
PYTHON CODE
from datetime import timedelta, date, datetime
'''
Goal: Extract individual appointment details from variable length email
Steps:
Remove all extraneous and new line characters.
Isolate each individual appointment and group its relevant details.
Derive appointment start and end times using appointment time and duration.
Return all appointments in a list.
'''
def format_appt_times(appt_dict):
appt_start_str = appt_dict.get("appt_start")
appt_dur_str = appt_dict.get("appt_length")
# isolate hour and minutes from appointment time
appt_s_hour = int(appt_start_str[:appt_start_str.find(":")])
if ("pm" in appt_start_str.lower()):
appt_s_hour = 12 if appt_s_hour + 12 >= 24 else appt_s_hour + 12
appt_s_min = int(appt_start_str[appt_start_str.find(":") + 1 :
appt_start_str.find(":") + 3])
# isolate hour and minutes from duration time
appt_d_hour = 0
appt_d_min = 0
if ("h" in appt_dur_str):
appt_d_hour = int(appt_dur_str[:appt_dur_str.find("h")])
if ("m" in appt_dur_str):
appt_d_min = int(appt_dur_str[appt_dur_str.find("m") - 2 : appt_dur_str.find("m")])
# NOTE: adjust timedelta hours depending on your relation to UTC
# create datetime objects for appointment start and end times
time_zone = timedelta(hours=0)
tdy = date.today() - time_zone
duration = timedelta(hours=appt_d_hour, minutes=appt_d_min)
appt_start_dto = datetime(year=tdy.year,
month=tdy.month,
day=tdy.day,
hour=appt_s_hour,
minute=appt_s_min)
appt_end_dto = appt_start_dto + duration
# return properly formatted datetime as string for use in next step.
return (appt_start_dto.strftime("%Y-%m-%dT%H:%M"),
appt_end_dto.strftime("%Y-%m-%dT%H:%M"))
def partition_list(target, part_size):
for data in range(0, len(target), part_size):
yield target[data : data + part_size]
def main():
# Remove all extraneous and new line characters.
email_body = input_data.get("email_body")
head,delin,*email_body,delin,foot = [text for text in email_body.splitlines() if text != ""]
appointment_list = []
# Isolate each individual appointment and group its relevant details.
for text in partition_list(email_body, 4):
template = {
"appt_start" : text[0],
"appt_end" : None,
"appt_length" : text[1],
"appt_title" : text[2],
"appt_client" : text[3]
}
appointment_list.append(template)
for appt in appointment_list:
appt["appt_start"], appt["appt_end"] = format_appt_times(appt)
return appointment_list
return main()
I am not sure of your familiarity with Python, or programming more generally, but the comments in the code explain what each section is doing. If you have any specific questions regarding aspects of the code let me know. Assuming your email template does not change this setup should work exactly as needed. Let me know if anything is unclear.
UPDATE
I thought it best to address your question in the original answer should anyone else have similar questions.
explaining how this code is removing the extra characters:
There is actually a fair bit going on in the first line, so I will do my best to break it down, and provide resources where necessary.
The code in question:
head,delin,*email_body,delin,foot = [text for text in email_body.splitlines() if text != ""]
First step here was to break the text into manageable chunks. I did so with the line email_body.splitlines() which, by default, breaks strings into a list at each newline character found (you can specify your own delimiter).
If we were to inspect the list at this moment its contents would be something of the following:
["================", "", "Today's Schedule", "", "9:30 AM", "", "3h", ..., "[Client #3 name]", "", "Projected Revenue", "", "==================="]
You will notice there is a fair amount of information in there that we really don't want.
First lets look at the "" elements. These are left over as a result of the blank lines between each line of text, which even though they are blank do still have newline characters at the end of them. There a number of ways you could address this within python. We could simply write a for-loop to go through and copy all elements that are not "" to a new list.
To me this felt like additional work, and besides, Python offers list comprehension for just such a scenario. I won't go too deep into list comprehension as there is a lot that can be said about it, and in more insightful ways than I could muster, but it essentially allows you to provide logic against a set of 'data' to form a list. In this case, I specifically wanted to filter out the "" elements returned from the call to splitlines().
And so you will see I address this with the following line
[text for text in email_body.splitlines() if text != ""]
With that we have a list as above less the "" elements. Now we must turn our attention towards the more 'dynamic' garbage strings. Again there are a number of ways to do this. A, not particularly flexible, option could be to simply store the strings we want to remove in variables something to the effect of:
garb_1 = "==================="
garb_2 = "Projected Revenue"
garb_3 = ...
and once again filter the list with yet another for-loop. I instead chose to leverage Python's list unpacking idiom. Which allows us to 'unpack' list objects (and I believe tuples) into variables. As an example:
one, two, three = ["a", "b", "c"]
I'm sure you can guess what is happening above, as long as we provide the same number of variables as are in the list we can 'unpack' it in this fashion. But wait! In our case we don't know how long the list is going to be as it is entirely dependent on the number of appointments you have for any given day. Well this is where star unpacking enters to elevate the functionality. Using my code as the example:
head,delin,*email_body,delin,foot = [text for text in email_body.splitlines() if text != ""]
The *, in plain-English, is saying "I don't know how many elements to expect just give me all of them in a list". As we know that there will always be two lines of garbage at the beginning and end of the email we can assign them to throw away variables and capture everything in between using our variable length *email_body container.
With all of this complete we now have a list with only the data we are looking to capture. If, as you say, there are additional lines of garbage before or after the email_body, you can simply add additional throw away variables to account for them.
Once again feel free to ask any follow up questions.
Michael
Resources
List Comprehension
Star Unpacking
Apologise in advance for the previous post. #xavdid did a great job helping me out. Due to my lack of expertise and knowledge in this field I failed to express accurately what I needed. I believe I have now enough information to express what I want to achieve. So I will do my best to express it here.
Here is my input information, Keys and Values. Each position of the keys corresponds to the position of the values.
I believed in order to solve this problem, I would need to know when a book starts and when it doesn't start. I was wrong.
All I need is to match the predefined keys with their values and group them together.
By predefined keys I mean returning only these 7 keys :"Project Details,Project Title,Addons,Upgrade,Word Count,Ebook Type,Upload your file here" with their values (ignore the other keys)
Example of 3 books:
Input Data Keys: Project Details,Project Title,Addons,Upgrade,Word
Count,Ebook Type,_builder_info,_builder_id,_master_builder,Upload your
file here,_builder_id,_master_builder,_builder_id,_builder_info,Ebook
Type,Word Count,Upgrade,Addons,Project Title,Project Details,Project
Details,Project Title,Addons,Upgrade,Word Count,Ebook
Type,_builder_info,_builder_id,_master_builder,Upload your file
here,_builder_id
Input Data Values: Book Description 3,Book Title 3,Book Cover Design - $59.00,No Package,Standard 10K - $270.00,Standard Ebook,Start~~//www.shappify-cdn.com/images/282516/127828454/001_Ebook Standard 325x325.png~~start,start1526659928051,1,https://cdn.shopify.com/s/files/1/0012/8814/2906/uploads/778dfc3dbdf278441776e9f5dd763910.png,start1526659928051,1,start1526659872230,Start~~//www.shappify-cdn.com/images/282516/127828455/001_Ebook Technical 325x325 (1).png~~start,Technical Ebook,Technical 15K - $450.00,No Package,No Addons,Book Title 2,Book Description 2,Book 1 Description,Book 1 Title,No Addons,Essential Package - $79.00,Standard 20K - $540.00,Standard Ebook,Start~~//www.shappify-cdn.com/images/282516/127828458/001_Ebook Standard 20k 325x325.png~~start,start1526659838425,1,https://cdn.shopify.com/s/files/1/0012/8814/2906/uploads/c09635c2e003fd8779a19651e36f4315.png,start1526659838425
Output desired:
[{'Ebook Type': 'Standard Ebook'},{'Ebook Type':'Technical Ebook'},{'Ebook Type':'Standard Ebook'}],
[{'Word Count': 'Standard 10K - $270.00'}, {'Word Count': 'Technical 15K - $450.00'},{'Word Count': 'Standard 20K - $540.00'}]
[{Upgrade: 'No Package'},{Upgrade: 'No Package'},{Upgrade: 'Essential Package - $79.00'}]
[{Project Title: 'Book Title 3'}, {Project Title: 'Book Title 2'}, {Project Title: 'Book Title 1'}]
[{'Project Details': 'Book Description 3'},{'Project Details': 'Book Description 2'},{'Project Details':'Book 1 Description'}],
[{'Addons: 'Book Cover Design - $59.00',{Addons:'No Addons'},{Addons:'No Addons'}],
[{'Upload your file here':'https://cdn.shopify.com/s/files/1/0012/8814/2906/uploads/778dfc3dbdf278441776e9f5dd763910.png'},{'Upload your file here':https://cdn.shopify.com/s/files/1/0012/8814/2906/uploads/c09635c2e003fd8779a19651e36f4315.png'}]
Thank you very much
Ok! So I think I have this how we want it. The big issues are the assumptions we're making about the structure of the data, namely:
there are never commas in values (A book titled "Murder, She Wrote" would bust this because of the comma splitting)
the keys can be in any order relative to each other (might do any number of titles before descriptions) but title 3 will aways come (somewhere) before title 2
Given that, we need to split up our inputs, separate into the 7 keys we want, and then build books out of those arrays. That is the following:
const keys = inputData.keys.split(',')
const values = inputData.values.split(',')
const keysWeWant = "Project Details,Project Title,Addons,Upgrade,Word Count,Ebook Type,Upload your file here".split(',')
let orderedValues = {}
// fill arrays so we can blindly push
keysWeWant.forEach(k => orderedValues[k] = [])
keys.forEach((key, index) => {
if (keysWeWant.includes(key)) {
orderedValues[key].push(values[index])
}
})
console.log(orderedValues)
// have to cut up books now
// don't know how many we have, hopefully our values have the right number of items
// .map(Object) is because of this: https://stackoverflow.com/questions/35578478/array-prototype-fill-with-object-passes-reference-and-not-new-instance
let result = Array(orderedValues['Project Title'].length)
.fill()
.map(Object)
for (const keyWeWant in orderedValues) {
orderedValues[keyWeWant].forEach((val, index) => {
result[index][keyWeWant] = val
})
}
// put book 1 first, this can be removed
result.reverse()
return result
you can see it in action here. Worth noting that in the sample data, there were only two "upload your file here", so the last book (book 1) is missing that key.
Also, when you test this in the zap editor, it'll look like only 1 item is returned. That's just for the test. It'll work normally when it's on an running for real (everything after the code step will run once for each item in the returned array).
Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 5 years ago.
Improve this question
I have an array of objects in which I need to check if it meets a specific criteria.
What I've done is looped through the array and then matched it with the ruby include? object. Problem is I've noticed that there are instances where this causes some codes to return true, when they really should be returning false.
group.plan_codes.each do |code|
normalized_plan_code = code.upcase.gsub(" ", "").gsub("+", "")
normalized_plan_code.include? coverage['plan_description'].upcase.gsub(" ", "").gsub("+", "")
end
I'm basically taking these group.plan_codes and matching them with the coverage['plan_description']. Problem I found was that if the code was something like group plan submitting a code like not group plan would still return true because the group plan is included in the plan description
Would anyone know a better way about doing this? I was thinking it could stop looking after the first element is completed, but am a little caught up on the ruby detect
Use a Regex or a straight equality test (==). For sake of clarity, let's assume (that I'm understanding your question correctly and) that you have an array such as:
plans = [ 'not group plan', 'group plan' ]
and you are trying to find the second element:
including = 'group plan'
plans.detect { |plan| plan.include?(including) }
this returns "not group plan", the first element, because it also includes the string 'group plan'. To remedy that, using regex you could use something like:
plans.detect { |plan| plan.match?(/\A#{Regexp.escape(including)}\z/) }
Now, this returns the second element, because you're looking for an exact match. Since it is an exact match, though, you could also use something simpler:
plans.detect { |plan| plan == including }
What the regex gives you is if each plan can include multiple items:
plans = ['plan a,not group plan,plan b', 'plan a,group plan,plan b']
Which is a comma separated list of plan codes and you're looking for any plan that includes 'group plan', now you can use the regex:
plans.detect { |plan| plan.match?(/,#{Regexp.escape(including)},/) }
and have the second element returned. You'll need to work the regex into a format that works for how you are saving the plan codes (in this example, I chose comma separated list, you might have tabs or semicolons or whatever else. If you have just a white space separated list of codes that can contain whitespace, you need to do more work and reject any items that include any codes that are longer and include the code you're looking for.
I'm developing an employee time management app in swift, I would be happy to help you with the structure of the data and how to extract some queries.
The user database is like this:
https://i.stack.imgur.com/hfSXM.jpg
{"Database":
{"users"
{"3iHTIn1MicMdPbgEV6nnMy5ijHq1":
{ "company" : "My Company",
"email" : "edel#gmail.com",
"name" : "Tom",
"type" : "Employee"
}
}
}
}
The user will clock in and out when start and finish job.
Is better to open new child in the project for the dates and the hours or to add to the user child with the date? (please see the queries that I want to pull before answer)
Help with the queries:
Pull all the dates and the hours for this month for user.
Pull all the user that have same company name and work now (the field clock in are full and the field clock out are empty).
Pull list all the company names that have for all users and delete duplicate (is better to make another child in the project with only company’s names?)
Thanks!!
A few thoughts. I would probably denormalize the data because you have several ways you want to query it. I'm using some pseudo code to keep the answer short(er)
Pull all the dates and the hours for this month for user.
the challenge here is querying by two child nodes; one of them being a certain user and the other being a range of dates. The solution is a compound query of uid and YYMM (year month)
here's the structure:
jobs
job_0
start: 170518090135
end: 170518170515
uid_yymm: uid_0_1705
uid: uid_0
company_id: company_0
comp_status: company_0_true
job_1
start: 1705190835
end: 17051910430
uid_yymm: uid_0_1705
uid: uid_1
company_id: company_0
comp_status: company_0_false
queryOrdered(byChild: "uid_yymm").queryEqual(to value: "uid_0_1705")
This will query all of the jobs for uid_0 with the year being 2017 and month being 05.
Pull all the user that have same company name and work now (the field
clock in are full and the field clock out are empty).
Searching for no data can make things more complex with NoSQL so we'll just avoid that completely. Again, a compound query will do the job with the above structure on the comp_status node.
queryOrdered(byChild: "comp_status").queryEqual(to value: "company_0_true")
this query will return all of the users that work for company_0 that are (true) on the clock. when they go off the clock, as in uid_1's case, set to company_0_false.
Pull list all the company names that have for all users and delete
duplicate (is better to make another child in the project with only
company’s names?)
This is a little vague but I think the answer is yes, have a separate company names node but keep a reference to the company in the users node.
Database
users
3iHTIn1MicMdPbgEV6nnMy5ijHq1
company: "company_0"
email: "edel#gmail.com"
name: "Tom"
type: "Employee"
6889js9i99s9ksij8asd88as8d8
company: "company_1"
email: "bill#gmail.com"
name: "bill"
type: "Employee"
companies
company_0
comp_name: "Electric Company"
company_1
comp_name: "Bad Company"
http://about.digg.com/blog/looking-future-cassandra
I've found this article about Digg's move to Cassandra. But I didn't get the author's idea of Bucket for pair (user,item). Little more details on the idea would be helpful to me to understand the solution better.
Thanks
It sounds like they are using one row in a super column family per user with one super column per item; a subcolumn for an item super column represents a friend who dugg the item. At least in pycassa, this makes an insert as simple as:
column_family.insert(user, {item: {friend: ''}})
They could also have done this a couple of other ways, and I'm not sure which they chose.
One is to use a standard column family, use a (user,item) combination for the row key, and use one column per friend who dugg the item:
column_family.insert(user + item, {friend: ''})
Another is to use a standard column family, use just (user) for the row key, and use an (item, friend) combination for the column name:
column_family.insert(user, {item + friend: ''})
Doesn't sound like this is what they used, but it's an acceptable option as well.